package com.limegroup.gnutella.library;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.RandomAccess;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.SortedSet;
import java.util.TreeSet;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import com.limegroup.gnutella.util.IOUtils;
import com.limegroup.gnutella.util.CommonUtils;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
/**
* A container for storing serialized objects to disk.
* This supports only storing objects that fit in the Collections framework.
* Either Collections or Maps.
* All collections are returned as synchronized on this container.
*/
class Container {
private static final Log LOG = LogFactory.getLog(Container.class);
private final Map STORED = new HashMap();
private final String filename;
/**
* Constructs a new container with the given filename.
* It will always save to this name in the user's
* setting's directory, also loading the data from disk.
*/
Container(String name) {
filename = name;
load();
}
/**
* Loads data from disk. This requires the data either be
* a Map or a Collection if it already existed (in order
* to refresh data, instead of replace it).
*/
void load() {
// Read without grabbing the lock.
Map read = readFromDisk();
synchronized(this) {
// Simple case -- no stored data yet.
if(STORED.isEmpty()) {
STORED.putAll(read);
} else {
// If data was stored, we can't replace, we have to refresh.
for(Iterator i = read.entrySet().iterator(); i.hasNext(); ) {
Map.Entry next = (Map.Entry)i.next();
Object k = next.getKey();
Object v = next.getValue();
Object storedV = STORED.get(k);
if(storedV == null) {
// Another simple case -- key wasn't stored yet.
STORED.put(k, v);
} else {
synchronized(storedV) {
// We can only refresh if both values are either
// Collections or Maps.
if(v instanceof Collection && storedV instanceof Collection) {
Collection cv = (Collection)storedV;
cv.clear();
cv.addAll((Collection)v);
} else if(v instanceof Map && storedV instanceof Map) {
Map mv = (Map)storedV;
mv.clear();
mv.putAll((Map)v);
} else if(LOG.isWarnEnabled()) {
LOG.warn("Unable to reload data, key: " + k);
}
}
}
}
}
}
}
/**
* Retrieves a set from the Container. If the object
* stored is not null or is not a set, a Set is inserted instead.
*
* The returned sets are synchronized, but the serialized sets are NOT SYNCHRONIZED.
* This means that the future can change what they synchronize on easily.
*/
synchronized Set getSet(String name) {
Object data = STORED.get(name);
if (data != null) {
return (Set)data;
}
else {
Set set = Collections.synchronizedSet(new HashSet());
STORED.put(name, set);
return set;
}
}
/**
* Clears all entries. This assumes all entries are either Collections or Maps.
*/
synchronized void clear() {
for(Iterator i = STORED.entrySet().iterator(); i.hasNext(); ) {
Map.Entry next = (Map.Entry)i.next();
Object v = next.getValue();
synchronized(v) {
if(v instanceof Collection)
((Collection)v).clear();
else if(v instanceof Map)
((Map)v).clear();
else if(LOG.isDebugEnabled())
LOG.debug("Unable to clear data, key: " + next.getKey());
}
}
}
/**
* Saves the data to disk.
*/
void save() {
Map toSave;
synchronized(this) {
toSave = new HashMap(STORED.size());
// This assumes that all objects are basic Collections objects.
// If any aren't, we ignore them.
// Ideally we would use Cloneable, but the method is protected.
for(Iterator i = STORED.entrySet().iterator(); i.hasNext(); ) {
Map.Entry next = (Map.Entry)i.next();
Object k = next.getKey();
Object v = next.getValue();
synchronized(v) {
if(v instanceof SortedSet)
toSave.put(k, new TreeSet((SortedSet)v));
else if(v instanceof Set)
toSave.put(k, new HashSet((Set)v));
else if(v instanceof Map)
toSave.put(k, new HashMap((Map)v));
else if(v instanceof List) {
if (v instanceof RandomAccess)
toSave.put(k, new ArrayList((List)v));
else
toSave.put(k, new LinkedList((List)v));
}
else {
if(LOG.isWarnEnabled())
LOG.warn("Update to clone! key: " + k);
toSave.put(k, v);
}
}
}
}
writeToDisk(toSave);
}
/**
* Saves the given object to disk.
*/
private void writeToDisk(Object o) {
File f = new File(CommonUtils.getUserSettingsDir(), filename);
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
oos.writeObject(o);
oos.flush();
} catch(IOException iox) {
LOG.warn("Can't write to disk!", iox);
} finally {
IOUtils.close(oos);
}
}
/**
* Reads a Map from disk.
*/
private Map readFromDisk() {
File f = new File(CommonUtils.getUserSettingsDir(), filename);
ObjectInputStream ois = null;
Map map = null;
try {
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)));
map = (Map)ois.readObject();
} catch(ClassCastException cce) {
LOG.warn("Not a map!", cce);
} catch(IOException iox) {
LOG.warn("Can't write to disk!", iox);
} catch(Throwable x) {
LOG.warn("Error reading!", x);
} finally {
IOUtils.close(ois);
}
if (map != null) {
HashMap toReturn = new HashMap(map.size());
for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry)i.next();
Object k = entry.getKey();
Object v = entry.getValue();
if(v instanceof SortedSet)
toReturn.put(k, Collections.synchronizedSortedSet((SortedSet)v));
else if(v instanceof Set)
toReturn.put(k, Collections.synchronizedSet((Set)v));
else if(v instanceof Map)
toReturn.put(k, Collections.synchronizedMap((Map)v));
else if(v instanceof List)
toReturn.put(k, Collections.synchronizedList((List)v));
else {
if(LOG.isWarnEnabled())
LOG.warn("Update to clone! key: " + k);
toReturn.put(k, v);
}
}
return toReturn;
}
else {
return new HashMap();
}
}
}