/*******************************************************************************
* Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Gabor Bergmann - initial API and implementation
*******************************************************************************/
package org.eclipse.incquery.runtime.base.comprehension;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMap.Entry;
/**
* @author Bergmann Gábor
*
* Does not directly visit derived links, unless marked as a WellBehavingFeature. Derived edges are
* automatically interpreted correctly in these cases: - EFeatureMaps - eOpposites of containments
*
*
*/
public class EMFModelComprehension {
/**
* Should not traverse this feature directly. It is still possible that it can be represented in IQBase if
* {@link #representable(EStructuralFeature)} is true.
*/
public static boolean untraversableDirectly(EStructuralFeature feature) {
boolean suspect = feature.isDerived() || feature.isVolatile();
if (suspect) {
// override support here
// (e.g. if manual notifications available, or no changes expected afterwards)
suspect = !WellbehavingDerivedFeatureRegistry.isWellbehavingFeature(feature);
// TODO verbose flag somewhere to ease debugging (for such warnings)
// TODO add warning about not visited subtree (containment, FeatureMap and annotation didn't define
// otherwise)
}
return suspect;
}
/**
* This feature can be represented in IQBase.
*/
public static boolean representable(EStructuralFeature feature) {
if (!untraversableDirectly(feature))
return true;
if (feature instanceof EReference) {
final EReference reference = (EReference) feature;
if (reference.isContainer() && representable(reference.getEOpposite()))
return true;
}
boolean isMixed = "mixed".equals(EcoreUtil.getAnnotation(feature.getEContainingClass(),
ExtendedMetaData.ANNOTATION_URI, "kind"));
if (isMixed)
return true; // TODO maybe check the "name"=":mixed" or ":group" feature for representability?
final String groupAnnotation = EcoreUtil.getAnnotation(feature, ExtendedMetaData.ANNOTATION_URI, "group");
if (groupAnnotation != null && groupAnnotation.length() > 1 && '#' == groupAnnotation.charAt(0)) {
final String groupFeatureName = groupAnnotation.substring(1);
final EStructuralFeature groupFeature = feature.getEContainingClass().getEStructuralFeature(
groupFeatureName);
return representable(groupFeature);
}
return false;
}
public static void traverseModel(EMFVisitor visitor, Notifier source) {
if (source == null)
return;
if (source instanceof EObject) {
traverseObject(visitor, (EObject) source);
} else if (source instanceof Resource) {
traverseResource(visitor, (Resource) source);
} else if (source instanceof ResourceSet) {
traverseResourceSet(visitor, (ResourceSet) source);
}
}
public static void traverseResourceSet(EMFVisitor visitor, ResourceSet source) {
if (source == null)
return;
final List<Resource> resources = new ArrayList<Resource>(source.getResources());
for (Resource resource : resources) {
traverseResource(visitor, resource);
}
}
public static void traverseResource(EMFVisitor visitor, Resource source) {
if (source == null)
return;
if (visitor.pruneSubtrees(source))
return;
final EList<EObject> contents = source.getContents();
for (EObject eObject : contents) {
traverseObject(visitor, eObject);
}
}
public static void traverseObject(EMFVisitor visitor, EObject source) {
if (source == null)
return;
if (source.eIsProxy()) {
if (visitor.forceProxyResolution())
source = EcoreUtil.resolve(source, source);
if (source.eIsProxy()) {
visitor.visitUnresolvableProxyObject(source);
return;
}
}
visitor.visitElement(source);
for (EStructuralFeature feature : source.eClass().getEAllStructuralFeatures()) {
if (untraversableDirectly(feature))
continue;
final boolean visitorPrunes = visitor.pruneFeature(feature);
if (visitorPrunes && !unprunableFeature(visitor, source, feature))
continue;
if (feature.isMany()) {
Collection<?> targets = (Collection<?>) source.eGet(feature);
for (Object target : targets) {
traverseFeatureInternal(visitor, source, feature, target, visitorPrunes);
}
} else {
Object target = source.eGet(feature);
if (target != null)
traverseFeatureInternal(visitor, source, feature, target, visitorPrunes);
}
}
}
private static boolean unprunableFeature(EMFVisitor visitor, EObject source, EStructuralFeature feature) {
return (feature instanceof EAttribute && EcorePackage.eINSTANCE.getEFeatureMapEntry().equals(
((EAttribute) feature).getEAttributeType()))
|| (feature instanceof EReference && ((EReference) feature).isContainment() && (!visitor
.pruneSubtrees(source) || ((EReference) feature).getEOpposite() != null));
}
public static void traverseFeature(EMFVisitor visitor, EObject source, EStructuralFeature feature, Object target) {
if (target == null)
return;
if (untraversableDirectly(feature))
return;
traverseFeatureInternalSimple(visitor, source, feature, target);
}
private static void traverseFeatureInternalSimple(EMFVisitor visitor, EObject source, EStructuralFeature feature,
Object target) {
final boolean visitorPrunes = visitor.pruneFeature(feature);
if (visitorPrunes && !unprunableFeature(visitor, source, feature))
return;
traverseFeatureInternal(visitor, source, feature, target, visitorPrunes);
}
/**
* @pre target != null
*/
private static void traverseFeatureInternal(EMFVisitor visitor, EObject source, EStructuralFeature feature,
Object target, boolean visitorPrunes) {
if (feature instanceof EAttribute) {
if (!visitorPrunes)
visitor.visitAttribute(source, (EAttribute) feature, target);
if (target instanceof FeatureMap.Entry) { // emulated derived edge based on FeatureMap
Entry entry = (FeatureMap.Entry) target;
final EStructuralFeature emulated = entry.getEStructuralFeature();
final Object emulatedTarget = entry.getValue();
emulateUntraversableFeature(visitor, source, emulated, emulatedTarget);
}
} else if (feature instanceof EReference) {
EReference reference = (EReference) feature;
EObject targetObject = (EObject) target;
if (targetObject.eIsProxy()) {
if (visitor.forceProxyResolution())
targetObject = EcoreUtil.resolve(targetObject, source);
if (targetObject.eIsProxy()) {
visitor.visitUnresolvableProxyFeature(source, reference, targetObject);
return;
}
}
if (reference.isContainment()) {
if (!visitorPrunes)
visitor.visitInternalContainment(source, reference, targetObject);
if (!visitor.pruneSubtrees(source))
traverseObject(visitor, targetObject);
final EReference opposite = reference.getEOpposite();
if (opposite != null) { // emulated derived edge based on container opposite
emulateUntraversableFeature(visitor, targetObject, opposite, source);
}
} else {
// if (containedElements.contains(target))
if (!visitorPrunes)
visitor.visitNonContainmentReference(source, reference, targetObject);
}
// else
// visitor.visitExternalReference(source, reference, targetObject);
}
}
/**
* Emulates a derived edge, if it is not visited otherwise
*
* @pre target != null
*/
private static void emulateUntraversableFeature(EMFVisitor visitor, EObject source,
final EStructuralFeature emulated, final Object target) {
if (untraversableDirectly(emulated))
traverseFeatureInternalSimple(visitor, source, emulated, target);
}
}