Skip to content

Instantly share code, notes, and snippets.

@vsavkin
Created July 13, 2014 21:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vsavkin/ba34e2c13655c3afecda to your computer and use it in GitHub Desktop.
Save vsavkin/ba34e2c13655c3afecda to your computer and use it in GitHub Desktop.
hammock.mapper spike
library mapper;
import 'package:hammock/hammock_types.dart';
import 'dart:mirrors';
//----------------------------
//----------- META -----------
//----------------------------
class Mappable {
final Symbol constructor;
const Mappable({this.constructor: const Symbol("")});
}
//----------------------------
//----- CORE INTERFACES ------
//----------------------------
abstract class Mapper<T> {
toData(T t);
T fromData(data);
}
typedef Instantiator(Type type, Symbol constructor, id);
//----------------------------
//------ INSTANTIATORS -------
//----------------------------
Object simpleInstantiator(Type type, Symbol constructor, id) =>
reflectClass(type).newInstance(constructor, []).reflectee;
class IdentityMapInstantiator {
final Map<Type, Map> instances = {};
Object call(Type type, Symbol constructor, id) {
instances.putIfAbsent(type, () => {});
final instMap = instances[type];
instMap.putIfAbsent(id, () => simpleInstantiator(type, constructor, id));
return instMap[id];
}
}
//----------------------------
//--------- MAPPERS ----------
//----------------------------
class _MappableObjectToData {
final Mappers ms;
final obj;
Type type;
ClassMirror cm;
InstanceMirror im;
_MappableObjectToData(this.ms, this.obj) {
type = obj.runtimeType;
cm = reflectClass(type);
im = reflect(obj);
}
call() {
return publicFields.fold({}, (content, field) {
final name = MirrorSystem.getName(field.simpleName);
content[name] = ms.toData(im.getField(field.simpleName).reflectee);
return content;
});
}
get publicFields => cm.instanceMembers.values
.where((m) => m.isGetter && !m.isPrivate && m.isSynthetic)
.where((m) => m.simpleName != #hashCode && m.simpleName != #runtimeType);
}
class _UpdateMappableObject {
Mappers ms;
var obj;
dynamic data;
Type type;
ClassMirror cm;
InstanceMirror im;
_UpdateMappableObject(this.ms, this.obj, this.data) {
type = obj.runtimeType;
cm = reflectClass(type);
im = reflect(obj);
}
call() {
publicFields.forEach((field) {
var name = MirrorSystem.getName(field.simpleName);
name = name.substring(0, name.length - 1);
if (data.containsKey(name)) {
final setterTypeMirror = field.parameters.first.type;
final setterType = setterTypeMirror.reflectedType;
im.setField(new Symbol(name), ms._fromData(setterType, data[name], setterTypeMirror));
}
});
}
get publicFields => cm.instanceMembers.values.where((m) => m.isSetter && !m.isPrivate);
}
class _ScopedMappers {
Mappers _hammockMapper;
Type _type;
_ScopedMappers(this._hammockMapper, this._type);
toData(obj) => _hammockMapper.toData(obj);
fromData(data) => _hammockMapper.fromData(_type, data);
}
class Mappers {
final _globalMappers = {};
var instantiator = simpleInstantiator;
void registerMapper(Type type, Mapper mapper) {
_globalMappers[type] = mapper;
}
toData(obj) {
final type = obj.runtimeType;
listToData() => obj.map(toData).toList();
if (obj == null) return null;
if (obj is List) return listToData();
if (_globalMappers.containsKey(type)) return _globalMappers[type].toData(obj);
if (_mappable(obj.runtimeType)) return new _MappableObjectToData(this, obj)();
return obj;
}
fromData(Type type, data) {
return _fromData(type, data, reflectClass(type));
}
updateMappableObject(obj, data) {
new _UpdateMappableObject(this, obj, data)();
}
_fromData(Type type, data, ClassMirror classMirror) {
listToObjects() {
final tm = classMirror.typeArguments.first;
return data.map((d) => fromData(tm.reflectedType, d)).toList();
}
if (data == null) return null;
if (data is List) return listToObjects();
if (_globalMappers.containsKey(type)) return _globalMappers[type].fromData(data);
if (_mappable(type)) {
final obj = instantiator(type, _constructor(type), data["id"]);
updateMappableObject(obj, data);
return obj;
}
return data;
}
Mapper mapperFor(Type type) =>
_globalMappers.containsKey(type) ?
_globalMappers[type] :
new _ScopedMappers(this, type);
bool _mappable(type) {
final cm = reflectClass(type);
return cm.metadata.map((m) => m.reflectee).any((m) => m is Mappable);
}
Symbol _constructor(type) {
final cm = reflectClass(type);
final mappable = cm.metadata.map((m) => m.reflectee).firstWhere((m) => m is Mappable);
return mappable.constructor;
}
}
//--------------------------------
//-- HAMMOCK ADAPTER AND CONFIG --
//--------------------------------
class HammockAdapter {
Mappers ms;
String resourceType;
Type type;
HammockAdapter(this.ms, this.resourceType, this.type);
Resource serialize(obj) {
return resource(resourceType, obj.id, ms.toData(obj));
}
Object deserialize(Resource res) {
return ms.fromData(type, res.content);
}
Object update(obj, CommandResponse resp) {
ms.updateMappableObject(obj, resp.content);
return obj;
}
}
class HammockConfigurationBuilder {
Mappers ms;
final List adapters = [];
HammockConfigurationBuilder() {
ms = new Mappers();
ms.instantiator = new IdentityMapInstantiator();
}
HammockConfigurationBuilder resource(String resourceType, Type type) {
adapters.add(new HammockAdapter(ms, resourceType, type));
return this;
}
HammockConfigurationBuilder mapper(Type type, Mapper m) {
ms.registerMapper(type, m);
return this;
}
Map createHammockConfig() {
return adapters.fold({}, (config, adapter) {
config[adapter.resourceType] = {
"type": adapter.type,
"serializer" : adapter.serialize,
"deserializer" : {
"query" : adapter.deserialize,
"commands" : adapter.update
}
};
return config;
});
}
}
mappers() => new HammockConfigurationBuilder();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment