--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2010 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.binding;
+
+import java.util.IdentityHashMap;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.Set;\r
+import java.util.TreeMap;\r
+import java.util.TreeSet;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.accessor.reference.ChildReference;\r
+import org.simantics.databoard.accessor.reference.IndexReference;\r
+import org.simantics.databoard.accessor.reference.LabelReference;\r
+import org.simantics.databoard.accessor.reference.NameReference;\r
+import org.simantics.databoard.adapter.AdaptException;\r
+import org.simantics.databoard.adapter.Adapter;\r
+import org.simantics.databoard.adapter.AdapterConstructionException;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.binding.error.RuntimeBindingException;\r
+import org.simantics.databoard.binding.impl.BindingPrintContext;\r
+import org.simantics.databoard.type.MapType;\r
+import org.simantics.databoard.util.IdentityPair;\r
+
+/**
+ * This is a binding of Map Type and a Java Object
+ *
+ * @see MapType
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ */
+public abstract class MapBinding extends Binding {
+
+ protected Binding keyBinding;
+ protected Binding valueBinding;
+
+ /**
+ * Create new map binding. Creates new data type.
+ *
+ * @param keyBinding
+ * @param valueBinding
+ */
+ public MapBinding(Binding keyBinding, Binding valueBinding)
+ {
+ super();
+// if (keyBinding==null || valueBinding==null) throw new IllegalArgumentException("null arg");
+ this.type = new MapType(keyBinding.type(), valueBinding.type());
+ this.keyBinding = keyBinding;
+ this.valueBinding = valueBinding;
+ }
+
+ /**
+ * Create new map binding for a type.
+ *
+ * @param mapType
+ * @param keyBinding
+ * @param valueBinding
+ */
+ public MapBinding(MapType mapType, Binding keyBinding, Binding valueBinding)
+ {
+ super();
+// if (keyBinding==null || valueBinding==null) throw new IllegalArgumentException("null arg");
+ this.keyBinding = keyBinding;
+ this.valueBinding = valueBinding;
+ this.type = mapType;
+ }
+
+ @Override
+ public MapType type() {
+ return (MapType) type;
+ }
+
+ public Binding getKeyBinding() {
+ return keyBinding;
+ }
+
+ public Binding getValueBinding() {
+ return valueBinding;
+ }
+
+ public void setKeyBinding(Binding keyBinding) {
+ this.keyBinding = keyBinding;
+ if (!type().keyType.equals(keyBinding.type()))
+ throw new IllegalArgumentException("Binding for "+type().keyType+" expected, got "+keyBinding.type());
+ }
+
+ public void setValueBinding(Binding valueBinding) {
+ this.valueBinding = valueBinding;
+ if (!type().valueType.equals(valueBinding.type()))
+ throw new IllegalArgumentException("Binding for "+type().valueType+" expected, got "+valueBinding.type());
+ }
+
+ public abstract Object create() throws BindingException;
+
+ /**
+ * Create a new map with initial values.
+ * The values of the initialMap are accessible with the respective key and value binding.
+ *
+ * @param initialMap
+ * @return map object
+ * @throws BindingException
+ */
+ public abstract Object create(Map<?, ?> initialMap) throws BindingException;
+
+ /**
+ * Create a new map with initial values.
+ * The values of the initialMap are accessible with the respective key and value binding.
+ *
+ * @param keys
+ * @param values
+ * @return map object
+ * @throws BindingException
+ */
+ public abstract Object create(List<Object> keys, List<Object> values) throws BindingException;
+
+ /**
+ * Create a new map with initial values.
+ * The values of the initialMap are accessible with the key and value binding.
+ *
+ * @param keys
+ * @param values
+ * @return map object
+ * @throws BindingException
+ */
+ public abstract Object create(Object[] keys, Object[] values) throws BindingException;
+
+ public abstract int size(Object map) throws BindingException;
+
+ /**
+ * Return the value to which the specified key is mapped.
+ * If the key is not mapped, BindingException is thrown.
+ * The key and the value objects are accessible with the respective bindings.
+ *
+ * @param map
+ * @param key
+ * @return value
+ * @throws BindingException
+ */
+ public abstract Object get(Object map, Object key) throws BindingException;
+ public abstract boolean containsKey(Object map, Object key) throws BindingException;
+ public abstract boolean containsValue(Object map, Object value) throws BindingException;
+ public abstract <K, V> void put(Object map, K key, V value) throws BindingException;
+ public abstract Object remove(Object map, Object key) throws BindingException;
+ public abstract <K, V> void putAll(Object mapTo, Map<K, V> mapFrom) throws BindingException;
+ public abstract <K, V> void getAll(Object mapFrom, Map<K, V> to) throws BindingException;
+
+ /**
+ * Get keys and values, in order
+ *
+ * @param mapFrom
+ * @param keys
+ * @param values
+ * @throws BindingException
+ */
+ public abstract void getAll(Object mapFrom, Object[] keys, Object[] values) throws BindingException;
+
+ /**
+ * Get keys in order
+ *
+ * @param map
+ * @return keys
+ * @throws BindingException
+ */
+ public abstract Object[] getKeys(Object map) throws BindingException;
+
+ public abstract void getKeys(Object map, Set<Object> keys) throws BindingException;
+ \r
+ /**\r
+ * Count the number of entries between two keyes\r
+ * \r
+ * @param src\r
+ * @param from\r
+ * @param fromInclusive\r
+ * @param end \r
+ * @param endInclusive\r
+ * @throws BindingException\r
+ */\r
+ public abstract int count(Object src, Object from, boolean fromInclusive, Object end, boolean endInclusive) throws BindingException;\r
+ \r
+ /**\r
+ * Read a range of entries\r
+ * \r
+ * @param src\r
+ * @param from\r
+ * @param fromInclusive\r
+ * @param end \r
+ * @param endInclusive\r
+ * @param dstKeyArrayBinding\r
+ * @param dstKeyArray\r
+ * @param dstValueArrayBinding\r
+ * @param dstValueArray\r
+ * @param resultLimit maximum number of entries to read, -1 for no limit\r
+ * @return the number of entries read \r
+ * @throws BindingException\r
+ */\r
+ public abstract int getEntries(\r
+ Object src, \r
+ Object from, boolean fromInclusive, Object end, boolean endInclusive, \r
+ ArrayBinding dstKeyArrayBinding, Object dstKeyArray, \r
+ ArrayBinding dstValueArrayBinding, Object dstValueArray, \r
+ int resultLimit) throws BindingException;\r
+
+ /**
+ * Get values in order
+ *
+ * @param map
+ * @return values
+ * @throws BindingException
+ */
+ public abstract Object[] getValues(Object map) throws BindingException;
+ public abstract void clear(Object map) throws BindingException;
+
+ // Views considered - more knowledge required
+
+ /**
+ * Assert the instance is valid and follows restrictions set in data type.
+ * Assertions:
+ * 1. correct instance
+ * 2. assertion of each key and value
+ *
+ * @param map the instance
+ * @param validInstances a collection of validated instances or <code>null</code>
+ * @throws BindingException on invalid instance
+ */
+ @Override
+ public void assertInstaceIsValid(Object map, Set<Object> validInstances) throws BindingException {
+ if (!isInstance(map)) throw new BindingException("Not a map");
+ for (Object key : getKeys(map)) {
+ keyBinding.assertInstaceIsValid(key, validInstances);
+ Object value = get(map, key);
+ valueBinding.assertInstaceIsValid(value, validInstances);
+ }
+ }
+
+ @Override
+ public void accept(Visitor1 v, Object obj) {
+ v.visit(this, obj);
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> v) {
+ return v.visit(this);
+ }
+ \r
+ @Override\r
+ public void readFrom(Binding srcBinding, Object src, Object dst)\r
+ throws BindingException {\r
+ try {\r
+ MapBinding sb = (MapBinding) srcBinding;\r
+ Binding dkb = getKeyBinding();\r
+ Binding dvb = getValueBinding();\r
+ Set<Object> oldKeys = new TreeSet<Object>(dkb);\r
+ getKeys(dst, oldKeys);\r
+ Binding skb = sb.getKeyBinding();\r
+ Binding svb = sb.getValueBinding();\r
+ boolean cbImmutable = dvb.isImmutable();\r
+ Adapter ka = Bindings.adapterFactory.getAdapter(skb, dkb, false, false);\r
+ Adapter va = cbImmutable ? Bindings.adapterFactory.getAdapter(svb, dvb, false, true) : null;\r
+ \r
+ // Copy keys from other map\r
+ for (Object sk : sb.getKeys(src)) {\r
+ Object dk = ka.adapt(sk);\r
+ Object sv = sb.get(src, sk);\r
+ if (cbImmutable) {\r
+ Object dv = va.adapt(sv);\r
+ put(dst, dk, dv);\r
+ } else\r
+ if (containsKey(dst, dk)) {\r
+ Object dv = get(dst, dk);\r
+ dv = dvb.readFromTry(svb, sv, dv);\r
+ put(dst, dk, dv);\r
+ } else {\r
+ Object dv = dvb.createDefault();\r
+ dv = dvb.readFromTry(svb, sv, dv);\r
+ put(dst, dk, dv);\r
+ }\r
+ oldKeys.remove(dk);\r
+ }\r
+ \r
+ // Remove unused keys\r
+ for (Object k : oldKeys) remove(dst, k);\r
+\r
+ } catch (AdapterConstructionException e) {\r
+ throw new BindingException(e);\r
+ } catch (AdaptException e) {\r
+ throw new BindingException(e);\r
+ }\r
+ }\r
+ \r
+
+ @Override
+ public int deepHashValue(Object map, IdentityHashMap<Object, Object> hashedObjects) throws BindingException {
+ int result = 0;
+ Object keys[] = getKeys(map);
+ Object values[] = getValues(map);
+ int len = size(map);
+ for (int i=0; i<len; i++) {
+ Object key = keys[i];
+ Object value = values[i];
+
+ int keyHash = keyBinding.deepHashValue(key, hashedObjects);
+ int valueHash = valueBinding.deepHashValue(value, hashedObjects);
+
+ result += (keyHash ^ valueHash);
+ }
+ return result;
+ }
+
+ @Override
+ public int deepCompare(Object o1, Object o2,
+ Set<IdentityPair<Object, Object>> compareHistory)
+ throws BindingException {
+ // Compare sizes
+ int l1 = size(o1);
+ int l2 = size(o2);
+ int dif = l1 - l2;
+ if (dif!=0)
+ return dif;
+ // Compare elements
+ Binding k = getKeyBinding();
+ Binding v = getValueBinding();
+ TreeMap<Object, Object> e1 = new TreeMap<Object, Object>( k );
+ TreeMap<Object, Object> e2 = new TreeMap<Object, Object>( k );
+ getAll(o1, e1);
+ getAll(o2, e2);
+
+ Iterator<Entry<Object, Object>> i1 = e1.entrySet().iterator();
+ Iterator<Entry<Object, Object>> i2 = e2.entrySet().iterator();
+ while (i1.hasNext()) {
+ Entry<Object, Object> h1 = i1.next();
+ Entry<Object, Object> h2 = i2.next();
+ dif = k.deepCompare(h1.getKey(), h2.getKey(), compareHistory);
+ if (dif!=0)
+ return dif;
+ dif = v.deepCompare(h1.getValue(), h2.getValue(), compareHistory);
+ if (dif!=0)
+ return dif;
+ i1.remove();
+ i2.remove();
+ }
+ return 0;
+
+ }
+
+ public Object createUnchecked(Object[] keys, Object[] values)
+ throws RuntimeBindingException {
+ try {
+ return create(keys, values);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ public Object createUnchecked(List<Object> keys, List<Object> values) throws RuntimeBindingException
+ {
+ try {
+ return create(keys, values);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ public Object createUnchecked(Map<Object, Object> initialMap)
+ throws RuntimeBindingException {
+ try {
+ return create(initialMap);
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ public Object createUnchecked() throws RuntimeBindingException {
+ try {
+ return create();
+ } catch (BindingException e) {
+ throw new RuntimeBindingException(e);
+ }
+ }
+
+ public abstract Object getFirstKey(Object map);
+ public abstract Object getLastKey(Object map);
+ public abstract Object getLowerKey(Object map, Object key);
+ public abstract Object getFloorKey(Object map, Object key);
+ public abstract Object getCeilingKey(Object map, Object key);
+ public abstract Object getHigherKey(Object map, Object key);
+
+ \r
+ @Override\r
+ protected void toString(Object value, BindingPrintContext ctx) throws BindingException {\r
+ Binding keyBinding = getKeyBinding();\r
+ Binding valueBinding = getValueBinding();\r
+ ctx.b.append("{ ");\r
+ boolean first = true;\r
+ for(Object key : getKeys(value)) {\r
+ if(first)\r
+ first = false;\r
+ else {\r
+ ctx.b.append(", ");\r
+ if ( !ctx.singleLine ) ctx.b.append('\n');\r
+ }\r
+ keyBinding.toString(key, ctx);\r
+ ctx.b.append(" = ");\r
+ valueBinding.toString(get(value, key), ctx);\r
+ }\r
+ ctx.b.append(" }");\r
+ }\r
+\r
+ @Override\r
+ public Binding getComponentBinding(ChildReference path) {\r
+ if (path==null) return this;\r
+ if (path instanceof IndexReference) {\r
+ IndexReference ir = (IndexReference) path;\r
+ if (ir.index==0) return keyBinding.getComponentBinding(path.childReference);\r
+ if (ir.index==1) return valueBinding.getComponentBinding(path.childReference);\r
+ }\r
+ if (path instanceof LabelReference) {\r
+ LabelReference lr = (LabelReference) path;\r
+ if (lr.label.equals("0") || lr.label.equals("key")) return keyBinding.getComponentBinding(path.childReference);\r
+ if (lr.label.equals("1") || lr.label.equals("value")) return valueBinding.getComponentBinding(path.childReference);\r
+ }\r
+ if (path instanceof NameReference) {\r
+ NameReference nr = (NameReference) path;\r
+ if (nr.name.equals("key")) return keyBinding.getComponentBinding(path.childReference);\r
+ if (nr.name.equals("value")) return valueBinding.getComponentBinding(path.childReference);\r
+ }\r
+ throw new IllegalArgumentException();\r
+ } \r
+\r
+ \r
+ @Override\r
+ public int getComponentCount() {\r
+ return 2;\r
+ }\r
+ \r
+ @Override\r
+ public Binding getComponentBinding(int index) {\r
+ if (index==0) return keyBinding;\r
+ if (index==1) return valueBinding;\r
+ throw new IllegalArgumentException();\r
+ } \r
+\r
+ @Override\r
+ protected boolean deepEquals(Object obj,\r
+ Set<IdentityPair<Binding, Binding>> compareHistory) {\r
+ MapBinding o = (MapBinding)obj;\r
+ return super.deepEquals( obj, compareHistory ) && keyBinding.equals(o.keyBinding, compareHistory) && valueBinding.equals(o.valueBinding, compareHistory);\r
+ }\r
+ \r
+ @Override\r
+ public int deepHashCode(IdentityHashMap<Object, Object> hashedObjects) {\r
+ return super.deepHashCode(hashedObjects) + 13 * keyBinding.hashCode(hashedObjects) + 17 * valueBinding.hashCode(hashedObjects);\r
+ }
+}