]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.databoard/src/org/simantics/databoard/accessor/impl/DirectoryMap.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / accessor / impl / DirectoryMap.java
1 /*******************************************************************************\r
2  * Industry THTH ry.\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
8  * \r
9  * Contributors:\r
10  *    VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.databoard.accessor.impl;\r
13 \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
27 \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
63 \r
64 /**\r
65  * DirectoryMap is a file backed map implementation where keys are filenames\r
66  * and values are corresponding files. \r
67  * <p>\r
68  * This class is an implmentation to Map(Variant, Variant) -Accessor.\r
69  * \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
76  * <p>\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
81  * <p>\r
82  * DirectoryMap must be closed with #close();     \r
83  *\r
84  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
85  */\r
86 public class DirectoryMap implements MapAccessor, CloseableAccessor {\r
87 \r
88         /** Key binding */\r
89         final static Binding KEY_BINDING = Bindings.STR_VARIANT;\r
90         \r
91         /** Cache of sub-accessors */\r
92         FileLibrary files;\r
93         \r
94         /** Monitors directory for file changes */\r
95         DirectoryWatch dir;\r
96         \r
97         /** Folder */\r
98         File path;\r
99         \r
100         /** Listeners */\r
101         ListenerEntry listeners = null;\r
102         \r
103         /** Parent, optional */\r
104         Accessor parent; \r
105 \r
106         /** Accessor params */\r
107         AccessorParams params;\r
108         \r
109         DirectoryListener dirListener = new DirectoryListener() {\r
110                 @Override\r
111                 public void onWatchEvent(DirectoryEvent e) {\r
112                         \r
113                 }\r
114         };\r
115 \r
116         public DirectoryMap(File directory) {\r
117                 this(directory, null, AccessorParams.DEFAULT);\r
118         }\r
119 \r
120         public DirectoryMap(File directory, Accessor parent) {\r
121                 this(directory, parent, AccessorParams.DEFAULT);\r
122         }\r
123 \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
128 \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
138                         }};\r
139                 \r
140                 dir = new DirectoryWatch(path, filter);\r
141                 \r
142                 dir.addListener( dirListener );\r
143                 \r
144                 files = new FileLibrary();\r
145         }\r
146         \r
147         public void close() {\r
148                 dir.removeListener( dirListener );\r
149                 dir.close();\r
150                 files.close();\r
151         }\r
152         \r
153         static MapType type = new MapType(Datatypes.VARIANT, Datatypes.VARIANT);\r
154         public MapType type() {\r
155                 return type;\r
156         }\r
157 \r
158         private String fileToKey(File f) {\r
159                 String filename = f.getName();\r
160                 String keyStr = filename.substring(0, filename.length()-4);\r
161                 return keyStr;\r
162         }\r
163         \r
164         private File keyToFile(String keyStr) {\r
165                 return new File(path, keyStr + ".dbb" );\r
166         }\r
167         \r
168         @Override\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
174                 \r
175                 // Close all file handles\r
176                 files.close();\r
177                 \r
178                 // Delete files\r
179                 for (File f : dir.files()) {\r
180                         if (!files.deleteFile(f)) {\r
181                                 failList.add(f);\r
182                         }\r
183                         if (hasListeners) {\r
184                                 String keyStr = fileToKey(f);\r
185                                 keys.add(keyStr);\r
186                         }\r
187                 }\r
188 \r
189                 // Re-read directory\r
190                 dir.refresh();\r
191                 \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
200                                         emitEvent(le, e);\r
201                                 }\r
202                         }                                                       \r
203                         le = le.next;\r
204                 }                               \r
205                 \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
211                                 sb.append(' ');\r
212                                 sb.append(f.toString());\r
213                         }\r
214                         // HAX\r
215                         throw new AccessorException(sb.toString());\r
216                 }\r
217                 \r
218                                 \r
219         }\r
220                 \r
221         @Override\r
222         public String toString() {\r
223                 return dir.toString();\r
224         }\r
225         \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
231                 try {\r
232                         Object result = binding.createDefault();\r
233                         for (File f : dir.files()) {\r
234                                 // Create Key\r
235                                 String keyStr = fileToKey(f);\r
236                                 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, mb.getKeyBinding());\r
237                                 \r
238                                 // Read value\r
239                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);\r
240                                 Object value = va.getValue(mb.getValueBinding());\r
241                                 \r
242                                 mb.put(result, key, value);\r
243                         }\r
244                         return result;\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
251                 }\r
252         }\r
253         \r
254         @Override\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
262                 try {\r
263                         TreeSet<Object> dstKeys = new TreeSet<Object>(dkb);\r
264                         db.getKeys(dst, dstKeys);\r
265                         \r
266                         for (File f : dir.files()) {\r
267                                 // Create Key\r
268                                 String keyStr = fileToKey(f);\r
269                                 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, dkb);\r
270                                 \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
274                                 \r
275                                 db.put(dst, key, v);\r
276                                 dstKeys.remove(key);\r
277                         }\r
278                         \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
287                 }\r
288                 \r
289         }\r
290         \r
291         @Override\r
292         public boolean getValue(ChildReference path, Binding binding, Object obj) throws AccessorException {\r
293                 try {\r
294                         Accessor a = getComponent(path);\r
295                         a.getValue(binding, obj);\r
296                         return true;\r
297                 } catch (ReferenceException re) {\r
298                         return false;\r
299                 } catch (AccessorConstructionException e) {\r
300                         throw new AccessorException(e);\r
301                 }\r
302         }       \r
303         \r
304         public Object getValue(ChildReference path, Binding binding) throws AccessorException {\r
305                 try {\r
306                         Accessor a = getComponent(path);\r
307                         return a.getValue(binding);\r
308                 } catch (ReferenceException re) {\r
309                         return null;\r
310                 } catch (AccessorConstructionException e) {\r
311                         throw new AccessorException(e);\r
312                 }\r
313         }\r
314         \r
315         \r
316         @Override\r
317         public boolean containsKey(Binding keyBinding, Object key)\r
318                         throws AccessorException {\r
319                 try {\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
325                 }\r
326         }\r
327 \r
328         @Override\r
329         public boolean containsValue(Binding valueBinding, Object value)\r
330                         throws AccessorException {\r
331                 try {\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
338                         }\r
339                         return false;\r
340                 } catch (AccessorConstructionException e) {\r
341                         throw new AccessorException(e);\r
342                 }               \r
343         }\r
344 \r
345         @Override\r
346         public Object get(Binding keyBinding, Object key, Binding valueBinding)\r
347                         throws AccessorException {\r
348                 try {\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
353                 }\r
354         }\r
355 \r
356         /**\r
357          * Get the value as a variant\r
358          *  \r
359          * @param keyBinding\r
360          * @param key\r
361          * @return value\r
362          * @throws AccessorException\r
363          */\r
364         public MutableVariant getAsVariant(Binding keyBinding, Object key)\r
365                         throws AccessorException {\r
366                 try {\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
372                         return result;\r
373                 } catch (AccessorConstructionException e) {\r
374                         throw new AccessorException(e);\r
375                 } catch (BindingConstructionException e) {\r
376                         throw new AccessorException(e);\r
377                 }\r
378         }\r
379 \r
380         @Override\r
381         public void getAll(Binding keyBinding, Binding valueBinding,\r
382                         Map<Object, Object> to) throws AccessorException {\r
383                 try {\r
384                         for (File f : dir.files()) {    \r
385                                 // Create key\r
386                                 String keyStr = fileToKey(f);\r
387                                 Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding);\r
388 \r
389                                 // Read value\r
390                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);\r
391                                 Object value = va.getValue(valueBinding);\r
392                                 \r
393                                 to.put(key, value);                             \r
394                         }\r
395                 } catch (AdaptException e) {\r
396                         throw new AccessorException(e);\r
397                 } catch (AccessorConstructionException e) {\r
398                         throw new AccessorException(e);\r
399                 }                       \r
400         }\r
401 \r
402         @Override\r
403         public void getAll(Binding keyBinding, Binding valueBinding, Object[] keys,\r
404                         Object[] values) throws AccessorException {\r
405                 try {\r
406                         Set<String> fileKeys = createKeys();\r
407                         \r
408                         int i=0;\r
409                         for (String keyStr : fileKeys) {                                \r
410                                 // Read value\r
411                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);\r
412                                 Object value = va.getValue(valueBinding);\r
413                                 \r
414                                 Object key2 = params.adapterScheme.adapt(keyStr, KEY_BINDING, keyBinding);\r
415                                 keys[i] = key2;\r
416                                 values[i] = value;\r
417                                 i++;\r
418                         }\r
419                 } catch (AdaptException e) {\r
420                         throw new AccessorException(e);\r
421                 } catch (AccessorConstructionException e) {\r
422                         throw new AccessorException(e);\r
423                 }                       \r
424         }\r
425         \r
426 \r
427         @Override\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
432         }\r
433 \r
434         @Override\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
441         }\r
442         \r
443 \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
447                 \r
448                 for (File f : files) {\r
449                         String filename = f.getName();\r
450                         String str = filename.substring(0, filename.length()-4);\r
451                         keys.add(str);\r
452                 }\r
453                 \r
454                 return keys;\r
455         }\r
456         \r
457         @Override\r
458         public Object getCeilingKey(Binding keyBinding, Object key)\r
459                         throws AccessorException {\r
460                 try {\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
471                 }\r
472         }\r
473 \r
474         @Override\r
475         public Object getFirstKey(Binding keyBinding) throws AccessorException {\r
476                 List<File> files = dir.files();\r
477                 String firstKey = null;\r
478                 \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
483                                 firstKey = str;\r
484                         } else {\r
485                                 if (KEY_BINDING.compare(str, firstKey)<0) firstKey = str;\r
486                         }\r
487                 }\r
488                 if (firstKey==null) return null;\r
489                 \r
490                 try {\r
491                         return params.adapterScheme.adapt(firstKey, KEY_BINDING, keyBinding);\r
492                 } catch (AdaptException e) {\r
493                         throw new AccessorException(e);\r
494                 }\r
495         }\r
496 \r
497         @Override\r
498         public Object getFloorKey(Binding keyBinding, Object key)\r
499                         throws AccessorException {\r
500                 try {\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
510                 }\r
511         }\r
512 \r
513         @Override\r
514         public Object getHigherKey(Binding keyBinding, Object key)\r
515                         throws AccessorException {\r
516                 try {\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
526                 }\r
527         }\r
528 \r
529         @Override\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
534                 try {\r
535                         Adapter a = params.adapterScheme.getAdapter(KEY_BINDING, keyBinding, true, false);\r
536                         int index = 0;\r
537                         for (String key : keys) {                                                               \r
538                                 result[index++] = a.adapt( key );\r
539                         }\r
540                 } catch (AdaptException e) {\r
541                         throw new AccessorException(e);\r
542                 } catch (AdapterConstructionException e) {\r
543                         throw new AccessorException(e);\r
544                 }\r
545                 \r
546                 return result;\r
547         }\r
548 \r
549         @Override\r
550         public Object getLastKey(Binding keyBinding) throws AccessorException {\r
551                 List<File> files = dir.files();\r
552                 String lastKey = null;\r
553                 \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
558                                 lastKey = str;\r
559                         } else {\r
560                                 if (KEY_BINDING.compare(str, lastKey)>0) lastKey = str;\r
561                         }\r
562                 }\r
563                 if (lastKey==null) return null;\r
564                 \r
565                 try {\r
566                         return params.adapterScheme.adapt(lastKey, KEY_BINDING, keyBinding);\r
567                 } catch (AdaptException e) {\r
568                         throw new AccessorException(e);\r
569                 }\r
570         }\r
571 \r
572         @Override\r
573         public Object getLowerKey(Binding keyBinding, Object key)\r
574                         throws AccessorException {\r
575                 try {\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
585                 }\r
586         }\r
587 \r
588         public FileVariantAccessor getExistingAccessor(Binding keyBinding,\r
589                         Object key) throws AccessorConstructionException {\r
590                 try {\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
596                 }       \r
597         }\r
598         \r
599         @SuppressWarnings("unchecked")\r
600         @Override\r
601         public FileVariantAccessor getValueAccessor(Binding keyBinding, Object key) throws AccessorConstructionException {\r
602                 try {\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
607                         \r
608                         // Create new accessor\r
609                         sa = files.getFile(file);\r
610 \r
611                         // Add component interest sets\r
612                         ListenerEntry le = listeners;\r
613                         if (le!=null) {\r
614                                 MutableVariant kv = new MutableVariant(keyBinding, key);\r
615                                 while (le!=null) {                              \r
616                                         MapInterestSet is = le.getInterestSet();\r
617         \r
618                                         // Generic element interest\r
619                                         InterestSet gis = is.getComponentInterest(); \r
620                                         if (gis != null) {\r
621                                                 try {\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
626                                                 }\r
627                                         }\r
628                                                 \r
629                                         // Specific element interest\r
630                                         InterestSet cis = is.getComponentInterest(kv); \r
631                                         if (cis != null) {\r
632                                                 try {\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
637                                                 }\r
638                                         }\r
639                                         \r
640                                         // Next listener\r
641                                         le = le.next;\r
642                                 }\r
643                         }\r
644                         \r
645                         return sa;\r
646                 } catch (AdaptException e) {\r
647                         throw new AccessorConstructionException(e);\r
648                 }       \r
649         }\r
650 \r
651         @Override\r
652         public Object[] getValues(Binding valueBinding) throws AccessorException {              \r
653                 try {\r
654                         Set<String> keys = createKeys();\r
655                         int count = keys.size();\r
656                         Object[] result = new Object[ count ];\r
657                         \r
658                         Iterator<String> iter = keys.iterator();\r
659                         for (int i=0; i<count; i++) {\r
660                                 String keyStr = iter.next();\r
661                                 // Read the file\r
662                                 VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);\r
663                                 Object value = va.getValue(valueBinding);                               \r
664                                 result[i] = value;\r
665                         }\r
666                         return result;\r
667                 } catch (AccessorConstructionException e) {\r
668                         throw new AccessorException(e);\r
669                 }                       \r
670         }\r
671 \r
672         /**\r
673          * \r
674          * @param keyBinding\r
675          * @param key\r
676          * @param valueBinding\r
677          * @param value\r
678          * @return true if new file was created\r
679          * @throws AccessorException\r
680          */\r
681         private boolean putLocal(Binding keyBinding, Object key, Binding valueBinding,\r
682                         Object value) throws AccessorException {\r
683                 try {\r
684                         // Write\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
690                         va.flush();\r
691                         if (created) {\r
692                                 dir.add(file);\r
693                         }\r
694                                                 \r
695                         // Key variant\r
696                         MutableVariant kv = new MutableVariant(KEY_BINDING, _key);\r
697                         \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
704                                                 \r
705                                                 MutableVariant vv = null;\r
706                                                 if (is.inValuesOf(kv)) vv = new MutableVariant(valueBinding, valueBinding.isImmutable() ? value : valueBinding.clone(value));\r
707                                                 \r
708                                                 if (!created) {\r
709                                                         // Notify about new assignment to old value\r
710                                                         ValueAssigned e = new ValueAssigned( new KeyReference(kv), vv);\r
711                                                         emitEvent(le, e);\r
712                                                 } else {\r
713                                                         // Notify about new entry\r
714                                                         MapEntryAdded e = new MapEntryAdded(kv, vv);\r
715                                                         emitEvent(le, e);\r
716                                                 }\r
717                                         }\r
718                                         \r
719                                         le = le.next;\r
720                                 }\r
721                         }\r
722                         return created; \r
723                 } catch (AdaptException e) {\r
724                         throw new AccessorException(e);\r
725                 } catch (AccessorConstructionException e) {\r
726                         throw new AccessorException(e);\r
727                 }               \r
728         }\r
729         \r
730         @Override\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
734         }\r
735 \r
736         @Override\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
744                 }\r
745         }\r
746 \r
747         @Override\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
757                 }\r
758         }       \r
759 \r
760         @Override\r
761         public void remove(Binding keyBinding, Object key) throws AccessorException {\r
762 \r
763                 try {\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
768                                                 \r
769                         files.expunge();\r
770                         \r
771                         boolean deleteOk = files.deleteFile(file);\r
772                         \r
773                         if (!deleteOk) {\r
774                                 throw new AccessorException("Failed to delete "+file);\r
775                         } else {\r
776                                 dir.remove(file);\r
777                         }\r
778                         \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
787                                                 emitEvent(le, e);\r
788                                         }                                                                       \r
789                                         le = le.next;\r
790                                 }               \r
791                         }\r
792                         \r
793                 } catch (AdaptException e) {\r
794                         throw new AccessorException(e);\r
795                 }       \r
796                 \r
797         }\r
798 \r
799         public boolean setValue(ChildReference path, Binding binding, Object obj) throws AccessorException {\r
800                 try {\r
801                         Accessor a = getComponent(path);\r
802                         a.setValue(binding, obj);\r
803                         return true;\r
804                 } catch (ReferenceException re) {\r
805                         return false;\r
806                 } catch (AccessorConstructionException e) {\r
807                         throw new AccessorException(e);\r
808                 }\r
809         }\r
810         \r
811         @Override\r
812         public void setValue(Binding mapBinding, Object newMap)\r
813                         throws AccessorException {\r
814                 try {\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
821                         \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
825                         \r
826                         //HashSet<File> modifiedFiles = new HashSet<File>();\r
827                         HashSet<File> addedFiles = new HashSet<File>(writeFiles);\r
828                         addedFiles.removeAll(oldFiles);\r
829                         \r
830                         // Write\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
838                                                                 \r
839                                 boolean existed = oldFiles.contains(file);                      \r
840                                 FileVariantAccessor va = files.createFile(file);\r
841                                 \r
842                                 va.setValue(mb.getValueBinding(), value);\r
843                                 va.flush();\r
844                                 \r
845                                 // Key variant\r
846                                 MutableVariant kv = new MutableVariant(KEY_BINDING, _key);\r
847                                 \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
854                                                         \r
855                                                         MutableVariant vv = null;\r
856                                                         if (is.inValuesOf(kv)) vv = new MutableVariant(valueBinding, valueBinding.isImmutable() ? value : valueBinding.clone(value));\r
857                                                         \r
858                                                         if (existed) {\r
859                                                                 // Notify about new assignment to old value\r
860                                                                 ValueAssigned e = new ValueAssigned( new KeyReference(kv), vv);\r
861                                                                 emitEvent(le, e);\r
862                                                         } else {\r
863                                                                 // Notify about new entry\r
864                                                                 MapEntryAdded e = new MapEntryAdded(kv, vv);\r
865                                                                 emitEvent(le, e);\r
866                                                         }\r
867                                                 }\r
868                                                 \r
869                                                 le = le.next;\r
870                                         }\r
871                                 }\r
872                         }\r
873 \r
874                         HashSet<File> removedFiles = new HashSet<File>(oldFiles);\r
875                         removedFiles.removeAll(writeFiles);\r
876                         \r
877                         // Remove old files\r
878                         files.expunge();\r
879                         if (!removedFiles.isEmpty()) {\r
880                                 List<File> failList = new ArrayList<File>();\r
881                                 for (File f : removedFiles) {\r
882                                         \r
883                                         String filename = f.getName();\r
884                                         String keyStr = filename.substring(0, filename.length()-4);                                     \r
885                                         \r
886                                         boolean deleted = files.deleteFile(f);\r
887                                         if ( !deleted ) {\r
888                                                 failList.add(f);\r
889                                         } else {\r
890                                                 \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
899                                                                         emitEvent(le, e);\r
900                                                                 }                                                                       \r
901                                                                 le = le.next;\r
902                                                         }               \r
903                                                 }                                       \r
904                                         }\r
905                                         \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
910                                                         sb.append(' ');\r
911                                                         sb.append(ff.toString());\r
912                                                 }\r
913                                                 throw new AccessorException(sb.toString());\r
914                                         }\r
915                                 }\r
916                         }\r
917                         dir.refresh();\r
918                                                 \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
927                 }               \r
928         }\r
929 \r
930         @Override\r
931         public int size() throws AccessorException {\r
932                 dir.refresh();\r
933                 return dir.files().size();\r
934         }\r
935 \r
936         @Override       \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
940                                 \r
941                 try {\r
942                         for (File f : dir.files()) {\r
943                                 String filename = f.getName();\r
944                                 String keyStr = filename.substring(0, filename.length()-4);\r
945                                 \r
946                                 Accessor sa = getExistingAccessor(KEY_BINDING, keyStr);\r
947                                 if (sa==null) continue;\r
948                                 \r
949                                 MutableVariant key = new MutableVariant(KEY_BINDING, keyStr);\r
950                                 InterestSet cis = is.getComponentInterest();\r
951                                 if (cis!=null) { \r
952                                         ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) );\r
953                                         sa.addListener(listener, cis, childPath, executor);                             \r
954                                 }\r
955                                 cis = is.getComponentInterest( key );\r
956                                 if (cis!=null) {\r
957                                         ChildReference childPath = ChildReference.concatenate( path, new KeyReference(key) );\r
958                                         sa.addListener(listener, cis, childPath, executor);                             \r
959                                 }\r
960                         }       \r
961                 } catch (AccessorConstructionException e) {\r
962                         throw new AccessorException(e);\r
963                 }\r
964                         \r
965         }\r
966 \r
967         @Override\r
968         public void apply(List<Event> cs, LinkedList<Event> rollback) throws AccessorException {\r
969                 try {\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
978                                         }                                       \r
979                                 } else {\r
980                                         Accessor sa = getComponent(e.reference);\r
981                                         // Apply changes\r
982                                         single.clear();\r
983                                         Event noRefEvent = e.clone(null);\r
984                                         single.add(noRefEvent);\r
985                                         sa.apply(single, rollback);\r
986                                 }\r
987                         }\r
988                 } catch (AccessorConstructionException ae) {\r
989                         throw new AccessorException(ae);\r
990                 }               \r
991         }\r
992         \r
993         Event applyLocal(Event e, boolean makeRollback) throws AccessorException {\r
994                 Event rollback = null;\r
995                 try {\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
1001                                 }\r
1002                                 setValue(va.newValue.getBinding(), va.newValue.getValue());\r
1003                                 return rollback;\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
1010                                 \r
1011                                 if (makeRollback) {                             \r
1012                                         rollback = new MapEntryRemoved( ea.key );\r
1013                                 }\r
1014                                 \r
1015                                 put(ea.key.getBinding(), ea.key.getValue(), ea.value.getBinding(), ea.value.getValue());\r
1016                                 \r
1017                         } else if (e instanceof MapEntryRemoved) {\r
1018                                 MapEntryRemoved er = (MapEntryRemoved) e;\r
1019                                 \r
1020                                 if (makeRollback) {\r
1021                                         boolean hadValue = containsKey(er.key.getBinding(), er.key.getValue());\r
1022                                         \r
1023                                         if (hadValue) {                         \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
1027                                         } else {\r
1028                                                 rollback = new MapEntryRemoved( er.key.clone() );\r
1029                                         }\r
1030                                 }\r
1031                                 \r
1032                                 remove( er.key.getBinding(), er.key.getValue() );\r
1033                                 \r
1034                         } else throw new AccessorException("Cannot apply "+e.getClass().getName()+" to Map Type");\r
1035                         \r
1036                         return rollback;\r
1037                 } catch (BindingConstructionException e2) {\r
1038                         throw new AccessorException( e2 );\r
1039                 }\r
1040         }\r
1041         \r
1042 \r
1043         @SuppressWarnings("unchecked")\r
1044         @Override\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
1049                         try {\r
1050                                 LabelReference lr = (LabelReference) reference;\r
1051                                                                 \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
1055                                 \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
1061                         }\r
1062                 } else if (reference instanceof KeyReference) {\r
1063                         try {\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
1069         \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
1076                         }\r
1077                 } \r
1078                 throw new ReferenceException(reference.getClass().getName()+" is not a reference of a map");    \r
1079         }\r
1080 \r
1081         @Override\r
1082         public void removeListener(Listener listener) throws AccessorException {\r
1083                 detachListener(listener);\r
1084         }\r
1085         \r
1086         protected ListenerEntry detachListener(Listener listener) throws AccessorException {\r
1087                 ListenerEntry e = listeners;\r
1088                 ListenerEntry p = null;\r
1089                 while (e!=null) {\r
1090                         // Found match\r
1091                         if (e.listener == listener) {\r
1092                                 // The match was the first entry of the linked list\r
1093                                 if (p==null) {\r
1094                                         listeners = e.next;\r
1095                                         return e;\r
1096                                 }\r
1097                                 // Some other entry, unlink e\r
1098                                 p.next = e.next;\r
1099                                 return e;\r
1100                         }\r
1101                         p = e;\r
1102                         e = e.next;\r
1103                 }\r
1104                 return null;            \r
1105         }\r
1106 \r
1107         protected void emitEvent(ListenerEntry le, Event e) {           \r
1108                 e.reference = ChildReference.concatenate(le.path, e.reference);\r
1109                 le.emitEvent(e);\r
1110         }       \r
1111 \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
1116         }       \r
1117         \r
1118         \r
1119 }\r
1120 \r