package org.sef4j.core.util.factorydef;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.sef4j.core.util.Handle;
import org.sef4j.core.util.HandleGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
/**
* repository of object created by definition
*
* <p>
* This class handles creation of object by delegating to corresponding factory,<br/>
* and ensure uniqueness of created objects with key def.<br/>
* It manages reference counter per object, returning handles to safely register/unregister objects
* </p>
*/
public class SharedObjectByDefRepository<TDef,T> {
private static final Logger LOG = LoggerFactory.getLogger(SharedObjectByDefRepository.class);
private final String displayObjectName;
private HandleGenerator handleGenerator = new HandleGenerator();
private ObjectByDefRepositories owner;
private Map<DefKeyPair<TDef>,Entry<T>> entries = new HashMap<DefKeyPair<TDef>,Entry<T>>();
private Object entriesLock = new Object();
// use copy-on-write?
private List<SharedObjectByDefFactory<TDef,T>> registeredFactories = new ArrayList<SharedObjectByDefFactory<TDef,T>>();
// ------------------------------------------------------------------------
public SharedObjectByDefRepository(ObjectByDefRepositories owner, String displayObjectName) {
this.owner = owner;
this.displayObjectName = displayObjectName;
}
public void close() {
synchronized(entriesLock) {
for(Entry<T> e : entries.values()) {
doUnregister(e);
}
this.entries.clear();
}
// this.entries = null;
}
// ------------------------------------------------------------------------
public <X extends T> SharedRef<X> getOrCreateByDef(TDef def) {
return getOrCreateByDef(def, null);
}
/**
* register an object by its definition
* internally create on first use and increment reference counter
*
* @param def
* @return
*/
@SuppressWarnings("unchecked")
public <X extends T> SharedRef<X> getOrCreateByDef(TDef def, Object key) {
DefKeyPair<TDef> defKey = new DefKeyPair<TDef>(def, key);
Entry<T> entry;
Handle handle;
synchronized(entriesLock) {
entry = entries.get(defKey);
if (entry == null) {
// create T
entry = doCreateObjByDef(def);
entries.put(defKey, entry);
}
handle = handleGenerator.generate();
entry.handles.add(handle);
}
return new SharedRef<X>(this, handle, (X) entry.object);
}
protected Entry<T> doCreateObjByDef(TDef def) {
SharedObjectByDefFactory<TDef,T> factory = findMatchingFactoryFor(def);
if (factory == null) {
throw new UnsupportedOperationException("Factory not found for def: " + def);
}
String displayName = ""; // TODO
List<SharedRef<?>> dependencyRefs = new ArrayList<SharedRef<?>>();
DependencyObjectCreationContext ctx = new DependencyObjectCreationContext(owner, dependencyRefs, displayName);
// *** the biggy ***
T obj = factory.create(def, ctx);
dependencyRefs = dependencyRefs.isEmpty()? ImmutableList.copyOf(dependencyRefs) : null;
return new Entry<T>(obj, dependencyRefs);
}
/**
* unregister an object by its created handle
* @param handle
*/
public void unregister(Handle handle) {
// note: could also double index by handle..
synchronized(entriesLock) {
Entry<T> entry = null;
for(Iterator<Entry<T>> iter = entries.values().iterator(); iter.hasNext(); ) {
Entry<T> e = iter.next();
if (e.handles.remove(handle)) {
entry = e;
if (entry.handles.isEmpty()) {
// remove this entry from map + recursive unregister dependencies + close T
iter.remove();
doUnregister(entry);
}
break;
}
}
if (entry == null) {
LOG.warn("unregister " + handle + " => not found! .. ignore, do nothing");
}
}
}
private void doUnregister(Entry<T> entry) {
// recursively unregister dependencies
if (entry.dependencyRefs != null && !entry.dependencyRefs.isEmpty()) {
for(SharedRef<?> depRef : entry.dependencyRefs) {
depRef.close();
}
entry.dependencyRefs = null;
}
T obj = entry.object;
entry.object = null;
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch(Exception ex) {
LOG.warn("Failed to close properly " + entry.object + " ... ignore, no rethrow!");
}
}
}
public SharedObjectByDefFactory<TDef,T> findMatchingFactoryFor(TDef def) {
SharedObjectByDefFactory<TDef,T> res = null;
for(SharedObjectByDefFactory<TDef,T> f : registeredFactories) {
if (f.accepts(def)) {
res = f;
break;
}
}
return res;
}
public void registerFactory(SharedObjectByDefFactory<TDef,T> factory) {
registeredFactories.add(factory);
}
// ------------------------------------------------------------------------
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(displayObjectName + "Repository[");
synchronized(entriesLock) {
sb.append(entries.size() + "entry(ies)");
}
sb.append(", " + registeredFactories.size() + " registeredFactories");
sb.append("]");
return sb.toString();
}
// ------------------------------------------------------------------------
private static final class DefKeyPair<TDef> { // yet another 1000th Pair<A,B> class !!!
private final TDef def;
private final Object key;
public DefKeyPair(TDef def, Object key) {
this.key = key;
this.def = def;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((def == null) ? 0 : def.hashCode());
result = prime * result + ((key == null) ? 0 : key.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
@SuppressWarnings("unchecked")
DefKeyPair<TDef> other = (DefKeyPair<TDef>) obj;
if (def == null) {
if (other.def != null)
return false;
} else if (!def.equals(other.def))
return false;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
return true;
}
}
private static final class Entry<T> {
T object;
Set<Handle> handles = new HashSet<Handle>();
List<SharedRef<?>> dependencyRefs;
public Entry(T object, List<SharedRef<?>> dependencyRefs) {
this.object = object;
this.dependencyRefs = dependencyRefs;
}
}
}