| /******************************************************************************* |
| * Copyright (c) 2009, 2018 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * Axel Uhl - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ocl.ecore.opposites; |
| |
| 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.Set; |
| import java.util.WeakHashMap; |
| |
| import org.eclipse.emf.common.notify.Notifier; |
| import org.eclipse.emf.ecore.EAnnotation; |
| 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.Setting; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.util.ECrossReferenceAdapter; |
| import org.eclipse.emf.ecore.xmi.impl.EMOFExtendedMetaData; |
| import org.eclipse.ocl.ecore.internal.OCLEcorePlugin; |
| import org.eclipse.ocl.ecore.internal.OCLStatusCodes; |
| import org.eclipse.ocl.util.CollectionUtil; |
| |
| /** |
| * Opposite references declared by the annotation detail |
| * {@link OppositeEndFinder#PROPERTY_OPPOSITE_ROLE_NAME_KEY} on an |
| * {@link EAnnotation} with {@link EAnnotation#getSource() source} |
| * {@link EMOFExtendedMetaData#EMOF_PACKAGE_NS_URI_2_0} are retrieved by |
| * scanning and caching the Ecore packages that this opposite end finder is |
| * aware of at the time of the request. The set of those packages is taken to be |
| * the set of {@link EPackage}s resolved by an {@link org.eclipse.emf.ecore.EPackage.Registry EPackage.Registry} |
| * provided to {@link #getInstance(org.eclipse.emf.ecore.EPackage.Registry) getInstance(EPackage.Registry)}, or the default |
| * {@link org.eclipse.emf.ecore.EPackage.Registry EPackage.Registry} otherwise. In particular, this won't load any Ecore |
| * bundles not yet loaded. |
| * <p> |
| * |
| * This also means that with this implementation of the {@link OppositeEndFinder} |
| * interface it is necessary to resolve a package in the registry on which this |
| * opposite end finder is based before issuing a request expecting to find |
| * hidden opposites from that package. This can, e.g., be triggered by calling |
| * {@link org.eclipse.emf.ecore.EPackage.Registry#getEPackage(String) EPackage.Registry.getEPackage(String)} for the package's URI.<p> |
| * |
| * Navigation across those references is performed either by |
| * {@link EObject#eContainer()} in case of containment references or by looking |
| * for an {@link ECrossReferenceAdapter} along the containment hierarchy of the |
| * source object from which to start the navigation. Only if such an adapter is |
| * registered along the composition/containment hierarchy can the navigation be |
| * performed successfully. Note also that this will only consider references |
| * that have been currently loaded which can give rather random results. For |
| * more predictable results, an implementation should be used that relies on a |
| * well-defined query scope and a corresponding query engine.<p> |
| * |
| * Instances of this class are cached in a {@link WeakHashMap}, keyed by the |
| * {@link org.eclipse.emf.ecore.EPackage.Registry EPackage.Registry} object used for {@link #getInstance(org.eclipse.emf.ecore.EPackage.Registry) getInstance(EPackage.Registry)}. |
| * This means that if the registry gets eligible for garbage collection then |
| * so will this opposite end finder.<p> |
| * |
| * @author Axel Uhl |
| * @since 3.1 |
| * |
| */ |
| public class DefaultOppositeEndFinder |
| implements OppositeEndFinder { |
| |
| private static DefaultOppositeEndFinder instanceForDefaultRegistry = null; |
| |
| /** |
| * The set of packages for which the hidden opposites are already cached in |
| * {@link #oppositeCache}. |
| */ |
| private final Set<EPackage> packages; |
| |
| private final Map<EClass, Map<String, Set<EReference>>> oppositeCache; |
| |
| /** |
| * The package registry in which before each request the set of packages already |
| * resolved is compared to {@link #packages}. Packages that got resolved in the |
| * registry but weren't cached yet are cached before executing the request. |
| */ |
| private final EPackage.Registry registry; |
| |
| /** |
| * Scans all packages from the default {@link org.eclipse.emf.ecore.EPackage.Registry EPackage.Registry} that have already been |
| * loaded. While this is convenient, please keep in mind that this may be fairly random a |
| * definition of a package set as loading of packages happens on demand. You will get more |
| * predictable results using {@link #getInstance(org.eclipse.emf.ecore.EPackage.Registry) getInstance(EPackage.Registry)}. |
| */ |
| public static DefaultOppositeEndFinder getInstance() { |
| return getInstance(EPackage.Registry.INSTANCE); |
| } |
| |
| /** |
| * Scans all packages from the <code>registry</code> specified that have already been |
| * loaded. While this is convenient, please keep in mind that this may be fairly random a |
| * definition of a package set as loading of packages happens on demand. You will get more |
| * predictable results using {@link #getInstance(org.eclipse.emf.ecore.EPackage.Registry) getInstance(EPackage.Registry)}. |
| */ |
| public static DefaultOppositeEndFinder getInstance(EPackage.Registry registry) { |
| DefaultOppositeEndFinder result; |
| if (registry == EPackage.Registry.INSTANCE) { |
| if (instanceForDefaultRegistry == null) { |
| instanceForDefaultRegistry = new DefaultOppositeEndFinder(EPackage.Registry.INSTANCE); |
| } |
| result = instanceForDefaultRegistry; |
| } else { |
| result = new DefaultOppositeEndFinder(registry); |
| } |
| return result; |
| } |
| |
| public DefaultOppositeEndFinder(EPackage.Registry registry) { |
| this.registry = registry; |
| this.packages = new HashSet<EPackage>(); |
| this.oppositeCache = new HashMap<EClass, Map<String,Set<EReference>>>(); |
| } |
| |
| public void findOppositeEnds(EClassifier classifier, String name, |
| List<EReference> ends) { |
| if (classifier instanceof EClass) { |
| EClass eClass = (EClass) classifier; |
| updateOppositeCache(); |
| Map<String, Set<EReference>> oppositesOnEClass = oppositeCache.get(eClass); |
| if (oppositesOnEClass != null) { |
| Set<EReference> refs = oppositesOnEClass.get(name); |
| if (refs != null) { |
| Set<EReference> references = oppositesOnEClass.get(name); |
| for (EReference ref : references) { |
| ends.add(ref); |
| } |
| } |
| } |
| // search in superclasses if nothing found yet |
| if (ends.isEmpty()) { |
| for (EClassifier sup : eClass.getESuperTypes()) { |
| findOppositeEnds(sup, name, ends); |
| } |
| } |
| } |
| } |
| |
| public Map<String, EReference> getAllOppositeEnds( |
| EClassifier classifier) { |
| Map<String, EReference> result = new HashMap<String, EReference>(); |
| if (classifier instanceof EClass) { |
| EClass eClass = (EClass) classifier; |
| updateOppositeCache(); |
| Map<String, Set<EReference>> oppositesOnClass = oppositeCache.get(eClass); |
| if (oppositesOnClass != null) { |
| for (String oppositeName : oppositesOnClass.keySet()) { |
| Set<EReference> refs = oppositesOnClass.get(oppositeName); |
| if (refs.size() > 0) { |
| result.put(oppositeName, refs.iterator().next()); |
| } |
| } |
| } |
| // add all inherited opposite property definitions |
| for (EClassifier sup : eClass.getESuperTypes()) { |
| Map<String, EReference> supOppositeEnds = getAllOppositeEnds(sup); |
| for (String supOppositeEndName : supOppositeEnds.keySet()) { |
| // add superclass's opposite only if not already found in |
| // subclass; this implements the "subclass definitions override" |
| // behavior |
| if (!result.containsKey(supOppositeEndName)) { |
| result.put(supOppositeEndName, |
| supOppositeEnds.get(supOppositeEndName)); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Lazily initialize the opposite cache for the {@link #packages} for which this opposite |
| * end finder is responsible |
| */ |
| private synchronized void updateOppositeCache() { |
| for (Object pkg : registry.values()) { |
| // if it's not a package descriptor indicating a not yet loaded package... |
| if (pkg instanceof EPackage) { |
| EPackage ePackage = (EPackage) pkg; |
| // ... and it hasn't been cached yet, cache it |
| if (packages.add(ePackage)) { |
| cachePackage(ePackage); |
| } |
| } |
| } |
| } |
| |
| private void cachePackage(EPackage ePackage) { |
| for (EClassifier c : ePackage.getEClassifiers()) { |
| if (c instanceof EClass) { |
| EClass eClass = (EClass) c; |
| for (EReference ref : eClass.getEReferences()) { |
| EAnnotation ann = ref |
| .getEAnnotation(EMOFExtendedMetaData.EMOF_PACKAGE_NS_URI_2_0); |
| if (ann != null) { |
| String oppositeName = ann.getDetails().get( |
| OppositeEndFinder.PROPERTY_OPPOSITE_ROLE_NAME_KEY); |
| if (oppositeName != null) { |
| cache((EClass) ref.getEType(), oppositeName, ref); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void cache(EClass c, String oppositeName, EReference ref) { |
| Map<String, Set<EReference>> cache = oppositeCache.get(c); |
| if (cache == null) { |
| cache = new HashMap<String, Set<EReference>>(); |
| oppositeCache.put(c, cache); |
| } |
| Set<EReference> set = cache.get(oppositeName); |
| if (set == null) { |
| set = new HashSet<EReference>(); |
| cache.put(oppositeName, set); |
| } |
| set.add(ref); |
| } |
| |
| /** |
| * If on the <code>target</code> or any of its containers up to the |
| * {@link ResourceSet} there is a {@link ECrossReferenceAdapter} registered, |
| * uses it for navigating <code>property</code> in reverse. In this case, a |
| * non- <code>null</code> collection is returned which contains those |
| * {@link EObject}s on which navigating <code>property</code> leads to |
| * <code>target</code>. The "forward" scope is just whatever the |
| * {@link ECrossReferenceAdapter} sees that is expected to be registered on |
| * <code>target</code> |
| * |
| * @param target |
| * must be a non-<code>null</code> {@link EObject} |
| */ |
| public Collection<EObject> navigateOppositePropertyWithForwardScope( |
| EReference property, EObject target) { |
| return navigateOppositePropertyWithSymmetricScope(property, target); |
| } |
| |
| public Collection<EObject> navigateOppositePropertyWithBackwardScope( |
| EReference property, EObject target) { |
| return navigateOppositePropertyWithSymmetricScope(property, target); |
| } |
| |
| private Collection<EObject> navigateOppositePropertyWithSymmetricScope( |
| EReference property, EObject target) { |
| Collection<EObject> result = null; |
| if (property.isContainment()) { |
| EObject resultCandidate = target.eContainer(); |
| if (resultCandidate != null) { |
| // first check if the container is assignment-compatible to the |
| // property's owning type: |
| if (property.getEContainingClass().isInstance(resultCandidate)) { |
| Object propertyValue = resultCandidate.eGet(property); |
| if (propertyValue == target |
| || (propertyValue instanceof Collection<?> && ((Collection<?>) propertyValue) |
| .contains(target))) { |
| result = Collections.singleton(resultCandidate); |
| } |
| } |
| } |
| } else { |
| ECrossReferenceAdapter crossReferenceAdapter = getCrossReferenceAdapter(target); |
| if (crossReferenceAdapter != null) { |
| result = CollectionUtil.createNewBag(); |
| Collection<Setting> settings = crossReferenceAdapter |
| .getInverseReferences(target); |
| for (Setting setting : settings) { |
| if (setting.getEStructuralFeature() == property) { |
| result.add(setting.getEObject()); |
| } |
| } |
| } else { |
| OCLEcorePlugin.warning(OCLStatusCodes.IGNORED_EXCEPTION_WARNING, |
| "Trying to reverse-navigate reference of " + target //$NON-NLS-1$ |
| + " without ECrossReferenceAdapter attached"); //$NON-NLS-1$ |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Search for an {@link ECrossReferenceAdapter} up <code>target</code>'s |
| * containmeht hierarchy |
| */ |
| private ECrossReferenceAdapter getCrossReferenceAdapter(EObject target) { |
| ECrossReferenceAdapter result = ECrossReferenceAdapter |
| .getCrossReferenceAdapter(target); |
| if (result == null && target.eContainer() != null) { |
| result = getCrossReferenceAdapter(target.eContainer()); |
| } |
| return result; |
| } |
| |
| /** |
| * This default implementation uses an {@link AllInstancesContentAdapter} on |
| * <code>context</code>'s root context (see |
| * {@link AllInstancesContentAdapter#getInstanceForRootContextOf(Notifier)}) |
| * which is created lazily if it isn't set yet. Note that for larger |
| * resource sets with many resources and elements this won't scale very well |
| * as all resources will be scanned for elements conforming to |
| * <code>cls</code>. Also, no scoping other than tracing to the root context |
| * based on <code>context</code> is performed. |
| */ |
| public Set<EObject> getAllInstancesSeeing(EClass cls, Notifier context) { |
| return AllInstancesContentAdapter.getInstanceForRootContextOf(context) |
| .allInstances(cls); |
| } |
| |
| public Set<EObject> getAllInstancesSeenBy(EClass cls, Notifier context) { |
| return getAllInstancesSeeing(cls, context); |
| } |
| |
| } |