Created
July 13, 2014 21:14
-
-
Save vsavkin/ba34e2c13655c3afecda to your computer and use it in GitHub Desktop.
hammock.mapper spike
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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