Преглед изворни кода

Begin development of InvercargillJson, add Properties and PropertyMapping system

Billy Barrow пре 11 месеци
родитељ
комит
113e54b6af

+ 44 - 0
src/json/Json.vala

@@ -0,0 +1,44 @@
+
+namespace InvercargillJson {
+
+
+    public interface JsonGLibObjectWrapper<T> {
+        public abstract T json_glib_object { get; }
+    }
+
+    public class JsonDatum : Object, Invercargill.Element {
+
+        private Json.Node node;
+
+        public bool assignable_to_type (GLib.Type type) {
+            if(node.get_node_type() == Json.NodeType.ARRAY) {
+                return type.is_a (typeof(JsonArray)) || type.is_a (typeof(Json.Array));
+            }
+            if(node.get_node_type() == Json.NodeType.ARRAY) {
+                return type.is_a (typeof(JsonArray)) || type.is_a (typeof(Json.Array));
+            }
+            if(node.get_value_type().is_a(type)) {
+                return true;
+            }
+            return false;
+        }
+        public bool is_null () {
+            return node.is_null();
+        }
+        public bool try_get_as<T> (out T result) {
+            if(typeof(T) == typeof(Json.Node)) {
+                result = node;
+                return true;
+            }
+
+
+            result = null;
+            return false;
+        }
+        public GLib.Type? type () {
+            return typeof(Json.Node);
+        }
+
+    }
+
+}

+ 57 - 0
src/json/JsonArray.vala

@@ -0,0 +1,57 @@
+
+namespace InvercargillJson {
+
+    public class JsonArray : Invercargill.IndexedCollection<Json.Node>, JsonNodes, JsonGLibObjectWrapper<Json.Array> {
+
+        private Json.Array array;
+
+        public Json.Array json_glib_object { get { return array; } }
+
+        public JsonArray() {
+            array = new Json.Array();
+        }
+
+        public JsonArray.from_existing(Json.Array array) {
+            this.array = array;
+        }
+
+        public override Invercargill.Tracker<Json.Node> get_tracker() {
+            var i = 0;
+            return new Invercargill.LambdaTracker<Json.Node>(
+                () => {
+                    return i < array.get_length();
+                },
+                () => {
+                    return array.get_element(i++);
+                });
+        }
+
+        public new override Json.Node @get(int index) {
+            return array.get_element(index);
+        }
+        public override int index_of(Invercargill.PredicateDelegate<Json.Node> predicate) {
+            return with_positions().first_or_default(i => predicate(i.item))?.position ?? -1;
+        }
+        public override void remove(int index) {
+            array.remove_element(index);
+        }
+        public new override void @set(int index, Json.Node item) {
+            assert_not_reached();
+        }
+        public override void add(Json.Node item) {
+            array.add_element(item);
+        }
+        public override void remove_first_where(Invercargill.PredicateDelegate<Json.Node> predicate) {
+            var index = index_of(predicate);
+            if(index >= 0) {
+                array.remove_element(index);
+            }
+        }
+        public override void remove_where(Invercargill.PredicateDelegate<Json.Node> predicate) {
+            with_positions()
+                .where(i => predicate(i.item))
+                .iterate(i => array.remove_element(i.position));
+        }
+    }
+
+}

+ 50 - 0
src/json/JsonNodes.vala

@@ -0,0 +1,50 @@
+
+using Invercargill;
+namespace InvercargillJson {
+
+    public interface JsonNodes : Enumerable<Json.Node> {
+
+        public virtual Enumerable<JsonArray> as_arrays() {
+            return select<JsonArray>(n => new JsonArray.from_existing(n.get_array()));
+        }
+
+        public virtual Enumerable<bool> as_bools() {
+            return select<bool>(n => n.get_boolean());
+        }
+
+        public virtual Doubles as_doubles() {
+            return select<double?>(n => n.get_double()).promote_to<Doubles>();
+        }
+
+        public virtual Signed64BitIntegers as_integers() {
+            return select<int64?>(n => n.get_int()).promote_to<Signed64BitIntegers>();
+        }
+
+        public virtual Enumerable<string> as_strings() {
+            return select<string>(n => n.get_string());
+        }
+
+        public virtual Enumerable<JsonObject> as_objects() {
+            return select<JsonObject>(n => new JsonObject.from_existing(n.get_object()));
+        }
+
+        public virtual JsonArray to_json_array() {
+            var array = new JsonArray();
+            array.add_all(this);
+            return array;
+        }
+
+    }
+
+    private class JsonNodesPromotionImplementation : ProxyEnumerable<Json.Node>, JsonNodes, Promotion<Json.Node> {
+        public bool can_wrap(GLib.Type element_type) {
+            return element_type.is_a(typeof(Json.Node));
+        }
+        public Enumerable<Json.Node> wrap(Enumerable<Json.Node> enumerable) {
+            var wrapped = new JsonNodesPromotionImplementation();
+            wrapped.inner = enumerable;
+            return wrapped;
+        }
+    }
+
+}

+ 81 - 0
src/json/JsonObject.vala

@@ -0,0 +1,81 @@
+using Invercargill;
+
+namespace InvercargillJson {
+
+    public class JsonObject : Associative<string, Json.Node>, JsonGLibObjectWrapper<Json.Object> {
+
+        private Json.Object object;
+
+        public Json.Object json_glib_object { get { return object; } }
+
+        public JsonObject() {
+            object = new Json.Object();
+        }
+
+        public JsonObject.from_existing(Json.Object object) {
+            this.object = object;
+        }
+        public override void clear (string key) {
+            object.foreach_member ((o, n) => o.remove_member (n));
+        }
+        public override bool has (string key) {
+            return object.has_member(key);
+        }
+        public new override void set (string key, Json.Node value) {
+            object.set_member(key, value);
+        }
+        public override bool try_get (string key, out Json.Node value) {
+            if(has(key)) {
+                value = object.get_member(key);
+                return true;
+            }
+            value = null;
+            return false;
+        }
+        public override Tracker<KeyValuePair<string, Json.Node>> get_tracker () {
+            var keys = object.get_members ();
+            var i = 0;
+            return new LambdaTracker<KeyValuePair<string, Json.Node>>(
+                () => {
+                    return i < keys.length();
+                },
+                () => {
+                    return new KeyValuePair<string, Json.Node> (keys.nth_data(i), object.get_member(keys.nth_data(i)));
+                });
+        }
+
+        public JsonArray? get_array(string key) {
+            var item = object.get_array_member(key);
+            if(item != null) {
+                return new JsonArray.from_existing(item);
+            }
+            return null;
+        }
+
+        public bool? get_bool(string key) {
+            return object.get_boolean_member(key);
+        }
+
+        public double? get_double(string key) {
+            return object.get_double_member(key);
+        }
+
+        public int64? get_integer(string key) {
+            return object.get_int_member(key);
+        }
+
+        public string? get_string(string key) {
+            return object.get_string_member(key);
+        }
+
+        public JsonObject? get_object(string key) {
+            var item = object.get_object_member(key);
+            if(item != null) {
+                return new JsonObject.from_existing(item);
+            }
+            return null;
+        }
+
+    }
+
+}

+ 10 - 0
src/json/PromotionRegistration.c

@@ -0,0 +1,10 @@
+
+#include "invercargill-json.h"
+#include "invercargill.h"
+
+VALA_EXTERN GType invercargill_json_json_nodes_promotion_implementation_get_type (void) G_GNUC_CONST ;
+
+__attribute__((constructor))
+static void register_promotions() {
+    invercargill_register_promotion (INVERCARGILL_JSON_TYPE_JSON_NODES, invercargill_json_json_nodes_promotion_implementation_get_type());
+}

+ 35 - 0
src/json/meson.build

@@ -0,0 +1,35 @@
+
+dependencies = [
+    dependency('glib-2.0'),
+    dependency('gobject-2.0'),
+    dependency('gee-0.8'),
+    dependency('json-glib-1.0'),
+    invercargill_dep
+]
+
+sources = files('Json.vala')
+sources += files('PromotionRegistration.c')
+sources += files('JsonNodes.vala')
+sources += files('JsonArray.vala')
+sources += files('JsonObject.vala')
+
+invercargill_json = shared_library('invercargill-json', sources,
+    dependencies: dependencies,
+    install: true,
+    vala_gir: 'invercargill_json-1.0.gir',
+    install_dir: [true, true, true, true]
+)
+invercargill_json_dep = declare_dependency(link_with: invercargill_json, include_directories: include_directories('.'))
+
+pkg = import('pkgconfig')
+pkg.generate(invercargill_json,
+    version : '0.1',
+    name : 'invercargill-json',)
+    
+g_ir_compiler = find_program('g-ir-compiler')
+custom_target('invercargill-json typelib', command: [g_ir_compiler, '--shared-library=libinvercargill-json.so', '--output', '@OUTPUT@', meson.current_build_dir() / 'invercargill_json-1.0.gir'],
+              output: 'invercargill_json-1.0.typelib',
+              depends: invercargill_json,
+              install: true,
+              install_dir: get_option('libdir') / 'girepository-1.0')
+              

+ 2 - 1
src/lib/Associative/Associative.vala

@@ -1,10 +1,11 @@
 namespace Invercargill {
 
-    public abstract class Associative<TKey, TValue> : Collection<KeyValuePair<TKey, TValue>> {
+    public abstract class Associative<TKey, TValue> : Collection<KeyValuePair<TKey, TValue>>, KeyValues<TKey, TValue> {
 
         public abstract new void @set(TKey key, TValue value);
         public abstract bool try_get(TKey key, out TValue value);
         public abstract void clear(TKey key); 
+        public abstract bool has(TKey key);
         
         public virtual TValue? get_or_default(TKey key) {
             TValue val;

+ 6 - 1
src/lib/Associative/Dictionary.vala

@@ -2,7 +2,7 @@ using Invercargill.Convert;
 
 namespace Invercargill {
 
-    public class Dictionary<TKey, TValue> : Associative<TKey, TValue> {
+    public class Dictionary<TKey, TValue> : Associative<TKey, TValue>, KeyValues<TKey, TValue> {
 
         private HashTable<TKey, TValue> hash_table;
 
@@ -78,6 +78,11 @@ namespace Invercargill {
         public override Tracker<KeyValuePair<TKey, TValue>> get_tracker () {
             return new DictionaryTracker<TKey, TValue> (this);
         }
+        public override bool has (TKey key) {
+            lock(hash_table) {
+                return hash_table.contains (key);
+            }
+        }
 
         private class DictionaryTracker<TKey, TValue> : Tracker<KeyValuePair<TKey, TValue>> {
 

+ 3 - 0
src/lib/Associative/KeyValuePair.vala

@@ -10,6 +10,9 @@ namespace Invercargill {
             this.value = value;
         }
 
+        public Type key_type { get { return typeof(TKey); }}
+        public Type value_type { get { return typeof(TValue); }}
+
     }
 
 }

+ 14 - 0
src/lib/Associative/KeyValues.vala

@@ -0,0 +1,14 @@
+
+namespace Invercargill {
+
+    public interface KeyValues<TKey, TValue> : Enumerable<KeyValuePair<TKey, TValue>> {
+
+        public abstract bool try_get(TKey key, out TValue value);
+        public abstract bool has(TKey key);
+        public abstract new TValue @get(TKey key) throws IndexError;
+        public abstract TValue? get_or_default(TKey key);
+
+
+    }
+
+}

+ 10 - 0
src/lib/Associative/Properties.vala

@@ -0,0 +1,10 @@
+
+namespace Invercargill {
+
+    public interface Properties : Enumerable<KeyValuePair<string, Element>>, KeyValues<string, Element> {
+    }
+
+    private class DictionaryProperties : Dictionary<string, Element>, Properties {
+    }
+
+}

+ 95 - 0
src/lib/Associative/PropertyMapper.vala

@@ -0,0 +1,95 @@
+namespace Invercargill {
+
+    public delegate TProp PropertyGetter<TClass, TProp>(TClass object);
+    public delegate void PropertySetter<TClass, TProp>(TClass object, TProp value) throws IndexError, ElementError;
+    public delegate T ObjectConstructor<T>();
+    public class PropertyMapper<T> {
+
+        private Vector<PropertyMapping<T>> mappings;
+        private ObjectConstructor<T> constructor;
+
+        internal PropertyMapper(Vector<PropertyMapping<T>> mappings, ObjectConstructor<T> constructor) {
+            this.mappings = mappings;
+            this.constructor = constructor;
+        }
+
+        public void map_into(T object, KeyValues<string, Element> properties) throws IndexError, ElementError {
+            foreach (var mapping in mappings) {
+                mapping.setter(object, properties[mapping.name]);
+            }
+        }
+
+        public T materialise(Properties properties) throws IndexError, ElementError {
+            var obj = constructor();
+            map_into(obj, properties);
+            return obj;
+        }
+
+        public Properties map_from(T object) {
+            var properties = new DictionaryProperties();
+            foreach (var mapping in mappings) {
+                properties[mapping.name] = mapping.getter(object);
+            }
+            return properties;
+        }
+
+    }
+
+    private class PropertyMapping<T> {
+        public string name;
+        public PropertyGetter<T, Element> getter;
+        public PropertySetter<T, Element> setter;
+    }
+
+    public class PropertyMapperBuilder<T> {
+
+        private Vector<PropertyMapping<T>> mappings;
+        private ObjectConstructor<T> constructor;
+
+        public PropertyMapperBuilder() {
+            mappings = new Vector<PropertyMapping<T>>();
+            constructor = () => Object.new(typeof(T));
+        }
+
+        public PropertyMapperBuilder<T> map<TProp>(string name, owned PropertyGetter<T, TProp> getter, owned PropertySetter<T, TProp> setter) {
+            if(typeof(TProp).is_a(typeof(Element))) {
+                add_mapping(new PropertyMapping<T>() {
+                    name = name,
+                    getter = (owned)getter,
+                    setter = (owned)setter
+                });
+            }
+            else {
+                add_mapping(new PropertyMapping<T>() {
+                    name = name,
+                    getter = (o) => new NativeElement<TProp>(getter(o)),
+                    setter = (o,d) => setter(o, d.as<TProp>())
+                });
+            }
+            return this;
+        }
+
+        public PropertyMapperBuilder<T> map_with<TObj>(string name, owned PropertyGetter<T, TObj> getter, owned PropertySetter<T, TObj> setter, PropertyMapper<TObj> mapper) {
+            add_mapping(new PropertyMapping<T>() {
+                name = name,
+                getter = (o) => new NativeElement<Properties>(mapper.map_from(getter(o))),
+                setter = (o,d) => setter(o, mapper.materialise(d.as<Properties>()))
+            });
+            return this;
+        }
+
+        public PropertyMapperBuilder<T> set_constructor(ObjectConstructor<T> constructor) {
+            this.constructor = constructor;
+            return this;
+        }
+
+        public PropertyMapper<T> build() {
+            return new PropertyMapper<T>(mappings, constructor);
+        }
+
+        private void add_mapping(PropertyMapping<T> mapping) {
+            mappings.add(mapping);
+        }
+    }
+
+}

+ 3 - 2
src/lib/Concrete/ProxyEnumerable.vala

@@ -202,11 +202,12 @@ namespace Invercargill {
         public override Enumerable<PositionItemPair<T>> with_positions() {
             return inner.with_positions();
         }
-
-        
         public override Set<T> to_set(HashFunc<T>? hash_func = null, EqualFunc<T>? equal_func = null) {
             return inner.to_set(hash_func, equal_func);
         }
+        public override Elements to_elements() {
+            return inner.to_elements();
+        }
 
     }
 

+ 0 - 0
src/lib/Converter.vala


+ 134 - 0
src/lib/Element.vala

@@ -0,0 +1,134 @@
+namespace Invercargill {
+    
+
+    // TODO: Reform Datum to have a type, and add specific to_x functions, with perhaps a to<T> convenience function.
+    // all of these can return errors
+    // int as_int()                             for type NUMBER
+    // int64 as_int64()                         for type NUMBER
+    // uint64 as_uint64()                       for type NUMBER
+    // ...
+    // string as_string()                       for type STRING or NUMBER
+    // Enumerable<Datum> as_enumerable()        for type ENUMERABLE
+    // KeyValues<Datum, Datum> as_keyvalues()   for type KEY_VALUES or PROPERTIES
+    // Properties as_properties()               for type PROPERTIES
+
+    // Add mappable interface with
+    // protected PropertyMapper
+    // public Properties to_properties()
+    // public void populate(Properties props)
+
+
+    public interface Elements : Enumerable<Element> {}
+    private class ElementSeries : Series<Element>, Elements {}
+
+    public enum ElementCategory {
+        NULL,
+        BOOL,
+        NUMBER,
+        STRING,
+        ELEMENTS,
+        KEY_VALUES,
+        PROPERTIES
+    }
+
+    public interface Element : Object {
+
+        public abstract bool assignable_to_type(Type type);
+        public abstract Type? type();
+        public abstract bool is_null();
+        public abstract bool try_get_as<T>(out T result);
+
+        public virtual bool is_type(Type type) {
+            return this.type().is_a(type);
+        }
+
+        public virtual bool assignable_to<T>() {
+            return assignable_to_type(typeof(T));
+        }
+
+        public virtual bool @is<T>() {
+            return is_type(typeof(T));
+        }
+
+        public virtual string type_name() {
+            return type().name();
+        }
+
+        public virtual T? @as<T>() throws ElementError {
+            T result;
+            if(try_get_as<T>(out result)) {
+                return result;
+            }
+            throw new ElementError.INVALID_CONVERSION(@"Could not convert from $(type_name()) to $(typeof(T).name()).");
+        }
+
+
+        public virtual string to_string() {
+            return @"Element[$(type_name())]";
+        }
+
+    }
+
+
+    public class NativeElement<T> : Object, Element {
+
+        private T object;
+
+        public NativeElement(T obj) {
+            object = obj;
+        }
+
+        public bool assignable_to_type(GLib.Type type) {
+            if(is_type(type)) {
+                return true;
+            }
+            if(typeof(T).is_a(typeof(UntypedEnumerable)) && type == typeof(Elements)) {
+                return true;
+            }
+            return false;
+        }
+
+        public GLib.Type? type() {
+            return typeof(T);
+        }
+
+        public bool is_null() {
+            return object == null;
+        }
+
+        public bool try_get_as<TOut>(out TOut result) {
+            if(typeof(T).is_a(typeof(UntypedEnumerable)) && typeof(TOut) == typeof(Elements)) {
+                result = (TOut)((UntypedEnumerable)object).to_elements();
+                return true;
+            }
+            if(assignable_to<TOut>()) {
+                result = (TOut)object;
+                return true;
+            }
+            result = null;
+            return false;
+        }
+    }
+
+    public class NullElement : Object, Element {
+
+        public bool assignable_to_type(GLib.Type type) {
+            return true;
+        }
+        public GLib.Type? type() {
+            return null;
+        }
+        public bool is_null() {
+            return true;
+        }
+        public bool try_get_as<T>(out T result) {
+            result = null;
+            return true;
+        }
+        public string to_string() {
+            return "Element[null]";
+        }
+
+    }
+
+}

+ 30 - 10
src/lib/Enumerable.vala

@@ -1,7 +1,18 @@
 
 namespace Invercargill {
 
-    public abstract class Enumerable<T> : Object {
+    public abstract class UntypedEnumerable : Object {
+        public abstract Type element_type { get; }
+
+        public abstract int count();
+        public abstract TPromotion try_promote_to<TPromotion>() throws PromotionError;
+        public abstract TPromotion promote_to<TPromotion>();
+        public abstract Object[] to_object_array() throws SequenceError;
+        public abstract Elements to_elements();
+
+    }
+
+    public abstract class Enumerable<T> : UntypedEnumerable {
 
         public abstract Tracker<T> get_tracker();
 
@@ -54,7 +65,7 @@ namespace Invercargill {
             return array;
         }
 
-        public virtual int count() {
+        public override int count() {
             var count = 0;
             iterate(i => count++);
             return count;
@@ -233,7 +244,7 @@ namespace Invercargill {
             return keys.select<Grouping<TKey, T>>(g => new Grouping<TKey, T>(g, this.where(i => key_equality(g, key_selector(i)))));
         }
 
-        public virtual Enumerable<T> with(T item) {
+        public virtual Enumerable<T> @with(T item) {
             var seq = new Series<T>();
             seq.add(item);
             return concat(seq);
@@ -316,7 +327,7 @@ namespace Invercargill {
             });
         }
 
-        public virtual Object[] to_object_array() throws SequenceError {
+        public override Object[] to_object_array() throws SequenceError {
             if(!typeof(T).is_object()) {
                 throw new SequenceError.INVALID_TYPE("Can only make an object array of an Enumerable<T> where T is derrived from GLib.Object");
             }
@@ -329,24 +340,27 @@ namespace Invercargill {
             return vector;
         }
 
-        public virtual Type element_type { get {
+        public override Type element_type { get {
             return typeof(T);
         }}
 
-        public virtual TPromotion try_promote_to<TPromotion>() throws PromotionError {
+        public override TPromotion try_promote_to<TPromotion>() throws PromotionError {
             var type = typeof(TPromotion);
             if(get_type().is_a(type)) {
                 // Don't promote if we are already target type
                 return this;
             }
+
+            resolve_promotion<TPromotion>(ref type);
+
             if(!type.is_instantiatable()) {
-                throw new PromotionError.INVALID_PROMOTION_TYPE(@"Provided promotion type $(type.name()) is not instansiatable.");
+                throw new PromotionError.INVALID_PROMOTION_TYPE(@"Promotion type $(type.name()) is not instansiatable.");
             }
             if(!type.is_a(typeof(Promotion))) {
-                throw new PromotionError.INVALID_PROMOTION_TYPE(@"Provided promotion type $(type.name()) does not implement Invercargill.Promotion.");
+                throw new PromotionError.INVALID_PROMOTION_TYPE(@"Promotion type $(type.name()) does not implement Invercargill.Promotion.");
             }
             if(!type.is_a(typeof(Enumerable))) {
-                throw new PromotionError.INVALID_PROMOTION_TYPE(@"Provided promotion type $(type.name()) does not implement Invercargill.Enumerable.");
+                throw new PromotionError.INVALID_PROMOTION_TYPE(@"Promotion type $(type.name()) does not inherit from Invercargill.Enumerable.");
             }
 
             var promotion = Object.new(type);
@@ -357,7 +371,7 @@ namespace Invercargill {
             return ((Promotion)promotion).wrap(this);
         }
 
-        public virtual TPromotion promote_to<TPromotion>() {
+        public override TPromotion promote_to<TPromotion>() {
             try {
                 return try_promote_to<TPromotion>();
             }
@@ -398,6 +412,12 @@ namespace Invercargill {
             return new PositionQuery<T>(this);
         }
 
+        public override Elements to_elements() {
+            var series = new ElementSeries();
+            series.add_all(select<Element>(i => new NativeElement<T>(i)));
+            return series;
+        }
+
         private PredicateDelegate<T> resolve_nullable_predicate(PredicateDelegate<T>? predicate) {
             if(predicate == null) {
                 return (p) => true;

+ 4 - 0
src/lib/Errors.vala

@@ -20,4 +20,8 @@ namespace Invercargill {
         INVALID_PROMOTION_TYPE,
         INCOMPATIBLE_ELEMENT_TYPE
     }
+
+    public errordomain ElementError {
+        INVALID_CONVERSION,
+    }
 }

+ 21 - 2
src/lib/Promotion.vala

@@ -1,8 +1,27 @@
 namespace Invercargill {
-    
+
+    private static Dictionary<Type, Type> promotion_registry = null;
+
+    public static void register_promotion(Type generic, Type promotion) {
+        if(promotion_registry == null) {
+            promotion_registry = new Dictionary<Type, Type>();
+        }   
+        promotion_registry[generic] = promotion;
+    }
+
+    public static void resolve_promotion<T>(ref Type type) {
+        if(promotion_registry == null) {
+            return;
+        }
+        Type promotion;
+        if(promotion_registry.try_get(typeof(T), out promotion)) {
+            type = promotion;
+        }
+    }
+
     public interface Promotion<T> {
 
-        public abstract Enumerable<T> wrap(Enumerable<T> enumerable);
+        public abstract Enumerable<T> wrap(Enumerable<T> enumerable) throws PromotionError;
 
         public abstract bool can_wrap(Type element_type);
     }

+ 5 - 0
src/lib/meson.build

@@ -19,6 +19,8 @@ sources += files('Safety.vala')
 sources += files('Promotion.vala')
 sources += files('Grouping.vala')
 sources += files('Interfaces.vala')
+sources += files('Element.vala')
+sources += files('Converter.vala')
 
 sources += files('Queries/Query.vala')
 sources += files('Queries/Transform.vala')
@@ -59,6 +61,9 @@ sources += files('Associative/Associative.vala')
 sources += files('Associative/KeyValuePair.vala')
 sources += files('Associative/Dictionary.vala')
 sources += files('Associative/Index.vala')
+sources += files('Associative/KeyValues.vala')
+sources += files('Associative/Properties.vala')
+sources += files('Associative/PropertyMapper.vala')
 
 invercargill = shared_library('invercargill', sources,
     dependencies: dependencies,

+ 2 - 0
src/meson.build

@@ -1,6 +1,8 @@
 project('invercargill', 'vala', 'c')
 subdir('lib')
 dependencies += invercargill_dep
+subdir('json')
+dependencies += invercargill_json_dep
 subdir('tests')
 
 # dependencies += dependency('fuse')

+ 29 - 0
src/tests/Integration/Promotion.vala

@@ -1,5 +1,7 @@
 using Invercargill;
 using Invercargill.Convert;
+using InvercargillJson;
+using Json;
 
 void promotion_tests() {
 
@@ -24,4 +26,31 @@ void promotion_tests() {
         assert_true(data == new_data);
     });
 
+    Test.add_func("/invercargill/promotions/json", () => {
+
+        var json_object_array = new Json.Node[] {
+            new_string_node("Glengarry"),
+            new_string_node("Gladstone"),
+            new_string_node("Strathern"),
+            new_string_node("Richmond"),
+            new_string_node("Georgetown"),
+        };
+
+        var nodes = ate(json_object_array).promote_to<JsonNodes>();
+        var suburbs = nodes.as_strings().to_array();
+        
+        assert_cmpint(5, CompareOperator.EQ, suburbs.length);
+        assert_cmpstr("Glengarry", CompareOperator.EQ, suburbs[0]);
+        assert_cmpstr("Gladstone", CompareOperator.EQ, suburbs[1]);
+        assert_cmpstr("Strathern", CompareOperator.EQ, suburbs[2]);
+        assert_cmpstr("Richmond", CompareOperator.EQ, suburbs[3]);
+        assert_cmpstr("Georgetown", CompareOperator.EQ, suburbs[4]);
+    });
+
+}
+
+private Json.Node new_string_node(string str) {
+    var node = new Json.Node(NodeType.VALUE);
+    node.set_string(str);
+    return node;
 }

+ 42 - 0
src/tests/Integration/PropertyMapper.vala

@@ -0,0 +1,42 @@
+using Invercargill;
+using Invercargill.Convert;
+using InvercargillJson;
+using Json;
+
+void property_mapper_tests() {
+
+    Test.add_func("/invercargill/property_mapper/map_class_to_and_from_properties", () => {
+
+        var mapper = new PropertyMapperBuilder<MappedClass>()
+            .set_constructor(() => new MappedClass())
+            .map<string>("name", c => c.name, (c, v) => c.name = v)
+            .map<int>("number", c => c.number, (c, v) => c.number = v)
+            .build();
+
+        var original = new MappedClass() {
+            name = "Billy Barrow",
+            not_mapped = "Invercargill",
+            number = 1999
+        };
+
+        var properties = mapper.map_from(original);
+
+        var copy = mapper.materialise(properties);
+
+        assert(properties.where(p => p.value.assignable_to<string>()).no(p => p.value.as<string>() == "Invercargill"));
+        assert(properties.where(p => p.value.assignable_to<string>()).any(p => p.value.as<string>() == "Billy Barrow"));
+        assert_cmpint(original.number, CompareOperator.EQ, copy.number);
+        assert_cmpstr(original.name, CompareOperator.EQ, copy.name);
+        assert_null(copy.not_mapped);
+
+    });
+
+}
+
+private class MappedClass {
+
+    public int number { get; set; }   
+    public string name { get; set; }
+    public string not_mapped { get; set; }
+
+}

+ 1 - 0
src/tests/TestRunner.vala

@@ -18,6 +18,7 @@ public static int main(string[] args) {
     promotion_tests();
     numbers_test();
     dictionary_tests();
+    property_mapper_tests();
     
     Test.run();
     

+ 1 - 0
src/tests/meson.build

@@ -17,6 +17,7 @@ sources += files('Integration/Arrays.vala')
 sources += files('Integration/Promotion.vala')
 sources += files('Integration/Numbers.vala')
 sources += files('Integration/Dictionary.vala')
+sources += files('Integration/PropertyMapper.vala')
 
 sources += files('Speed/SpeedTest.vala')
 sources += files('Speed/Series.vala')