/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2011, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data.efeature.impl; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.geotools.data.efeature.EFeature; import org.geotools.data.efeature.EFeatureIDFactory; import org.geotools.data.efeature.EFeaturePackage; import org.geotools.data.efeature.internal.EFeatureDelegate; import org.geotools.util.WeakHashSet; import org.geotools.util.logging.Logging; /** * Default implementation of {@link EFeatureIDFactory} instance. * <p> * This implementation generates unique IDs for * each {@link EAttribute} is manages. * * @author kengu - 26. mai 2011 * */ public class EFeatureIDFactoryImpl implements EFeatureIDFactory { public static final String DEFAULT_PREFIX = "F"; private static final Logger LOGGER = Logging.getLogger(EFeatureIDFactoryImpl.class); protected String ePrefix; /** * This map contains a mapping from each EObject to the ID created for it by this factory. */ protected Map<URI,WeakHashMap<EObject,Long>> eInverseIDMap = new HashMap<URI,WeakHashMap<EObject, Long>>(); /** * This map contains a mapping from each ID to each {@link EObject} assigned to it. * <b>NOTE</b>: Only objects already assigned an ID are allowed to have * the same ID. This only happens if multiple {@link EObject}s are constructed * from the same EMF {@link Resource}. This occurs when {@link EObject} are queried * multiple times from the resource, and the garbage collector has not yet disposed * older {@link EObject} instances not longer referenced by any client code. * A {@link WeakHashSet} is used to ensure that this factory does not prevent the * garbage collection. */ protected Map<URI,Map<Long,WeakHashSet<EObject>>> eIDMap = new HashMap<URI,Map<Long, WeakHashSet<EObject>>>(); /** * This map contains the next ID for each {@link EAttribute} this factory create IDs for. * It allows for faster creation of unique IDs for each {@link EAttribute} by removing * the step of calculating maximum ID value among existing IDs. */ protected Map<URI,Long> eNextIDMap = new HashMap<URI,Long>(); /** * This map ensures that only one {@link EAttribute} per {@link EClass} is allowed * to be used as an unique ID. This is introduced to reduce the complexity, but could be * relaxed if necessary. * * TODO: Add option to allow multiple ID attribute registrations per {@link EClass}? */ protected Map<EClass,EAttribute> eAttributeMap = new HashMap<EClass, EAttribute>(); // ----------------------------------------------------- // Constructors // ----------------------------------------------------- /** * Default constructor. * <p> * This factory only handles {@link EObject} instances which * implements the {@link EFeature} interface. * @see {@link EFeaturePackage#getEFeature_ID()} * @throws IllegalArgumentException If the * {@link EDataType#getInstanceClass() data type} of one or * more {@link EAttribute#getEAttributeType()}s are not {@link String}. */ public EFeatureIDFactoryImpl() throws IllegalArgumentException { this(DEFAULT_PREFIX, EFeaturePackage.eINSTANCE.getEFeature_ID()); } /** * Constructor which tells this factory to generate IDs * for given {@link EAttribute} instances. * @param ePrefix - custom ID prefix * @param eAttributes - array of {@link EAttribute} instances * @see {@link EFeaturePackage#getEFeature_ID()} * @throws IllegalArgumentException If the * {@link EDataType#getInstanceClass() data type} of one or * more {@link EAttribute#getEAttributeType()}s are not {@link String}. */ public EFeatureIDFactoryImpl(String ePrefix, EAttribute... eAttributes) throws IllegalArgumentException { this.ePrefix = ePrefix; for(EAttribute it : eAttributes) { add(it.getEContainingClass(), it); } } // ----------------------------------------------------- // EFeatureIDFactory implementation // ----------------------------------------------------- @Override public String getPrefix() { return ePrefix; } @Override public EAttribute add(EObject eObject) throws IllegalArgumentException { return add(eImpl(eObject).eClass()); } @Override public EAttribute add(EClass eClass) throws IllegalArgumentException { // // Check if have ID attribute? // EAttribute eAtt = eClass.getEIDAttribute(); if(eAtt==null) { for(EAttribute it : eClass.getEAllAttributes()) { String eType = it.getEAttributeType().getInstanceClassName(); if("java.lang.String".equals(eType)) { eAtt = it; break; } } } // // verify non-null // if(eAtt==null) { throw new IllegalArgumentException("Did not find " + "any EAttribute ID in " + eClass.getName()); } // // Add attribute // add(eClass,eAtt); // // Finished // return eAtt; } @Override public void add(EClass eClass, EAttribute eID) { EAttribute eAtt = eAttributeMap.get(eClass); if(eAtt!=null) { if(eAtt!=eID) { throw new IllegalArgumentException("EAttribute " + eAtt.getName() + " already as ID added for " + "EClass " + eClass.getName()); } } else { String eType = eID.getEAttributeType().getInstanceClassName(); if(!"java.lang.String".equals(eType)) { throw new IllegalArgumentException("EAttribute " + eID.getName() + " contains " + eType + ", not java.lang.String"); } eAttributeMap.put(eClass, eID); } } @Override public boolean creates(EObject eObject) { return creates(eImpl(eObject).eClass()); } @Override public boolean creates(EClass eClass) { return eAttributeMap.containsKey(eClass); } @Override public boolean creates(EAttribute eAttribute) { return eAttributeMap.containsValue(eAttribute); } @Override public boolean contains(URI eURI) { return eIDMap.containsKey(eURI); } @Override public boolean contains(EObject eObject) { EObject eImpl = eImpl(eObject); for(Map<EObject,Long> it : eInverseIDMap.values()) { if(it.containsKey(eImpl)) { return true; } } return false; } @Override public Map<EObject,String> getIDMap(URI eURI) { Map<EObject,Long> eCachedIDs = eInverseIDMap.get(eURI); if(eCachedIDs!=null) { WeakHashMap<EObject,String> eIDs = new WeakHashMap<EObject, String>(eCachedIDs.size()); for(Entry<EObject,Long> it : eCachedIDs.entrySet()) { eIDs.put(it.getKey(), ePrefix + it.getValue()); } return eIDs; } return Collections.emptyMap(); } @Override public String getID(EObject eObject) { // // Get implementation (strips away EFeatureDelegates) // EObject eImpl = eImpl(eObject); // // Verify EObject // verify("EObject", eImpl, true, false, true); // // Get URI // URI eURI = eImpl.eResource().getURI(); // // Forward // return eGetID(eURI,eImpl); } @Override public String createID(EObject eObject) throws IllegalArgumentException { // // Get implementation (strips away EFeatureDelegates) // EObject eImpl = eImpl(eObject); // // Verify EObject // verify("eObject", eImpl, true, true, true); // // Get ID attribute // EAttribute eAttribute = eAttributeMap.get(eImpl.eClass()); // // Verify // verify("eAttribute",eAttribute,true); // // Get URI // URI eURI = eImpl.eResource().getURI(); // // Verify // verify("eURI",eURI,true); // // Get set value // String eSetID = eGetID(eURI, eImpl); // // Forward to implementation // String eNewID = (eSetID==null || eSetID.length()==0 ? eCreateID(eURI, eImpl, eImpl.eClass(), eAttribute) : eUseID(eURI, eImpl, eAttribute, eSetID, true)); // // If EObject is an EFeature, then set ID. // Otherwise, throw an IllegalArgumentException // return eInverseSet(eObject,eNewID,eSetID); } @Override public String useID(EObject eObject, String eID) { // // Get implementation (strips away EFeatureDelegates) // EObject eImpl = eImpl(eObject); // // Verify eObject // verify("eObject",eImpl,true,true,true); // // Get URI // URI eURI = eImpl.eResource().getURI(); // // Verify eURI // verify("eURI",eURI,true); // // Get ID attribute // EAttribute eAttribute = eAttributeMap.get(eImpl.eClass()); // // Get current value // String eSetID = (String)eImpl.eGet(eAttribute); // // Check if set // boolean eIsSet = !(eSetID==null || eSetID.length()==0); // // ---------------------------------------------------------- // Cache ID as used // ---------------------------------------------------------- // If ID is not set in given object, make sure that ID is // unique, if not unique a unique ID is returned. If ID is // set in given object, then just mark it as used and return // it unchanged. See eIDMap for more information. // eID = eUseID(eURI, eImpl, eAttribute, eID, eIsSet); // // If EObject is an EFeature, then set ID. // Otherwise, throw an IllegalArgumentException // return eInverseSet(eObject,eID,eSetID); } @Override public String disposeID(EObject eObject) { // // Get implementation (strips away EFeatureDelegates) // EObject eImpl = eImpl(eObject); // // Verify eObject // verify("eObject",eImpl,true,true,false); // // // // Get URI // // // URI eURI = eImpl.eResource().getURI(); // // // // Verify URI // // // verify("eURI",eURI,true); // // Get ID attribute // EAttribute eAttribute = eAttributeMap.get(eImpl.eClass()); // // Get current ID // String eID = (String)eImpl.eGet(eAttribute); // // Verify // verify("eID",eID,true); // // Forward // return eDisposeID(eImpl, eID); } // ----------------------------------------------------- // Helper methods // ----------------------------------------------------- protected String eGetID(URI eURI, EObject eImpl) { // // Get map // Map<EObject,Long> eIDs = eInverseIDMap.get(eURI); // // Locate ID if exists? // if(eIDs!=null) { Long uniqueID = eIDs.get(eImpl); if(uniqueID!=null) { return ePrefix + uniqueID; } } return null; } protected Long eNextID(URI eURI) { // // Get next unique ID from map // Long uID = eNextIDMap.get(eURI); // // First ID in given resource? // if(uID==null) uID = 1L; // // Finished // return uID; } /** * Ensure that ID is unique. If not, return one that is. * <p> * This method used the {@link #eInverseIDMap} to ensure that * the next ID is is unique * @param eImpl - {@link EObject} instance * @param eImpl - the object with ID * @param uID - proposed ID * @return actual unique ID */ protected Long eUniqueID(URI eURI, EObject eImpl, Long uID) { // // Get Map of cached ID for given URI // Map<EObject,Long> eCachedIDs = eInverseIDMap.get(eURI); // // Found cached IDs? // if(eCachedIDs!=null) { // // Check if an equal ID value is already cached for given object // if(uID.equals(eCachedIDs.get(eImpl))) return uID; // // Initialize upper bounds for safe exit // int size = eCachedIDs.size(); int count = 0; // // Continue until unique is found or end of map reached // while(eCachedIDs.containsValue(uID) && count<size) { uID++; count++; } } // // Finished // return uID; } /** * Create ID from given information. */ protected String eCreateID(URI eURI, EObject eImpl, EClass eClass, EAttribute eID) throws IllegalArgumentException { // // Initialize unique id to "unknown" // Long uID = 0L; // // Try to get unique ID as string // String sID = getID(eImpl); // // Get current ID // if( !(sID==null || sID.length()==0) ) { // // Remove prefix // sID = sID.replace(ePrefix, ""); // // Convert to long // uID = Long.decode(sID); } // // Current ID is "unknown"? // if(uID==0L) { // // Get next ID from map // uID = eNextIDMap.get(eURI); // // Start to create IDs for new resource? // if(uID==null) { // // Set first ID in this series // uID = 1L; } else { // // Validate against cached IDs // uID = eUniqueID(eURI, eImpl, uID); } } // // Add to ID maps // return eCacheID(eURI,eImpl,uID); } protected String eUseID(URI eURI, EObject eImpl, EAttribute eAttribute, String eID, boolean eIsSet) { // // Remove prefix // eID = eID.replace(ePrefix, ""); // // Convert to long // Long uID = Long.decode(eID); // // Ensure that is is unique? // if(!eIsSet) { uID = eUniqueID(eURI, eImpl, uID); } // // Cache the ID as used // return eCacheID(eURI, eImpl, uID); } protected String eCacheID(URI eURI, EObject eImpl, Long uID) { // // Ensure that EFeatureDelegates are not cached // if(eImpl instanceof EFeatureDelegate) { throw new IllegalArgumentException("IDs can not be associated with EFeatureDelegate instances"); } // // Get Object cached for given ID, create it if not found // Map<Long,WeakHashSet<EObject>> eCachedIDSets = eIDMap.get(eURI); if(eCachedIDSets==null) { eCachedIDSets = new HashMap<Long,WeakHashSet<EObject>>(); eIDMap.put(eURI, eCachedIDSets); } WeakHashSet<EObject> eObjectSet = eCachedIDSets.get(uID); if(eObjectSet==null) { eObjectSet = new WeakHashSet<EObject>(EObject.class); eCachedIDSets.put(uID, eObjectSet); } // // Add object to hash set // eObjectSet.add(eImpl); // // Get EObjects cached for given URI, create it if not found // WeakHashMap<EObject,Long> eInverseIDs = eInverseIDMap.get(eURI); if(eInverseIDs==null) { eInverseIDs = new WeakHashMap<EObject,Long>(); eInverseIDMap.put(eURI, eInverseIDs); } // // Add to ID cache // eInverseIDs.put(eImpl, uID); // // Update next ID? // if(eNextID(eURI)<=uID) { eNextIDMap.put(eURI,uID+1); } // // Finished // return ePrefix+uID; } protected String eInverseSet(EObject eObject, String eNewID, String eSetID) { if((eObject instanceof EFeature)) { if(!eNewID.equals(eSetID)) { if(eObject instanceof EFeatureImpl) { return ((EFeatureImpl)eObject).eInternal().eSetID(eNewID, false); } else if(eObject instanceof EFeatureDelegate) { return ((EFeatureDelegate)eObject).eInternal().eSetID(eNewID, false); } // // ------------------------------------------------------ // !!! EFeature not implemented !!! // ------------------------------------------------------ // Just as implementors or EObject are expected to // implement InternalEObject, implementors of EFeature // are expected to implement EFeatureInternal or // EFeatureDelegate. These implementations have guards // that break the recursive call sequence // EFeature.setID() -> EFeatureIDFactory.useID() -> // EFeature.setID(), handles the context startup // problem and much more which direct implementors of // EFeature must handle themselves. Try // // // This will probably fail, but let's try it out anyway ... // try { ((EFeature)eObject).setID(eNewID); } catch (Exception e) { // // Notify that this is a unrecoverable errors // String msg = "EFeature is implemented correctly. " + "Extend EFeatureImpl or use a EFeatureDelegate instead. "; // // Log message as severe. This should give the implementor some additional hints... // LOGGER.log(Level.SEVERE, msg + e.getMessage(), e); // // Throw it again // throw ((IllegalArgumentException)(new IllegalArgumentException(msg).initCause(e))); } } return eNewID; } // // This should never happen... // throw new IllegalArgumentException("'" + eObject + " does not implement EFeature"); } protected String eDisposeID(EObject eObject, String eID) throws IllegalArgumentException { // // Remove prefix // eID = eID.replace(ePrefix, ""); // // Convert to long // Long uID = Long.decode(eID); // // Find URI // URI eURI = null; for(Entry<URI,Map<Long,WeakHashSet<EObject>>> it : eIDMap.entrySet()) { WeakHashSet<EObject> eSet = it.getValue().get(uID); if(eSet!=null && eSet.contains(eObject)) { eURI = it.getKey(); } } // // Verify that URI was found // if(eURI==null) { // // No IDs created for given resource // throw new IllegalArgumentException("Object " + eObject.getClass().getSimpleName() + "[" + eID + " ] not found"); } // // Verify that URI was // WeakHashMap<EObject,Long> eCachedIDs = eInverseIDMap.get(eURI); if(eCachedIDs==null) { // // No IDs created for given resource // throw new IllegalArgumentException("No IDs created for " + "resource [" + eURI + " ]"); } // // Get ID from cache // uID = eCachedIDs.get(eObject); // // ID not found? if(uID == null) { // // No ID created for given object // throw new IllegalArgumentException("No ID created for " + eObject); } if(eID.equals(ePrefix+uID)) { // // Not same as ID created for given object // throw new IllegalArgumentException("Expected ID " + eID + "," + "found " + ePrefix + uID); } // // Dispose ID // eCachedIDs.remove(eObject); // // Finished // return eID; } protected void verify(String eType, Object object, boolean isNonNull) { if(isNonNull && object==null) { throw new NullPointerException(eType + " can not be null"); } } protected void verify(String eType, EObject eObject, boolean isNonNull, boolean isValid, boolean isLoaded) { verify(eType, eObject,isNonNull); if(isValid && !creates(eObject)) { throw new IllegalArgumentException(eType + " " + getName(eObject) + " is not valid"); } if(isLoaded && eObject.eResource()==null) { throw new IllegalStateException(eType + " " + getName(eObject) + " is not loaded into a resource"); } } protected static String getName(EObject eObject) { if(eObject==null) return "null"; if(eObject instanceof ENamedElement) { return ((ENamedElement)eObject).getName(); } return eObject.toString(); } protected EObject eImpl(EObject eObject) { if(eObject instanceof EFeatureDelegate) { eObject = ((EFeatureDelegate)eObject).eImpl(); } return eObject; } }