/******************************************************************************* * Copyright (c) 2010 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.databoard.accessor.java; import java.lang.ref.SoftReference; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.Executor; import org.simantics.databoard.Bindings; import org.simantics.databoard.accessor.Accessor; import org.simantics.databoard.accessor.MapAccessor; import org.simantics.databoard.accessor.error.AccessorConstructionException; import org.simantics.databoard.accessor.error.AccessorException; import org.simantics.databoard.accessor.error.ReferenceException; import org.simantics.databoard.accessor.event.Event; import org.simantics.databoard.accessor.event.MapEntryAdded; import org.simantics.databoard.accessor.event.MapEntryRemoved; import org.simantics.databoard.accessor.event.ValueAssigned; import org.simantics.databoard.accessor.impl.AccessorParams; import org.simantics.databoard.accessor.impl.ListenerEntry; import org.simantics.databoard.accessor.interestset.InterestSet; import org.simantics.databoard.accessor.interestset.MapInterestSet; import org.simantics.databoard.accessor.reference.ChildReference; import org.simantics.databoard.accessor.reference.KeyReference; import org.simantics.databoard.accessor.reference.LabelReference; import org.simantics.databoard.adapter.AdaptException; import org.simantics.databoard.adapter.Adapter; import org.simantics.databoard.adapter.AdapterConstructionException; import org.simantics.databoard.binding.ArrayBinding; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.MapBinding; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.mutable.MutableVariant; import org.simantics.databoard.type.MapType; public class JavaMap extends JavaObject implements MapAccessor { /** Accessors to children */ TreeMap> children; public JavaMap(Accessor parent, MapBinding binding, Object object, AccessorParams params) { super(parent, binding, object, params); children = new TreeMap>(binding.getKeyBinding()); } @Override public MapType type() { return (MapType) binding.type(); } @Override public MapBinding getBinding() { return (MapBinding) binding; } public Binding getKeyBinding() { return getBinding().getKeyBinding(); } public Binding getValueBinding() { return getBinding().getValueBinding(); } @Override public void clear() throws AccessorException { writeLock(); try { if (getBinding().size(object)==0) return; boolean hasListeners = listeners!=null; Binding kb = getKeyBinding(); Object[] keys = hasListeners ? getBinding().getKeys(object) : null; // Write getBinding().clear(object); // Disconnect sub-accessor for (SoftReference ref : children.values()) { JavaObject sa = ref.get(); if (sa==null) continue; sa.invalidatedNotification(); } children.clear(); // Notify Listeners ListenerEntry le = listeners; while (le!=null) { MapInterestSet is = le.getInterestSet(); for (Object key : keys) { MutableVariant var = new MutableVariant(kb, key); if (is.inNotificationsOf(var)) { MapEntryRemoved e = new MapEntryRemoved(var); emitEvent(le, e); } } le = le.next; } } catch (BindingException e) { throw new AccessorException( e ); } finally { writeUnlock(); } } /** * Remove element with local key * * @param localKey * @throws AccessorException */ void removeLocal(Object lk) throws AccessorException { writeLock(); try { boolean hadValue = getBinding().containsKey(object, lk); if (!hadValue) return; // Write getBinding().remove(object, lk); // Disconnect sub-accessor JavaObject sa = getExistingAccessor(lk); // Notify about disconnection of sub-accessor if (sa!=null) { sa.invalidatedNotification(); children.remove(lk); } // Notify Listeners if (listeners!=null) { MutableVariant var = new MutableVariant(getKeyBinding(), lk); ListenerEntry le = listeners; while (le!=null) { MapInterestSet is = le.getInterestSet(); if (is.inNotificationsOf(var)) { MapEntryRemoved e = new MapEntryRemoved(var); emitEvent(le, e); } le = le.next; } } } catch (BindingException e) { throw new AccessorException( e ); } finally { writeUnlock(); } } void putLocal(Object lk, Object lv) throws AccessorException { writeLock(); try { JavaObject sa = getExistingAccessor(lk); if (sa==null) { boolean hadOldValue = getBinding().containsKey(object, lk); // Init Binding kb = getKeyBinding(); Binding vb = getValueBinding(); // Compare to old value Object oldLv = null; if (hadOldValue) { oldLv = getBinding().get(object, lk); // Compare to existing value // boolean equal = vb.equals(oldLv, lv); // if (equal) return; } // Write getBinding().put(object, lk, lv); // Key variant MutableVariant kv = new MutableVariant(kb, lk); // Notify Listeners if (listeners!=null) { ListenerEntry le = listeners; while (le!=null) { MapInterestSet is = le.getInterestSet(); if (is.inNotificationsOf(kv)) { MutableVariant vv = null; if (is.inValuesOf(kv)) vv = new MutableVariant(vb, vb.isImmutable() ? lv : vb.clone(lv)); if (hadOldValue) { // Notify about new assignment to old value Event e = new ValueAssigned( new KeyReference(kv), vv); emitEvent(le, e); } else { // Notify about new entry MapEntryAdded e = new MapEntryAdded(kv, vv); emitEvent(le, e); } } le = le.next; } } } else { // Recursive write using existing sub-accessor sa.setValue(getValueBinding(), lv); } } catch (BindingException e) { throw new AccessorException( e ); } catch (AdaptException e) { throw new AccessorException( e ); } finally { writeUnlock(); } } @Override public void put(Binding keyBinding, Object key, Binding valueBinding, Object value) throws AccessorException { writeLock(); try { Object rk = key; Object rv = value; Adapter ka = params.adapterScheme.getAdapter(keyBinding, getKeyBinding(), true, false); Adapter va = params.adapterScheme.getAdapter(valueBinding, getValueBinding(), true, false); Object lk = ka.adapt( rk ); Object lv = va.adapt( rv ); putLocal(lk, lv); } catch (AdapterConstructionException e) { throw new AccessorException(e); } catch (AdaptException e) { throw new AccessorException(e); } finally { writeUnlock(); } } @Override public void putAll(Binding keyBinding, Binding valueBinding, Map from) throws AccessorException { writeLock(); try { // Convert keys and values Adapter ka = params.adapterScheme.getAdapter(keyBinding, getKeyBinding(), true, false); Adapter va = params.adapterScheme.getAdapter(valueBinding, getValueBinding(), true, false); for (Object rk : from.keySet()) { Object rv = from.get(rk); Object lk = ka.adapt( rk ); Object lv = va.adapt( rv ); putLocal(lk, lv); } } catch (AdaptException e) { throw new AccessorException( e ); } catch (AdapterConstructionException e) { throw new AccessorException( e ); } finally { writeUnlock(); } } @Override public void putAll(Binding keyBinding, Binding valueBinding, Object[] keys, Object[] values) throws AccessorException { writeLock(); try { // Convert keys and values int rs = keys.length; Adapter ka = params.adapterScheme.getAdapter(keyBinding, getKeyBinding(), true, false); Adapter va = params.adapterScheme.getAdapter(valueBinding, getValueBinding(), true, false); for (int i=0; i oldKeys = new TreeSet(getKeyBinding()); getBinding().getKeys(object, oldKeys); // 1. Put for (int i=0; i T getValueAccessor(Binding keyBinding, Object key) throws AccessorConstructionException { try { Object rk = key; Object lk = params.adapterScheme.getAdapter(keyBinding, getKeyBinding(), true, listeners!=null).adapt(rk); boolean hasKey = getBinding().containsKey(object, lk); if (!hasKey) { throw new AccessorConstructionException("Map doesn't contain the requested element"); } JavaObject sa = getExistingAccessor(lk); if (sa!=null) return (T) sa; readLock(); try { Binding vb = getBinding().getValueBinding(); Binding kb = getBinding().getKeyBinding(); Object lv = getBinding().get(object, lk); MutableVariant kv = new MutableVariant(kb, lk); // Instantiate correct sub accessor. sa = createSubAccessor(this, vb, lv, params); sa.keyInParent = lk; children.put(lk, new SoftReference(sa)); // Add component interest sets ListenerEntry le = listeners; while (le!=null) { MapInterestSet is = le.getInterestSet(); // Generic element interest InterestSet gis = is.getComponentInterest(); if (gis != null) { try { ChildReference childPath = ChildReference.concatenate(le.path, new KeyReference(kv) ); sa.addListener(le.listener, gis, childPath, le.executor); } catch (AccessorException e) { throw new AccessorConstructionException(e); } } // Specific element interest InterestSet cis = is.getComponentInterest(kv); if (cis != null) { try { ChildReference childPath = ChildReference.concatenate(le.path, new KeyReference(kv) ); sa.addListener(le.listener, cis, childPath, le.executor); } catch (AccessorException e) { throw new AccessorConstructionException(e); } } // Next listener le = le.next; } } finally { readUnlock(); } return (T) sa; } catch (BindingException e) { throw new AccessorConstructionException(e); } catch (AdaptException e) { throw new AccessorConstructionException(e); } catch (AdapterConstructionException e) { throw new AccessorConstructionException(e); } } /** * Get existing sub accessor * @param index * @return sub-accessor or null */ JavaObject getExistingAccessor(Object localKey) { SoftReference ref = children.get(localKey); if (ref==null) return null; JavaObject result = ref.get(); if (result==null) { children.remove(localKey); return null; } return result; } @SuppressWarnings("unchecked") @Override public T getComponent(ChildReference reference) throws AccessorConstructionException { if (reference==null) return (T) this; if (reference instanceof LabelReference) { LabelReference lr = (LabelReference) reference; try { Binding kb = getKeyBinding(); MutableVariant variant = (MutableVariant) adapt(lr.label, Bindings.STRING, Bindings.MUTABLE_VARIANT); Object value = variant.getValue(kb); Accessor result = (T) getValueAccessor(kb, value); if (reference.getChildReference() != null) result = result.getComponent(reference.getChildReference()); return (T) result; } catch (AdaptException e2) { throw new ReferenceException(e2); } catch (AdapterConstructionException e) { throw new ReferenceException(e); } } else if (reference instanceof KeyReference) { KeyReference ref = (KeyReference) reference; Accessor result = getValueAccessor(ref.key.getBinding(), ref.key.getValue()); if (reference.getChildReference() != null) result = result.getComponent(reference.getChildReference()); return (T) result; } throw new ReferenceException(reference.getClass().getName()+" is not a reference of a map"); } @Override public int size() throws AccessorException { readLock(); try { return getBinding().size(object); } catch (BindingException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public boolean containsKey(Binding keyBinding, Object key) throws AccessorException { readLock(); try { MapBinding mb = getBinding(); Binding lkb = getKeyBinding(); Binding rkb = keyBinding; Object rk = key; Object lk = adapt(rk, rkb, lkb); return mb.containsKey(object, lk); } catch (AdaptException e) { throw new AccessorException(e); } catch (BindingException e) { throw new AccessorException(e); } catch (AdapterConstructionException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public boolean containsValue(Binding valueBinding, Object value) throws AccessorException { readLock(); try { MapBinding mb = getBinding(); Binding lvb = getValueBinding(); Binding rvb = valueBinding; Object rv = value; Object lv = adapt(rv, rvb, lvb); return mb.containsValue(object, lv); } catch (AdaptException e) { throw new AccessorException(e); } catch (BindingException e) { throw new AccessorException(e); } catch (AdapterConstructionException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public Object get(Binding keyBinding, Object key, Binding valueBinding) throws AccessorException { readLock(); try { MapBinding mb = getBinding(); Binding lkb = getKeyBinding(); Binding rkb = keyBinding; Binding lvb = getValueBinding(); Binding rvb = valueBinding; Object rk = key; Object lk = adapt(rk, rkb, lkb); Object lv = mb.get(object, lk); if (lv == null) return null; Object rv = adapt(lv, lvb, rvb); return rv; } catch (AdaptException e) { throw new AccessorException(e); } catch (BindingException e) { throw new AccessorException(e); } catch (AdapterConstructionException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public int count(Binding keyBinding, Object from, boolean fromInclusive, Object end, boolean endInclusive) throws AccessorException { readLock(); try { MapBinding mb = getBinding(); Object lf = params.adapterScheme.adapt(from, keyBinding, getKeyBinding()); Object le = params.adapterScheme.adapt(end, keyBinding, getKeyBinding()); return mb.count(object, lf, fromInclusive, le, endInclusive); } catch (BindingException e) { throw new AccessorException(e); } catch (AdaptException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public int getEntries(Binding keyBinding, Object from, boolean fromInclusive, Object end, boolean endInclusive, ArrayBinding keyArrayBinding, Object keysArray, ArrayBinding valueArrayBinding, Object valueArray, int limit) throws AccessorException { readLock(); try { MapBinding mb = getBinding(); Object lfrom = params.adapterScheme.adapt(from, keyBinding, getKeyBinding()); Object lend = params.adapterScheme.adapt(end, keyBinding, getKeyBinding()); return mb.getEntries(object, lfrom, fromInclusive, lend, endInclusive, keyArrayBinding, keysArray, valueArrayBinding, valueArray, limit); } catch (BindingException e) { throw new AccessorException(e); } catch (AdaptException e) { throw new AccessorException(e); } finally { readUnlock(); } } @Override public void getAll(Binding keyBinding, Binding valueBinding, Map to) throws AccessorException { readLock(); try { MapBinding mb = getBinding(); Adapter ka = params.adapterScheme.getAdapter(getKeyBinding(), keyBinding, true, false); Adapter va = params.adapterScheme.getAdapter(getValueBinding(), valueBinding, true, false); int length = mb.size(object); Object[] keys = new Object[ length ]; Object[] values = new Object[ length ]; mb.getAll(object, keys, values); for (int i=0; i> entry : children.entrySet()) { JavaObject sa = entry.getValue().get(); if (sa==null) continue; Object key = entry.getKey(); MutableVariant vkey = new MutableVariant(kb, key); InterestSet cis = is.getComponentInterest(); if (cis!=null) { sa.removeListener(listener); } cis = is.getComponentInterest( vkey ); if (cis!=null) { sa.removeListener(listener); } } } @Override Event applyLocal(Event e, boolean makeRollback) throws AccessorException { Event rollback = null; if (e instanceof ValueAssigned) { ValueAssigned va = (ValueAssigned) e; if (makeRollback) rollback = new ValueAssigned(getBinding(), getValue(getBinding())); setValue(va.newValue.getBinding(), va.newValue.getValue()); return rollback; } else if (e instanceof MapEntryAdded) { MapEntryAdded ea = (MapEntryAdded) e; if (ea.key==null) throw new AccessorException("Cannot apply entry added event because key is missing"); if (ea.value==null) throw new AccessorException("Cannot apply entry added event because value is missing"); boolean hadValue = containsKey(ea.key.getBinding(), ea.key.getValue()); if (hadValue) throw new AccessorException("Could not add entry to key that already existed"); if (makeRollback) { rollback = new MapEntryRemoved( ea.key ); } put(ea.key.getBinding(), ea.key.getValue(), ea.value.getBinding(), ea.value.getValue()); } else if (e instanceof MapEntryRemoved) { MapEntryRemoved er = (MapEntryRemoved) e; if (makeRollback) { boolean hadValue = containsKey(er.key.getBinding(), er.key.getValue()); if (hadValue) { Object oldValueObj = get(er.key.getBinding(), er.key.getValue(), getBinding().getValueBinding()); MutableVariant oldKey = er.key; MutableVariant oldValue = new MutableVariant(getBinding().getValueBinding(), oldValueObj); rollback = new MapEntryAdded(oldKey, oldValue); } else { rollback = new MapEntryRemoved( er.key.clone() ); } } remove( er.key.getBinding(), er.key.getValue() ); } else throw new AccessorException("Cannot apply "+e.getClass().getName()+" to Map Type"); return rollback; } }