blob: 3e6fc4d2594f8a5ffeca2cf231f137193edd1d54 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import '../../sledge_errors.dart';
import '../change.dart';
import 'compressor.dart';
import 'converted_change.dart';
import 'key_value.dart';
/// Interface for converting T to Uint8List and back.
abstract class Converter<T> {
/// Returns a converter of type T.
factory Converter() {
final result = _converters[T];
if (result == null) {
throw new InternalSledgeError('No converter found for type `$T`.');
}
return result;
}
/// Returns default value for type T.
T get defaultValue;
/// Converts from Uint8List to T.
T deserialize(final Uint8List x);
/// Converts from T to Uint8List.
Uint8List serialize(final T x);
}
/// Class for converting Map<K, V> to List<KeyValue> and back.
class MapToKVListConverter<K, V> {
final Converter<K> _keyConverter;
final Converter<V> _valueConverter;
final Compressor _compressor = new Compressor();
/// Constructor.
MapToKVListConverter({Converter<K> keyConverter, Converter<V> valueConverter})
: _keyConverter = keyConverter ?? new Converter<K>(),
_valueConverter = valueConverter ?? new Converter<V>();
/// Converts from List<KeyValue> to Map<K, V>.
ConvertedChange<K, V> deserialize(final Change _input) {
final input = _uncompressKeysInChange(_input);
final result = new ConvertedChange<K, V>();
for (var keyValue in input.changedEntries) {
result.changedEntries[_keyConverter.deserialize(keyValue.key)] =
_valueConverter.deserialize(keyValue.value);
}
for (var key in input.deletedKeys) {
result.deletedKeys.add(_keyConverter.deserialize(key));
}
return result;
}
/// Converts from Map<K, V> to List<KeyValue>.
Change serialize(final ConvertedChange<K, V> input) {
final Change result = new Change();
for (final changedEntry in input.changedEntries.entries) {
result.changedEntries.add(new KeyValue(
_keyConverter.serialize(changedEntry.key),
_valueConverter.serialize(changedEntry.value)));
}
for (var key in input.deletedKeys) {
result.deletedKeys.add(_keyConverter.serialize(key));
}
return _compressKeysInChange(result);
}
/// Restores (key, value)s from stored in Ledger state.
Change _uncompressKeysInChange(final Change input) {
Change result = new Change();
for (final entry in input.changedEntries) {
result.changedEntries.add(_compressor.uncompressKeyInEntry(entry));
}
for (final key in input.deletedKeys) {
result.deletedKeys.add(_compressor.uncompressKey(key));
}
return result;
}
/// Prepares (key, value)s to store in Ledger. (getting rid of long keys)
Change _compressKeysInChange(final Change input) {
Change result = new Change();
for (final entry in input.changedEntries) {
result.changedEntries.add(_compressor.compressKeyInEntry(entry));
}
for (var key in input.deletedKeys) {
result.deletedKeys.add(_compressor.compressKey(key));
}
return result;
}
}
/// Converter for string.
class StringConverter implements Converter<String> {
/// Constructor.
const StringConverter();
@override
String get defaultValue => '';
@override
String deserialize(final Uint8List x) {
if (x.length.isOdd) {
throw new InternalSledgeError(
'Cannot parse String. Length should be even.');
}
return new String.fromCharCodes(
x.buffer.asUint16List(x.offsetInBytes, x.lengthInBytes ~/ 2));
}
@override
Uint8List serialize(final String x) {
return new Uint16List.fromList(x.codeUnits).buffer.asUint8List();
}
}
/// Converter for Int.
class IntConverter implements Converter<int> {
/// Constructor.
const IntConverter();
static const int _serializationLength = 8;
@override
int get defaultValue => 0;
@override
int deserialize(final Uint8List x) {
if (x.length != _serializationLength) {
throw new InternalSledgeError(
"Can't parse int: Length should be $_serializationLength, "
'found ${x.length} instead for input: `$x`.');
}
return x.buffer.asByteData().getInt64(x.offsetInBytes);
}
@override
Uint8List serialize(final int x) =>
new Uint8List(_serializationLength)..buffer.asByteData().setInt64(0, x);
}
/// Converter for double.
class DoubleConverter implements Converter<double> {
/// Constructor.
const DoubleConverter();
static const int _serializationLength = 8;
@override
double get defaultValue => 0.0;
@override
double deserialize(final Uint8List x) {
if (x.length != _serializationLength) {
throw new InternalSledgeError(
"Can't parse double: Length should be $_serializationLength, "
'found ${x.length} instead for input: `$x`.');
}
return x.buffer.asByteData().getFloat64(x.offsetInBytes);
}
@override
Uint8List serialize(final double x) =>
new Uint8List(_serializationLength)..buffer.asByteData().setFloat64(0, x);
}
/// Converter for bool.
class BoolConverter implements Converter<bool> {
/// Constructor.
const BoolConverter();
@override
bool get defaultValue => false;
@override
bool deserialize(final Uint8List x) {
if (x.lengthInBytes != 1) {
throw new InternalSledgeError(
"Can't parse bool. Length should be 1,"
'found ${x.lengthInBytes} bytes instead.');
}
if (x[0] != 0 && x[0] != 1) {
throw new InternalSledgeError(
"Can't parse bool. Value should be 0 or 1, "
'found ${x[0]} bytes instead.');
}
return x[0] == 1;
}
@override
// ignore: avoid_positional_boolean_parameters
Uint8List serialize(final bool x) {
return new Uint8List.fromList([x ? 1 : 0]);
}
}
/// Converter for Uint8List.
class Uint8ListConverter implements Converter<Uint8List> {
/// Constructor.
const Uint8ListConverter();
@override
Uint8List get defaultValue => new Uint8List(0);
@override
Uint8List deserialize(final Uint8List x) => x;
@override
Uint8List serialize(final Uint8List x) => x;
}
enum _NumInternalType { _int, _double }
/// Converter for num.
class NumConverter implements Converter<num> {
/// Constructor.
const NumConverter();
@override
num get defaultValue => 0;
// The length of the serialization:
// 1 byte for the type of the number (_int or _double)
//
// 8 bytes for number itself (64 bit integer or 64 bit double).
static const int _serializationLength = 9;
@override
num deserialize(final Uint8List x) {
if (x.length != _serializationLength) {
throw new InternalSledgeError(
"Can't parse double: Length should be $_serializationLength, "
'found ${x.length} instead for input: `$x`.');
}
if (x[0] >= _NumInternalType.values.length) {
throw new InternalSledgeError(
"Can't parse double: First byte should be 0 or 1, "
'found ${x[0]} instead.');
}
_NumInternalType type = _NumInternalType.values[x[0]];
num value;
switch (type) {
case _NumInternalType._int:
value = x.buffer.asByteData().getInt64(x.offsetInBytes + 1);
break;
case _NumInternalType._double:
value = x.buffer.asByteData().getFloat64(x.offsetInBytes + 1);
break;
}
return value;
}
@override
Uint8List serialize(final num x) {
final list = new Uint8List(_serializationLength);
final byteData = list.buffer.asByteData();
if (x.runtimeType == int) {
byteData
..setInt8(0, _NumInternalType._int.index)
..setInt64(1, x);
} else {
byteData
..setInt8(0, _NumInternalType._double.index)
..setFloat64(1, x);
}
return list;
}
}
const _converters = const <Type, Converter>{
int: const IntConverter(),
String: const StringConverter(),
double: const DoubleConverter(),
bool: const BoolConverter(),
Uint8List: const Uint8ListConverter(),
num: const NumConverter(),
};