/*******************************************************************************
* Industry THTH ry.
* Copyright (c) 2010- Association for Decentralized Information Management in
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.databoard.accessor.impl;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executor;
import org.simantics.databoard.Bindings;
import org.simantics.databoard.Datatypes;
import org.simantics.databoard.accessor.Accessor;
import org.simantics.databoard.accessor.CloseableAccessor;
import org.simantics.databoard.accessor.MapAccessor;
import org.simantics.databoard.accessor.VariantAccessor;
import org.simantics.databoard.accessor.error.AccessorConstructionException;
import org.simantics.databoard.accessor.error.AccessorException;
import org.simantics.databoard.accessor.error.ReferenceException;
import org.simantics.databoard.accessor.event.Event;
import org.simantics.databoard.accessor.event.MapEntryAdded;
import org.simantics.databoard.accessor.event.MapEntryRemoved;
import org.simantics.databoard.accessor.event.ValueAssigned;
import org.simantics.databoard.accessor.file.FileLibrary;
import org.simantics.databoard.accessor.file.FileVariantAccessor;
import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryEvent;
import org.simantics.databoard.accessor.impl.DirectoryWatch.DirectoryListener;
import org.simantics.databoard.accessor.interestset.InterestSet;
import org.simantics.databoard.accessor.interestset.MapInterestSet;
import org.simantics.databoard.accessor.reference.ChildReference;
import org.simantics.databoard.accessor.reference.KeyReference;
import org.simantics.databoard.accessor.reference.LabelReference;
import org.simantics.databoard.adapter.AdaptException;
import org.simantics.databoard.adapter.Adapter;
import org.simantics.databoard.adapter.AdapterConstructionException;
import org.simantics.databoard.binding.ArrayBinding;
import org.simantics.databoard.binding.Binding;
import org.simantics.databoard.binding.MapBinding;
import org.simantics.databoard.binding.VariantBinding;
import org.simantics.databoard.binding.error.BindingConstructionException;
import org.simantics.databoard.binding.error.BindingException;
import org.simantics.databoard.binding.error.RuntimeBindingException;
import org.simantics.databoard.binding.mutable.MutableVariant;
import org.simantics.databoard.type.Datatype;
import org.simantics.databoard.type.MapType;
/**
* DirectoryMap is a file backed map implementation where keys are filenames
* and values are corresponding files.
*
* This class is an implmentation to Map(Variant, Variant) -Accessor.
*
* Filenames have the following encoding:
* S.dbb String types, if string doesn't have the following
* control characters " : < > | ? * \ / [0..31]
* I.dbb Integer types
* L.dbb Long types
* H.dbb All other cases the value as binary
*
* File accessor is created if an entry opened as a sub-accessor.
* The file accessor is closed when all sub-accessors are released.
* The implementation is based on proxy instances and a reference queue.
* Once the queue is empty, file accessor is closed.
*
* DirectoryMap must be closed with #close();
*
* @author Toni Kalajainen
*/
public class DirectoryMap implements MapAccessor, CloseableAccessor {
/** Key binding */
final static Binding KEY_BINDING = Bindings.STR_VARIANT;
/** Cache of sub-accessors */
FileLibrary files;
/** Monitors directory for file changes */
DirectoryWatch dir;
/** Folder */
File path;
/** Listeners */
ListenerEntry listeners = null;
/** Parent, optional */
Accessor parent;
/** Accessor params */
AccessorParams params;
DirectoryListener dirListener = new DirectoryListener() {
@Override
public void onWatchEvent(DirectoryEvent e) {
}
};
public DirectoryMap(File directory) {
this(directory, null, AccessorParams.DEFAULT);
}
public DirectoryMap(File directory, Accessor parent) {
this(directory, parent, AccessorParams.DEFAULT);
}
public DirectoryMap(File directory, Accessor parent, AccessorParams params) {
this.parent = parent;
this.path = directory;
this.params = params;
// Filters .dbb files
FileFilter filter = new FileFilter() {
public boolean accept(File pathname) {
String filename = pathname.getName();
if (filename.length()==0) return false;
char c = filename.charAt(0);
if (c!='S' && c!='I' && c!='L' && c!='B') return false;
if (filename.endsWith(".dbb")) return true;
return filename.toLowerCase().endsWith(".dbb");
}};
dir = new DirectoryWatch(path, filter);
dir.addListener( dirListener );
files = new FileLibrary();
}
public void close() {
dir.removeListener( dirListener );
dir.close();
files.close();
}
static MapType type = new MapType(Datatypes.VARIANT, Datatypes.VARIANT);
public MapType type() {
return type;
}
private String fileToKey(File f) {
String filename = f.getName();
String keyStr = filename.substring(0, filename.length()-4);
return keyStr;
}
private File keyToFile(String keyStr) {
return new File(path, keyStr + ".dbb" );
}
@Override
public void clear() throws AccessorException {
//List deleteList = dir.files();
List failList = new ArrayList();
boolean hasListeners = listeners!=null;
List keys = hasListeners ? new ArrayList() : null;
// Close all file handles
files.close();
// Delete files
for (File f : dir.files()) {
if (!files.deleteFile(f)) {
failList.add(f);
}
if (hasListeners) {
String keyStr = fileToKey(f);
keys.add(keyStr);
}
}
// Re-read directory
dir.refresh();
// Notify Listeners
ListenerEntry le = listeners;
while (le!=null) {
MapInterestSet is = le.getInterestSet();
for (Object key : keys) {
MutableVariant var = new MutableVariant(KEY_BINDING, key);
if (is.inNotificationsOf(var)) {
MapEntryRemoved e = new MapEntryRemoved(var);
emitEvent(le, e);
}
}
le = le.next;
}
// Some files failed to delete
if (!failList.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("Failed to delete");
for (File f : failList) {
sb.append(' ');
sb.append(f.toString());
}
// HAX
throw new AccessorException(sb.toString());
}
}
@Override
public String toString() {
return dir.toString();
}
public Object getValue(Binding binding) throws AccessorException {
MapBinding mb = (MapBinding) binding;
if (mb.getKeyBinding() instanceof VariantBinding==false || mb.getValueBinding() instanceof VariantBinding==false)
throw new AccessorException("Map(Variant, Variant) Expected");
// Get all files as a single map
try {
Object result = binding.createDefault();
for (File f : dir.files()) {
// Create Key
String keyStr = fileToKey(f);
Object key = params.adapterScheme.adapt(keyStr, KEY_BINDING, mb.getKeyBinding());
// Read value
VariantAccessor va = getValueAccessor(KEY_BINDING, keyStr);
Object value = va.getValue(mb.getValueBinding());
mb.put(result, key, value);
}
return result;
} catch (BindingException e) {
throw new AccessorException(e);
} catch (AccessorConstructionException e) {
throw new AccessorException(e);
} catch (AdaptException e) {
throw new AccessorException(e);
}
}
@Override
public void getValue(Binding dstBinding, Object dst) throws AccessorException {
MapBinding db = (MapBinding) dstBinding;
Binding dkb = db.getKeyBinding();
Binding dvb = db.getValueBinding();
if (dkb instanceof VariantBinding==false || dvb instanceof VariantBinding==false)
throw new AccessorException("Map(Variant, Variant) Expected");
// Get all files as a single map
try {
TreeSet