1 /*******************************************************************************
\r
3 * Copyright (c) 2010- Association for Decentralized Information Management in
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.databoard.accessor.impl;
\r
14 import java.io.File;
\r
15 import java.io.FileFilter;
\r
16 import java.util.ArrayList;
\r
17 import java.util.Collection;
\r
18 import java.util.HashSet;
\r
19 import java.util.Iterator;
\r
20 import java.util.LinkedList;
\r
21 import java.util.List;
\r
22 import java.util.Map;
\r
23 import java.util.Map.Entry;
\r
24 import java.util.Set;
\r
25 import java.util.TreeSet;
\r
26 import java.util.concurrent.Executor;
\r
28 import org.simantics.databoard.Bindings;
\r
29 import org.simantics.databoard.Datatypes;
\r
30 import org.simantics.databoard.accessor.Accessor;
\r
31 import org.simantics.databoard.accessor.CloseableAccessor;
\r
32 import org.simantics.databoard.accessor.MapAccessor;
\r
33 import org.simantics.databoard.accessor.VariantAccessor;
\r
34 import org.simantics.databoard.accessor.error.AccessorConstructionException;
\r
35 import org.simantics.databoard.accessor.error.AccessorException;
\r
36 import org.simantics.databoard.accessor.error.ReferenceException;
\r
37 import org.simantics.databoard.accessor.event.Event;
\r
38 import org.simantics.databoard.accessor.event.MapEntryAdded;
\r
39 import org.simantics.databoard.accessor.event.MapEntryRemoved;
\r
40 import org.simantics.databoard.accessor.event.ValueAssigned;
\r
41 import org.simantics.databoard.accessor.file.FileLibrary;
\r
42 import org.simantics.databoard.accessor.file.FileVariantAccessor;
\r
43 import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryEvent;
\r
44 import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryListener;
\r
45 import org.simantics.databoard.accessor.interestset.InterestSet;
\r
46 import org.simantics.databoard.accessor.interestset.MapInterestSet;
\r
47 import org.simantics.databoard.accessor.reference.ChildReference;
\r
48 import org.simantics.databoard.accessor.reference.KeyReference;
\r
49 import org.simantics.databoard.accessor.reference.LabelReference;
\r
50 import org.simantics.databoard.adapter.AdaptException;
\r
51 import org.simantics.databoard.adapter.Adapter;
\r
52 import org.simantics.databoard.adapter.AdapterConstructionException;
\r
53 import org.simantics.databoard.binding.ArrayBinding;
\r
54 import org.simantics.databoard.binding.Binding;
\r
55 import org.simantics.databoard.binding.MapBinding;
\r
56 import org.simantics.databoard.binding.VariantBinding;
\r
57 import org.simantics.databoard.binding.error.BindingConstructionException;
\r
58 import org.simantics.databoard.binding.error.BindingException;
\r
59 import org.simantics.databoard.binding.error.RuntimeBindingException;
\r
60 import org.simantics.databoard.binding.mutable.MutableVariant;
\r
61 import org.simantics.databoard.type.Datatype;
\r
62 import org.simantics.databoard.type.MapType;
\r
65 * DirectoryMap is a file backed map implementation where keys are filenames
\r
66 * and values are corresponding files.
\r
68 * This class is an implmentation to Map(Variant, Variant) -Accessor.
\r
70 * Filenames have the following encoding:
\r
71 * S<string>.dbb String types, if string doesn't have the following
\r
72 * control characters " : < > | ? * \ / [0..31]
\r
73 * I<integer>.dbb Integer types
\r
74 * L<long>.dbb Long types
\r
75 * H<hex>.dbb All other cases the value as binary
\r
77 * File accessor is created if an entry opened as a sub-accessor.
\r
78 * The file accessor is closed when all sub-accessors are released.
\r
79 * The implementation is based on proxy instances and a reference queue.
\r
80 * Once the queue is empty, file accessor is closed.
\r
82 * DirectoryMap must be closed with #close();
\r
84 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
\r
86 public class DirectoryMap implements MapAccessor, CloseableAccessor {
\r
89 final static Binding KEY_BINDING = Bindings.STR_VARIANT;
\r
91 /** Cache of sub-accessors */
\r
94 /** Monitors directory for file changes */
\r
101 ListenerEntry listeners = null;
\r
103 /** Parent, optional */
\r
106 /** Accessor params */
\r
107 AccessorParams params;
\r
109 DirectoryListener dirListener = new DirectoryListener() {
\r
111 public void onWatchEvent(DirectoryEvent e) {
\r
116 public DirectoryMap(File directory) {
\r
117 this(directory, null, AccessorParams.DEFAULT);
\r
120 public DirectoryMap(File directory, Accessor parent) {
\r
121 this(directory, parent, AccessorParams.DEFAULT);
\r
124 public DirectoryMap(File directory, Accessor parent, AccessorParams params) {
\r
125 this.parent = parent;
\r
126 this.path = directory;
\r
127 this.params = params;
\r
129 // Filters .dbb files
\r
130 FileFilter filter = new FileFilter() {
\r
131 public boolean accept(File pathname) {
\r
132 String filename = pathname.getName();
\r
133 if (filename.length()==0) return false;
\r
134 char c = filename.charAt(0);
\r
135 if (c!='S' && c!='I' && c!='L' && c!='B') return false;
\r
136 if (filename.endsWith(".dbb")) return true;
\r
137 return filename.toLowerCase().endsWith(".dbb");
\r
140 dir = new DirectoryWatch(path, filter);
\r
142 dir.addListener( dirListener );
\r
144 files = new FileLibrary();
\r
147 public void close() {
\r
148 dir.removeListener( dirListener );
\r
153 static MapType type = new MapType(Datatypes.VARIANT, Datatypes.VARIANT);
\r
154 public MapType type() {
\r
158 private String fileToKey(File f) {
\r
159 String filename = f.getName();
\r
160 String keyStr = filename.substring(0, filename.length()-4);
\r
164 private File keyToFile(String keyStr) {
\r
165 return new File(path, keyStr + ".dbb" );
\r
169 public void clear() throws AccessorException {
\r
170 //List<File> deleteList = dir.files();
\r
171 List<File> failList = new ArrayList<File>();
\r
172 boolean hasListeners = listeners!=null;
\r
173 List<String> keys = hasListeners ? new ArrayList<String>() : null;
\r
175 // Close all file handles
\r
179 for (File f : dir.files()) {
\r
180 if (!files.deleteFile(f)) {
\r
183 if (hasListeners) {
\r
184 String keyStr = fileToKey(f);
\r
189 // Re-read directory
\r
192 // Notify Listeners
\r
193 ListenerEntry le = listeners;
\r
194 while (le!=null) {
\r
195 MapInterestSet is = le.getInterestSet();
\r
196 for (Object key : keys) {
\r
197 MutableVariant var = new MutableVariant(KEY_BINDING, key);
\r
198 if (is.inNotificationsOf(var)) {
\r
199 MapEntryRemoved e = new MapEntryRemoved(var);
\r
206 // Some files failed to delete
\r
207 if (!failList.isEmpty()) {
\r
208 StringBuilder sb = new StringBuilder();
\r
209 sb.append("Failed to delete");
\r
210 for (File f : failList) {
\r
212 sb.append(f.toString());
\r
215 throw new AccessorException(sb.toString());
\r
222 public String toString() {
\r
223 return dir.toString();
\r
226 public Object getValue(Binding binding) throws AccessorException {
\r
227 MapBinding mb = (MapBinding) binding;
\r
228 if (mb.getKeyBinding() instanceof VariantBinding==false || mb.getValueBinding() instanceof VariantBinding==false)
\r
229 throw new AccessorException("Map(Variant, Variant) Expected");
\r
230 // Get all files as a single map
\r
232 Object result = binding.createDefault();
\r
233 for (File f : dir.files()) {
\r
235 String keyStr = fileToKey(f);
\r
236 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, mb.getKeyBinding());
\r
239 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
\r
240 Object value = va.getValue(mb.getValueBinding());
\r
242 mb.put(result, key, value);
\r
245 } catch (BindingException e) {
\r
246 throw new AccessorException(e);
\r
247 } catch (AccessorConstructionException e) {
\r
248 throw new AccessorException(e);
\r
249 } catch (AdaptException e) {
\r
250 throw new AccessorException(e);
\r
255 public void getValue(Binding dstBinding, Object dst) throws AccessorException {
\r
256 MapBinding db = (MapBinding) dstBinding;
\r
257 Binding dkb = db.getKeyBinding();
\r
258 Binding dvb = db.getValueBinding();
\r
259 if (dkb instanceof VariantBinding==false || dvb instanceof VariantBinding==false)
\r
260 throw new AccessorException("Map(Variant, Variant) Expected");
\r
261 // Get all files as a single map
\r
263 TreeSet<Object> dstKeys = new TreeSet<Object>(dkb);
\r
264 db.getKeys(dst, dstKeys);
\r
266 for (File f : dir.files()) {
\r
268 String keyStr = fileToKey(f);
\r
269 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, dkb);
\r
271 Object v = db.containsKey(dst, key) ? db.get(dst, key) : dvb.createDefault();
\r
272 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
\r
273 va.getValue(dvb, v);
\r
275 db.put(dst, key, v);
\r
276 dstKeys.remove(key);
\r
279 for (Object key : dstKeys)
\r
280 db.remove(dst, key);
\r
281 } catch (BindingException e) {
\r
282 throw new AccessorException(e);
\r
283 } catch (AccessorConstructionException e) {
\r
284 throw new AccessorException(e);
\r
285 } catch (AdaptException e) {
\r
286 throw new AccessorException(e);
\r
292 public boolean getValue(ChildReference path, Binding binding, Object obj) throws AccessorException {
\r
294 Accessor a = getComponent(path);
\r
295 a.getValue(binding, obj);
\r
297 } catch (ReferenceException re) {
\r
299 } catch (AccessorConstructionException e) {
\r
300 throw new AccessorException(e);
\r
304 public Object getValue(ChildReference path, Binding binding) throws AccessorException {
\r
306 Accessor a = getComponent(path);
\r
307 return a.getValue(binding);
\r
308 } catch (ReferenceException re) {
\r
310 } catch (AccessorConstructionException e) {
\r
311 throw new AccessorException(e);
\r
317 public boolean containsKey(Binding keyBinding, Object key)
\r
318 throws AccessorException {
\r
320 String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
321 File file = keyToFile(key_);
\r
322 return dir.files().contains(file);
\r
323 } catch (AdaptException e) {
\r
324 throw new AccessorException(e);
\r
329 public boolean containsValue(Binding valueBinding, Object value)
\r
330 throws AccessorException {
\r
332 for (File f : dir.files()) {
\r
333 String key = fileToKey(f);
\r
334 VariantAccessor va = getValueAccessor(KEY_BINDING, key);
\r
335 Object v = va.getValue(valueBinding);
\r
336 boolean match = valueBinding.equals(v, value);
\r
337 if ( match ) return true;
\r
340 } catch (AccessorConstructionException e) {
\r
341 throw new AccessorException(e);
\r
346 public Object get(Binding keyBinding, Object key, Binding valueBinding)
\r
347 throws AccessorException {
\r
349 VariantAccessor va = getValueAccessor(keyBinding, key);
\r
350 return va.getValue(valueBinding);
\r
351 } catch (AccessorConstructionException e) {
\r
352 throw new AccessorException(e);
\r
357 * Get the value as a variant
\r
359 * @param keyBinding
\r
362 * @throws AccessorException
\r
364 public MutableVariant getAsVariant(Binding keyBinding, Object key)
\r
365 throws AccessorException {
\r
367 VariantAccessor va = getValueAccessor(keyBinding, key);
\r
368 Datatype type = va.getContentType();
\r
369 Binding binding = params.bindingScheme.getBinding(type);
\r
370 Object value = va.getContentValue(binding);
\r
371 MutableVariant result = new MutableVariant(binding, value);
\r
373 } catch (AccessorConstructionException e) {
\r
374 throw new AccessorException(e);
\r
375 } catch (BindingConstructionException e) {
\r
376 throw new AccessorException(e);
\r
381 public void getAll(Binding keyBinding, Binding valueBinding,
\r
382 Map<Object, Object> to) throws AccessorException {
\r
384 for (File f : dir.files()) {
\r
386 String keyStr = fileToKey(f);
\r
387 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding);
\r
390 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
\r
391 Object value = va.getValue(valueBinding);
\r
393 to.put(key, value);
\r
395 } catch (AdaptException e) {
\r
396 throw new AccessorException(e);
\r
397 } catch (AccessorConstructionException e) {
\r
398 throw new AccessorException(e);
\r
403 public void getAll(Binding keyBinding, Binding valueBinding, Object[] keys,
\r
404 Object[] values) throws AccessorException {
\r
406 Set<String> fileKeys = createKeys();
\r
409 for (String keyStr : fileKeys) {
\r
411 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
\r
412 Object value = va.getValue(valueBinding);
\r
414 Object key2 = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding);
\r
419 } catch (AdaptException e) {
\r
420 throw new AccessorException(e);
\r
421 } catch (AccessorConstructionException e) {
\r
422 throw new AccessorException(e);
\r
428 public int count(Binding keyBinding, Object from,
\r
429 boolean fromInclusive, Object end, boolean endInclusive)
\r
430 throws AccessorException {
\r
431 throw new AccessorException("Not implemented");
\r
435 public int getEntries(Binding keyBinding, Object from,
\r
436 boolean fromInclusive, Object end, boolean endInclusive,
\r
437 ArrayBinding keyArrayBinding, Object dstKeys,
\r
438 ArrayBinding valueArrayBinding, Object dstValues, int limit)
\r
439 throws AccessorException {
\r
440 throw new AccessorException("Not implemented");
\r
444 TreeSet<String> createKeys() throws RuntimeBindingException {
\r
445 List<File> files = dir.files();
\r
446 TreeSet<String> keys = new TreeSet<String>(KEY_BINDING);
\r
448 for (File f : files) {
\r
449 String filename = f.getName();
\r
450 String str = filename.substring(0, filename.length()-4);
\r
458 public Object getCeilingKey(Binding keyBinding, Object key)
\r
459 throws AccessorException {
\r
461 TreeSet<String> keys = createKeys();
\r
462 String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
463 if (keys.contains(k)) return key;
\r
464 Object res = keys.ceiling(k);
\r
465 if (res==null) return null;
\r
466 return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding);
\r
467 } catch (RuntimeBindingException e) {
\r
468 throw new AccessorException(e);
\r
469 } catch (AdaptException e) {
\r
470 throw new AccessorException(e);
\r
475 public Object getFirstKey(Binding keyBinding) throws AccessorException {
\r
476 List<File> files = dir.files();
\r
477 String firstKey = null;
\r
479 for (File f : files) {
\r
480 String filename = f.getName();
\r
481 String str = filename.substring(0, filename.length()-4);
\r
482 if (firstKey == null) {
\r
485 if (KEY_BINDING.compare(str, firstKey)<0) firstKey = str;
\r
488 if (firstKey==null) return null;
\r
491 return params.adapterScheme.adapt(firstKey, KEY_BINDING, keyBinding);
\r
492 } catch (AdaptException e) {
\r
493 throw new AccessorException(e);
\r
498 public Object getFloorKey(Binding keyBinding, Object key)
\r
499 throws AccessorException {
\r
501 TreeSet<String> keys = createKeys();
\r
502 String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
503 Object res = keys.floor(k);
\r
504 if (res==null) return null;
\r
505 return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding);
\r
506 } catch (RuntimeBindingException e) {
\r
507 throw new AccessorException(e);
\r
508 } catch (AdaptException e) {
\r
509 throw new AccessorException(e);
\r
514 public Object getHigherKey(Binding keyBinding, Object key)
\r
515 throws AccessorException {
\r
517 TreeSet<String> keys = createKeys();
\r
518 String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
519 Object res = keys.higher(k);
\r
520 if (res==null) return null;
\r
521 return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding);
\r
522 } catch (RuntimeBindingException e) {
\r
523 throw new AccessorException(e);
\r
524 } catch (AdaptException e) {
\r
525 throw new AccessorException(e);
\r
530 public Object[] getKeys(Binding keyBinding) throws AccessorException {
\r
531 TreeSet<String> keys = createKeys();
\r
532 Object[] result = new Object[keys.size()];
\r
533 if (keys.isEmpty()) return result;
\r
535 Adapter a = params.adapterScheme.getAdapter(KEY_BINDING, keyBinding, true, false);
\r
537 for (String key : keys) {
\r
538 result[index++] = a.adapt( key );
\r
540 } catch (AdaptException e) {
\r
541 throw new AccessorException(e);
\r
542 } catch (AdapterConstructionException e) {
\r
543 throw new AccessorException(e);
\r
550 public Object getLastKey(Binding keyBinding) throws AccessorException {
\r
551 List<File> files = dir.files();
\r
552 String lastKey = null;
\r
554 for (File f : files) {
\r
555 String filename = f.getName();
\r
556 String str = filename.substring(0, filename.length()-4);
\r
557 if (lastKey == null) {
\r
560 if (KEY_BINDING.compare(str, lastKey)>0) lastKey = str;
\r
563 if (lastKey==null) return null;
\r
566 return params.adapterScheme.adapt(lastKey, KEY_BINDING, keyBinding);
\r
567 } catch (AdaptException e) {
\r
568 throw new AccessorException(e);
\r
573 public Object getLowerKey(Binding keyBinding, Object key)
\r
574 throws AccessorException {
\r
576 TreeSet<String> keys = createKeys();
\r
577 String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
578 Object res = keys.lower(k);
\r
579 if (res==null) return null;
\r
580 return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding);
\r
581 } catch (RuntimeBindingException e) {
\r
582 throw new AccessorException(e);
\r
583 } catch (AdaptException e) {
\r
584 throw new AccessorException(e);
\r
588 public FileVariantAccessor getExistingAccessor(Binding keyBinding,
\r
589 Object key) throws AccessorConstructionException {
\r
591 String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
592 File file = new File(path, key_ + ".dbb" );
\r
593 return files.getExistingFile(file);
\r
594 } catch (AdaptException e) {
\r
595 throw new AccessorConstructionException(e);
\r
599 @SuppressWarnings("unchecked")
\r
601 public FileVariantAccessor getValueAccessor(Binding keyBinding, Object key) throws AccessorConstructionException {
\r
603 String keyStr = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
604 File file = keyToFile(keyStr);
\r
605 FileVariantAccessor sa = files.getExistingFile(file);
\r
606 if (sa!=null) return sa;
\r
608 // Create new accessor
\r
609 sa = files.getFile(file);
\r
611 // Add component interest sets
\r
612 ListenerEntry le = listeners;
\r
614 MutableVariant kv = new MutableVariant(keyBinding, key);
\r
615 while (le!=null) {
\r
616 MapInterestSet is = le.getInterestSet();
\r
618 // Generic element interest
\r
619 InterestSet gis = is.getComponentInterest();
\r
622 ChildReference childPath = ChildReference.concatenate(le.path, new KeyReference(kv) );
\r
623 sa.addListener(le.listener, gis, childPath, le.executor);
\r
624 } catch (AccessorException e) {
\r
625 throw new AccessorConstructionException(e);
\r
629 // Specific element interest
\r
630 InterestSet cis = is.getComponentInterest(kv);
\r
633 ChildReference childPath = ChildReference.concatenate(le.path, new KeyReference(kv) );
\r
634 sa.addListener(le.listener, cis, childPath, le.executor);
\r
635 } catch (AccessorException e) {
\r
636 throw new AccessorConstructionException(e);
\r
646 } catch (AdaptException e) {
\r
647 throw new AccessorConstructionException(e);
\r
652 public Object[] getValues(Binding valueBinding) throws AccessorException {
\r
654 Set<String> keys = createKeys();
\r
655 int count = keys.size();
\r
656 Object[] result = new Object[ count ];
\r
658 Iterator<String> iter = keys.iterator();
\r
659 for (int i=0; i<count; i++) {
\r
660 String keyStr = iter.next();
\r
662 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
\r
663 Object value = va.getValue(valueBinding);
\r
667 } catch (AccessorConstructionException e) {
\r
668 throw new AccessorException(e);
\r
674 * @param keyBinding
\r
676 * @param valueBinding
\r
678 * @return true if new file was created
\r
679 * @throws AccessorException
\r
681 private boolean putLocal(Binding keyBinding, Object key, Binding valueBinding,
\r
682 Object value) throws AccessorException {
\r
685 String _key = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
686 File file = new File(path, _key+".dbb");
\r
687 boolean created = !dir.files().contains(file);
\r
688 FileVariantAccessor va = files.createFile( file );
\r
689 va.setValue(valueBinding, value);
\r
696 MutableVariant kv = new MutableVariant(KEY_BINDING, _key);
\r
698 // Notify Listeners
\r
699 if (listeners!=null) {
\r
700 ListenerEntry le = listeners;
\r
701 while (le!=null) {
\r
702 MapInterestSet is = le.getInterestSet();
\r
703 if (is.inNotificationsOf(kv)) {
\r
705 MutableVariant vv = null;
\r
706 if (is.inValuesOf(kv)) vv = new MutableVariant(valueBinding, valueBinding.isImmutable() ? value : valueBinding.clone(value));
\r
709 // Notify about new assignment to old value
\r
710 ValueAssigned e = new ValueAssigned( new KeyReference(kv), vv);
\r
713 // Notify about new entry
\r
714 MapEntryAdded e = new MapEntryAdded(kv, vv);
\r
723 } catch (AdaptException e) {
\r
724 throw new AccessorException(e);
\r
725 } catch (AccessorConstructionException e) {
\r
726 throw new AccessorException(e);
\r
731 public void put(Binding keyBinding, Object key, Binding valueBinding,
\r
732 Object value) throws AccessorException {
\r
733 /*boolean created =*/ putLocal(keyBinding, key, valueBinding, value);
\r
737 public void putAll(Binding keyBinding, Binding valueBinding,
\r
738 Map<Object, Object> from) throws AccessorException {
\r
739 //boolean created = false;
\r
740 for (Entry<Object, Object> e : from.entrySet()) {
\r
741 Object key = e.getKey();
\r
742 Object value = e.getValue();
\r
743 /*created |=*/ putLocal(keyBinding, key, valueBinding, value);
\r
748 public void putAll(Binding keyBinding, Binding valueBinding, Object[] keys,
\r
749 Object[] values) throws AccessorException {
\r
750 //boolean created = false;
\r
751 if (keys.length!=values.length)
\r
752 throw new AccessorException("Array lengths mismatch");
\r
753 for (int i=0; i<keys.length; i++) {
\r
754 Object key = keys[i];
\r
755 Object value = values[i];
\r
756 /*created |=*/ putLocal(keyBinding, key, valueBinding, value);
\r
761 public void remove(Binding keyBinding, Object key) throws AccessorException {
\r
764 String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
\r
765 File file = new File(path, key_ + ".dbb" );
\r
766 if (!dir.files().contains(file)) return;
\r
767 // throw new AccessorException("File "+file+" does not exist");
\r
771 boolean deleteOk = files.deleteFile(file);
\r
774 throw new AccessorException("Failed to delete "+file);
\r
779 // Notify Listeners
\r
780 if (listeners!=null) {
\r
781 MutableVariant var = new MutableVariant(KEY_BINDING, key_);
\r
782 ListenerEntry le = listeners;
\r
783 while (le!=null) {
\r
784 MapInterestSet is = le.getInterestSet();
\r
785 if (is.inNotificationsOf(var)) {
\r
786 MapEntryRemoved e = new MapEntryRemoved(var);
\r
793 } catch (AdaptException e) {
\r
794 throw new AccessorException(e);
\r
799 public boolean setValue(ChildReference path, Binding binding, Object obj) throws AccessorException {
\r
801 Accessor a = getComponent(path);
\r
802 a.setValue(binding, obj);
\r
804 } catch (ReferenceException re) {
\r
806 } catch (AccessorConstructionException e) {
\r
807 throw new AccessorException(e);
\r
812 public void setValue(Binding mapBinding, Object newMap)
\r
813 throws AccessorException {
\r
815 MapBinding mb = (MapBinding) mapBinding;
\r
816 Binding valueBinding = mb.getValueBinding();
\r
817 int size = mb.size(newMap);
\r
818 Object keys[] = new Object[size];
\r
819 Object values[] = new Object[size];
\r
820 mb.getAll(newMap, keys, values);
\r
822 Adapter keyAdapter = params.adapterScheme.getAdapter(mb.getKeyBinding(), KEY_BINDING, true, false);
\r
823 HashSet<File> writeFiles = new HashSet<File>();
\r
824 List<File> oldFiles = dir.files();
\r
826 //HashSet<File> modifiedFiles = new HashSet<File>();
\r
827 HashSet<File> addedFiles = new HashSet<File>(writeFiles);
\r
828 addedFiles.removeAll(oldFiles);
\r
831 for (int i=0; i<keys.length; i++) {
\r
832 Object key = keys[i];
\r
833 Object value = values[i];
\r
834 String _key = (String) keyAdapter.adapt(key);
\r
835 String filename = _key + ".dbb";
\r
836 File file = new File(path, filename);
\r
837 writeFiles.add(file);
\r
839 boolean existed = oldFiles.contains(file);
\r
840 FileVariantAccessor va = files.createFile(file);
\r
842 va.setValue(mb.getValueBinding(), value);
\r
846 MutableVariant kv = new MutableVariant(KEY_BINDING, _key);
\r
848 // Notify Listeners
\r
849 if (listeners!=null) {
\r
850 ListenerEntry le = listeners;
\r
851 while (le!=null) {
\r
852 MapInterestSet is = le.getInterestSet();
\r
853 if (is.inNotificationsOf(kv)) {
\r
855 MutableVariant vv = null;
\r
856 if (is.inValuesOf(kv)) vv = new MutableVariant(valueBinding, valueBinding.isImmutable() ? value : valueBinding.clone(value));
\r
859 // Notify about new assignment to old value
\r
860 ValueAssigned e = new ValueAssigned( new KeyReference(kv), vv);
\r
863 // Notify about new entry
\r
864 MapEntryAdded e = new MapEntryAdded(kv, vv);
\r
874 HashSet<File> removedFiles = new HashSet<File>(oldFiles);
\r
875 removedFiles.removeAll(writeFiles);
\r
877 // Remove old files
\r
879 if (!removedFiles.isEmpty()) {
\r
880 List<File> failList = new ArrayList<File>();
\r
881 for (File f : removedFiles) {
\r
883 String filename = f.getName();
\r
884 String keyStr = filename.substring(0, filename.length()-4);
\r
886 boolean deleted = files.deleteFile(f);
\r
891 // Notify Listeners
\r
892 if (listeners!=null) {
\r
893 MutableVariant var = new MutableVariant(KEY_BINDING, keyStr);
\r
894 ListenerEntry le = listeners;
\r
895 while (le!=null) {
\r
896 MapInterestSet is = le.getInterestSet();
\r
897 if (is.inNotificationsOf(var)) {
\r
898 MapEntryRemoved e = new MapEntryRemoved(var);
\r
906 if (!failList.isEmpty()) {
\r
907 StringBuilder sb = new StringBuilder();
\r
908 sb.append("Failed to delete");
\r
909 for (File ff : failList) {
\r
911 sb.append(ff.toString());
\r
913 throw new AccessorException(sb.toString());
\r
919 } catch (BindingException e) {
\r
920 throw new AccessorException(e);
\r
921 } catch (AdaptException e) {
\r
922 throw new AccessorException(e);
\r
923 } catch (AdapterConstructionException e) {
\r
924 throw new AccessorException(e);
\r
925 } catch (AccessorConstructionException e) {
\r
926 throw new AccessorException(e);
\r
931 public int size() throws AccessorException {
\r
933 return dir.files().size();
\r
937 public void addListener(Listener listener, InterestSet interestSet, ChildReference path, Executor executor) throws AccessorException {
\r
938 listeners = ListenerEntry.link(listeners, listener, interestSet, path, executor);
\r
939 MapInterestSet is = (MapInterestSet) interestSet;
\r
942 for (File f : dir.files()) {
\r
943 String filename = f.getName();
\r
944 String keyStr = filename.substring(0, filename.length()-4);
\r
946 Accessor sa = getExistingAccessor(KEY_BINDING, keyStr);
\r
947 if (sa==null) continue;
\r
949 MutableVariant key = new MutableVariant(KEY_BINDING, keyStr);
\r
950 InterestSet cis = is.getComponentInterest();
\r
952 ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) );
\r
953 sa.addListener(listener, cis, childPath, executor);
\r
955 cis = is.getComponentInterest( key );
\r
957 ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) );
\r
958 sa.addListener(listener, cis, childPath, executor);
\r
961 } catch (AccessorConstructionException e) {
\r
962 throw new AccessorException(e);
\r
968 public void apply(List<Event> cs, LinkedList<Event> rollback) throws AccessorException {
\r
970 boolean makeRollback = rollback != null;
\r
971 ArrayList<Event> single = new ArrayList<Event>();
\r
972 for (Event e : cs) {
\r
973 if (e.reference==null) {
\r
974 Event rbe = applyLocal(e, makeRollback);
\r
975 if (makeRollback) {
\r
976 rbe.reference = e.reference;
\r
977 rollback.addFirst( rbe );
\r
980 Accessor sa = getComponent(e.reference);
\r
983 Event noRefEvent = e.clone(null);
\r
984 single.add(noRefEvent);
\r
985 sa.apply(single, rollback);
\r
988 } catch (AccessorConstructionException ae) {
\r
989 throw new AccessorException(ae);
\r
993 Event applyLocal(Event e, boolean makeRollback) throws AccessorException {
\r
994 Event rollback = null;
\r
996 if (e instanceof ValueAssigned) {
\r
997 ValueAssigned va = (ValueAssigned) e;
\r
998 if (makeRollback) {
\r
999 Binding binding = params.bindingScheme.getBinding(type());
\r
1000 rollback = new ValueAssigned(binding, getValue(binding));
\r
1002 setValue(va.newValue.getBinding(), va.newValue.getValue());
\r
1004 } else if (e instanceof MapEntryAdded) {
\r
1005 MapEntryAdded ea = (MapEntryAdded) e;
\r
1006 if (ea.key==null) throw new AccessorException("Cannot apply entry added event because key is missing");
\r
1007 if (ea.value==null) throw new AccessorException("Cannot apply entry added event because value is missing");
\r
1008 boolean hadValue = containsKey(ea.key.getBinding(), ea.key.getValue());
\r
1009 if (hadValue) throw new AccessorException("Could not add entry to key that already existed");
\r
1011 if (makeRollback) {
\r
1012 rollback = new MapEntryRemoved( ea.key );
\r
1015 put(ea.key.getBinding(), ea.key.getValue(), ea.value.getBinding(), ea.value.getValue());
\r
1017 } else if (e instanceof MapEntryRemoved) {
\r
1018 MapEntryRemoved er = (MapEntryRemoved) e;
\r
1020 if (makeRollback) {
\r
1021 boolean hadValue = containsKey(er.key.getBinding(), er.key.getValue());
\r
1024 MutableVariant oldKey = er.key;
\r
1025 MutableVariant oldValue = getAsVariant(er.key.getBinding(), er.key.getValue());
\r
1026 rollback = new MapEntryAdded(oldKey, oldValue);
\r
1028 rollback = new MapEntryRemoved( er.key.clone() );
\r
1032 remove( er.key.getBinding(), er.key.getValue() );
\r
1034 } else throw new AccessorException("Cannot apply "+e.getClass().getName()+" to Map Type");
\r
1037 } catch (BindingConstructionException e2) {
\r
1038 throw new AccessorException( e2 );
\r
1043 @SuppressWarnings("unchecked")
\r
1045 public <T extends Accessor> T getComponent(ChildReference reference)
\r
1046 throws AccessorConstructionException {
\r
1047 if (reference==null) return (T) this;
\r
1048 if (reference instanceof LabelReference) {
\r
1050 LabelReference lr = (LabelReference) reference;
\r
1052 MutableVariant variant = (MutableVariant) params.adapterScheme.adapt(lr.label, Bindings.STRING, Bindings.MUTABLE_VARIANT);
\r
1053 Object value = variant.getValue(KEY_BINDING);
\r
1054 Accessor result = (T) getValueAccessor(KEY_BINDING, value);
\r
1056 if (reference.getChildReference() != null)
\r
1057 result = result.getComponent(reference.getChildReference());
\r
1058 return (T) result;
\r
1059 } catch (AdaptException e) {
\r
1060 throw new ReferenceException(e);
\r
1062 } else if (reference instanceof KeyReference) {
\r
1064 KeyReference ref = (KeyReference) reference;
\r
1065 String keyStr = (String) params.adapterScheme.adapt(ref.key.getValue(), ref.key.getBinding(), KEY_BINDING);
\r
1066 File f = keyToFile(keyStr);
\r
1067 if (!dir.files().contains(f))
\r
1068 throw new AccessorConstructionException("Invalid reference "+ref.key);
\r
1070 Accessor result = getValueAccessor(KEY_BINDING, keyStr);
\r
1071 if (reference.getChildReference() != null)
\r
1072 result = result.getComponent(reference.getChildReference());
\r
1073 return (T) result;
\r
1074 } catch (AdaptException e) {
\r
1075 throw new ReferenceException(e);
\r
1078 throw new ReferenceException(reference.getClass().getName()+" is not a reference of a map");
\r
1082 public void removeListener(Listener listener) throws AccessorException {
\r
1083 detachListener(listener);
\r
1086 protected ListenerEntry detachListener(Listener listener) throws AccessorException {
\r
1087 ListenerEntry e = listeners;
\r
1088 ListenerEntry p = null;
\r
1091 if (e.listener == listener) {
\r
1092 // The match was the first entry of the linked list
\r
1094 listeners = e.next;
\r
1097 // Some other entry, unlink e
\r
1107 protected void emitEvent(ListenerEntry le, Event e) {
\r
1108 e.reference = ChildReference.concatenate(le.path, e.reference);
\r
1112 protected void emitEvents(ListenerEntry le, Collection<Event> events) {
\r
1113 for (Event e : events)
\r
1114 e.reference = ChildReference.concatenate(le.path, e.reference);
\r
1115 le.emitEvents(events);
\r