]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.databoard/src/org/simantics/databoard/accessor/impl/DirectoryMap.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / accessor / impl / DirectoryMap.java
1 /*******************************************************************************
2  * Industry THTH ry.
3  * Copyright (c) 2010- Association for Decentralized Information Management in
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  * 
9  * Contributors:
10  *    VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.databoard.accessor.impl;
13
14 import java.io.File;
15 import java.io.FileFilter;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Set;
25 import java.util.TreeSet;
26 import java.util.concurrent.Executor;
27
28 import org.simantics.databoard.Bindings;
29 import org.simantics.databoard.Datatypes;
30 import org.simantics.databoard.accessor.Accessor;
31 import org.simantics.databoard.accessor.CloseableAccessor;
32 import org.simantics.databoard.accessor.MapAccessor;
33 import org.simantics.databoard.accessor.VariantAccessor;
34 import org.simantics.databoard.accessor.error.AccessorConstructionException;
35 import org.simantics.databoard.accessor.error.AccessorException;
36 import org.simantics.databoard.accessor.error.ReferenceException;
37 import org.simantics.databoard.accessor.event.Event;
38 import org.simantics.databoard.accessor.event.MapEntryAdded;
39 import org.simantics.databoard.accessor.event.MapEntryRemoved;
40 import org.simantics.databoard.accessor.event.ValueAssigned;
41 import org.simantics.databoard.accessor.file.FileLibrary;
42 import org.simantics.databoard.accessor.file.FileVariantAccessor;
43 import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryEvent;
44 import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryListener;
45 import org.simantics.databoard.accessor.interestset.InterestSet;
46 import org.simantics.databoard.accessor.interestset.MapInterestSet;
47 import org.simantics.databoard.accessor.reference.ChildReference;
48 import org.simantics.databoard.accessor.reference.KeyReference;
49 import org.simantics.databoard.accessor.reference.LabelReference;
50 import org.simantics.databoard.adapter.AdaptException;
51 import org.simantics.databoard.adapter.Adapter;
52 import org.simantics.databoard.adapter.AdapterConstructionException;
53 import org.simantics.databoard.binding.ArrayBinding;
54 import org.simantics.databoard.binding.Binding;
55 import org.simantics.databoard.binding.MapBinding;
56 import org.simantics.databoard.binding.VariantBinding;
57 import org.simantics.databoard.binding.error.BindingConstructionException;
58 import org.simantics.databoard.binding.error.BindingException;
59 import org.simantics.databoard.binding.error.RuntimeBindingException;
60 import org.simantics.databoard.binding.mutable.MutableVariant;
61 import org.simantics.databoard.type.Datatype;
62 import org.simantics.databoard.type.MapType;
63
64 /**
65  * DirectoryMap is a file backed map implementation where keys are filenames
66  * and values are corresponding files. 
67  * <p>
68  * This class is an implmentation to Map(Variant, Variant) -Accessor.
69  * 
70  *  Filenames have the following encoding:
71  *    S<string>.dbb   String types, if string doesn't have the following 
72  *                    control characters " : < > | ? * \ /   [0..31]
73  *    I<integer>.dbb  Integer types
74  *    L<long>.dbb     Long types
75  *    H<hex>.dbb      All other cases the value as binary 
76  * <p>
77  * File accessor is created if an entry opened as a sub-accessor.
78  * The file accessor is closed when all sub-accessors are released.
79  * The implementation is based on proxy instances and a reference queue.
80  * Once the queue is empty, file accessor is closed.
81  * <p>
82  * DirectoryMap must be closed with #close();     
83  *
84  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
85  */
86 public class DirectoryMap implements MapAccessor, CloseableAccessor {
87
88         /** Key binding */
89         final static Binding KEY_BINDING = Bindings.STR_VARIANT;
90         
91         /** Cache of sub-accessors */
92         FileLibrary files;
93         
94         /** Monitors directory for file changes */
95         DirectoryWatch dir;
96         
97         /** Folder */
98         File path;
99         
100         /** Listeners */
101         ListenerEntry listeners = null;
102         
103         /** Parent, optional */
104         Accessor parent; 
105
106         /** Accessor params */
107         AccessorParams params;
108         
109         DirectoryListener dirListener = new DirectoryListener() {
110                 @Override
111                 public void onWatchEvent(DirectoryEvent e) {
112                         
113                 }
114         };
115
116         public DirectoryMap(File directory) {
117                 this(directory, null, AccessorParams.DEFAULT);
118         }
119
120         public DirectoryMap(File directory, Accessor parent) {
121                 this(directory, parent, AccessorParams.DEFAULT);
122         }
123
124         public DirectoryMap(File directory, Accessor parent, AccessorParams params) {
125                 this.parent = parent;
126                 this.path = directory;
127                 this.params = params;
128
129                 // Filters .dbb files
130                 FileFilter filter = new FileFilter() {
131                         public boolean accept(File pathname) {                          
132                                 String filename = pathname.getName();
133                                 if (filename.length()==0) return false;
134                                 char c = filename.charAt(0);
135                                 if (c!='S' && c!='I' && c!='L' && c!='B') return false;
136                                 if (filename.endsWith(".dbb")) return true;
137                                 return filename.toLowerCase().endsWith(".dbb");
138                         }};
139                 
140                 dir = new DirectoryWatch(path, filter);
141                 
142                 dir.addListener( dirListener );
143                 
144                 files = new FileLibrary();
145         }
146         
147         public void close() {
148                 dir.removeListener( dirListener );
149                 dir.close();
150                 files.close();
151         }
152         
153         static MapType type = new MapType(Datatypes.VARIANT, Datatypes.VARIANT);
154         public MapType type() {
155                 return type;
156         }
157
158         private String fileToKey(File f) {
159                 String filename = f.getName();
160                 String keyStr = filename.substring(0, filename.length()-4);
161                 return keyStr;
162         }
163         
164         private File keyToFile(String keyStr) {
165                 return new File(path, keyStr + ".dbb" );
166         }
167         
168         @Override
169         public void clear() throws AccessorException {
170                 //List<File> deleteList = dir.files();
171                 List<File> failList = new ArrayList<File>();
172                 boolean hasListeners = listeners!=null;
173                 List<String> keys = hasListeners ? new ArrayList<String>() : null;
174                 
175                 // Close all file handles
176                 files.close();
177                 
178                 // Delete files
179                 for (File f : dir.files()) {
180                         if (!files.deleteFile(f)) {
181                                 failList.add(f);
182                         }
183                         if (hasListeners) {
184                                 String keyStr = fileToKey(f);
185                                 keys.add(keyStr);
186                         }
187                 }
188
189                 // Re-read directory
190                 dir.refresh();
191                 
192                 // Notify Listeners
193                 ListenerEntry le = listeners;
194                 while (le!=null) {                              
195                         MapInterestSet is = le.getInterestSet();
196                         for (Object key : keys) {
197                                 MutableVariant var = new MutableVariant(KEY_BINDING, key);
198                                 if (is.inNotificationsOf(var)) {
199                                         MapEntryRemoved e = new MapEntryRemoved(var); 
200                                         emitEvent(le, e);
201                                 }
202                         }                                                       
203                         le = le.next;
204                 }                               
205                 
206                 // Some files failed to delete
207                 if (!failList.isEmpty()) {
208                         StringBuilder sb = new StringBuilder();
209                         sb.append("Failed to delete");
210                         for (File f : failList)  {
211                                 sb.append(' ');
212                                 sb.append(f.toString());
213                         }
214                         // HAX
215                         throw new AccessorException(sb.toString());
216                 }
217                 
218                                 
219         }
220                 
221         @Override
222         public String toString() {
223                 return dir.toString();
224         }
225         
226         public Object getValue(Binding binding) throws AccessorException {
227                 MapBinding mb = (MapBinding) binding; 
228                 if (mb.getKeyBinding() instanceof VariantBinding==false || mb.getValueBinding() instanceof VariantBinding==false)
229                         throw new AccessorException("Map(Variant, Variant) Expected");
230                 // Get all files as a single map
231                 try {
232                         Object result = binding.createDefault();
233                         for (File f : dir.files()) {
234                                 // Create Key
235                                 String keyStr = fileToKey(f);
236                                 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, mb.getKeyBinding());
237                                 
238                                 // Read value
239                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
240                                 Object value = va.getValue(mb.getValueBinding());
241                                 
242                                 mb.put(result, key, value);
243                         }
244                         return result;
245                 } catch (BindingException e) {
246                         throw new AccessorException(e);
247                 } catch (AccessorConstructionException e) {
248                         throw new AccessorException(e);
249                 } catch (AdaptException e) {
250                         throw new AccessorException(e);
251                 }
252         }
253         
254         @Override
255         public void getValue(Binding dstBinding, Object dst) throws AccessorException {
256                 MapBinding db = (MapBinding) dstBinding; 
257                 Binding dkb = db.getKeyBinding();
258                 Binding dvb = db.getValueBinding();
259                 if (dkb instanceof VariantBinding==false || dvb instanceof VariantBinding==false)
260                         throw new AccessorException("Map(Variant, Variant) Expected");
261                 // Get all files as a single map
262                 try {
263                         TreeSet<Object> dstKeys = new TreeSet<Object>(dkb);
264                         db.getKeys(dst, dstKeys);
265                         
266                         for (File f : dir.files()) {
267                                 // Create Key
268                                 String keyStr = fileToKey(f);
269                                 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, dkb);
270                                 
271                                 Object v = db.containsKey(dst, key) ? db.get(dst, key) : dvb.createDefault();
272                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
273                                 va.getValue(dvb, v);
274                                 
275                                 db.put(dst, key, v);
276                                 dstKeys.remove(key);
277                         }
278                         
279                         for (Object key : dstKeys)
280                                 db.remove(dst, key);
281                 } catch (BindingException e) {
282                         throw new AccessorException(e);
283                 } catch (AccessorConstructionException e) {
284                         throw new AccessorException(e);
285                 } catch (AdaptException e) {
286                         throw new AccessorException(e);
287                 }
288                 
289         }
290         
291         @Override
292         public boolean getValue(ChildReference path, Binding binding, Object obj) throws AccessorException {
293                 try {
294                         Accessor a = getComponent(path);
295                         a.getValue(binding, obj);
296                         return true;
297                 } catch (ReferenceException re) {
298                         return false;
299                 } catch (AccessorConstructionException e) {
300                         throw new AccessorException(e);
301                 }
302         }       
303         
304         public Object getValue(ChildReference path, Binding binding) throws AccessorException {
305                 try {
306                         Accessor a = getComponent(path);
307                         return a.getValue(binding);
308                 } catch (ReferenceException re) {
309                         return null;
310                 } catch (AccessorConstructionException e) {
311                         throw new AccessorException(e);
312                 }
313         }
314         
315         
316         @Override
317         public boolean containsKey(Binding keyBinding, Object key)
318                         throws AccessorException {
319                 try {
320                         String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
321                         File file = keyToFile(key_);
322                         return dir.files().contains(file);
323                 } catch (AdaptException e) {
324                         throw new AccessorException(e);
325                 }
326         }
327
328         @Override
329         public boolean containsValue(Binding valueBinding, Object value)
330                         throws AccessorException {
331                 try {
332                         for (File f : dir.files()) {
333                                 String key = fileToKey(f);
334                                 VariantAccessor va = getValueAccessor(KEY_BINDING, key);
335                                 Object v = va.getValue(valueBinding);
336                                 boolean match = valueBinding.equals(v, value);
337                                 if ( match ) return true;
338                         }
339                         return false;
340                 } catch (AccessorConstructionException e) {
341                         throw new AccessorException(e);
342                 }               
343         }
344
345         @Override
346         public Object get(Binding keyBinding, Object key, Binding valueBinding)
347                         throws AccessorException {
348                 try {
349                         VariantAccessor va = getValueAccessor(keyBinding, key);
350                         return va.getValue(valueBinding);
351                 } catch (AccessorConstructionException e) {
352                         throw new AccessorException(e);
353                 }
354         }
355
356         /**
357          * Get the value as a variant
358          *  
359          * @param keyBinding
360          * @param key
361          * @return value
362          * @throws AccessorException
363          */
364         public MutableVariant getAsVariant(Binding keyBinding, Object key)
365                         throws AccessorException {
366                 try {
367                         VariantAccessor va = getValueAccessor(keyBinding, key);
368                         Datatype type = va.getContentType();
369                         Binding binding = params.bindingScheme.getBinding(type);
370                         Object value = va.getContentValue(binding);
371                         MutableVariant result = new MutableVariant(binding, value);
372                         return result;
373                 } catch (AccessorConstructionException e) {
374                         throw new AccessorException(e);
375                 } catch (BindingConstructionException e) {
376                         throw new AccessorException(e);
377                 }
378         }
379
380         @Override
381         public void getAll(Binding keyBinding, Binding valueBinding,
382                         Map<Object, Object> to) throws AccessorException {
383                 try {
384                         for (File f : dir.files()) {    
385                                 // Create key
386                                 String keyStr = fileToKey(f);
387                                 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding);
388
389                                 // Read value
390                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
391                                 Object value = va.getValue(valueBinding);
392                                 
393                                 to.put(key, value);                             
394                         }
395                 } catch (AdaptException e) {
396                         throw new AccessorException(e);
397                 } catch (AccessorConstructionException e) {
398                         throw new AccessorException(e);
399                 }                       
400         }
401
402         @Override
403         public void getAll(Binding keyBinding, Binding valueBinding, Object[] keys,
404                         Object[] values) throws AccessorException {
405                 try {
406                         Set<String> fileKeys = createKeys();
407                         
408                         int i=0;
409                         for (String keyStr : fileKeys) {                                
410                                 // Read value
411                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
412                                 Object value = va.getValue(valueBinding);
413                                 
414                                 Object key2 = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding);
415                                 keys[i] = key2;
416                                 values[i] = value;
417                                 i++;
418                         }
419                 } catch (AdaptException e) {
420                         throw new AccessorException(e);
421                 } catch (AccessorConstructionException e) {
422                         throw new AccessorException(e);
423                 }                       
424         }
425         
426
427         @Override
428         public int count(Binding keyBinding, Object from,
429                         boolean fromInclusive, Object end, boolean endInclusive)
430                         throws AccessorException {
431                 throw new AccessorException("Not implemented");
432         }
433
434         @Override
435         public int getEntries(Binding keyBinding, Object from,
436                         boolean fromInclusive, Object end, boolean endInclusive,
437                         ArrayBinding keyArrayBinding, Object dstKeys,
438                         ArrayBinding valueArrayBinding, Object dstValues, int limit)
439                         throws AccessorException {
440                 throw new AccessorException("Not implemented");
441         }
442         
443
444         TreeSet<String> createKeys() throws RuntimeBindingException {
445                 List<File> files = dir.files();
446                 TreeSet<String> keys = new TreeSet<String>(KEY_BINDING);
447                 
448                 for (File f : files) {
449                         String filename = f.getName();
450                         String str = filename.substring(0, filename.length()-4);
451                         keys.add(str);
452                 }
453                 
454                 return keys;
455         }
456         
457         @Override
458         public Object getCeilingKey(Binding keyBinding, Object key)
459                         throws AccessorException {
460                 try {
461                         TreeSet<String> keys = createKeys();
462                         String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
463                         if (keys.contains(k)) return key;
464                         Object res = keys.ceiling(k);
465                         if (res==null) return null;
466                         return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding);
467                 } catch (RuntimeBindingException e) {
468                         throw new AccessorException(e);
469                 } catch (AdaptException e) {
470                         throw new AccessorException(e);
471                 }
472         }
473
474         @Override
475         public Object getFirstKey(Binding keyBinding) throws AccessorException {
476                 List<File> files = dir.files();
477                 String firstKey = null;
478                 
479                 for (File f : files) {
480                         String filename = f.getName();
481                         String str = filename.substring(0, filename.length()-4);
482                         if (firstKey == null) {
483                                 firstKey = str;
484                         } else {
485                                 if (KEY_BINDING.compare(str, firstKey)<0) firstKey = str;
486                         }
487                 }
488                 if (firstKey==null) return null;
489                 
490                 try {
491                         return params.adapterScheme.adapt(firstKey, KEY_BINDING, keyBinding);
492                 } catch (AdaptException e) {
493                         throw new AccessorException(e);
494                 }
495         }
496
497         @Override
498         public Object getFloorKey(Binding keyBinding, Object key)
499                         throws AccessorException {
500                 try {
501                         TreeSet<String> keys = createKeys();
502                         String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
503                         Object res = keys.floor(k);
504                         if (res==null) return null;
505                         return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding);
506                 } catch (RuntimeBindingException e) {
507                         throw new AccessorException(e);
508                 } catch (AdaptException e) {
509                         throw new AccessorException(e);
510                 }
511         }
512
513         @Override
514         public Object getHigherKey(Binding keyBinding, Object key)
515                         throws AccessorException {
516                 try {
517                         TreeSet<String> keys = createKeys();
518                         String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
519                         Object res = keys.higher(k);
520                         if (res==null) return null;
521                         return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding);
522                 } catch (RuntimeBindingException e) {
523                         throw new AccessorException(e);
524                 } catch (AdaptException e) {
525                         throw new AccessorException(e);
526                 }
527         }
528
529         @Override
530         public Object[] getKeys(Binding keyBinding) throws AccessorException {
531                 TreeSet<String> keys = createKeys();
532                 Object[] result = new Object[keys.size()];
533                 if (keys.isEmpty()) return result;
534                 try {
535                         Adapter a = params.adapterScheme.getAdapter(KEY_BINDING, keyBinding, true, false);
536                         int index = 0;
537                         for (String key : keys) {                                                               
538                                 result[index++] = a.adapt( key );
539                         }
540                 } catch (AdaptException e) {
541                         throw new AccessorException(e);
542                 } catch (AdapterConstructionException e) {
543                         throw new AccessorException(e);
544                 }
545                 
546                 return result;
547         }
548
549         @Override
550         public Object getLastKey(Binding keyBinding) throws AccessorException {
551                 List<File> files = dir.files();
552                 String lastKey = null;
553                 
554                 for (File f : files) {
555                         String filename = f.getName();
556                         String str = filename.substring(0, filename.length()-4);
557                         if (lastKey == null) {
558                                 lastKey = str;
559                         } else {
560                                 if (KEY_BINDING.compare(str, lastKey)>0) lastKey = str;
561                         }
562                 }
563                 if (lastKey==null) return null;
564                 
565                 try {
566                         return params.adapterScheme.adapt(lastKey, KEY_BINDING, keyBinding);
567                 } catch (AdaptException e) {
568                         throw new AccessorException(e);
569                 }
570         }
571
572         @Override
573         public Object getLowerKey(Binding keyBinding, Object key)
574                         throws AccessorException {
575                 try {
576                         TreeSet<String> keys = createKeys();
577                         String k = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
578                         Object res = keys.lower(k);
579                         if (res==null) return null;
580                         return params.adapterScheme.adapt(res, KEY_BINDING, keyBinding);
581                 } catch (RuntimeBindingException e) {
582                         throw new AccessorException(e);
583                 } catch (AdaptException e) {
584                         throw new AccessorException(e);
585                 }
586         }
587
588         public FileVariantAccessor getExistingAccessor(Binding keyBinding,
589                         Object key) throws AccessorConstructionException {
590                 try {
591                         String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
592                         File file = new File(path, key_ + ".dbb" );
593                         return files.getExistingFile(file);
594                 } catch (AdaptException e) {
595                         throw new AccessorConstructionException(e);
596                 }       
597         }
598         
599         @SuppressWarnings("unchecked")
600         @Override
601         public FileVariantAccessor getValueAccessor(Binding keyBinding, Object key) throws AccessorConstructionException {
602                 try {
603                         String keyStr = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
604                         File file = keyToFile(keyStr);
605                         FileVariantAccessor sa = files.getExistingFile(file);
606                         if (sa!=null) return sa;
607                         
608                         // Create new accessor
609                         sa = files.getFile(file);
610
611                         // Add component interest sets
612                         ListenerEntry le = listeners;
613                         if (le!=null) {
614                                 MutableVariant kv = new MutableVariant(keyBinding, key);
615                                 while (le!=null) {                              
616                                         MapInterestSet is = le.getInterestSet();
617         
618                                         // Generic element interest
619                                         InterestSet gis = is.getComponentInterest(); 
620                                         if (gis != null) {
621                                                 try {
622                                                         ChildReference childPath = ChildReference.concatenate(le.path, new KeyReference(kv) );
623                                                         sa.addListener(le.listener, gis, childPath, le.executor);
624                                                 } catch (AccessorException e) {
625                                                         throw new AccessorConstructionException(e);
626                                                 }
627                                         }
628                                                 
629                                         // Specific element interest
630                                         InterestSet cis = is.getComponentInterest(kv); 
631                                         if (cis != null) {
632                                                 try {
633                                                         ChildReference childPath = ChildReference.concatenate(le.path, new KeyReference(kv) );
634                                                         sa.addListener(le.listener, cis, childPath, le.executor);
635                                                 } catch (AccessorException e) {
636                                                         throw new AccessorConstructionException(e);
637                                                 }
638                                         }
639                                         
640                                         // Next listener
641                                         le = le.next;
642                                 }
643                         }
644                         
645                         return sa;
646                 } catch (AdaptException e) {
647                         throw new AccessorConstructionException(e);
648                 }       
649         }
650
651         @Override
652         public Object[] getValues(Binding valueBinding) throws AccessorException {              
653                 try {
654                         Set<String> keys = createKeys();
655                         int count = keys.size();
656                         Object[] result = new Object[ count ];
657                         
658                         Iterator<String> iter = keys.iterator();
659                         for (int i=0; i<count; i++) {
660                                 String keyStr = iter.next();
661                                 // Read the file
662                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
663                                 Object value = va.getValue(valueBinding);                               
664                                 result[i] = value;
665                         }
666                         return result;
667                 } catch (AccessorConstructionException e) {
668                         throw new AccessorException(e);
669                 }                       
670         }
671
672         /**
673          * 
674          * @param keyBinding
675          * @param key
676          * @param valueBinding
677          * @param value
678          * @return true if new file was created
679          * @throws AccessorException
680          */
681         private boolean putLocal(Binding keyBinding, Object key, Binding valueBinding,
682                         Object value) throws AccessorException {
683                 try {
684                         // Write
685                         String _key = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
686                         File file = new File(path, _key+".dbb");
687                         boolean created = !dir.files().contains(file);
688                         FileVariantAccessor va = files.createFile( file );
689                         va.setValue(valueBinding, value);
690                         va.flush();
691                         if (created) {
692                                 dir.add(file);
693                         }
694                                                 
695                         // Key variant
696                         MutableVariant kv = new MutableVariant(KEY_BINDING, _key);
697                         
698                         // Notify Listeners
699                         if (listeners!=null) {
700                                 ListenerEntry le = listeners;
701                                 while (le!=null) {                              
702                                         MapInterestSet is = le.getInterestSet();
703                                         if (is.inNotificationsOf(kv)) {
704                                                 
705                                                 MutableVariant vv = null;
706                                                 if (is.inValuesOf(kv)) vv = new MutableVariant(valueBinding, valueBinding.isImmutable() ? value : valueBinding.clone(value));
707                                                 
708                                                 if (!created) {
709                                                         // Notify about new assignment to old value
710                                                         ValueAssigned e = new ValueAssigned( new KeyReference(kv), vv);
711                                                         emitEvent(le, e);
712                                                 } else {
713                                                         // Notify about new entry
714                                                         MapEntryAdded e = new MapEntryAdded(kv, vv);
715                                                         emitEvent(le, e);
716                                                 }
717                                         }
718                                         
719                                         le = le.next;
720                                 }
721                         }
722                         return created; 
723                 } catch (AdaptException e) {
724                         throw new AccessorException(e);
725                 } catch (AccessorConstructionException e) {
726                         throw new AccessorException(e);
727                 }               
728         }
729         
730         @Override
731         public void put(Binding keyBinding, Object key, Binding valueBinding,
732                         Object value) throws AccessorException {
733                 /*boolean created =*/ putLocal(keyBinding, key, valueBinding, value);
734         }
735
736         @Override
737         public void putAll(Binding keyBinding, Binding valueBinding,
738                         Map<Object, Object> from) throws AccessorException {
739                 //boolean created = false;
740                 for (Entry<Object, Object> e : from.entrySet()) {
741                         Object key = e.getKey();
742                         Object value = e.getValue();
743                         /*created |=*/ putLocal(keyBinding, key, valueBinding, value);
744                 }
745         }
746
747         @Override
748         public void putAll(Binding keyBinding, Binding valueBinding, Object[] keys,
749                         Object[] values) throws AccessorException {
750                 //boolean created = false;
751                 if (keys.length!=values.length)
752                         throw new AccessorException("Array lengths mismatch");
753                 for (int i=0; i<keys.length; i++) {
754                         Object key = keys[i];
755                         Object value = values[i];
756                         /*created |=*/ putLocal(keyBinding, key, valueBinding, value);
757                 }
758         }       
759
760         @Override
761         public void remove(Binding keyBinding, Object key) throws AccessorException {
762
763                 try {
764                         String key_ = (String) params.adapterScheme.adapt(key, keyBinding, KEY_BINDING);
765                         File file = new File(path, key_ + ".dbb" );
766                         if (!dir.files().contains(file)) return;
767 //                              throw new AccessorException("File "+file+" does not exist");
768                                                 
769                         files.expunge();
770                         
771                         boolean deleteOk = files.deleteFile(file);
772                         
773                         if (!deleteOk) {
774                                 throw new AccessorException("Failed to delete "+file);
775                         } else {
776                                 dir.remove(file);
777                         }
778                         
779                         // Notify Listeners
780                         if (listeners!=null) {
781                                 MutableVariant var = new MutableVariant(KEY_BINDING, key_);
782                                 ListenerEntry le = listeners;
783                                 while (le!=null) {                              
784                                         MapInterestSet is = le.getInterestSet();                                
785                                         if (is.inNotificationsOf(var)) {
786                                                 MapEntryRemoved e = new MapEntryRemoved(var); 
787                                                 emitEvent(le, e);
788                                         }                                                                       
789                                         le = le.next;
790                                 }               
791                         }
792                         
793                 } catch (AdaptException e) {
794                         throw new AccessorException(e);
795                 }       
796                 
797         }
798
799         public boolean setValue(ChildReference path, Binding binding, Object obj) throws AccessorException {
800                 try {
801                         Accessor a = getComponent(path);
802                         a.setValue(binding, obj);
803                         return true;
804                 } catch (ReferenceException re) {
805                         return false;
806                 } catch (AccessorConstructionException e) {
807                         throw new AccessorException(e);
808                 }
809         }
810         
811         @Override
812         public void setValue(Binding mapBinding, Object newMap)
813                         throws AccessorException {
814                 try {
815                         MapBinding mb = (MapBinding) mapBinding;
816                         Binding valueBinding = mb.getValueBinding();
817                         int size = mb.size(newMap);
818                         Object keys[] = new Object[size];
819                         Object values[] = new Object[size];
820                         mb.getAll(newMap, keys, values);
821                         
822                         Adapter keyAdapter = params.adapterScheme.getAdapter(mb.getKeyBinding(), KEY_BINDING, true, false);
823                         HashSet<File> writeFiles = new HashSet<File>();
824                         List<File> oldFiles = dir.files();
825                         
826                         //HashSet<File> modifiedFiles = new HashSet<File>();
827                         HashSet<File> addedFiles = new HashSet<File>(writeFiles);
828                         addedFiles.removeAll(oldFiles);
829                         
830                         // Write
831                         for (int i=0; i<keys.length; i++) {
832                                 Object key = keys[i];
833                                 Object value = values[i];
834                                 String _key = (String) keyAdapter.adapt(key);
835                                 String filename = _key + ".dbb";
836                                 File file = new File(path, filename);
837                                 writeFiles.add(file);
838                                                                 
839                                 boolean existed = oldFiles.contains(file);                      
840                                 FileVariantAccessor va = files.createFile(file);
841                                 
842                                 va.setValue(mb.getValueBinding(), value);
843                                 va.flush();
844                                 
845                                 // Key variant
846                                 MutableVariant kv = new MutableVariant(KEY_BINDING, _key);
847                                 
848                                 // Notify Listeners
849                                 if (listeners!=null) {
850                                         ListenerEntry le = listeners;
851                                         while (le!=null) {                              
852                                                 MapInterestSet is = le.getInterestSet();
853                                                 if (is.inNotificationsOf(kv)) {
854                                                         
855                                                         MutableVariant vv = null;
856                                                         if (is.inValuesOf(kv)) vv = new MutableVariant(valueBinding, valueBinding.isImmutable() ? value : valueBinding.clone(value));
857                                                         
858                                                         if (existed) {
859                                                                 // Notify about new assignment to old value
860                                                                 ValueAssigned e = new ValueAssigned( new KeyReference(kv), vv);
861                                                                 emitEvent(le, e);
862                                                         } else {
863                                                                 // Notify about new entry
864                                                                 MapEntryAdded e = new MapEntryAdded(kv, vv);
865                                                                 emitEvent(le, e);
866                                                         }
867                                                 }
868                                                 
869                                                 le = le.next;
870                                         }
871                                 }
872                         }
873
874                         HashSet<File> removedFiles = new HashSet<File>(oldFiles);
875                         removedFiles.removeAll(writeFiles);
876                         
877                         // Remove old files
878                         files.expunge();
879                         if (!removedFiles.isEmpty()) {
880                                 List<File> failList = new ArrayList<File>();
881                                 for (File f : removedFiles) {
882                                         
883                                         String filename = f.getName();
884                                         String keyStr = filename.substring(0, filename.length()-4);                                     
885                                         
886                                         boolean deleted = files.deleteFile(f);
887                                         if ( !deleted ) {
888                                                 failList.add(f);
889                                         } else {
890                                                 
891                                                 // Notify Listeners
892                                                 if (listeners!=null) {
893                                                         MutableVariant var = new MutableVariant(KEY_BINDING, keyStr);
894                                                         ListenerEntry le = listeners;
895                                                         while (le!=null) {                              
896                                                                 MapInterestSet is = le.getInterestSet();                                
897                                                                 if (is.inNotificationsOf(var)) {
898                                                                         MapEntryRemoved e = new MapEntryRemoved(var); 
899                                                                         emitEvent(le, e);
900                                                                 }                                                                       
901                                                                 le = le.next;
902                                                         }               
903                                                 }                                       
904                                         }
905                                         
906                                         if (!failList.isEmpty()) {
907                                                 StringBuilder sb = new StringBuilder();
908                                                 sb.append("Failed to delete");
909                                                 for (File ff : failList)  {
910                                                         sb.append(' ');
911                                                         sb.append(ff.toString());
912                                                 }
913                                                 throw new AccessorException(sb.toString());
914                                         }
915                                 }
916                         }
917                         dir.refresh();
918                                                 
919                 } catch (BindingException e) {
920                         throw new AccessorException(e);
921                 } catch (AdaptException e) {
922                         throw new AccessorException(e);
923                 } catch (AdapterConstructionException e) {
924                         throw new AccessorException(e);
925                 } catch (AccessorConstructionException e) {
926                         throw new AccessorException(e);
927                 }               
928         }
929
930         @Override
931         public int size() throws AccessorException {
932                 dir.refresh();
933                 return dir.files().size();
934         }
935
936         @Override       
937         public void addListener(Listener listener, InterestSet interestSet, ChildReference path, Executor executor) throws AccessorException {
938                 listeners = ListenerEntry.link(listeners, listener, interestSet, path, executor);               
939                 MapInterestSet is = (MapInterestSet) interestSet;
940                                 
941                 try {
942                         for (File f : dir.files()) {
943                                 String filename = f.getName();
944                                 String keyStr = filename.substring(0, filename.length()-4);
945                                 
946                                 Accessor sa = getExistingAccessor(KEY_BINDING, keyStr);
947                                 if (sa==null) continue;
948                                 
949                                 MutableVariant key = new MutableVariant(KEY_BINDING, keyStr);
950                                 InterestSet cis = is.getComponentInterest();
951                                 if (cis!=null) { 
952                                         ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) );
953                                         sa.addListener(listener, cis, childPath, executor);                             
954                                 }
955                                 cis = is.getComponentInterest( key );
956                                 if (cis!=null) {
957                                         ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) );
958                                         sa.addListener(listener, cis, childPath, executor);                             
959                                 }
960                         }       
961                 } catch (AccessorConstructionException e) {
962                         throw new AccessorException(e);
963                 }
964                         
965         }
966
967         @Override
968         public void apply(List<Event> cs, LinkedList<Event> rollback) throws AccessorException {
969                 try {
970                         boolean makeRollback = rollback != null;
971                         ArrayList<Event> single = new ArrayList<Event>();
972                         for (Event e : cs) {
973                                 if (e.reference==null) {
974                                         Event rbe = applyLocal(e, makeRollback);
975                                         if (makeRollback) {
976                                                 rbe.reference = e.reference;
977                                                 rollback.addFirst( rbe );
978                                         }                                       
979                                 } else {
980                                         Accessor sa = getComponent(e.reference);
981                                         // Apply changes
982                                         single.clear();
983                                         Event noRefEvent = e.clone(null);
984                                         single.add(noRefEvent);
985                                         sa.apply(single, rollback);
986                                 }
987                         }
988                 } catch (AccessorConstructionException ae) {
989                         throw new AccessorException(ae);
990                 }               
991         }
992         
993         Event applyLocal(Event e, boolean makeRollback) throws AccessorException {
994                 Event rollback = null;
995                 try {
996                         if (e instanceof ValueAssigned) {
997                                 ValueAssigned va = (ValueAssigned) e;
998                                 if (makeRollback) {
999                                         Binding binding = params.bindingScheme.getBinding(type());
1000                                         rollback = new ValueAssigned(binding, getValue(binding));                               
1001                                 }
1002                                 setValue(va.newValue.getBinding(), va.newValue.getValue());
1003                                 return rollback;
1004                         } else if (e instanceof MapEntryAdded) {
1005                                 MapEntryAdded ea = (MapEntryAdded) e;
1006                                 if (ea.key==null) throw new AccessorException("Cannot apply entry added event because key is missing");
1007                                 if (ea.value==null) throw new AccessorException("Cannot apply entry added event because value is missing");
1008                                 boolean hadValue = containsKey(ea.key.getBinding(), ea.key.getValue());
1009                                 if (hadValue) throw new AccessorException("Could not add entry to key that already existed");
1010                                 
1011                                 if (makeRollback) {                             
1012                                         rollback = new MapEntryRemoved( ea.key );
1013                                 }
1014                                 
1015                                 put(ea.key.getBinding(), ea.key.getValue(), ea.value.getBinding(), ea.value.getValue());
1016                                 
1017                         } else if (e instanceof MapEntryRemoved) {
1018                                 MapEntryRemoved er = (MapEntryRemoved) e;
1019                                 
1020                                 if (makeRollback) {
1021                                         boolean hadValue = containsKey(er.key.getBinding(), er.key.getValue());
1022                                         
1023                                         if (hadValue) {                         
1024                                                 MutableVariant oldKey = er.key;
1025                                                 MutableVariant oldValue = getAsVariant(er.key.getBinding(), er.key.getValue());
1026                                                 rollback = new MapEntryAdded(oldKey, oldValue);
1027                                         } else {
1028                                                 rollback = new MapEntryRemoved( er.key.clone() );
1029                                         }
1030                                 }
1031                                 
1032                                 remove( er.key.getBinding(), er.key.getValue() );
1033                                 
1034                         } else throw new AccessorException("Cannot apply "+e.getClass().getName()+" to Map Type");
1035                         
1036                         return rollback;
1037                 } catch (BindingConstructionException e2) {
1038                         throw new AccessorException( e2 );
1039                 }
1040         }
1041         
1042
1043         @SuppressWarnings("unchecked")
1044         @Override
1045         public <T extends Accessor> T getComponent(ChildReference reference)
1046                         throws AccessorConstructionException {
1047                 if (reference==null) return (T) this;
1048                 if (reference instanceof LabelReference) {
1049                         try {
1050                                 LabelReference lr = (LabelReference) reference;
1051                                                                 
1052                                 MutableVariant variant = (MutableVariant) params.adapterScheme.adapt(lr.label, Bindings.STRING, Bindings.MUTABLE_VARIANT);
1053                                 Object value = variant.getValue(KEY_BINDING);                           
1054                                 Accessor result = (T) getValueAccessor(KEY_BINDING, value);
1055                                 
1056                                 if (reference.getChildReference() != null)
1057                                         result = result.getComponent(reference.getChildReference());
1058                                 return (T) result;
1059                         } catch (AdaptException e) {
1060                                 throw new ReferenceException(e);
1061                         }
1062                 } else if (reference instanceof KeyReference) {
1063                         try {
1064                                 KeyReference ref = (KeyReference) reference;                    
1065                                 String keyStr = (String) params.adapterScheme.adapt(ref.key.getValue(), ref.key.getBinding(), KEY_BINDING);                                             
1066                                 File f = keyToFile(keyStr);
1067                                 if (!dir.files().contains(f))
1068                                         throw new AccessorConstructionException("Invalid reference "+ref.key);
1069         
1070                                 Accessor result = getValueAccessor(KEY_BINDING, keyStr);
1071                                 if (reference.getChildReference() != null)
1072                                         result = result.getComponent(reference.getChildReference());
1073                                 return (T) result;
1074                         } catch (AdaptException e) {
1075                                 throw new ReferenceException(e);
1076                         }
1077                 } 
1078                 throw new ReferenceException(reference.getClass().getName()+" is not a reference of a map");    
1079         }
1080
1081         @Override
1082         public void removeListener(Listener listener) throws AccessorException {
1083                 detachListener(listener);
1084         }
1085         
1086         protected ListenerEntry detachListener(Listener listener) throws AccessorException {
1087                 ListenerEntry e = listeners;
1088                 ListenerEntry p = null;
1089                 while (e!=null) {
1090                         // Found match
1091                         if (e.listener == listener) {
1092                                 // The match was the first entry of the linked list
1093                                 if (p==null) {
1094                                         listeners = e.next;
1095                                         return e;
1096                                 }
1097                                 // Some other entry, unlink e
1098                                 p.next = e.next;
1099                                 return e;
1100                         }
1101                         p = e;
1102                         e = e.next;
1103                 }
1104                 return null;            
1105         }
1106
1107         protected void emitEvent(ListenerEntry le, Event e) {           
1108                 e.reference = ChildReference.concatenate(le.path, e.reference);
1109                 le.emitEvent(e);
1110         }       
1111
1112         protected void emitEvents(ListenerEntry le, Collection<Event> events) {
1113                 for (Event e : events)
1114                         e.reference = ChildReference.concatenate(le.path, e.reference);
1115                 le.emitEvents(events);
1116         }       
1117         
1118         
1119 }
1120