namespace Invercargill { public delegate TProp PropertyGetter(TClass object) throws Error; public delegate bool PropertyPredicate(TClass object); public delegate void PropertySetter(TClass object, TProp value) throws Error; public delegate void PropertyDefaultSetter(TClass object); public delegate Tout PropertyGetterTransformer(Tin object); public delegate Tout PropertySetterTransformer(Tin object) throws Error; public delegate T ObjectConstructor(); public class PropertyMapper : Mapper { private Vector> mappings; private ObjectConstructor constructor; public static PropertyMapper build_for(Func> func) { int location = (int)func; return keyed_once>(location, () => { var builder = new PropertyMapperBuilder(); func(builder); return builder.build(); }); } internal PropertyMapper(Vector> mappings, ObjectConstructor constructor) { this.mappings = mappings; this.constructor = constructor; } public void map_into(T object, ReadOnlyAssociative properties) throws Error { foreach (var mapping in mappings) { var exists = properties.has(mapping.name); if(mapping.undefined_setter != null && !exists) { mapping.undefined_setter(object); continue; } if(mapping.undefined_setter == null && !exists){ throw new IndexError.KEY_NOT_FOUND(@"Failed to map into $(typeof(T).name()): Property \"$(mapping.name)\" does not have an undefined setter and no matching property was found"); } var element = properties[mapping.name]; if(mapping.null_setter != null && element.is_null()) { mapping.null_setter(object); continue; } if(mapping.null_setter == null && element.is_null()) { throw new ElementError.INVALID_CONVERSION(@"Failed to map into $(typeof(T).name()): Property \"$(mapping.name)\" does not have a null setter and a null value was found"); } mapping.setter(object, properties[mapping.name]); } } public override T materialise(Properties properties) throws Error { var obj = constructor(); map_into(obj, properties); return obj; } public override Properties map_from(T object) throws Error { var properties = new PropertiesDictionary(); foreach (var mapping in mappings) { // Check undefined if(mapping.undefined_check != null && mapping.undefined_check(object)){ continue; } // Check null if(mapping.null_check != null && mapping.null_check(object)) { properties[mapping.name] = new NullElement(); continue; } // Assign value var val = mapping.getter(object); properties[mapping.name] = val; } return properties; } } private class PropertyMapping { public string name; public PropertyGetter getter; public PropertySetter setter; public PropertyPredicate? null_check; public PropertyPredicate? undefined_check; public PropertyDefaultSetter? null_setter; public PropertyDefaultSetter? undefined_setter; } public class PropertyMapperBuilder { private Vector> mappings; private ObjectConstructor constructor; public PropertyMapperBuilder() { mappings = new Vector>(); constructor = () => Object.new(typeof(T)); } public virtual PropertyMappingBuilder map(string name, owned PropertyGetter getter, owned PropertySetter setter) { PropertyMapping mapping; if(typeof(TProp).is_a(typeof(Element))) { mapping = new PropertyMapping() { name = name, getter = (owned)getter, setter = (owned)setter, }; } else { mapping = new PropertyMapping() { name = name, getter = (o) => new NativeElement(getter(o)), setter = (o,d) => setter(o, d.as()) }; } add_mapping(mapping); return new PropertyMappingBuilder(this, mapping); } public virtual PropertyMappingBuilder map_properties_with(string name, owned PropertyGetter getter, owned PropertySetter setter, PropertyMapper mapper) { return map_with(name, getter, setter, mapper); } public virtual PropertyMappingBuilder map_with(string name, owned PropertyGetter getter, owned PropertySetter setter, Mapper mapper) { var mapping = new PropertyMapping() { name = name, getter = (o) => new NativeElement(mapper.map_from(getter(o))), setter = (o,d) => setter(o, mapper.materialise(d.as())) }; add_mapping(mapping); return new PropertyMappingBuilder(this, mapping); } public virtual PropertyMappingBuilder map_many(string name, owned PropertyGetter> getter, owned PropertySetter> setter) { var mapping = new PropertyMapping() { name = name, getter = (o) => new NativeElement(getter(o).to_elements()), setter = (o,d) => setter(o, d.as().elements_as()) }; add_mapping(mapping); return new PropertyMappingBuilder(this, mapping);; } public virtual PropertyMappingBuilder map_property_groups_with(string name, owned PropertyGetter> getter, owned PropertySetter> setter, PropertyMapper mapper) { return map_many_with(name, getter, setter, mapper); } public virtual PropertyMappingBuilder map_many_with(string name, owned PropertyGetter> getter, owned PropertySetter> setter, Mapper mapper) { var mapping = new PropertyMapping() { name = name, getter = (o) => new NativeElement(getter(o).attempt_select(i => mapper.map_from(i)).to_elements()), setter = (o,d) => { var collection = new Series(); foreach (var properties in d.as().elements_as()) { collection.add(mapper.materialise(properties)); } setter(o, collection.seal()); } }; return new PropertyMappingBuilder(this, mapping);; } public virtual PropertyMapperBuilder set_constructor(ObjectConstructor constructor) { this.constructor = constructor; return this; } public virtual PropertyMapper build() { return new PropertyMapper(mappings, constructor); } private void add_mapping(PropertyMapping mapping) { mappings.add(mapping); } } public class PropertyMappingBuilder : PropertyMapperBuilder { private PropertyMapperBuilder inner; private PropertyMapping mapping; internal PropertyMappingBuilder(PropertyMapperBuilder builder, PropertyMapping mapping) { inner = builder; this.mapping = mapping; } public PropertyMappingBuilder null_when(owned PropertyPredicate predicate) { mapping.null_check = predicate; return this; } public PropertyMappingBuilder undefined_when(owned PropertyPredicate predicate) { mapping.undefined_check = predicate; return this; } public PropertyMappingBuilder when_null(owned PropertyDefaultSetter setter) { mapping.null_setter = setter; return this; } public PropertyMappingBuilder when_undefined(owned PropertyDefaultSetter setter) { mapping.undefined_setter = setter; return this; } public override PropertyMappingBuilder map(string name, owned PropertyGetter getter, owned PropertySetter setter) { return inner.map(name, (owned)getter, (owned)setter); } public override PropertyMappingBuilder map_properties_with(string name, owned PropertyGetter getter, owned PropertySetter setter, PropertyMapper mapper) { return inner.map_properties_with(name, (owned)getter, (owned)setter, mapper); } public override PropertyMappingBuilder map_with(string name, owned PropertyGetter getter, owned PropertySetter setter, Mapper mapper) { return inner.map_with(name, (owned)getter, (owned)setter, mapper); } public override PropertyMappingBuilder map_many(string name, owned PropertyGetter> getter, owned PropertySetter> setter) { return inner.map_many(name, (owned)getter, (owned)setter); } public override PropertyMappingBuilder map_property_groups_with(string name, owned PropertyGetter> getter, owned PropertySetter> setter, PropertyMapper mapper) { return inner.map_property_groups_with(name, getter, setter, mapper); } public override PropertyMappingBuilder map_many_with(string name, owned PropertyGetter> getter, owned PropertySetter> setter, Mapper mapper) { return inner.map_many_with(name, (owned)getter, (owned)setter, mapper); } public override PropertyMapperBuilder set_constructor(ObjectConstructor constructor) { return inner.set_constructor(constructor); } public override PropertyMapper build() { return inner.build(); } } }