X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Faccessor%2Fimpl%2FDirectoryMap.java;fp=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Faccessor%2Fimpl%2FDirectoryMap.java;h=5a94b04ca4cd51d3e3ce2b4d8e72696f4477dde5;hp=647aab8239aab6b6c72ec314551069362230c450;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hpb=24e2b34260f219f0d1644ca7a138894980e25b14 diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/accessor/impl/DirectoryMap.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/accessor/impl/DirectoryMap.java index 647aab823..5a94b04ca 100644 --- a/bundles/org.simantics.databoard/src/org/simantics/databoard/accessor/impl/DirectoryMap.java +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/accessor/impl/DirectoryMap.java @@ -1,1120 +1,1120 @@ -/******************************************************************************* - * Industry THTH ry. - * Copyright (c) 2010- Association for Decentralized Information Management in - * 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.impl; - -import java.io.File; -import java.io.FileFilter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.Executor; - -import org.simantics.databoard.Bindings; -import org.simantics.databoard.Datatypes; -import org.simantics.databoard.accessor.Accessor; -import org.simantics.databoard.accessor.CloseableAccessor; -import org.simantics.databoard.accessor.MapAccessor; -import org.simantics.databoard.accessor.VariantAccessor; -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.file.FileLibrary; -import org.simantics.databoard.accessor.file.FileVariantAccessor; -import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryEvent; -import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryListener; -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.VariantBinding; -import org.simantics.databoard.binding.error.BindingConstructionException; -import org.simantics.databoard.binding.error.BindingException; -import org.simantics.databoard.binding.error.RuntimeBindingException; -import org.simantics.databoard.binding.mutable.MutableVariant; -import org.simantics.databoard.type.Datatype; -import org.simantics.databoard.type.MapType; - -/** - * DirectoryMap is a file backed map implementation where keys are filenames - * and values are corresponding files. - *

- * This class is an implmentation to Map(Variant, Variant) -Accessor. - * - * Filenames have the following encoding: - * S.dbb String types, if string doesn't have the following - * control characters " : < > | ? * \ / [0..31] - * I.dbb Integer types - * L.dbb Long types - * H.dbb All other cases the value as binary - *

- * File accessor is created if an entry opened as a sub-accessor. - * The file accessor is closed when all sub-accessors are released. - * The implementation is based on proxy instances and a reference queue. - * Once the queue is empty, file accessor is closed. - *

- * DirectoryMap must be closed with #close(); - * - * @author Toni Kalajainen - */ -public class DirectoryMap implements MapAccessor, CloseableAccessor { - - /** Key binding */ - final static Binding KEY_BINDING = Bindings.STR_VARIANT; - - /** Cache of sub-accessors */ - FileLibrary files; - - /** Monitors directory for file changes */ - DirectoryWatch dir; - - /** Folder */ - File path; - - /** Listeners */ - ListenerEntry listeners = null; - - /** Parent, optional */ - Accessor parent; - - /** Accessor params */ - AccessorParams params; - - DirectoryListener dirListener = new DirectoryListener() { - @Override - public void onWatchEvent(DirectoryEvent e) { - - } - }; - - public DirectoryMap(File directory) { - this(directory, null, AccessorParams.DEFAULT); - } - - public DirectoryMap(File directory, Accessor parent) { - this(directory, parent, AccessorParams.DEFAULT); - } - - public DirectoryMap(File directory, Accessor parent, AccessorParams params) { - this.parent = parent; - this.path = directory; - this.params = params; - - // Filters .dbb files - FileFilter filter = new FileFilter() { - public boolean accept(File pathname) { - String filename = pathname.getName(); - if (filename.length()==0) return false; - char c = filename.charAt(0); - if (c!='S' && c!='I' && c!='L' && c!='B') return false; - if (filename.endsWith(".dbb")) return true; - return filename.toLowerCase().endsWith(".dbb"); - }}; - - dir = new DirectoryWatch(path, filter); - - dir.addListener( dirListener ); - - files = new FileLibrary(); - } - - public void close() { - dir.removeListener( dirListener ); - dir.close(); - files.close(); - } - - static MapType type = new MapType(Datatypes.VARIANT, Datatypes.VARIANT); - public MapType type() { - return type; - } - - private String fileToKey(File f) { - String filename = f.getName(); - String keyStr = filename.substring(0, filename.length()-4); - return keyStr; - } - - private File keyToFile(String keyStr) { - return new File(path, keyStr + ".dbb" ); - } - - @Override - public void clear() throws AccessorException { - //List deleteList = dir.files(); - List failList = new ArrayList(); - boolean hasListeners = listeners!=null; - List keys = hasListeners ? new ArrayList() : null; - - // Close all file handles - files.close(); - - // Delete files - for (File f : dir.files()) { - if (!files.deleteFile(f)) { - failList.add(f); - } - if (hasListeners) { - String keyStr = fileToKey(f); - keys.add(keyStr); - } - } - - // Re-read directory - dir.refresh(); - - // Notify Listeners - ListenerEntry le = listeners; - while (le!=null) { - MapInterestSet is = le.getInterestSet(); - for (Object key : keys) { - MutableVariant var = new MutableVariant(KEY_BINDING, key); - if (is.inNotificationsOf(var)) { - MapEntryRemoved e = new MapEntryRemoved(var); - emitEvent(le, e); - } - } - le = le.next; - } - - // Some files failed to delete - if (!failList.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("Failed to delete"); - for (File f : failList) { - sb.append(' '); - sb.append(f.toString()); - } - // HAX - throw new AccessorException(sb.toString()); - } - - - } - - @Override - public String toString() { - return dir.toString(); - } - - public Object getValue(Binding binding) throws AccessorException { - MapBinding mb = (MapBinding) binding; - if (mb.getKeyBinding() instanceof VariantBinding==false || mb.getValueBinding() instanceof VariantBinding==false) - throw new AccessorException("Map(Variant, Variant) Expected"); - // Get all files as a single map - try { - Object result = binding.createDefault(); - for (File f : dir.files()) { - // Create Key - String keyStr = fileToKey(f); - Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, mb.getKeyBinding()); - - // Read value - VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr); - Object value = va.getValue(mb.getValueBinding()); - - mb.put(result, key, value); - } - return result; - } catch (BindingException e) { - throw new AccessorException(e); - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } catch (AdaptException e) { - throw new AccessorException(e); - } - } - - @Override - public void getValue(Binding dstBinding, Object dst) throws AccessorException { - MapBinding db = (MapBinding) dstBinding; - Binding dkb = db.getKeyBinding(); - Binding dvb = db.getValueBinding(); - if (dkb instanceof VariantBinding==false || dvb instanceof VariantBinding==false) - throw new AccessorException("Map(Variant, Variant) Expected"); - // Get all files as a single map - try { - TreeSet dstKeys = new TreeSet(dkb); - db.getKeys(dst, dstKeys); - - for (File f : dir.files()) { - // Create Key - String keyStr = fileToKey(f); - Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, dkb); - - Object v = db.containsKey(dst, key) ? db.get(dst, key) : dvb.createDefault(); - VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr); - va.getValue(dvb, v); - - db.put(dst, key, v); - dstKeys.remove(key); - } - - for (Object key : dstKeys) - db.remove(dst, key); - } catch (BindingException e) { - throw new AccessorException(e); - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } catch (AdaptException e) { - throw new AccessorException(e); - } - - } - - @Override - public boolean getValue(ChildReference path, Binding binding, Object obj) throws AccessorException { - try { - Accessor a = getComponent(path); - a.getValue(binding, obj); - return true; - } catch (ReferenceException re) { - return false; - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } - } - - public Object getValue(ChildReference path, Binding binding) throws AccessorException { - try { - Accessor a = getComponent(path); - return a.getValue(binding); - } catch (ReferenceException re) { - return null; - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } - } - - - @Override - public boolean containsKey(Binding keyBinding, Object key) - throws AccessorException { - try { - String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); - File file = keyToFile(key_); - return dir.files().contains(file); - } catch (AdaptException e) { - throw new AccessorException(e); - } - } - - @Override - public boolean containsValue(Binding valueBinding, Object value) - throws AccessorException { - try { - for (File f : dir.files()) { - String key = fileToKey(f); - VariantAccessor va = getValueAccessor(KEY_BINDING, key); - Object v = va.getValue(valueBinding); - boolean match = valueBinding.equals(v, value); - if ( match ) return true; - } - return false; - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } - } - - @Override - public Object get(Binding keyBinding, Object key, Binding valueBinding) - throws AccessorException { - try { - VariantAccessor va = getValueAccessor(keyBinding, key); - return va.getValue(valueBinding); - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } - } - - /** - * Get the value as a variant - * - * @param keyBinding - * @param key - * @return value - * @throws AccessorException - */ - public MutableVariant getAsVariant(Binding keyBinding, Object key) - throws AccessorException { - try { - VariantAccessor va = getValueAccessor(keyBinding, key); - Datatype type = va.getContentType(); - Binding binding = params.bindingScheme.getBinding(type); - Object value = va.getContentValue(binding); - MutableVariant result = new MutableVariant(binding, value); - return result; - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } catch (BindingConstructionException e) { - throw new AccessorException(e); - } - } - - @Override - public void getAll(Binding keyBinding, Binding valueBinding, - Map to) throws AccessorException { - try { - for (File f : dir.files()) { - // Create key - String keyStr = fileToKey(f); - Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding); - - // Read value - VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr); - Object value = va.getValue(valueBinding); - - to.put(key, value); - } - } catch (AdaptException e) { - throw new AccessorException(e); - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } - } - - @Override - public void getAll(Binding keyBinding, Binding valueBinding, Object[] keys, - Object[] values) throws AccessorException { - try { - Set fileKeys = createKeys(); - - int i=0; - for (String keyStr : fileKeys) { - // Read value - VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr); - Object value = va.getValue(valueBinding); - - Object key2 = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding); - keys[i] = key2; - values[i] = value; - i++; - } - } catch (AdaptException e) { - throw new AccessorException(e); - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } - } - - - @Override - public int count(Binding keyBinding, Object from, - boolean fromInclusive, Object end, boolean endInclusive) - throws AccessorException { - throw new AccessorException("Not implemented"); - } - - @Override - public int getEntries(Binding keyBinding, Object from, - boolean fromInclusive, Object end, boolean endInclusive, - ArrayBinding keyArrayBinding, Object dstKeys, - ArrayBinding valueArrayBinding, Object dstValues, int limit) - throws AccessorException { - throw new AccessorException("Not implemented"); - } - - - TreeSet createKeys() throws RuntimeBindingException { - List files = dir.files(); - TreeSet keys = new TreeSet(KEY_BINDING); - - for (File f : files) { - String filename = f.getName(); - String str = filename.substring(0, filename.length()-4); - keys.add(str); - } - - return keys; - } - - @Override - public Object getCeilingKey(Binding keyBinding, Object key) - throws AccessorException { - try { - TreeSet keys = createKeys(); - String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); - if (keys.contains(k)) return key; - Object res = keys.ceiling(k); - if (res==null) return null; - return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding); - } catch (RuntimeBindingException e) { - throw new AccessorException(e); - } catch (AdaptException e) { - throw new AccessorException(e); - } - } - - @Override - public Object getFirstKey(Binding keyBinding) throws AccessorException { - List files = dir.files(); - String firstKey = null; - - for (File f : files) { - String filename = f.getName(); - String str = filename.substring(0, filename.length()-4); - if (firstKey == null) { - firstKey = str; - } else { - if (KEY_BINDING.compare(str, firstKey)<0) firstKey = str; - } - } - if (firstKey==null) return null; - - try { - return params.adapterScheme.adapt(firstKey, KEY_BINDING, keyBinding); - } catch (AdaptException e) { - throw new AccessorException(e); - } - } - - @Override - public Object getFloorKey(Binding keyBinding, Object key) - throws AccessorException { - try { - TreeSet keys = createKeys(); - String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); - Object res = keys.floor(k); - if (res==null) return null; - return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding); - } catch (RuntimeBindingException e) { - throw new AccessorException(e); - } catch (AdaptException e) { - throw new AccessorException(e); - } - } - - @Override - public Object getHigherKey(Binding keyBinding, Object key) - throws AccessorException { - try { - TreeSet keys = createKeys(); - String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); - Object res = keys.higher(k); - if (res==null) return null; - return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding); - } catch (RuntimeBindingException e) { - throw new AccessorException(e); - } catch (AdaptException e) { - throw new AccessorException(e); - } - } - - @Override - public Object[] getKeys(Binding keyBinding) throws AccessorException { - TreeSet keys = createKeys(); - Object[] result = new Object[keys.size()]; - if (keys.isEmpty()) return result; - try { - Adapter a = params.adapterScheme.getAdapter(KEY_BINDING, keyBinding, true, false); - int index = 0; - for (String key : keys) { - result[index++] = a.adapt( key ); - } - } catch (AdaptException e) { - throw new AccessorException(e); - } catch (AdapterConstructionException e) { - throw new AccessorException(e); - } - - return result; - } - - @Override - public Object getLastKey(Binding keyBinding) throws AccessorException { - List files = dir.files(); - String lastKey = null; - - for (File f : files) { - String filename = f.getName(); - String str = filename.substring(0, filename.length()-4); - if (lastKey == null) { - lastKey = str; - } else { - if (KEY_BINDING.compare(str, lastKey)>0) lastKey = str; - } - } - if (lastKey==null) return null; - - try { - return params.adapterScheme.adapt(lastKey, KEY_BINDING, keyBinding); - } catch (AdaptException e) { - throw new AccessorException(e); - } - } - - @Override - public Object getLowerKey(Binding keyBinding, Object key) - throws AccessorException { - try { - TreeSet keys = createKeys(); - String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); - Object res = keys.lower(k); - if (res==null) return null; - return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding); - } catch (RuntimeBindingException e) { - throw new AccessorException(e); - } catch (AdaptException e) { - throw new AccessorException(e); - } - } - - public FileVariantAccessor getExistingAccessor(Binding keyBinding, - Object key) throws AccessorConstructionException { - try { - String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); - File file = new File(path, key_ + ".dbb" ); - return files.getExistingFile(file); - } catch (AdaptException e) { - throw new AccessorConstructionException(e); - } - } - - @SuppressWarnings("unchecked") - @Override - public FileVariantAccessor getValueAccessor(Binding keyBinding, Object key) throws AccessorConstructionException { - try { - String keyStr = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); - File file = keyToFile(keyStr); - FileVariantAccessor sa = files.getExistingFile(file); - if (sa!=null) return sa; - - // Create new accessor - sa = files.getFile(file); - - // Add component interest sets - ListenerEntry le = listeners; - if (le!=null) { - MutableVariant kv = new MutableVariant(keyBinding, key); - 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; - } - } - - return sa; - } catch (AdaptException e) { - throw new AccessorConstructionException(e); - } - } - - @Override - public Object[] getValues(Binding valueBinding) throws AccessorException { - try { - Set keys = createKeys(); - int count = keys.size(); - Object[] result = new Object[ count ]; - - Iterator iter = keys.iterator(); - for (int i=0; i from) throws AccessorException { - //boolean created = false; - for (Entry e : from.entrySet()) { - Object key = e.getKey(); - Object value = e.getValue(); - /*created |=*/ putLocal(keyBinding, key, valueBinding, value); - } - } - - @Override - public void putAll(Binding keyBinding, Binding valueBinding, Object[] keys, - Object[] values) throws AccessorException { - //boolean created = false; - if (keys.length!=values.length) - throw new AccessorException("Array lengths mismatch"); - for (int i=0; i writeFiles = new HashSet(); - List oldFiles = dir.files(); - - //HashSet modifiedFiles = new HashSet(); - HashSet addedFiles = new HashSet(writeFiles); - addedFiles.removeAll(oldFiles); - - // Write - for (int i=0; i removedFiles = new HashSet(oldFiles); - removedFiles.removeAll(writeFiles); - - // Remove old files - files.expunge(); - if (!removedFiles.isEmpty()) { - List failList = new ArrayList(); - for (File f : removedFiles) { - - String filename = f.getName(); - String keyStr = filename.substring(0, filename.length()-4); - - boolean deleted = files.deleteFile(f); - if ( !deleted ) { - failList.add(f); - } else { - - // Notify Listeners - if (listeners!=null) { - MutableVariant var = new MutableVariant(KEY_BINDING, keyStr); - 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; - } - } - } - - if (!failList.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("Failed to delete"); - for (File ff : failList) { - sb.append(' '); - sb.append(ff.toString()); - } - throw new AccessorException(sb.toString()); - } - } - } - dir.refresh(); - - } catch (BindingException e) { - throw new AccessorException(e); - } catch (AdaptException e) { - throw new AccessorException(e); - } catch (AdapterConstructionException e) { - throw new AccessorException(e); - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } - } - - @Override - public int size() throws AccessorException { - dir.refresh(); - return dir.files().size(); - } - - @Override - public void addListener(Listener listener, InterestSet interestSet, ChildReference path, Executor executor) throws AccessorException { - listeners = ListenerEntry.link(listeners, listener, interestSet, path, executor); - MapInterestSet is = (MapInterestSet) interestSet; - - try { - for (File f : dir.files()) { - String filename = f.getName(); - String keyStr = filename.substring(0, filename.length()-4); - - Accessor sa = getExistingAccessor(KEY_BINDING, keyStr); - if (sa==null) continue; - - MutableVariant key = new MutableVariant(KEY_BINDING, keyStr); - InterestSet cis = is.getComponentInterest(); - if (cis!=null) { - ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) ); - sa.addListener(listener, cis, childPath, executor); - } - cis = is.getComponentInterest( key ); - if (cis!=null) { - ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) ); - sa.addListener(listener, cis, childPath, executor); - } - } - } catch (AccessorConstructionException e) { - throw new AccessorException(e); - } - - } - - @Override - public void apply(List cs, LinkedList rollback) throws AccessorException { - try { - boolean makeRollback = rollback != null; - ArrayList single = new ArrayList(); - for (Event e : cs) { - if (e.reference==null) { - Event rbe = applyLocal(e, makeRollback); - if (makeRollback) { - rbe.reference = e.reference; - rollback.addFirst( rbe ); - } - } else { - Accessor sa = getComponent(e.reference); - // Apply changes - single.clear(); - Event noRefEvent = e.clone(null); - single.add(noRefEvent); - sa.apply(single, rollback); - } - } - } catch (AccessorConstructionException ae) { - throw new AccessorException(ae); - } - } - - Event applyLocal(Event e, boolean makeRollback) throws AccessorException { - Event rollback = null; - try { - if (e instanceof ValueAssigned) { - ValueAssigned va = (ValueAssigned) e; - if (makeRollback) { - Binding binding = params.bindingScheme.getBinding(type()); - rollback = new ValueAssigned(binding, getValue(binding)); - } - 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) { - MutableVariant oldKey = er.key; - MutableVariant oldValue = getAsVariant(er.key.getBinding(), er.key.getValue()); - 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; - } catch (BindingConstructionException e2) { - throw new AccessorException( e2 ); - } - } - - - @SuppressWarnings("unchecked") - @Override - public T getComponent(ChildReference reference) - throws AccessorConstructionException { - if (reference==null) return (T) this; - if (reference instanceof LabelReference) { - try { - LabelReference lr = (LabelReference) reference; - - MutableVariant variant = (MutableVariant) params.adapterScheme.adapt(lr.label, Bindings.STRING, Bindings.MUTABLE_VARIANT); - Object value = variant.getValue(KEY_BINDING); - Accessor result = (T) getValueAccessor(KEY_BINDING, value); - - if (reference.getChildReference() != null) - result = result.getComponent(reference.getChildReference()); - return (T) result; - } catch (AdaptException e) { - throw new ReferenceException(e); - } - } else if (reference instanceof KeyReference) { - try { - KeyReference ref = (KeyReference) reference; - String keyStr = (String) params.adapterScheme.adapt(ref.key.getValue(), ref.key.getBinding(), KEY_BINDING); - File f = keyToFile(keyStr); - if (!dir.files().contains(f)) - throw new AccessorConstructionException("Invalid reference "+ref.key); - - Accessor result = getValueAccessor(KEY_BINDING, keyStr); - if (reference.getChildReference() != null) - result = result.getComponent(reference.getChildReference()); - return (T) result; - } catch (AdaptException e) { - throw new ReferenceException(e); - } - } - throw new ReferenceException(reference.getClass().getName()+" is not a reference of a map"); - } - - @Override - public void removeListener(Listener listener) throws AccessorException { - detachListener(listener); - } - - protected ListenerEntry detachListener(Listener listener) throws AccessorException { - ListenerEntry e = listeners; - ListenerEntry p = null; - while (e!=null) { - // Found match - if (e.listener == listener) { - // The match was the first entry of the linked list - if (p==null) { - listeners = e.next; - return e; - } - // Some other entry, unlink e - p.next = e.next; - return e; - } - p = e; - e = e.next; - } - return null; - } - - protected void emitEvent(ListenerEntry le, Event e) { - e.reference = ChildReference.concatenate(le.path, e.reference); - le.emitEvent(e); - } - - protected void emitEvents(ListenerEntry le, Collection events) { - for (Event e : events) - e.reference = ChildReference.concatenate(le.path, e.reference); - le.emitEvents(events); - } - - -} - +/******************************************************************************* + * Industry THTH ry. + * Copyright (c) 2010- Association for Decentralized Information Management in + * 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.impl; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.Executor; + +import org.simantics.databoard.Bindings; +import org.simantics.databoard.Datatypes; +import org.simantics.databoard.accessor.Accessor; +import org.simantics.databoard.accessor.CloseableAccessor; +import org.simantics.databoard.accessor.MapAccessor; +import org.simantics.databoard.accessor.VariantAccessor; +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.file.FileLibrary; +import org.simantics.databoard.accessor.file.FileVariantAccessor; +import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryEvent; +import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryListener; +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.VariantBinding; +import org.simantics.databoard.binding.error.BindingConstructionException; +import org.simantics.databoard.binding.error.BindingException; +import org.simantics.databoard.binding.error.RuntimeBindingException; +import org.simantics.databoard.binding.mutable.MutableVariant; +import org.simantics.databoard.type.Datatype; +import org.simantics.databoard.type.MapType; + +/** + * DirectoryMap is a file backed map implementation where keys are filenames + * and values are corresponding files. + *

+ * This class is an implmentation to Map(Variant, Variant) -Accessor. + * + * Filenames have the following encoding: + * S.dbb String types, if string doesn't have the following + * control characters " : < > | ? * \ / [0..31] + * I.dbb Integer types + * L.dbb Long types + * H.dbb All other cases the value as binary + *

+ * File accessor is created if an entry opened as a sub-accessor. + * The file accessor is closed when all sub-accessors are released. + * The implementation is based on proxy instances and a reference queue. + * Once the queue is empty, file accessor is closed. + *

+ * DirectoryMap must be closed with #close(); + * + * @author Toni Kalajainen + */ +public class DirectoryMap implements MapAccessor, CloseableAccessor { + + /** Key binding */ + final static Binding KEY_BINDING = Bindings.STR_VARIANT; + + /** Cache of sub-accessors */ + FileLibrary files; + + /** Monitors directory for file changes */ + DirectoryWatch dir; + + /** Folder */ + File path; + + /** Listeners */ + ListenerEntry listeners = null; + + /** Parent, optional */ + Accessor parent; + + /** Accessor params */ + AccessorParams params; + + DirectoryListener dirListener = new DirectoryListener() { + @Override + public void onWatchEvent(DirectoryEvent e) { + + } + }; + + public DirectoryMap(File directory) { + this(directory, null, AccessorParams.DEFAULT); + } + + public DirectoryMap(File directory, Accessor parent) { + this(directory, parent, AccessorParams.DEFAULT); + } + + public DirectoryMap(File directory, Accessor parent, AccessorParams params) { + this.parent = parent; + this.path = directory; + this.params = params; + + // Filters .dbb files + FileFilter filter = new FileFilter() { + public boolean accept(File pathname) { + String filename = pathname.getName(); + if (filename.length()==0) return false; + char c = filename.charAt(0); + if (c!='S' && c!='I' && c!='L' && c!='B') return false; + if (filename.endsWith(".dbb")) return true; + return filename.toLowerCase().endsWith(".dbb"); + }}; + + dir = new DirectoryWatch(path, filter); + + dir.addListener( dirListener ); + + files = new FileLibrary(); + } + + public void close() { + dir.removeListener( dirListener ); + dir.close(); + files.close(); + } + + static MapType type = new MapType(Datatypes.VARIANT, Datatypes.VARIANT); + public MapType type() { + return type; + } + + private String fileToKey(File f) { + String filename = f.getName(); + String keyStr = filename.substring(0, filename.length()-4); + return keyStr; + } + + private File keyToFile(String keyStr) { + return new File(path, keyStr + ".dbb" ); + } + + @Override + public void clear() throws AccessorException { + //List deleteList = dir.files(); + List failList = new ArrayList(); + boolean hasListeners = listeners!=null; + List keys = hasListeners ? new ArrayList() : null; + + // Close all file handles + files.close(); + + // Delete files + for (File f : dir.files()) { + if (!files.deleteFile(f)) { + failList.add(f); + } + if (hasListeners) { + String keyStr = fileToKey(f); + keys.add(keyStr); + } + } + + // Re-read directory + dir.refresh(); + + // Notify Listeners + ListenerEntry le = listeners; + while (le!=null) { + MapInterestSet is = le.getInterestSet(); + for (Object key : keys) { + MutableVariant var = new MutableVariant(KEY_BINDING, key); + if (is.inNotificationsOf(var)) { + MapEntryRemoved e = new MapEntryRemoved(var); + emitEvent(le, e); + } + } + le = le.next; + } + + // Some files failed to delete + if (!failList.isEmpty()) { + StringBuilder sb = new StringBuilder(); + sb.append("Failed to delete"); + for (File f : failList) { + sb.append(' '); + sb.append(f.toString()); + } + // HAX + throw new AccessorException(sb.toString()); + } + + + } + + @Override + public String toString() { + return dir.toString(); + } + + public Object getValue(Binding binding) throws AccessorException { + MapBinding mb = (MapBinding) binding; + if (mb.getKeyBinding() instanceof VariantBinding==false || mb.getValueBinding() instanceof VariantBinding==false) + throw new AccessorException("Map(Variant, Variant) Expected"); + // Get all files as a single map + try { + Object result = binding.createDefault(); + for (File f : dir.files()) { + // Create Key + String keyStr = fileToKey(f); + Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, mb.getKeyBinding()); + + // Read value + VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr); + Object value = va.getValue(mb.getValueBinding()); + + mb.put(result, key, value); + } + return result; + } catch (BindingException e) { + throw new AccessorException(e); + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } catch (AdaptException e) { + throw new AccessorException(e); + } + } + + @Override + public void getValue(Binding dstBinding, Object dst) throws AccessorException { + MapBinding db = (MapBinding) dstBinding; + Binding dkb = db.getKeyBinding(); + Binding dvb = db.getValueBinding(); + if (dkb instanceof VariantBinding==false || dvb instanceof VariantBinding==false) + throw new AccessorException("Map(Variant, Variant) Expected"); + // Get all files as a single map + try { + TreeSet dstKeys = new TreeSet(dkb); + db.getKeys(dst, dstKeys); + + for (File f : dir.files()) { + // Create Key + String keyStr = fileToKey(f); + Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, dkb); + + Object v = db.containsKey(dst, key) ? db.get(dst, key) : dvb.createDefault(); + VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr); + va.getValue(dvb, v); + + db.put(dst, key, v); + dstKeys.remove(key); + } + + for (Object key : dstKeys) + db.remove(dst, key); + } catch (BindingException e) { + throw new AccessorException(e); + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } catch (AdaptException e) { + throw new AccessorException(e); + } + + } + + @Override + public boolean getValue(ChildReference path, Binding binding, Object obj) throws AccessorException { + try { + Accessor a = getComponent(path); + a.getValue(binding, obj); + return true; + } catch (ReferenceException re) { + return false; + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } + } + + public Object getValue(ChildReference path, Binding binding) throws AccessorException { + try { + Accessor a = getComponent(path); + return a.getValue(binding); + } catch (ReferenceException re) { + return null; + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } + } + + + @Override + public boolean containsKey(Binding keyBinding, Object key) + throws AccessorException { + try { + String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); + File file = keyToFile(key_); + return dir.files().contains(file); + } catch (AdaptException e) { + throw new AccessorException(e); + } + } + + @Override + public boolean containsValue(Binding valueBinding, Object value) + throws AccessorException { + try { + for (File f : dir.files()) { + String key = fileToKey(f); + VariantAccessor va = getValueAccessor(KEY_BINDING, key); + Object v = va.getValue(valueBinding); + boolean match = valueBinding.equals(v, value); + if ( match ) return true; + } + return false; + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } + } + + @Override + public Object get(Binding keyBinding, Object key, Binding valueBinding) + throws AccessorException { + try { + VariantAccessor va = getValueAccessor(keyBinding, key); + return va.getValue(valueBinding); + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } + } + + /** + * Get the value as a variant + * + * @param keyBinding + * @param key + * @return value + * @throws AccessorException + */ + public MutableVariant getAsVariant(Binding keyBinding, Object key) + throws AccessorException { + try { + VariantAccessor va = getValueAccessor(keyBinding, key); + Datatype type = va.getContentType(); + Binding binding = params.bindingScheme.getBinding(type); + Object value = va.getContentValue(binding); + MutableVariant result = new MutableVariant(binding, value); + return result; + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } catch (BindingConstructionException e) { + throw new AccessorException(e); + } + } + + @Override + public void getAll(Binding keyBinding, Binding valueBinding, + Map to) throws AccessorException { + try { + for (File f : dir.files()) { + // Create key + String keyStr = fileToKey(f); + Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding); + + // Read value + VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr); + Object value = va.getValue(valueBinding); + + to.put(key, value); + } + } catch (AdaptException e) { + throw new AccessorException(e); + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } + } + + @Override + public void getAll(Binding keyBinding, Binding valueBinding, Object[] keys, + Object[] values) throws AccessorException { + try { + Set fileKeys = createKeys(); + + int i=0; + for (String keyStr : fileKeys) { + // Read value + VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr); + Object value = va.getValue(valueBinding); + + Object key2 = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding); + keys[i] = key2; + values[i] = value; + i++; + } + } catch (AdaptException e) { + throw new AccessorException(e); + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } + } + + + @Override + public int count(Binding keyBinding, Object from, + boolean fromInclusive, Object end, boolean endInclusive) + throws AccessorException { + throw new AccessorException("Not implemented"); + } + + @Override + public int getEntries(Binding keyBinding, Object from, + boolean fromInclusive, Object end, boolean endInclusive, + ArrayBinding keyArrayBinding, Object dstKeys, + ArrayBinding valueArrayBinding, Object dstValues, int limit) + throws AccessorException { + throw new AccessorException("Not implemented"); + } + + + TreeSet createKeys() throws RuntimeBindingException { + List files = dir.files(); + TreeSet keys = new TreeSet(KEY_BINDING); + + for (File f : files) { + String filename = f.getName(); + String str = filename.substring(0, filename.length()-4); + keys.add(str); + } + + return keys; + } + + @Override + public Object getCeilingKey(Binding keyBinding, Object key) + throws AccessorException { + try { + TreeSet keys = createKeys(); + String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); + if (keys.contains(k)) return key; + Object res = keys.ceiling(k); + if (res==null) return null; + return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding); + } catch (RuntimeBindingException e) { + throw new AccessorException(e); + } catch (AdaptException e) { + throw new AccessorException(e); + } + } + + @Override + public Object getFirstKey(Binding keyBinding) throws AccessorException { + List files = dir.files(); + String firstKey = null; + + for (File f : files) { + String filename = f.getName(); + String str = filename.substring(0, filename.length()-4); + if (firstKey == null) { + firstKey = str; + } else { + if (KEY_BINDING.compare(str, firstKey)<0) firstKey = str; + } + } + if (firstKey==null) return null; + + try { + return params.adapterScheme.adapt(firstKey, KEY_BINDING, keyBinding); + } catch (AdaptException e) { + throw new AccessorException(e); + } + } + + @Override + public Object getFloorKey(Binding keyBinding, Object key) + throws AccessorException { + try { + TreeSet keys = createKeys(); + String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); + Object res = keys.floor(k); + if (res==null) return null; + return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding); + } catch (RuntimeBindingException e) { + throw new AccessorException(e); + } catch (AdaptException e) { + throw new AccessorException(e); + } + } + + @Override + public Object getHigherKey(Binding keyBinding, Object key) + throws AccessorException { + try { + TreeSet keys = createKeys(); + String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); + Object res = keys.higher(k); + if (res==null) return null; + return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding); + } catch (RuntimeBindingException e) { + throw new AccessorException(e); + } catch (AdaptException e) { + throw new AccessorException(e); + } + } + + @Override + public Object[] getKeys(Binding keyBinding) throws AccessorException { + TreeSet keys = createKeys(); + Object[] result = new Object[keys.size()]; + if (keys.isEmpty()) return result; + try { + Adapter a = params.adapterScheme.getAdapter(KEY_BINDING, keyBinding, true, false); + int index = 0; + for (String key : keys) { + result[index++] = a.adapt( key ); + } + } catch (AdaptException e) { + throw new AccessorException(e); + } catch (AdapterConstructionException e) { + throw new AccessorException(e); + } + + return result; + } + + @Override + public Object getLastKey(Binding keyBinding) throws AccessorException { + List files = dir.files(); + String lastKey = null; + + for (File f : files) { + String filename = f.getName(); + String str = filename.substring(0, filename.length()-4); + if (lastKey == null) { + lastKey = str; + } else { + if (KEY_BINDING.compare(str, lastKey)>0) lastKey = str; + } + } + if (lastKey==null) return null; + + try { + return params.adapterScheme.adapt(lastKey, KEY_BINDING, keyBinding); + } catch (AdaptException e) { + throw new AccessorException(e); + } + } + + @Override + public Object getLowerKey(Binding keyBinding, Object key) + throws AccessorException { + try { + TreeSet keys = createKeys(); + String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); + Object res = keys.lower(k); + if (res==null) return null; + return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding); + } catch (RuntimeBindingException e) { + throw new AccessorException(e); + } catch (AdaptException e) { + throw new AccessorException(e); + } + } + + public FileVariantAccessor getExistingAccessor(Binding keyBinding, + Object key) throws AccessorConstructionException { + try { + String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); + File file = new File(path, key_ + ".dbb" ); + return files.getExistingFile(file); + } catch (AdaptException e) { + throw new AccessorConstructionException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public FileVariantAccessor getValueAccessor(Binding keyBinding, Object key) throws AccessorConstructionException { + try { + String keyStr = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING); + File file = keyToFile(keyStr); + FileVariantAccessor sa = files.getExistingFile(file); + if (sa!=null) return sa; + + // Create new accessor + sa = files.getFile(file); + + // Add component interest sets + ListenerEntry le = listeners; + if (le!=null) { + MutableVariant kv = new MutableVariant(keyBinding, key); + 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; + } + } + + return sa; + } catch (AdaptException e) { + throw new AccessorConstructionException(e); + } + } + + @Override + public Object[] getValues(Binding valueBinding) throws AccessorException { + try { + Set keys = createKeys(); + int count = keys.size(); + Object[] result = new Object[ count ]; + + Iterator iter = keys.iterator(); + for (int i=0; i from) throws AccessorException { + //boolean created = false; + for (Entry e : from.entrySet()) { + Object key = e.getKey(); + Object value = e.getValue(); + /*created |=*/ putLocal(keyBinding, key, valueBinding, value); + } + } + + @Override + public void putAll(Binding keyBinding, Binding valueBinding, Object[] keys, + Object[] values) throws AccessorException { + //boolean created = false; + if (keys.length!=values.length) + throw new AccessorException("Array lengths mismatch"); + for (int i=0; i writeFiles = new HashSet(); + List oldFiles = dir.files(); + + //HashSet modifiedFiles = new HashSet(); + HashSet addedFiles = new HashSet(writeFiles); + addedFiles.removeAll(oldFiles); + + // Write + for (int i=0; i removedFiles = new HashSet(oldFiles); + removedFiles.removeAll(writeFiles); + + // Remove old files + files.expunge(); + if (!removedFiles.isEmpty()) { + List failList = new ArrayList(); + for (File f : removedFiles) { + + String filename = f.getName(); + String keyStr = filename.substring(0, filename.length()-4); + + boolean deleted = files.deleteFile(f); + if ( !deleted ) { + failList.add(f); + } else { + + // Notify Listeners + if (listeners!=null) { + MutableVariant var = new MutableVariant(KEY_BINDING, keyStr); + 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; + } + } + } + + if (!failList.isEmpty()) { + StringBuilder sb = new StringBuilder(); + sb.append("Failed to delete"); + for (File ff : failList) { + sb.append(' '); + sb.append(ff.toString()); + } + throw new AccessorException(sb.toString()); + } + } + } + dir.refresh(); + + } catch (BindingException e) { + throw new AccessorException(e); + } catch (AdaptException e) { + throw new AccessorException(e); + } catch (AdapterConstructionException e) { + throw new AccessorException(e); + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } + } + + @Override + public int size() throws AccessorException { + dir.refresh(); + return dir.files().size(); + } + + @Override + public void addListener(Listener listener, InterestSet interestSet, ChildReference path, Executor executor) throws AccessorException { + listeners = ListenerEntry.link(listeners, listener, interestSet, path, executor); + MapInterestSet is = (MapInterestSet) interestSet; + + try { + for (File f : dir.files()) { + String filename = f.getName(); + String keyStr = filename.substring(0, filename.length()-4); + + Accessor sa = getExistingAccessor(KEY_BINDING, keyStr); + if (sa==null) continue; + + MutableVariant key = new MutableVariant(KEY_BINDING, keyStr); + InterestSet cis = is.getComponentInterest(); + if (cis!=null) { + ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) ); + sa.addListener(listener, cis, childPath, executor); + } + cis = is.getComponentInterest( key ); + if (cis!=null) { + ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) ); + sa.addListener(listener, cis, childPath, executor); + } + } + } catch (AccessorConstructionException e) { + throw new AccessorException(e); + } + + } + + @Override + public void apply(List cs, LinkedList rollback) throws AccessorException { + try { + boolean makeRollback = rollback != null; + ArrayList single = new ArrayList(); + for (Event e : cs) { + if (e.reference==null) { + Event rbe = applyLocal(e, makeRollback); + if (makeRollback) { + rbe.reference = e.reference; + rollback.addFirst( rbe ); + } + } else { + Accessor sa = getComponent(e.reference); + // Apply changes + single.clear(); + Event noRefEvent = e.clone(null); + single.add(noRefEvent); + sa.apply(single, rollback); + } + } + } catch (AccessorConstructionException ae) { + throw new AccessorException(ae); + } + } + + Event applyLocal(Event e, boolean makeRollback) throws AccessorException { + Event rollback = null; + try { + if (e instanceof ValueAssigned) { + ValueAssigned va = (ValueAssigned) e; + if (makeRollback) { + Binding binding = params.bindingScheme.getBinding(type()); + rollback = new ValueAssigned(binding, getValue(binding)); + } + 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) { + MutableVariant oldKey = er.key; + MutableVariant oldValue = getAsVariant(er.key.getBinding(), er.key.getValue()); + 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; + } catch (BindingConstructionException e2) { + throw new AccessorException( e2 ); + } + } + + + @SuppressWarnings("unchecked") + @Override + public T getComponent(ChildReference reference) + throws AccessorConstructionException { + if (reference==null) return (T) this; + if (reference instanceof LabelReference) { + try { + LabelReference lr = (LabelReference) reference; + + MutableVariant variant = (MutableVariant) params.adapterScheme.adapt(lr.label, Bindings.STRING, Bindings.MUTABLE_VARIANT); + Object value = variant.getValue(KEY_BINDING); + Accessor result = (T) getValueAccessor(KEY_BINDING, value); + + if (reference.getChildReference() != null) + result = result.getComponent(reference.getChildReference()); + return (T) result; + } catch (AdaptException e) { + throw new ReferenceException(e); + } + } else if (reference instanceof KeyReference) { + try { + KeyReference ref = (KeyReference) reference; + String keyStr = (String) params.adapterScheme.adapt(ref.key.getValue(), ref.key.getBinding(), KEY_BINDING); + File f = keyToFile(keyStr); + if (!dir.files().contains(f)) + throw new AccessorConstructionException("Invalid reference "+ref.key); + + Accessor result = getValueAccessor(KEY_BINDING, keyStr); + if (reference.getChildReference() != null) + result = result.getComponent(reference.getChildReference()); + return (T) result; + } catch (AdaptException e) { + throw new ReferenceException(e); + } + } + throw new ReferenceException(reference.getClass().getName()+" is not a reference of a map"); + } + + @Override + public void removeListener(Listener listener) throws AccessorException { + detachListener(listener); + } + + protected ListenerEntry detachListener(Listener listener) throws AccessorException { + ListenerEntry e = listeners; + ListenerEntry p = null; + while (e!=null) { + // Found match + if (e.listener == listener) { + // The match was the first entry of the linked list + if (p==null) { + listeners = e.next; + return e; + } + // Some other entry, unlink e + p.next = e.next; + return e; + } + p = e; + e = e.next; + } + return null; + } + + protected void emitEvent(ListenerEntry le, Event e) { + e.reference = ChildReference.concatenate(le.path, e.reference); + le.emitEvent(e); + } + + protected void emitEvents(ListenerEntry le, Collection events) { + for (Event e : events) + e.reference = ChildReference.concatenate(le.path, e.reference); + le.emitEvents(events); + } + + +} +