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