mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-04-30 00:00:01 -04:00
TMI-TIFF: Fixed several bugs in the LittleEndianDataInputStream needed for proper TIFF output (should affect other things as well...)
This commit is contained in:
@@ -28,6 +28,13 @@
|
||||
|
||||
package com.twelvemonkeys.util;
|
||||
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* PersistentMap
|
||||
*
|
||||
@@ -35,27 +42,293 @@ package com.twelvemonkeys.util;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PersistentMap.java,v 1.0 May 13, 2009 2:31:29 PM haraldk Exp$
|
||||
*/
|
||||
public class PersistentMap {
|
||||
// TODO: Implement Map
|
||||
// TODO: Delta synchronization (db?)
|
||||
public class PersistentMap<K extends Serializable, V extends Serializable> extends AbstractMap<K, V>{
|
||||
public static final FileFilter DIRECTORIES = new FileFilter() {
|
||||
public boolean accept(File file) {
|
||||
return file.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[All folders]";
|
||||
}
|
||||
};
|
||||
private static final String INDEX = ".index";
|
||||
|
||||
private final File root;
|
||||
private final Map<K, UUID> index = new LinkedHashMap<K, UUID>();
|
||||
|
||||
private boolean mutable = true;
|
||||
|
||||
|
||||
// Idea 2.0:
|
||||
// - Create directory per hashCode
|
||||
// - Create file per object in that directory
|
||||
// - Name file after serialized form of key? Base64?
|
||||
// - Special case for String/Integer/Long etc?
|
||||
// - Or create index file in directory with serialized objects + name (uuid) of file
|
||||
|
||||
// TODO: Consider single index file? Or a few? In root directory instead of each directory
|
||||
// Consider a RAF/FileChannel approach instead of streams - how do we discard portions of a RAF?
|
||||
// - Need to keep track of used/unused parts of file, scan for gaps etc...?
|
||||
// - Need to periodically truncate and re-build the index (always as startup, then at every N puts/removes?)
|
||||
|
||||
/*public */PersistentMap(String id) {
|
||||
this(new File(FileUtil.getTempDirFile(), id));
|
||||
}
|
||||
|
||||
public PersistentMap(File root) {
|
||||
this.root = notNull(root);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (!root.exists() && !root.mkdirs()) {
|
||||
throw new IllegalStateException(String.format("'%s' does not exist/could not be created", root.getAbsolutePath()));
|
||||
}
|
||||
else if (!root.isDirectory()) {
|
||||
throw new IllegalStateException(String.format("'%s' exists but is not a directory", root.getAbsolutePath()));
|
||||
}
|
||||
|
||||
if (!root.canRead()) {
|
||||
throw new IllegalStateException(String.format("'%s' is not readable", root.getAbsolutePath()));
|
||||
}
|
||||
|
||||
if (!root.canWrite()) {
|
||||
mutable = false;
|
||||
}
|
||||
|
||||
FileUtil.visitFiles(root, DIRECTORIES, new Visitor<File>() {
|
||||
public void visit(File dir) {
|
||||
// - Read .index file
|
||||
// - Add entries to index
|
||||
ObjectInputStream input = null;
|
||||
try {
|
||||
input = new ObjectInputStream(new FileInputStream(new File(dir, INDEX)));
|
||||
while (true) {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
K key = (K) input.readObject();
|
||||
String fileName = (String) input.readObject();
|
||||
index.put(key, UUID.fromString(fileName));
|
||||
}
|
||||
}
|
||||
catch (EOFException eof) {
|
||||
// break here
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(input);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
return new AbstractSet<Entry<K, V>>() {
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator() {
|
||||
return new Iterator<Entry<K, V>>() {
|
||||
Iterator<Entry<K, UUID>> indexIter = index.entrySet().iterator();
|
||||
|
||||
public boolean hasNext() {
|
||||
return indexIter.hasNext();
|
||||
}
|
||||
|
||||
public Entry<K, V> next() {
|
||||
return new Entry<K, V>() {
|
||||
final Entry<K, UUID> entry = indexIter.next();
|
||||
|
||||
public K getKey() {
|
||||
return entry.getKey();
|
||||
}
|
||||
|
||||
public V getValue() {
|
||||
K key = entry.getKey();
|
||||
int hash = key != null ? key.hashCode() : 0;
|
||||
return readVal(hash, entry.getValue());
|
||||
}
|
||||
|
||||
public V setValue(V value) {
|
||||
K key = entry.getKey();
|
||||
int hash = key != null ? key.hashCode() : 0;
|
||||
return writeVal(key, hash, entry.getValue(), value, getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
indexIter.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return index.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return index.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
V oldVal = null;
|
||||
|
||||
UUID uuid = index.get(key);
|
||||
int hash = key != null ? key.hashCode() : 0;
|
||||
|
||||
if (uuid != null) {
|
||||
oldVal = readVal(hash, uuid);
|
||||
}
|
||||
|
||||
return writeVal(key, hash, uuid, value, oldVal);
|
||||
}
|
||||
|
||||
private V writeVal(K key, int hash, UUID uuid, V value, V oldVal) {
|
||||
if (!mutable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
File bucket = new File(root, hashToFileName(hash));
|
||||
if (!bucket.exists() && !bucket.mkdirs()) {
|
||||
throw new IllegalStateException(String.format("Could not create bucket '%s'", bucket));
|
||||
}
|
||||
|
||||
if (uuid == null) {
|
||||
// No uuid means new entry
|
||||
uuid = UUID.randomUUID();
|
||||
|
||||
File idx = new File(bucket, INDEX);
|
||||
|
||||
ObjectOutputStream output = null;
|
||||
try {
|
||||
output = new ObjectOutputStream(new FileOutputStream(idx, true));
|
||||
output.writeObject(key);
|
||||
output.writeObject(uuid.toString());
|
||||
|
||||
index.put(key, uuid);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(output);
|
||||
}
|
||||
}
|
||||
|
||||
File entry = new File(bucket, uuid.toString());
|
||||
if (value != null) {
|
||||
ObjectOutputStream output = null;
|
||||
try {
|
||||
output = new ObjectOutputStream(new FileOutputStream(entry));
|
||||
output.writeObject(value);
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(output);
|
||||
}
|
||||
}
|
||||
else if (entry.exists()) {
|
||||
if (!entry.delete()) {
|
||||
throw new IllegalStateException(String.format("'%s' could not be deleted", entry));
|
||||
}
|
||||
}
|
||||
|
||||
return oldVal;
|
||||
}
|
||||
|
||||
private String hashToFileName(int hash) {
|
||||
return Integer.toString(hash, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
UUID uuid = index.get(key);
|
||||
|
||||
if (uuid != null) {
|
||||
int hash = key != null ? key.hashCode() : 0;
|
||||
return readVal(hash, uuid);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private V readVal(final int hash, final UUID uuid) {
|
||||
File bucket = new File(root, hashToFileName(hash));
|
||||
File entry = new File(bucket, uuid.toString());
|
||||
|
||||
if (entry.exists()) {
|
||||
ObjectInputStream input = null;
|
||||
try {
|
||||
input = new ObjectInputStream(new FileInputStream(entry));
|
||||
//noinspection unchecked
|
||||
return (V) input.readObject();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(input);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
// TODO!!!
|
||||
return super.remove(key);
|
||||
}
|
||||
|
||||
// TODO: Should override size, put, get, remove, containsKey and containsValue
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Memory mapped file?
|
||||
Delta sync?
|
||||
|
||||
Persistent format
|
||||
|
||||
Header
|
||||
File ID 4-8 bytes
|
||||
Size
|
||||
Size (entries)
|
||||
|
||||
Entry pointer array block
|
||||
Size
|
||||
Next entry pointer block address
|
||||
Entry 1 address
|
||||
PersistentEntry pointer array block (PersistentEntry 0)
|
||||
Size (bytes)
|
||||
Next entry pointer block address (0 if last)
|
||||
PersistentEntry 1 address/offset + key
|
||||
...
|
||||
Entry n address
|
||||
PersistentEntry n address/offset + key
|
||||
|
||||
PersistentEntry 1
|
||||
Size (bytes)?
|
||||
Serialized value or pointer array block
|
||||
...
|
||||
PersistentEntry n
|
||||
Size (bytes)?
|
||||
Serialized value or pointer array block
|
||||
|
||||
Entry 1
|
||||
...
|
||||
Entry n
|
||||
|
||||
*/
|
||||
Reference in New Issue
Block a user