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