--- /dev/null
+/*******************************************************************************\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