package org.geotools.data.efeature.internal;
import java.util.HashMap;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.geotools.data.efeature.EFeature;
import org.geotools.data.efeature.EFeatureContext;
import org.geotools.data.efeature.EFeatureContextFactory;
import org.geotools.data.efeature.EFeatureFolderInfo;
import org.geotools.data.efeature.EFeatureInfo;
import org.geotools.data.efeature.EFeatureListener;
import org.geotools.data.efeature.EFeatureStatus;
import org.geotools.data.efeature.EFeatureUtils;
import org.geotools.data.efeature.EStructureInfo;
import org.geotools.util.WeakHashSet;
/**
* {@link EFeatureInfo} cache class.
* <p>
* Each {@link EFeatureInfo} instance is uniquely identified by a key.
* <p>
* The key is constructed from the structure information and has
* following format:
* <pre>
* eKey := <eNsURI>/<eFolder>/<eFeature>
*
* where
*
* {@link EFeatureInfo#eNsURI() <eNsURI>} := is the {@link EPackage#getNsURI() EMF package namespace URI}
* {@link EFeatureInfo#eFolderName() <eFolder>} := is the {@link EFeatureFolderInfo#eName() folder name}
* {@link EFeatureInfo#eName() <eFeature>} := is the {@link EClass#getName() name} of the {@link EFeature} compatible {@link EClass implementation}
* </pre>
* <p>
* <b>NOTE</b>: {@link EFeatureInfo} instances are cached using hard
* references. Each {@link EFeatureInfo} instance is required to call
* {@link #detach(EFeatureInfo)} when the context is
* {@link EFeatureContextFactory#dispose(EFeatureContext) disposed}
* to allow the garbage collector to reclaim allocated memory.
* </p>
* @author kengu, 24. apr. 2011
*
*/
public final class EFeatureInfoCache {
/**
* {@link EFeatureListener} event type flag
*/
public static final int CACHE = 0;
/**
* Set of locally unique (in context) structure IDs (keys)
* mapped to {@link EFeatureInfo} instances
*/
private final HashMap<String, EFeatureInfo>
eCache = new HashMap<String, EFeatureInfo>();
/**
* Set of weakly referenced {@link EStructureInfo} listeners
*/
@SuppressWarnings("rawtypes")
protected WeakHashSet<EFeatureListener>
eListeners = new WeakHashSet<EFeatureListener>(
EFeatureListener.class);
/**
* Cache DENIED status
*/
private final EFeatureStatus DENIED =
EFeatureUtils.newStatus(this, EFeatureStatus.FAILURE, "FeatureInfo was not allowed to be cached", null);
/**
* Cache ALLOWED status
*/
private final EFeatureStatus ALLOWED =
EFeatureUtils.newStatus(this, EFeatureStatus.SUCCESS, "FeatureInfo was cached", null);
// -----------------------------------------------------
// Constructors
// -----------------------------------------------------
/**
* EFeatureInfo constructor.
*/
public EFeatureInfoCache(EFeatureListener<EFeatureInfoCache> eVoter) {
eListeners.add(eVoter);
}
// -----------------------------------------------------
// EFeatureInfoCache methods
// -----------------------------------------------------
/**
* Try to add given {@link EFeatureInfo} to cache.
* <p>
* Attachment is allowed iff the {@link EFeatureInfo}:
* <ol>
* <li>is not already {@link #contains(EFeatureInfo) contained}</li>
* <li>belongs to the same
* {@link EFeatureContext context} as this cache</li>
* </ol>
* </p>
* @return a {@link EFeatureStatus} instance
*/
public final EFeatureStatus attach(EFeatureInfo eInfo) {
if(contains(eInfo)) return DENIED;
EFeatureStatus eStatus = vote(eInfo);
if(eStatus.isSuccess()) {
eCache.put(createKey(eInfo),eInfo);
}
return eStatus;
}
/**
* Try to remove given {@link EFeatureInfo} from cache.
* <p>
* Detachment is allowed iff the {@link EFeatureInfo}:
* <ol>
* <li>is {@link #contains(EFeatureInfo) contained}</li>
* <li>belongs to the same {@link EFeatureContext context} as this cache</li>
* </ol>
* </p>
* @return a {@link EFeatureStatus} instance
*/
public final EFeatureStatus detach(EFeatureInfo eInfo) {
if(!contains(eInfo)) return DENIED;
EFeatureStatus eStatus = vote(eInfo);
if(eStatus.isSuccess()) eCache.remove(createKey(eInfo));
return eStatus;
}
/**
* Check if a {@link EFeatureInfo} instance is cached for
* the {@link EClass} defining given {@link EObject}
* <p>
* This is a convenience method calling {@link #contains(EClass)}
* </p>
* @param eObject - the {@link EObject} instance
* @return <code>true</code> if given instance is cached
* @see {@link #contains(EClass)}
*/
public final boolean contains(EObject eObject) {
return contains(createKey(eObject));
}
/**
* Check if a {@link EFeatureInfo} instance is cached for given {@link EClass}.
* <p>
* Since a {@link EClass}es only contains information
* about the {@link EPackage#getNsURI() eNsURI}
* and EFeature {@link EClass#getName() root name}, this method only checks
* for {@link EFeatureInfo structures} instances that does not belong to any folder.
* <p>
* <b>NOTE</b>: This only checks for <i>key equivalence</i>. If two keys are
* equal within the same {@link EFeatureContext}, the two structures must
* by definition be equal.
* </p>
* @param eClass - the {@link EClass} instance
* @return <code>true</code> if given instance is cached
*/
public final boolean contains(EClass eClass) {
return contains(createKey(eClass));
}
/**
* Check if given {@link EFeatureInfo} instance is cached.
* <p>
* <b>NOTE</b>: This only checks for <i>key equivalence</i>. If two keys are
* equal within the same {@link EFeatureContext}, the two structures must
* by definition be equal.
* </p>
* @param eInfo - the {@link EFeatureInfo} instance
* @return <code>true</code> if given instance is cached
*/
public final boolean contains(EFeatureInfo eInfo) {
return contains(createKey(eInfo));
}
/**
* Check if a {@link EFeatureInfo} instance with given key is cached.
* <p>
* <b>NOTE</b>: If two keys are equal within the same
* {@link EFeatureContext}, the two structures must by definition be
* equal.
* </p>
* @param eInfo - the {@link EFeatureInfo} instance
* @return <code>true</code> if instance with given key is cached
*/
public final boolean contains(String eKey) {
return eCache.containsKey(eKey);
}
/**
* Check if a {@link EFeatureInfo} instance with given key fragments is cached.
* <p>
* <b>NOTE</b>: If two keys are equal within the same
* {@link EFeatureContext}, the two structures must by definition be
* equal.
* </p>
* @param eInfo - the {@link EFeatureInfo} instance
* @return <code>true</code> if instance with given key fragments is cached
*/
public final boolean contains(String eNsURI, String eFolder, String eFeature) {
return eCache.containsKey(createKey(eNsURI, eFolder, eFeature));
}
/**
* Get the {@link EFeatureInfo#isPrototype() prototype}
* {@link EFeatureInfo} instance cached for the {@link EClass}
* defining given {@link EObject}.
* </p>
* @param eObject - the {@link EObject} instance
* @return a {@link EFeatureInfo} if found,<code>null</code> otherwise.
*/
public final EFeatureInfo get(EObject eObject) {
return get(eObject.eClass());
}
/**
* Get the {@link EFeatureInfo#isPrototype() prototype}
* {@link EFeatureInfo} instance cached for given {@link EClass}.
* </p>
* @param eFixture - the {@link EObject} instance
* @return a {@link EFeatureInfo} if found,<code>null</code> otherwise.
*/
public final EFeatureInfo get(EClass eClass) {
return get(eClass.getEPackage().getName(),eClass);
}
/**
*
* @param eFolder
* @param eClass
* @return
*/
public final EFeatureInfo get(String eFolder, EClass eClass) {
//
// Get name space URI string
//
String eNsURI = EFeatureUtils.eGetNsURI(eClass);
//
// Try to get cached structure
//
EFeatureInfo eInfo = get(eNsURI,eFolder,eClass.getName());
if(eInfo!=null && EcoreUtil.equals(eInfo.eClass(),eClass)) {
return eInfo;
}
//
// Not found
//
return null;
}
/**
*
* @param eKey
* @return
*/
public final EFeatureInfo get(String eKey) {
return eCache.get(eKey);
}
/**
*
* @param eNsURI
* @param eFolder
* @param eFeature
* @return
*/
public final EFeatureInfo get(String eNsURI, String eFolder, String eFeature) {
return get(createKey(eNsURI, eFolder, eFeature));
}
// -----------------------------------------------------
// Helper methods
// -----------------------------------------------------
public static final String createKey(EObject eObject) {
return createKey(eObject.eClass());
}
public static final String createKey(EClass eClass) {
String eNsURI = eClass.getEPackage().getNsURI();
return createKey(eNsURI,null,eClass.getName());
}
public static final String createKey(EFeatureInfo eInfo) {
return createKey(eInfo.eNsURI(),eInfo.eFolderName(),eInfo.eName());
}
private static final String createKey(String eNsURI, String eFolder, String eFeature) {
return eNsURI + "/" + eFolder + "/" + eFeature;
}
/**
* Check if all voters agree to cache given {@link EFeatureInfo structure}.
* @param eInfo - the structure
* @return {@link #ALLOWED} if all agree, {@link #DENIED} else.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private final EFeatureStatus vote(EFeatureInfo eInfo) {
for (EFeatureListener it : eListeners) {
if(!it.onChange(this, CACHE, null, eInfo)) {
return DENIED;
}
}
return ALLOWED;
}
}