package org.geotools.data.efeature;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.InternalEObject;
import org.geotools.data.efeature.internal.EFeatureDelegate;
import org.geotools.data.efeature.internal.EFeatureInfoCache;
/**
*
* @author kengu
*
*/
public class EFeatureContextInfo extends EStructureInfo<EFeatureContextInfo> {
/**
* Map of {@link EFeaturePackageInfo} instance
*/
protected Map<String, EFeaturePackageInfo>
ePackageMap = new HashMap<String, EFeaturePackageInfo>();
/**
* Cached {@link EFeatureInfoCache} for this context.
*/
protected EFeatureInfoCache eFeatureInfoCache;
/**
* {@link EFeatureInfoCache} voter
*/
protected EFeatureListener<EFeatureInfoCache> eFeatureInfoCacheVoter;
// -----------------------------------------------------
// EFeatureContextInfo methods
// -----------------------------------------------------
@Override
public boolean isAvailable() {
if(super.isAvailable()){
for (EFeaturePackageInfo it : synchronize(this).values()) {
if (it.isAvailable())
return true;
}
}
return false;
}
/**
* Get {@link EFeaturePackageInfo package information} mapped to
* {@link EFeaturePackageInfo#eNsURI() namespace URIs}
* <p>
* @throws IllegalStateException If {@link #isDisposed() disposed},
* or in{@link #isValid() valid}.
*/
public Map<String, EFeaturePackageInfo> ePackageMap() {
verify();
return synchronize(this);
}
/**
* Get {@link EFeaturePackageInfo} with given
* {@link EPackage#getNsURI() namespace URI}.
* @param eNsURI
* @throws IllegalArgumentException If not found
* @throws IllegalStateException If {@link #isDisposed() disposed}.
*/
public EFeaturePackageInfo eGetPackageInfo(String eNsURI) {
verify();
EFeaturePackageInfo eInfo = synchronize(this).get(eNsURI);
if(eInfo==null) {
throw new IllegalArgumentException(
"EFeaturePackageInfo [" + eNsURI + "] not found");
}
return eInfo;
}
/**
* Check if a {@link EFeatureDataStore} with with given
* {@link EPackage#getNsURI() namespace URI}.
* </p>
* @param eNsURI - EMF model {@link EPackage package} name space URI
* @throws IllegalStateException If {@link #isDisposed() disposed}.
*/
public boolean contains(String eNsURI) {
verify();
return synchronize(this).get(eNsURI)!=null;
}
/**
* 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 boolean contains(EObject eObject) {
return eFeatureInfoCache().contains(eObject);
}
/**
* Get the {@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 EFeatureInfo eGetFeatureInfo(EObject eObject) {
return eFeatureInfoCache().get(eObject);
}
/**
* Get the {@link EFeatureInfo} instance cached for given {@link EClass}.
* </p>
* @param eClass - the {@link EClass} instance
* @return a {@link EFeatureInfo} if found, <code>null</code> otherwise.
*/
public EFeatureInfo eGetFeatureInfo(EClass eClass) {
return eFeatureInfoCache().get(eClass);
}
/**
* Get the {@link EFeatureInfo} instance cached for
* {@link EFeatureFolderInfo folder} and {@link EClass class}.
* </p>
* @param eFolder - the {@link EFeatureFolderInfo} name
* @param eClass - the {@link EClass} instance
* @return a {@link EFeatureInfo} if found, <code>null</code> otherwise.
*/
public EFeatureInfo eGetFeatureInfo(String eFolder, EClass eClass) {
return eFeatureInfoCache().get(eFolder, eClass);
}
/**
* Check if a given {@link EFeatureInfo} exists in structure.
* <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 boolean contains(EFeatureInfo eInfo) {
return eFeatureInfoCache().contains(
eInfo.eNsURI,eInfo.eFolderName,eInfo.eName());
}
/**
* Check if a {@link EFeatureInfo#isPrototype() prototype}
* 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 EFeature prototypes (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 boolean contains(EClass eClass) {
return eFeatureInfoCache().contains(eClass);
}
/**
* Adapt given {@link EObject} into an EFeature.
* @param eObject - {@link EObject} to be adapted into this context
* @return new {@link EFeature} instance
* @throws IllegalArgumentException If adaption failed.
*/
public EFeature eAdapt(EObject eObject) throws IllegalArgumentException {
//
// Get context
//
EFeatureContext eContext = eContext();
//
// Get package
//
EPackage ePackage = eObject.eClass().getEPackage();
//
// Verify that package is contained in context
//
if(!eContext.contains(ePackage)) {
throw new IllegalArgumentException("EPackage [" + ePackage.getNsURI() + "] not found");
}
//
// Check if object is in cache
//
EFeatureInfo eStructure = eFeatureInfoCache().get(eObject);
if(eStructure==null) {
//
// Create structure in this context
//
eStructure = EFeatureInfo.create(eContext(), eObject, new EFeatureHints());
//
// Validate structure
//
eStructure.validate(ePackage, null);
}
//
// Adapt directly?
//
if(eObject instanceof EFeature) {
((EFeature)eObject).setStructure(eStructure);
return (EFeature)eObject;
}
//
// Create an delegate
//
return EFeatureDelegate.create(eStructure, (InternalEObject)eObject, true);
}
/**
* Adapt given {@link EFeatureInfo structure} to this {@link EFeatureContext#eContext() context}.
* @param eInfo - {@link EFeatureInfo} to be adapted into this context
* @param copy - if <code>true</code>, a copy of given {@link EFeatureInfo structure} is adapted.
* If <code>false</code>, the structure is claimed from it's context (moved into to this context).
* @return new {@link EFeatureInfo} instance if 'copy' is <code>true</code>, same otherwise
* @throws IllegalArgumentException If adaption failed.
*/
public EFeatureInfo eAdapt(EFeatureInfo eInfo, boolean copy) throws IllegalArgumentException {
//
// Get contexts
//
EFeatureContext eOldContext = eInfo.eContext(false);
EFeatureContext eNewContext = eContext(false);
//
// Get change flag
//
boolean eChanged = (eOldContext!=eNewContext);
//
// Update references?
//
if(eChanged) {
//
// Assume adaptation is required
//
boolean bAdapt = true;
//
// Check if cached instance exists with same key, use it if it
// has same context as this (which is should have)
//
EFeatureInfo eEqual = eFeatureInfoCache().get(EFeatureInfoCache.createKey(eInfo));
if(eEqual!=null) {
// -----------------------------------------------------------------
// Use already attached instance it instead of adapting given?
// -----------------------------------------------------------------
// This is an optimization, ensuring that adaptation only occurs
// when absolutely required. It leverages the fact that
// -----------------------------------------------------------------
if(eEqual.eEqualTo(eInfo)) {
return eEqual;
}
}
//
// Try to do the adaptation?
//
if(bAdapt) {
return doAdapt(eInfo, copy);
}
}
//
// Finished
//
return eInfo;
}
/**
* Validate structure against the {@link #eContext()}.
* @throws IllegalStateException If {@link #isDisposed() disposed}.
*/
public EFeatureStatus validate()
{
//
// 1) Verify that not disposed
//
if(isDisposed)
{
return failure(this, eContextID, "Is disposed");
}
//
// Invalidate structure
//
invalidate(false);
//
// 2) Verify that a EFeatureIDFactory instance is given
//
if(eContext(false).eIDFactory()==null)
{
return failure(this, eContextID, "No EFeatureIDFactory instance found");
}
//
// Synchronize with EFeatureContext counterpart.
//
EChange eChange = new EChange();
Map<String,EFeaturePackageInfo> ePackageMap = eChange.synchronize(this);
//
// 3) Validate all packages
//
for(EFeaturePackageInfo it : ePackageMap.values())
{
EFeatureStatus s;
if((s = it.validate()).isFailure())
{
return s;
}
}
//
// Commit changes to structure
//
eChange.commit(this);
//
// Finished
//
return structureIsValid(eContextID());
}
// -----------------------------------------------------
// EStructureInfo implementation
// -----------------------------------------------------
@Override
protected void doDispose() {
eContext = null;
ePackageMap.clear();
}
@Override
protected void doInvalidate(boolean deep) {
if (deep) {
for (EStructureInfo<?> it : ePackageMap.values()) {
it.doInvalidate(true);
}
}
}
@Override
protected EFeatureContextInfo eParentInfo(boolean checkIsValid) {
return this;
}
/**
* Adapt given {@link EFeatureInfo} to this {@link EFeatureContextInfo#eContext() context}
* <p>
* <b>NOTE</b>: This method attaches the {@link EFeatureInfo} instance
* with given {@link EFeatureContext context} {@link EFeatureContextInfo structure},
* and adds the {@link EClass#getEIDAttribute()} to the
* {@link EFeatureContext#eIDFactory()}.
* </p>
* @param eInfo - {@link EFeatureInfo} instance to adapt into this context
* @param copy - if <code>true</code>, copy {@link EFeatureInfo} instance
* into this context. If <code>false</code>, claim it (move into this context).
* @return new {@link EFeatureInfo} instance if copy, same otherwise
* @throws IllegalArgumentException If adaption failed.
*/
protected EFeatureInfo doAdapt(EFeatureInfo eInfo, boolean copy) {
//
// Clone EFeature structure?
//
if(copy) {
//
// Do a deep copy.
//
eInfo = new EFeatureInfo(eInfo,this);
} else {
//
// Adapt structure to this context
//
eInfo.eAdapt(this);
}
//
// ----------------------------------------------
// Attach to context
// ----------------------------------------------
// This is an important step because:
// 1) It makes the context structure aware of
// 2) It allows the structure validation method
// to map it to package structure (folder).
// 3) If it fails now, nothing is yet added to
// the context
// ----------------------------------------------
EFeatureStatus eStatus;
if((eStatus = doAttach(eInfo)).isFailure()) {
//
// Notify
//
throw new IllegalArgumentException("EFeatureInfo " +
eInfo.eName() + " could not be adapted: " + eStatus.getMessage());
}
//
// Get this context
//
EFeatureContext eContext = eContext(false);
//
// Get EClass implementing EFeature
//
EClass eClass = eInfo.eClass();
//
// Add ID attribute to ID factory?
//
if(!eContext.eIDFactory().creates(eInfo.eIDAttribute())) {
eContext.eIDFactory().add(eClass, eInfo.eIDAttribute());
}
//
// Add package if not contained by new context
//
EPackage ePackage = eInfo.eClass().getEPackage();
if(!eContext.contains(ePackage)) {
//
// Add package to the structure
//
eContext.eAdd(ePackage);
//
// ----------------------------------------------
// Validate the structure from context down
// ----------------------------------------------
// This is an important step because:
// 1) It will add create "eURI/eFolder"
// structures, which given EFeatureInfo
// instance is added to if possible.
// 2) More to come...
// ----------------------------------------------
validate();
}
//
// Finished
//
return eInfo;
}
/**
* Get {@link EFeatureInfoCache} instance.
* <p>
* This enables reuse of {@link EFeatureInfo}
* instances, and ensures that only one
* instance exists for each uniquely different
* {@link EFeatureInfo structure}.
* </p>
*/
protected EFeatureInfoCache eFeatureInfoCache() {
if(eFeatureInfoCache==null) {
eFeatureInfoCache = new EFeatureInfoCache(eFeatureInfoCacheVoter());
}
return eFeatureInfoCache;
}
/**
* Add {@link EFeatureInfo} to cache.
* @return an {@link EFeatureStatus} instance.
*/
protected EFeatureStatus doAttach(EFeatureInfo eInfo) {
return eFeatureInfoCache().attach(eInfo);
}
/**
* Detach {@link EFeatureInfo} from context.
* </p>
* @return <code>true</code> if released was allowed.
* @throws IllegalArgumentException If release unexpectedly fails.
*/
protected boolean doDetach(EFeatureInfo eInfo) {
//
// Try to release from cache.
//
EFeatureStatus eStatus = eFeatureInfoCache().detach(eInfo);
//
// Allowed?
//
if(eStatus.isSuccess()) {
//
// Get parent structure
//
EStructureInfo<?> eStructure = eInfo.eParentInfo(false);
//
// Found (not dangling)?
//
if(eStructure instanceof EFeatureFolderInfo) {
//
// Cast to EFeatureFolderInfo
//
EFeatureFolderInfo eFolderInfo = (EFeatureFolderInfo)eStructure;
//
// Remove object from folder's feature map
//
EFeatureInfo eRemoved = eFolderInfo.eFeatureInfoMap.remove(eInfo.eName());
if(eInfo!=eRemoved) {
//
// Successfully removed structure from context
//
return true;
}
//
// Remove object does not match given, add it again?
//
if(eRemoved!=null) {
eFolderInfo.eFeatureInfoMap.put(eInfo.eName(), eInfo);
}
//
// Break out, try to re-attach to cache...
//
} else {
//
// Successfully removed dangling structure from context
//
return true;
}
}
//
// Try to add again
//
eStatus = eFeatureInfoCache().attach(eInfo);
//
// Validate state
//
if(eStatus.isFailure()) {
throw new IllegalStateException("Release failed. EFeatureInfoCache is inconsistent.");
}
//
// Not allowed
//
return false;
}
protected EFeatureListener<EFeatureInfoCache> eFeatureInfoCacheVoter() {
if(eFeatureInfoCacheVoter==null) {
eFeatureInfoCacheVoter = new EFeatureListener<EFeatureInfoCache>() {
@Override
public boolean onChange(EFeatureInfoCache source, int property, Object oldValue,
Object newValue) {
//
// Only allow structures in the same context as this
// by EFeatureCacheInfo#cache(EFeatureInfo)
//
if(newValue instanceof EFeatureInfo) {
return eContext(false)==((EFeatureInfo)newValue).eContext(false);
}
return false;
}
};
}
return eFeatureInfoCacheVoter;
}
// -----------------------------------------------------
// Helper methods
// -----------------------------------------------------
/**
* Create a {@link EFeatureContext} instance from cached
* {@link EFeatureContext} instance.
* <p>
* @param eFactory - {@link EFeatureContextFactory} instance
* @param eContextID - {@link EFeatureContext} instance id.
* @param eHints - construction hints.
* @throws IllegalArgumentException If the instance for some reason could
* not be constructed.
* @see {@link EFeatureHints} - Hints about attribute mappings etc.
*/
protected static final EFeatureContextInfo create(
EFeatureContextFactory eFactory, String eContextID, EFeatureHints eHints)
throws IllegalArgumentException
{
EFeatureContext eContext = eFactory.eContext(eContextID);
EFeatureContextInfo eInfo = new EFeatureContextInfo();
eInfo.eHints = eHints;
eInfo.eContextID = eContextID;
eInfo.eFactory = new WeakReference<EFeatureContextFactory>(eFactory);
eInfo.eContext = new WeakReference<EFeatureContext>(eContext);
synchronize(eInfo);
return eInfo;
}
/**
* Synchronize given {@link EFeatureContextInfo structure}
* with it's {@link EFeatureContextFactory factory} counterpart.
* <p>
* This is required since {@link EPackage packages} are
* allowed to be added to and removed from the context.
* </p>
* @param eContextInfo - {@link EFeatureContextInfo structure}
* @return a {@link Map} from {@link EFeatureContextInfo#eContextID() eContexID} to
* {@link EFeatureContextInfo} instances.
*/
protected static final Map<String, EFeaturePackageInfo> synchronize(EFeatureContextInfo eContextInfo)
{
EChange eChange = new EChange();
eChange.synchronize(eContextInfo);
return eChange.commit(eContextInfo);
}
// -----------------------------------------------------
// Inner classes
// -----------------------------------------------------
/**
* @author kengu - 29. mai 2011
*/
private static class EChange {
/**
* Cached {@link EFeaturePackageInfo} changes
*/
private Map<String, EFeaturePackageInfo> eMap;
/**
* Synchronize given {@link EFeatureContextInfo structure}
* with it's {@link EFeatureContextFactory factory} counterpart.
* <p>
* This is required since {@link EPackage packages} are
* allowed to be added to and removed from the context.
* </p>
* @param eContextInfo - {@link EFeatureContextInfo structure}
* @return a {@link Map} from {@link EFeatureContextInfo#eContextID() eContexID} to
* {@link EFeatureContextInfo} instances.
*/
public final Map<String, EFeaturePackageInfo> synchronize(EFeatureContextInfo eContextInfo)
{
//
// Ensure thread safe access to package map
//
synchronized(eContextInfo.ePackageMap) {
//
// Get information
//
eMap = eContextInfo.ePackageMap;
//
// Get factory instance and context id
//
EFeatureContextFactory eFactory = eContextInfo.eFactory(false);
//
// Get context instance
//
EFeatureContext eContext = eContextInfo.eContext(false);
//
// Get current set of EPackage NsURIs
//
List<String> eNsURIs = eContext.eNsURIs();
// Initialize map of new store structures
//
Map<String, EFeaturePackageInfo>
eNewMap = new HashMap<String, EFeaturePackageInfo>(eNsURIs.size());
// Add all packages
//
for(String eNsURI : eNsURIs)
{
// Get structure information
//
EFeaturePackageInfo ePackageInfo = eMap.get(eNsURI);
// Not found or overwrite?
//
if(ePackageInfo==null)
{
// Create the package structure
//
ePackageInfo = EFeaturePackageInfo.create(
eFactory, eContextInfo, eNsURI);
/// Put to map of new structures
//
eNewMap.put(eNsURI, ePackageInfo);
}
}
// Remove disposed packages
//
eMap.keySet().retainAll(eNsURIs);
// Put new to current map
//
eMap.putAll(eNewMap);
// Finished
//
return Collections.unmodifiableMap(eMap);
}
}
/**
* Commit cached changes to context structure
*
* @param eContextInfo
*/
public Map<String, EFeaturePackageInfo> commit(EFeatureContextInfo eContextInfo) {
synchronized(eContextInfo.ePackageMap) {
eContextInfo.ePackageMap = eMap;
return eMap;
}
}
}
}