blob: 30802e9c498ab83270c9a3f383e889e46d7ae1ca [file] [log] [blame]
/*******************************************************************************
* 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);
}
}