package org.geotools.data.efeature;
import static org.geotools.data.efeature.EFeatureConstants.DEFAULT_SRID;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.zip.Adler32;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.geotools.data.Transaction;
import org.geotools.data.efeature.impl.ESimpleFeatureImpl;
import org.geotools.data.efeature.internal.EFeatureVoidIDFactory;
import org.geotools.factory.Hints.Key;
import org.geotools.feature.NameImpl;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.referencing.CRS;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.InternationalString;
import com.vividsolutions.jts.geom.Geometry;
/**
*
* @author kengu
*
*/
public class EFeatureInfo extends EStructureInfo<EStructureInfo<?>> {
/**
* Mutable property: {@link #getSRID()} </p>
*
* @see {@link #setSRID(String)} - invalidates all {@link EFeatureGeometryInfo#getDescriptor()
* geometry descriptors}
* @see {@link EStructureInfo#addListener(EStructureInfoListener)} - listen for SRID changes.
*/
public static final int SRID = EFeaturePackage.EFEATURE__SRID;
/**
* Mutable property: {@link #eGetDefaultGeometryName()} </p>
*
* @see {@link #eSetDefaultGeometryName(String)}
* @see {@link EStructureInfo#addListener(EStructureInfoListener)} - listen for default geometry
* name changes.
*/
public static final int DEFAULT_GEOMETRY_NAME = 2;
/**
* @see #eUID()
*/
protected Long eUID;
protected String eNsURI;
protected String eFolderName;
protected String eClassName;
protected String eReferenceName;
protected String srid = DEFAULT_SRID;
protected CoordinateReferenceSystem crs;
protected boolean eIsRoot = true;
protected String eIDAttributeName;
protected String eSRIDAttributeName;
protected String eDefaultAttributeName;
protected String eDefaultGeometryName;
protected WeakReference<EClass> eClass;
protected WeakReference<EClass> eParentClass;
protected WeakReference<EReference> eReference;
protected SimpleFeatureType featureType;
protected SimpleFeatureBuilder builder;
/**
* Map of name to all geometry {@link EAttribute}s.
* (optimization)
*/
protected Map<String,EAttribute> eGeometryMap;
/**
* Map of name to all non-geometry {@link EAttribute}s.
* (optimization)
*/
protected Map<String,EAttribute> eAttributeMap;
/**
* Map of name to all non-geometry {@link EAttribute}s.
* (optimization)
*/
protected Map<String,EAttribute> eAllAttributeMap;
/**
* Maps {@link #eClass()} attributes to attributes in {@link EFeature}.
*/
protected Map<EAttribute,EAttribute> eMappingMap = new HashMap<EAttribute, EAttribute>();
/**
* {@link EAttribute} id to non-geometry {@link EFeatureAttributeInfo} instance {@link Map}
*/
protected Map<String, EFeatureAttributeInfo> eAttributeInfoMap;
/**
* {@link EFeatureGeometry} id to {@link EFeatureGeometryInfo} instance {@link Map}
*/
public Map<String, EFeatureGeometryInfo> eGeometryInfoMap;
/**
* {@link EAttribute} id to all {@link EFeatureAttributeInfo} instances {@link Map}
* (optimization)
*/
protected Map<String, EFeatureAttributeInfo> eAllAttributeInfoMap;
// -----------------------------------------------------
// Constructors
// -----------------------------------------------------
/**
* Default constructor
*/
protected EFeatureInfo() { /*NOP*/ }
/**
* {@link EFeatureInfo} copy constructor.
* <p>
* This method copies the structure into given context.
* <p>
* If the parent structure is not found in the new context,
* the new instance become a
* {@link EStructureInfo#isPrototype() prototype}.
* </p>
* <b>NOTE</b>: This method only adds a one-way reference from
* copied instance to given {@link EFeatureContext context}.
* No reference is added from the context to this feature. It does not
* attach the {@link EFeatureInfo} instance with given
* {@link EFeatureContext context} {@link EFeatureContextInfo structure},
* nor adds the {@link EClass#getEIDAttribute()} to the
* {@link EFeatureContext#eIDFactory()}. This must be done manually.
* </p>
* @param eInfo - copy from this {@link EFeatureInfo} instance
* @param eContextInfo - copy into this context
* @see {@link EFeatureContextInfo#doAdapt(EFeatureInfo, boolean)}
*/
protected EFeatureInfo(EFeatureInfo eInfo, EFeatureContextInfo eContextInfo) {
//
// Forward (copies context, state and hints)
//
super(eInfo, eContextInfo);
//
// Mark as being structural equal to given structure
//
this.eUID = eInfo.eUID;
//
// Copy context path
//
this.eNsURI = eInfo.eNsURI;
this.eFolderName = eInfo.eFolderName;
//
// Copy EClass information
//
this.eClassName = eInfo.eClassName;
this.eClass = new WeakReference<EClass>(eInfo.eClass());
//
// Copy parent EClass information (folder is an EClass)
//
this.eReferenceName = eInfo.eReferenceName;
this.eReference = new WeakReference<EReference>(eInfo.eReference());
this.eParentClass = new WeakReference<EClass>(eInfo.eParentClass());
//
// Copy ID attribute information
//
this.eIDAttributeName = eInfo.eIDAttributeName;
//
// Copy SRID attribute information
//
this.eSRIDAttributeName = eInfo.eSRIDAttributeName;
//
// Copy default geometry attribute information
//
this.eDefaultAttributeName = eInfo.eDefaultAttributeName;
//
// Copy other attributes
//
this.isAvailable = eInfo.isAvailable;
//
// Prepare new hash maps
//
this.eGeometryInfoMap = EFeatureUtils.newMap(eInfo.eGeometryInfoMap);
this.eAttributeInfoMap = EFeatureUtils.newMap(eInfo.eAttributeInfoMap);
//
// Loop over all attribute structures, copy them and add to maps
//
for(EFeatureAttributeInfo it : eInfo.eAttributeInfoMap.values()) {
it = new EFeatureAttributeInfo(it,this);
eAttributeInfoMap.put(it.eName,it);
}
for(EFeatureGeometryInfo it : eInfo.eGeometryInfoMap.values()) {
it = new EFeatureGeometryInfo(it,this);
eGeometryInfoMap.put(it.eName,it);
}
//
// Pass mappings directly (is copied when made immutable below)
//
eMappingMap = eInfo.eMappingMap;
//
// Optimize structure lookup methods
//
eOptimize(this);
//
// Make structure immutable (it is allowed to not be modified)
//
eImmutable(this);
}
/**
* Unique ID which enables efficient <i>{@link EFeatureInfo structure}
* equivalence</i> checking.
* <p>
* <b>NOTE</b>: This equivalence is not the same as
* <i>{@link Object#equals(Object) object equivalence}</i>,
* it only states that the structure is equal. Since the
* structure is immutable, a single unique value is enough to
* compare two structures for equivalence. Mutable fields like
* {@link #getSRID()} is not part of the structure. It can therefore
* not be assumed that if two structure have the same {@link #eUID}, then
* the {@link #getSRID()} are also the same.
* </p>
* @throws IllegalStateException If {@link EStructureInfo#isDisposed() disposed} or
* {@link EStructureInfo#isValid() not valid}.
*/
public Long eUID() {
verify(true);
return eUID;
}
@Override
public boolean isAvailable() {
// --------------------------------------------------------
// Is available and is a prototype or has at least
// one geometry defined?
// --------------------------------------------------------
// Prototypes must be available since prototypes are used
// in queries matching all EFeature implementations. If
// prototypes are unavailable, getFeatureType() returns
// null, which breaks the construction of query instances.
//
// See EFeatureUtils#toEFeatureQuery(TreeIterator,Filter)
// --------------------------------------------------------
//
return super.isAvailable() && (isPrototype() || !eGeometryInfoMap.isEmpty());
}
/**
* Check if {@link EFeature} is a root.
* <p>
* A {@link EFeature} roots are not referenced (contained).
* </p>
*
* @return <code>true</code> if EObject root.
*/
public boolean isRoot() {
return eIsRoot;
}
/**
* Get {@link Feature} name.
* <p>
* If this {@link #isRoot() is a root}, the {@link EClass} name is returned, else the name of
* the {@link EObject#eContainmentFeature() containment reference} to this {@link EFeature} is
* returned.
* </p>
*
* @return the {@link EFeature} name.
*/
public String eName() {
return eIsRoot ? eClassName : eReferenceName;
}
public String eNsURI() {
return eNsURI;
}
public String eFolderName() {
return eFolderName;
}
/**
* Get EFeature {@link EClass}
*/
public EClass eClass() {
return (eClass!=null ? eClass.get() : null);
}
/**
* Get EFeature {@link EClass} name
*/
public String eClassName() {
return eClassName;
}
/**
* Get name of {@link EReference} referencing this {@link EFeature}.
* <p>
* This is only set if the {@link EFeature} is not {@link #isRoot() a root}.
* </p>
*/
public EReference eReference() {
return (eReference!=null ? eReference.get() : null);
}
/**
* Get name of {@link EReference} referencing this {@link EFeature}.
* <p>
* This is only set if the {@link EFeature} is not {@link #isRoot() a root}.
* </p>
*/
public String eReferenceName() {
return eReferenceName;
}
/**
* Get this {@link EClass parent} class.
*/
public EClass eParentClass() {
return eParentClass!=null ? eParentClass.get() : null;
}
/**
* Get name of EFeature parent {@link EClass}
*/
public String eParentClassName() {
return eParentClass != null ? eParentClass().getName() : null;
}
/**
* Get {@link EStructureInfo structure parent} instance.
*/
@Override
protected EStructureInfo<?> eParentInfo(boolean checkIsValid) {
EFeaturePackageInfo eInfo = eContext(checkIsValid).
eStructure().eGetPackageInfo(eNsURI);
if(eInfo!=null) {
return eInfo.eGetFolderInfo(eFolderName);
}
return eContext(checkIsValid).eStructure();
}
/**
* Get ID {@link EAttribute}
*
* @return an {@link EAttribute} instance
*/
public EAttribute eIDAttribute() {
return eGetAttribute(eIDAttributeName);
}
/**
* Get SRID {@link EAttribute}
*
* @return an {@link EAttribute} instance
*/
public EAttribute eSRIDAttribute() {
return eGetAttribute(eSRIDAttributeName);
}
/**
* Get default {@link EFeatureGeometry} attribute.
* <p>
*
* @return a {@link EAttribute} instance, or <code>null</code> if no {@link EFeatureGeometry
* geometries} are defined.
*/
public EAttribute eDefaultGeometry() {
return eGetAttribute(eGetDefaultGeometryName());
}
public String eGetDefaultGeometryName() {
//
// 1) Try hints first
//
//
if (eDefaultGeometryName == null) {
for(String it : eGetDefaultGeometryNames(eHints)) {
if(eGeometryInfoMap.containsKey(it)) {
eDefaultGeometryName = it;
}
}
}
// 2) If no hints was found, try to find a geometry attribute
//
if (eDefaultGeometryName == null) {
String eFirstFound = null;
for (Entry<String, EFeatureGeometryInfo> it : eGeometryInfoMap.entrySet()) {
if (eFirstFound == null) {
eFirstFound = it.getKey();
}
if (it.getValue().isDefaultGeometry) {
eDefaultGeometryName = it.getKey();
break;
}
}
if (eDefaultGeometryName == null) {
if( !(eFirstFound==null || eFirstFound.length()==0) ) {
eGeometryInfoMap.get(eFirstFound).isDefaultGeometry = true;
} else {
eFirstFound = EFeatureConstants.DEFAULT_GEOMETRY_NAME;
}
eDefaultGeometryName = eFirstFound;
}
}
return eDefaultGeometryName;
}
public EFeatureGeometryInfo eSetDefaultGeometryName(String eNewName) {
String eOldName = eGetDefaultGeometryName();
if (!(eNewName == null || eNewName.length() == 0 && !eNewName.equals(eOldName))) {
EFeatureGeometryInfo eOldInfo = eGeometryInfoMap.get(eOldName);
EFeatureGeometryInfo eNewInfo = eGeometryInfoMap.get(eNewName);
if (eNewInfo != null) {
//
// Update
//
eDefaultGeometryName = eNewName;
//
// Keep structures in-sync
//
if (eOldInfo != null) {
eOldInfo.setIsDefaultGeometry(false);
}
eNewInfo.setIsDefaultGeometry(true);
//
// Notify change of mutable property
//
fireOnChange(DEFAULT_GEOMETRY_NAME, eOldName, eNewName);
//
// Change completed
//
return eNewInfo;
}
}
// No change
//
return null;
}
public EFeatureGeometryInfo eDefaultGeometryInfo() {
return eGeometryInfoMap.get(eDefaultGeometryName);
}
/**
* Get spatial reference ID.
* @return a SRID
* @see {@link CRS} - utility class for converting SRIDs into a
* {@link CoordinateReferenceSystem}
*/
public String getSRID() {
return srid;
}
/**
* Set spatial reference ID for all {@link EFeature} instances
*/
public CoordinateReferenceSystem setSRID(String newSRID) throws IllegalArgumentException {
final String oldSRID = getSRID();
if (isAvailable() && !(newSRID == null || newSRID.length() == 0)
&& !newSRID.equals(oldSRID)) {
// Update Spatial reference id
//
this.srid = newSRID;
// Try create new CRS from SRID
//
try {
CRSCache.decode(this, true);
} catch (Exception e) {
// Rollback to old SRID
//
srid = oldSRID;
// Throw expected exception
//
throw new IllegalArgumentException("Failed to decode SRID " + "'" + srid + "'", e);
}
// Keep structures in-sync
// (use this.srid since decodeCRS may enforce default value)
//
setSRID(oldSRID, this.srid, this.crs);
// Change completed
//
return this.crs;
}
// No change
//
return null;
}
protected final void setSRID(String oldSRID, String newSRID, CoordinateReferenceSystem newCRS) {
// Update Spatial reference id
//
this.srid = newSRID;
// Keep structures in-sync
//
for (EFeatureGeometryInfo it : eGeometryInfoMap.values()) {
it.setSRID(newSRID, newCRS);
}
// Notify listener of changes mutable property
//
fireOnChange(SRID, oldSRID, newSRID);
}
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
if(crs==null) {
try {
crs = CRSCache.decode(this, true);
} catch (Exception e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
throw new RuntimeException("Failed to decode SRID",e);
}
}
return crs;
}
public SimpleFeatureType getFeatureType() {
if (isAvailable() && featureType == null) {
featureType = new InnerSimpleFeatureTypeImpl();
}
return featureType;
}
public EAttribute eGetAttribute(String eName) {
EFeatureAttributeInfo eInfo = eGetAttributeInfo(eName, true);
if (eInfo != null) {
return eInfo.eAttribute();
}
return null;
}
public EFeatureAttributeInfo eGetAttributeInfo(String eName, boolean all) {
EFeatureAttributeInfo eInfo = eAttributeInfoMap.get(eName);
if (eInfo == null && all) {
return eGetGeometryInfo(eName);
}
return eInfo;
}
public EFeatureGeometryInfo eGetGeometryInfo(String eName) {
return eGeometryInfoMap.get(eName);
}
public boolean isGeometry(String eName) {
return eGeometryInfoMap.containsKey(eName);
}
public boolean isDefaultGeometry(String eName) {
return eDefaultGeometryName != null ? eDefaultGeometryName.equals(eName) : false;
}
public List<EAttribute> eGetAttributeList(boolean all) {
Collection<EAttribute> eItems =
(all ? eAllAttributeMap.values() : eAttributeMap.values());
List<EAttribute> eList = new ArrayList<EAttribute>(eItems);
return Collections.unmodifiableList(eList);
}
public List<EAttribute> eGetGeometryList() {
Collection<EAttribute> eItems = eGeometryMap.values();
List<EAttribute> eList = new ArrayList<EAttribute>(eItems);
return Collections.unmodifiableList(eList);
}
public Map<String, EAttribute> eGetAttributeMap(boolean all) {
return (all ? eAttributeMap : eAllAttributeMap);
}
public Map<String, EAttribute> eGetAttributeMap(String[] eNames, boolean all) {
Map<String, EAttribute> eFoundMap = new HashMap<String, EAttribute>();
Map<String, EAttribute> eAttrMap = EFeatureUtils.eGetAttributeMap(eClass());
for (String eName : eNames) {
if (eAttrMap.containsKey(eName)
&& (eAttributeInfoMap.containsKey(eName) || all
&& eGeometryInfoMap.containsKey(eName))) {
eFoundMap.put(eName, eAttrMap.get(eName));
}
}
return Collections.unmodifiableMap(eFoundMap);
}
public List<EFeatureAttributeInfo> eGetAttributeInfoList(boolean all) {
Collection<EFeatureAttributeInfo> eItems =
(all ? eAllAttributeInfoMap.values() : eAttributeInfoMap.values());
List<EFeatureAttributeInfo> eList = new ArrayList<EFeatureAttributeInfo>(eItems);
return Collections.unmodifiableList(eList);
}
public List<EFeatureGeometryInfo> eGetGeometryInfoList() {
Collection<EFeatureGeometryInfo> eItems = eGeometryInfoMap.values();
List<EFeatureGeometryInfo> eList = new ArrayList<EFeatureGeometryInfo>(eItems);
return Collections.unmodifiableList(eList);
}
public Map<String, EFeatureAttributeInfo> eGetAttributeInfoMap(boolean all) {
return (all ? eAllAttributeInfoMap : eAttributeInfoMap);
}
public Map<String, EFeatureGeometryInfo> eGetGeometryInfoMap() {
return Collections.unmodifiableMap(eGeometryInfoMap);
}
/**
* Check if a mapping for given {@link EStructuralFeature} exists
* @param eAttribute - mapped attribute
* @return an <code>true</code> if a mapping is found
*/
public final boolean eMappingExists(EStructuralFeature eFeature) {
return eMappingMap.containsKey(eFeature);
}
/**
* Get {@link EAttribute} mapped to given {@link EAttribute}
* @param eAttribute - mapped attribute
* @return an {@link EAttribute} if found.
* @throws IllegalArgumentException If given attribute is not
* mapping to anything in this structure
*/
public final EAttribute eMappedTo(EAttribute eAttribute)
throws IllegalArgumentException {
//
// Get mapping from given attribute to implementation
//
eAttribute = eMappingMap.get(eAttribute);
//
// Validate
//
if(eAttribute==null) {
throw new IllegalArgumentException("EAttribute " +
eAttribute + " is not mapped by " + this);
}
//
// Finished
//
return eAttribute;
}
/**
* Validate given {@link EObject object} against this structure.
* <p>
*
* </p>
* @param eObject - the {@link EObject} instance
*
* @return <code>true</code> if valid, <code>false</code> otherwise.
*/
public EFeatureStatus validate(EObject eObject) {
//
// Prepare
//
EClass eClass = eObject.eClass();
EPackage ePackage = eClass.getEPackage();
//
// Get parent class?
//
EClass eParent = eIsRoot ? null : EFeatureUtils.eGetContainingClass(eObject);
//
// Forward
//
return validate(ePackage,eParent);
}
/**
* Validate {@link EFeature} structure.
* <p>
* If this {@link EFeature} is not {@link #isRoot() a root}, given {@link EClass} instance is
* validated against the {@link #eReference() reference name} to ensure that such a reference
* exist.
* </p>
* @param ePackage - given {@link EPackage} instance
* @param eParent - {@link EClass} of given {@link EFeature} parent instance
* @return <code>true</code> if valid, <code>false</code> otherwise.
*/
public EFeatureStatus validate(EPackage ePackage, EClass eParent) {
//
// Initialize
//
EFeatureStatus s;
//
// Invalidate structure
//
doInvalidate(false);
//
// 1) Validate reference?
//
EReference eReference = null;
if (!eIsRoot) {
if (eParent == null) {
return failure(this,
eName(), "Feature mismatch: Flagged as !eIsRoot() but parent class is null");
}
eReference = EFeatureUtils.eGetReference(eParent, this.eReferenceName);
if (eReference == null) {
return failure(this, eName(), "Feature mismatch: EReference " + this.eReferenceName
+ " not found");
}
}
//
// 2) Validate EClass name
//
EClassifier eClassifier = ePackage.getEClassifier(eClassName);
if (!(eClassifier instanceof EClass)) {
return failure(this, eName(), "Feature mismatch: EClass " + eClassName + " not found");
}
EClass eClass = ((EClass) eClassifier);
//
// Get list of all EAttributes
//
Map<String, EAttribute> eAttrMap = EFeatureUtils.eGetAttributeMap(eClass);
//
// 3) Validate ID attribute
//
EAttribute eAttribute = eClass.getEIDAttribute();
if (eAttribute != null) {
//
// Get attribute name
//
String eName = eAttribute.getName();
//
// Get EAttribute ID instance
//
EFeatureAttributeInfo eInfo = eGetAttributeInfo(eName, true);
//
// Validate attribute
//
if(!(s = eInfo.validate(true, eAttribute)).isSuccess()) {
//
// Given EClass specified ID attribute was not a valid
//
return s;
}
} else if (!eAttrMap.containsKey(eIDAttributeName)) {
return failure(this, eName(), "Feature mismatch: ID EAttribute '" + eIDAttributeName + "' not found");
}
//
// EClass specified ID attribute is already validated
//
eAttrMap.remove(eAttribute);
//
// 4) Validate attributes
//
for (EFeatureAttributeInfo it : eAttributeInfoMap.values()) {
eAttribute = eAttrMap.get(it.eName);
if (eAttribute == null) {
return failure(this, eName(), "Feature mismatch: EAttribute " + it.eName
+ " not found in EClass");
}
boolean isID = eAttribute.getName().equals(eIDAttributeName);
if (!(s = it.validate(isID, eAttribute)).isSuccess()) {
return s;
}
}
//
// 5) Validate geometries
//
for (EFeatureGeometryInfo it : eGeometryInfoMap.values()) {
eAttribute = eAttrMap.get(it.eName);
if (eAttribute == null) {
return failure(this, eName(), "Feature mismatch: EGeometry " + it.eName
+ " not found in EClass");
}
boolean isID = eAttribute.getName().equals(eIDAttributeName);
if (!(s = it.validate(isID, eAttribute)).isSuccess()) {
return s;
}
}
//
// Store valid state
//
this.isValid = true;
//
// Store as weak references. This prevents memory leakage
// when doDispose() is not called explicitly.
//
this.eParentClass = new WeakReference<EClass>(eParent);
this.eReference = new WeakReference<EReference>(eReference);
//
// Create structure checksum
//
eUID = checksum();
//
// Confirm that structure is valid
//
return structureIsValid(eName());
}
/**
* Validate feature against this structure.
*
* @param feature - feature to be validated
* @return <code>true</code> if valid.
*/
public EFeatureStatus validate(Feature feature) {
//
// TODO: Does this work?
//
if (!getFeatureType().equals(feature.getType())) {
return featureIsInvalid();
}
// Confirm that feature is valid
//
return featureIsValid();
}
/**
* Check if given {@link EFeatureInfo structure} is equal to this
* @param eInfo - given structure
* @return <code>true</code> if structures are equal
*/
public boolean eEqualTo(EFeatureInfo eInfo) {
return eEqualTo(eInfo,true);
}
/**
* Get {@link EFeature#getID() ID} of given EObject
* @param eObject - an {@link EObject} instance
*/
public String toID(EObject eObject) {
return (String)eObject.eGet(eIDAttribute());
}
/**
* Create {@link ESimpleFeatureImpl} instance from given object.
* @param eObject - an {@link EObject} instance
* @param transaction TODO
*/
public ESimpleFeatureImpl toFeature(EObject eObject, Transaction transaction) {
return new ESimpleFeatureImpl(this,eObject, transaction);
}
public Map<String, Integer> eGetAttributeIndex() {
return ((InnerSimpleFeatureTypeImpl)getFeatureType()).index;
}
public EObject eNewInstance() {
return EcoreUtil.create(eClass());
}
// -----------------------------------------------------
// Object methods
// -----------------------------------------------------
@Override
public String toString() {
return getClass().getSimpleName() + "[" + eContextID + "]";
}
// -----------------------------------------------------
// EStructureInfo implementation
// -----------------------------------------------------
@Override
protected void doInvalidate(boolean deep) {
//
// Dispose weak references
//
this.eParentClass = null;
this.eReference = null;
//
// Do a deep invalidation?
//
if (deep) {
for (EStructureInfo<?> it : eGetAttributeInfoList(true)) {
it.doInvalidate(true);
}
}
}
@Override
protected void doAdapt() {
//
// Update ALL attributes (includes geometry structures)
//
for(EFeatureAttributeInfo it : eGetAttributeInfoList(true)) {
it.eAdapt(this);
}
}
@Override
protected void doDetach() {
//
// Detach from old context
//
eContext(false).eStructure().doDetach(this);
}
@Override
protected void doDispose() {
//
// Remove from EFeatureInfo cache in given context
// (required to prevent memory leakage)
//
eContext(false).eStructure().eFeatureInfoCache.detach(this);
//
// Forward to sub-structures
//
for (EFeatureAttributeInfo it : eGetAttributeInfoList(true)) {
it.dispose();
}
//
// Clear maps
//
this.eGeometryMap.clear();
this.eAttributeMap.clear();
this.eAllAttributeMap.clear();
this.eGeometryInfoMap.clear();
this.eAttributeInfoMap.clear();
this.eAllAttributeInfoMap.clear();
//
// Clear cached references to allow garbage collection
//
this.crs = null;
this.eClass = null;
this.eParentClass = null;
this.eReference = null;
this.featureType = null;
this.eGeometryMap = null;
this.eAttributeMap = null;
this.eAllAttributeMap = null;
this.eGeometryInfoMap = null;
this.eAttributeInfoMap = null;
this.eAllAttributeInfoMap = null;
}
/**
* Check if given {@link EFeatureInfo structure} is equal to this
* @param eInfo - given structure
* @return <code>true</code> if structures are equal
*/
protected boolean eEqualTo(EFeatureInfo eInfo, boolean checkIfValid) {
verify(checkIfValid);
return !(eUID==null || eInfo==null) ? eUID.equals(eInfo.eUID) : false;
}
/**
* Calculate structure checksum.
* <p>
* This method calculates the checksum by repeatedly applying
* {@link #eFeatureUID(EStructuralFeature)} on every
* {@link EStructuralFeature} in this structure, concatenating
* them together and finally calculating a checksum from the
* concatenated string of unique feature IDs.
* @return a {@link Adler32} checksum for given structure
*/
protected final Long checksum() {
//
// Prepare array of IDs
//
List<String> eIDs = new ArrayList<String>();
//
// Add EFeature ID EAttribute feature ID
//
eIDs.add(eFeatureUID(eIDAttribute()));
//
// Add all EAttribute feature IDs
//
for(EAttribute it : eGetAttributeMap(true).values()) {
eIDs.add(eFeatureUID(it));
}
//
// Add reference?
//
if(!isRoot()){
eIDs.add(eFeatureUID(eReference()));
}
//
// Sort array (structure is unordered, order it to ensure equal checksum)
//
Collections.sort(eIDs);
//
// Calculate checksum (Adler32 is much faster then CRC-32 and almost as accurate)
//
Adler32 a = new Adler32();
a.update(Arrays.toString(eIDs.toArray()).toString().getBytes());
//
// Finished
//
return a.getValue();
}
/**
* Create EFeature unique ID from given {@link EStructuralFeature feature}
* @param eEFeature {@link EStructuralFeature} instance
* @return a unique ID
*/
private static final String eFeatureUID(EStructuralFeature eEFeature) {
EClass eClass = eEFeature.getEContainingClass();
return EFeatureUtils.eGetNsURI(eClass)
+ "/" + eClass.getClassifierID()
+ "/" + eEFeature.getFeatureID();
}
// -----------------------------------------------------
// Public construction methods
// -----------------------------------------------------
/**
* Create EFeature {@link EFeatureInfo structure} from
* given EMF {@link EObject object} when the context is known.
* <p>
* </p>
* @param eContext - {@link EFeatureContext context} of given object
* @param eFeature - {@link EFeature} or EFeature compatible EMF {@link EObject object}.
* @param eHints - {@link EFeatureInfo} construction hints (ID attribute name etc.)
* @throws IllegalArgumentException If anything goes wrong.
* @see {@link EFeatureHints}
*/
public static EFeatureInfo create(EFeatureContext eContext,
EObject eFeature, EFeatureHints eHints) throws IllegalArgumentException {
//
// Get structure information
//
EClass eClass = (eFeature instanceof EClass ? (EClass)eFeature : eFeature.eClass());
EPackage ePackage = eClass.getEPackage();
String eNsURI = ePackage.getNsURI();
//
// Check cache first
//
EFeatureInfo eInfo = eContext.eStructure().eGetFeatureInfo(eClass);
//
// Does not exist?
//
if(eInfo == null) {
//
// Create new instance
//
eInfo = new EFeatureInfo();
//
// Set context
//
eInfo.eContextID = eContext.eContextID();
eInfo.eContext = new WeakReference<EFeatureContext>(eContext);
eInfo.eFactory = new WeakReference<EFeatureContextFactory>(eContext.eContextFactory());
//
// Set construction hints
//
eInfo.eHints = (eHints != null ? eHints : new EFeatureHints());
//
// Get SRID from hints
//
eInfo.srid = eGetSRID(eInfo.eHints);
//
// Defined the structure using default values
//
eInfo = define(eInfo, eContext, eNsURI, ePackage.getName(), eClass);
}
//
// Finished
//
return eInfo;
}
// -----------------------------------------------------
// Static Helper methods
// -----------------------------------------------------
/**
* Create EFeature {@link EFeatureInfo structure} from
* given EMF {@link EClass class} in given {@link EFeatureFolderInfo folder}.
* <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 eFolderInfo - the {@link EFeatureFolderInfo folder} instance
* @param eClass - {@link EFeature} or EFeature compatible EMF {@link EClass class} .
* @return a new {@link EFeatureInfo} instance if not defined,
* or the existing structure if defined
*/
protected static EFeatureInfo create(
EFeatureFolderInfo eFolderInfo, EClass eClass) {
//
// Get structure information
//
String eNsURI = eFolderInfo.eNsURI;
String eFolderName = eFolderInfo.eName;
//
// Get context information
//
EFeatureContext eContext = eFolderInfo.eContext(false);
//
// Check cache first
//
EFeatureInfo eInfo = eContext.eStructure().eGetFeatureInfo(eFolderName, eClass);
//
// Was not cached?
//
if (eInfo == null) {
//
// Create new instance
//
eInfo = new EFeatureInfo();
//
// Set context
//
eInfo.eFactory = eFolderInfo.eFactory;
eInfo.eContext = eFolderInfo.eContext;
eInfo.eContextID = eContext.eContextID();
//
// Set construction hints
//
eInfo.eHints = (eFolderInfo.eHints != null ? eFolderInfo.eHints : new EFeatureHints());
//
// Get SRID from hints
//
eInfo.srid = eGetSRID(eInfo.eHints);
//
// Forward
//
eInfo = define(eInfo, eContext, eNsURI, eFolderName, eClass);
}
//
// Finished
//
return eInfo;
}
// -----------------------------------------------------
// Static Helper methods
// -----------------------------------------------------
/**
* Define EFeature {@link EFeatureInfo structure} from given information.
* <p>
* <b>NOTE</b>: This method assumes that the {@link EFeatureContext} and
* {@link EFeatureHints} are already set. This method also attaches the
* {@link EFeatureInfo} instance with given context
* {@link EFeatureContextInfo structure}, and adds the
* {@link EClass#getEIDAttribute()} to the {@link EFeatureContext#eIDFactory()}.
* </p>
* @param eInfo
* @param eContext
* @param eNsURI
* @param eFolderName - the {@link EFeatureFolderInfo folder} instance
* @param eClass - {@link EFeature} or EFeature compatible EMF {@link EClass class} .
* @return a {@link EFeatureInfo} instance
* @throws IllegalArgumentException If anything goes wrong.
*/
private static EFeatureInfo define(
EFeatureInfo eInfo, EFeatureContext eContext,
String eNsURI, String eFolderName, EClass eClass)
throws IllegalArgumentException {
//
// Set context path
//
eInfo.eNsURI = eNsURI;
eInfo.eFolderName = eFolderName;
//
// Set EClass implementing EFeature
//
eInfo.eClassName = eClass.getName();
eInfo.eClass = new WeakReference<EClass>(eClass);
//
// Set ID attribute
//
EAttribute eIDAttribute = eGetIDAttribute(eClass,eInfo.eHints);
eInfo.eIDAttributeName = eIDAttribute.getName();
eInfo.eMappingMap.put(EFeaturePackage.eINSTANCE.getEFeature_ID(),eIDAttribute);
//
// Set SRID attribute
//
EAttribute eSRIDAttribute = eGetSRIDAttribute(eClass, eInfo.eHints);
eInfo.eSRIDAttributeName = eSRIDAttribute.getName();
eInfo.eMappingMap.put(EFeaturePackage.eINSTANCE.getEFeature_SRID(),eSRIDAttribute);
//
// Set default geometry attribute
//
EAttribute eDefaultAttribute = eGetDefaultAttribute(eClass, eInfo.eHints);
eInfo.eDefaultAttributeName = eDefaultAttribute.getName();
eInfo.eMappingMap.put(EFeaturePackage.eINSTANCE.getEFeature_Default(),eDefaultAttribute);
//
// Create EFeature attribute structures
//
EList<EAttribute> eAttributes = eClass.getEAllAttributes();
eInfo.eAttributeInfoMap = attributes(eInfo, eAttributes);
eInfo.eGeometryInfoMap = geometries(eInfo, eAttributes);
//
// Register ID attribute with ID factory?
//
if( !(eContext.eIDFactory() instanceof EFeatureVoidIDFactory) ) {
eContext.eIDFactory().add(eClass,eIDAttribute);
}
//
// Add to cache?
//
if(!eContext.eStructure().contains(eInfo)) {
EFeatureStatus eStatus;
if((eStatus = eContext.eStructure().doAttach(eInfo)).isFailure()) {
throw new IllegalArgumentException("Failed to cache "
+ eInfo.eName() + ": " + eStatus.getMessage());
}
}
//
// Optimize structure lookup methods
//
eOptimize(eInfo);
//
// Make immutable (this method also optimizes lookup)
//
eImmutable(eInfo);
//
// Finished
//
return eInfo;
}
protected static EAttribute eGetIDAttribute(EClass eClass, EFeatureHints eHints) {
EAttribute eIDAttribute = eGetAttribute(eClass, eHints, EFeatureHints.EFEATURE_ID_ATTRIBUTES);
if(eIDAttribute == null) {
eIDAttribute = eClass.getEIDAttribute();
}
return eIDAttribute;
}
protected static EAttribute eGetSRIDAttribute(EClass eClass, EFeatureHints eHints) {
EAttribute eIDAttribute = eGetAttribute(eClass, eHints, EFeatureHints.EFEATURE_SRID_ATTRIBUTES);
if(eIDAttribute == null) {
eIDAttribute = EFeaturePackage.eINSTANCE.getEFeature_SRID();
}
return eIDAttribute;
}
protected static EAttribute eGetDefaultAttribute(EClass eClass, EFeatureHints eHints) {
EAttribute eIDAttribute = eGetAttribute(eClass, eHints, EFeatureHints.EFEATURE_DEFAULT_ATTRIBUTES);
if(eIDAttribute == null) {
eIDAttribute = EFeaturePackage.eINSTANCE.getEFeature_Default();
}
return eIDAttribute;
}
protected static EAttribute eGetAttribute(EClass eClass, EFeatureHints eHints, Key eHint) {
EAttribute eAttribute = null;
if(eHints!=null) {
eAttribute = eHints.eGetAttribute(eClass, eHint);
}
return eAttribute;
}
protected static String eGetSRID(EFeatureHints eHints) {
Object eSRID = null;
if(eHints!=null) {
eSRID = eHints.get(EFeatureHints.EFEATURE_DEFAULT_SRID_HINT);
}
return eSRID !=null ? eSRID.toString() : DEFAULT_SRID;
}
@SuppressWarnings("unchecked")
protected static Set<String> eGetDefaultGeometryNames(EFeatureHints eHints) {
Object eNames = null;
if(eHints!=null) {
eNames = eHints.get(EFeatureHints.EFEATURE_DEFAULT_GEOMETRY_NAMES);
}
return eNames !=null ? (Set<String>)eNames :
new HashSet<String>(Arrays.asList(EFeatureConstants.DEFAULT_GEOMETRY_NAME));
}
protected static Map<String, EFeatureAttributeInfo> attributes(EFeatureInfo eFeatureInfo,
EList<EAttribute> eAttributes) {
//
// Prepare
//
Map<String, EFeatureAttributeInfo> eAttributeMap = EFeatureUtils.newMap(eAttributes.size());
//
// Get name of EAttribute ID
//
String eID = eFeatureInfo.eIDAttributeName;
//
// Inspect EMF attributes, adding all with non geometry values.
//
for (EAttribute it : eAttributes) {
//
// Does NOT contain geometry data?
//
if (!Geometry.class.isAssignableFrom(it.getEAttributeType().getInstanceClass())) {
String eName = it.getName();
eAttributeMap.put(eName, EFeatureAttributeInfo.create(
eFeatureInfo, eName.equals(eID), it));
}
}
return eAttributeMap;
}
protected static Map<String, EFeatureGeometryInfo> geometries(EFeatureInfo eFeatureInfo,
EList<EAttribute> eAttributes) {
//
// Prepare
//
String eDefault = null;
String srid = eFeatureInfo.srid;
CoordinateReferenceSystem crs = eFeatureInfo.crs;
Map<String, EFeatureGeometryInfo> eGeometryMap = EFeatureUtils.newMap(eAttributes.size());
//
// Inspect EMF attributes, adding all with geometry values.
//
for (EAttribute it : eAttributes) {
//
// Contains geometry data?
//
if (Geometry.class.isAssignableFrom(it.getEAttributeType().getInstanceClass())) {
//
// Initialize default geometry name?
//
String eName = it.getName();
if (eDefault == null) {
eDefault = eName;
}
//
// Create structure instance
//
EFeatureGeometryInfo eGeometryInfo =
EFeatureGeometryInfo.create(eFeatureInfo,
eName.equals(eDefault), srid, crs, it);
//
// Add to cache
//
eGeometryMap.put(eName, eGeometryInfo);
}
}
return eGeometryMap;
}
protected final EFeatureStatus featureIsValid() {
return success("Feature is valid: " + getClass().getSimpleName() + " [" + eName() + "]");
}
protected final EFeatureStatus featureIsInvalid() {
return failure(this, eName(), "Feature is invalid: "
+ getClass().getSimpleName()
+ " [" + eName() + "]");
}
/**
* Optimizes structure lookup.
* <p>
* @param eStructure
*/
protected static void eOptimize(EFeatureInfo eStructure) {
//
// Get collection sizes
//
int gsize = eStructure.eGeometryInfoMap.size();
int asize = eStructure.eAttributeInfoMap.size();
int tsize = asize + gsize;
//
// ------------------------------------------------
// 1) Prepare optimized lookup
// ------------------------------------------------
//
eStructure.eGeometryMap = EFeatureUtils.newMap(gsize);
eStructure.eAttributeMap = EFeatureUtils.newMap(asize);
eStructure.eAllAttributeMap = EFeatureUtils.newMap(tsize);
eStructure.eAllAttributeInfoMap = EFeatureUtils.newMap(tsize);
//
// ------------------------------------------------
// 2) Build lookup maps and collections
// ------------------------------------------------
//
for(EFeatureGeometryInfo it : eStructure.eGeometryInfoMap.values()) {
String eName = it.eName;
EAttribute eAttribute = it.eAttribute();
eStructure.eGeometryMap.put(eName,eAttribute);
eStructure.eAllAttributeMap.put(eName,eAttribute);
eStructure.eAllAttributeInfoMap.put(eName, it);
}
for(EFeatureAttributeInfo it : eStructure.eAttributeInfoMap.values()) {
String eName = it.eName;
EAttribute eAttribute = it.eAttribute();
eStructure.eAttributeMap.put(eName,eAttribute);
eStructure.eAllAttributeMap.put(eName,eAttribute);
eStructure.eAllAttributeInfoMap.put(eName, it);
}
}
/**
* Make given {@link EFeatureInfo structure} immutable.
* <p>
* {@link EFeature} is highly optimized, which depends on the
* structure being immutable. Note that EMF meta models might
* change over time (dynamic EMF). Each time a {@link EClass}
* change (attribute, containment, cross reference etc),
* the EMF meta model and structure might get out of sync.
* Change is tracked by the structure. If any change is detected,
* the structure is invalidated. If {@link #validate(EPackage, EClass)}
* fails, the whole structure must be recreated.
* <p/>
* TODO Add tracking of EMF meta model changes and implement
* synchronization mechanisms that update affected structures.
* Note that this implies that checksums must be recalculated,
* and that any cached values derived from the structure in live
* {@link EFeature} instances must be invalidated.
*/
protected static void eImmutable(EFeatureInfo eStructure) {
//
// Make copied lists and maps unmodifiable (structure is immutable)
//
eStructure.eMappingMap = Collections.unmodifiableMap(eStructure.eMappingMap);
eStructure.eGeometryMap = Collections.unmodifiableMap(eStructure.eGeometryMap);
eStructure.eAttributeMap = Collections.unmodifiableMap(eStructure.eAttributeMap);
eStructure.eAllAttributeMap = Collections.unmodifiableMap(eStructure.eAllAttributeMap);
eStructure.eGeometryInfoMap = Collections.unmodifiableMap(eStructure.eGeometryInfoMap);
eStructure.eAttributeInfoMap = Collections.unmodifiableMap(eStructure.eAttributeInfoMap);
eStructure.eAllAttributeInfoMap = Collections.unmodifiableMap(eStructure.eAllAttributeInfoMap);
//
// TODO Add invalidation adapter tracking changes in the EMF meta model
//
}
// -----------------------------------------------------
// SimpleFeatureDescriptor implementation
// -----------------------------------------------------
private class InnerSimpleFeatureTypeImpl implements SimpleFeatureType {
private Map<String, Integer> index;
private Map<Object, Object> userData;
private List<PropertyDescriptor> descriptors;
private Class<?> binding = Collection.class;
public InnerSimpleFeatureTypeImpl() {
index = buildIndex(this);
}
@Override
public boolean isIdentified() {
//
// Features are always identified
//
return true;
}
@Override
public boolean isAbstract() {
return false;
}
@Override
public GeometryDescriptor getGeometryDescriptor() {
EFeatureGeometryInfo eInfo = eGeometryInfoMap.get(eGetDefaultGeometryName());
return eInfo != null ? eInfo.getDescriptor() : null;
}
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return EFeatureInfo.this.getCoordinateReferenceSystem();
}
@Override
@SuppressWarnings("unchecked")
public Class<Collection<Property>> getBinding() {
return (Class<Collection<Property>>) binding;
}
@Override
public Collection<PropertyDescriptor> getDescriptors() {
if(descriptors==null) {
List<EFeatureAttributeInfo> eList = eGetAttributeInfoList(true);
descriptors = new ArrayList<PropertyDescriptor>(eList.size());
for (EFeatureAttributeInfo it : eList) {
descriptors.add(it.getDescriptor());
}
descriptors = Collections.unmodifiableList(descriptors);
}
return descriptors;
}
@Override
public boolean isInline() {
// Apparently not in use, return false
//
return false;
}
@Override
public AttributeType getSuper() {
return null;
}
@Override
public List<Filter> getRestrictions() {
return Collections.emptyList();
}
@Override
public InternationalString getDescription() {
return null;
}
@Override
public Map<Object, Object> getUserData() {
if (userData == null) {
userData = new HashMap<Object, Object>();
}
return userData;
}
/**
* Get EFeature {@link SimpleFeatureType type} name.
* <p>
* EFeature type names have the following format:
*
* <pre>
* eType=<eFolder>.<eFeature>
*
* where
*
* eFolder = {@link EFeature} folder name
* eFeature = {@link EFeature} class | reference name
* </pre>
* <p>
* The name is located in {@link Name#getLocalPart()}
* </p>
* @return a SimpleFeatureType {@link Name}
*/
@Override
public Name getName() {
String eFolder = EFeatureInfo.this.eFolderName;
String eFeature = EFeatureInfo.this.eName();
return new NameImpl(eFolder+"."+eFeature);
}
/**
* Get EFeature {@link SimpleFeatureType type} name.
* <p>
* EFeature type names have the following format:
*
* <pre>
* eType=<eFolder>.<eFeature>
*
* where
*
* eFolder = {@link EFeature} folder name
* eFeature = {@link EFeature} class | reference name
* </pre>
*
* @return a {@link SimpleFeatureType} name
*/
@Override
public String getTypeName() {
return getName().getLocalPart();
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public List<AttributeDescriptor> getAttributeDescriptors() {
return (List) getDescriptors();
}
@Override
public List<AttributeType> getTypes() {
synchronized (this) {
List<AttributeType> types = new ArrayList<AttributeType>();
for (EFeatureAttributeInfo it : eGetAttributeInfoList(true)) {
types.add(it.getDescriptor().getType());
}
return types;
}
}
@Override
public AttributeType getType(Name name) {
AttributeDescriptor attribute = getDescriptor(name);
if (attribute != null) {
return attribute.getType();
}
return null;
}
@Override
public AttributeType getType(String name) {
AttributeDescriptor attribute = getDescriptor(name);
if (attribute != null) {
return attribute.getType();
}
return null;
}
@Override
public AttributeType getType(int index) {
return getTypes().get(index);
}
@Override
public AttributeDescriptor getDescriptor(Name eName) {
return getDescriptor(eName.getURI());
}
@Override
public AttributeDescriptor getDescriptor(String eName) {
EFeatureAttributeInfo eInfo = eGetAttributeInfo(eName, true);
return (eInfo != null ? eInfo.getDescriptor() : null);
}
@Override
public AttributeDescriptor getDescriptor(int index) {
return getAttributeDescriptors().get(index);
}
@Override
public int indexOf(Name name) {
if (name.getNamespaceURI() == null) {
return indexOf(name.getLocalPart());
}
// otherwise do a full scan
int index = 0;
for (AttributeDescriptor descriptor : getAttributeDescriptors()) {
if (descriptor.getName().equals(name)) {
return index;
}
index++;
}
return -1;
}
@Override
public int indexOf(String name) {
Integer idx = index.get(name);
if (idx != null) {
return idx.intValue();
} else {
return -1;
}
}
@Override
public int getAttributeCount() {
return getAttributeDescriptors().size();
}
/**
* Builds the name -> position index used by simple features for fast attribute lookup
*
* @param featureType
* @return
*/
private Map<String, Integer> buildIndex(InnerSimpleFeatureTypeImpl featureType) {
// build an index of attribute name to index
Map<String, Integer> index = new HashMap<String, Integer>();
int i = 0;
for (AttributeDescriptor ad : featureType.getAttributeDescriptors()) {
index.put(ad.getLocalName(), i++);
}
if (featureType.getGeometryDescriptor() != null) {
index.put(null, index.get(featureType.getGeometryDescriptor().getLocalName()));
}
return Collections.unmodifiableMap(index);
}
}
}