/*
* Copyright (c) 2005, 2008 Borland Software Corporation
*
* 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:
* Radek Dvorak (Borland) - initial API and implementation
* Artem Tikhomirov (Borland) - refactoring
*/
package org.eclipse.gmf.internal.validate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.codegen.ecore.genmodel.GenClassifier;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticChain;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EModelElement;
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.ETypedElement;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.gmf.internal.validate.IDefElementProvider.ContextProvider;
import org.eclipse.gmf.internal.validate.IDefElementProvider.StringValProvider;
import org.eclipse.gmf.internal.validate.IDefElementProvider.TypeProvider;
import org.eclipse.gmf.internal.validate.expressions.IModelExpression;
import org.eclipse.gmf.internal.validate.expressions.IModelExpressionProvider;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.osgi.util.NLS;
public class DefUtils {
/**
* Prevent from instantation
*/
private DefUtils() {
}
public static class FeatureValProvider extends AbstractProvider implements StringValProvider {
private EStructuralFeature feature;
public FeatureValProvider(EStructuralFeature feature) {
if(feature == null) {
throw new IllegalArgumentException("null feature passed"); //$NON-NLS-1$
}
this.feature = feature;
setStatus(validateFeature(feature));
}
public String getValue(EObject contextInstance) {
if(!feature.getEContainingClass().isSuperTypeOf(contextInstance.eClass())) {
throw new IllegalArgumentException("Invalid context instance"); //$NON-NLS-1$
}
if(!getStatus().isOK()) {
return null;
}
Object value = contextInstance.eGet(feature);
assert value == null || value instanceof String;
return (String)contextInstance.eGet(feature);
}
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(feature.getEContainingClass().getName()).append("::").append(feature.getName()); //$NON-NLS-1$
return buf.toString();
}
private static IStatus validateFeature(EStructuralFeature feature) {
if(feature.getEType() == EcorePackage.eINSTANCE.getEString()) {
return Status.OK_STATUS;
}
String requiredType = LabelProvider.INSTANCE.getObjectLabel(
EcorePackage.eINSTANCE.getEString());
String foundType = LabelProvider.INSTANCE.getObjectLabel(feature.getEType());
String message = MessageFormat.format(
Messages.incompatibleTypes,
new Object[] { requiredType, foundType });
return GMFValidationPlugin.createStatus(IStatus.ERROR,
StatusCodes.INVALID_EXPRESSION_TYPE, message, null);
}
}
private abstract static class AbstractProvider implements IDefElementProvider {
private IStatus status = Status.OK_STATUS;
AbstractProvider() {}
protected void setStatus(IStatus status) {
if(status == null) {
throw new IllegalArgumentException("Null status"); //$NON-NLS-1$
}
this.status = status;
}
public IStatus getStatus() {
return status;
}
}
private static class ExpressionBasedProvider extends AbstractProvider implements IDefElementProvider {
private IModelExpression expression;
private EClassifier requiredType;
protected ExpressionBasedProvider(IModelExpression expression, EClassifier requiredType) {
this.expression = expression;
this.requiredType = requiredType;
if(!expression.getStatus().isOK()) {
setStatus(expression.getStatus());
} else {
EClassifier queryResultType = expression.getResultType();
assert queryResultType != null : "Expression must have type"; //$NON-NLS-1$
assert requiredType != null : "Required type must be defined"; //$NON-NLS-1$
if(!checkTypeAssignmentCompatibility(requiredType, queryResultType)) {
setStatus(getIncompatibleTypesStatus(requiredType, queryResultType));
}
}
}
protected IModelExpression getExpression() {
return expression;
}
protected EClassifier getRequiredType() {
return requiredType;
}
public Object evaluate(EObject resolutionContext) {
return expression.evaluate(resolutionContext);
}
public String toString() {
return expression.toString();
}
}
// Remark: Backward compatibility for String expression as qualified name of EClassifier
// With MDT OCL, TypeLiteral expression can be used, having the its referred type as
// the result of OCL expression evaluation
// TODO - Can be replaced by <code>ExpressionContextProvider</code> as soon as definitions in GMF models
// using OCL to define contexts do not use qualified name (String)
// @constraintsMeta(def="context", ocl="'ecore::EDoubleObject'") => @constraintsMeta(def="context", ocl="ecore::EDoubleObject")
public static class LookupByNameContextProvider extends ExpressionBasedProvider implements ContextProvider {
private Map<Object, EClassifier> contextCache = new HashMap<Object, EClassifier>(5);
private EPackage.Registry registry;
public LookupByNameContextProvider(IModelExpression expression, EPackage.Registry registry) {
super(expression, EcorePackage.eINSTANCE.getEString());
this.registry = (registry != null) ? registry : EPackage.Registry.INSTANCE;
}
public EClassifier getContextClassifier(EObject resolutionContext) {
if(getStatus().isOK()) {
Object typeNameObj = evaluate(resolutionContext);
if(typeNameObj instanceof String) {
if(contextCache.containsKey(typeNameObj)) {
return contextCache.get(typeNameObj);
}
String[] typeName = ((String)typeNameObj).split("::"); //$NON-NLS-1$
ArrayList<String> nameSeq = new ArrayList<String>(Arrays.asList(typeName));
if(typeName.length > 1) {
EClassifier contextClassifier = new EcoreEnvironmentFactory(registry).createEnvironment().lookupClassifier(nameSeq);
contextCache.put(typeNameObj, contextClassifier);
return contextClassifier;
}
}
}
return null;
}
}
public static class GenClassifierContextAdapter extends ExpressionBasedProvider implements ContextProvider {
public static boolean isGenClassifier(EClassifier eClassifier) {
if(eClassifier.getEPackage() == null ||
!GenModelPackage.eNS_URI.equals(eClassifier.getEPackage().getNsURI())) {
return false;
}
return GenModelPackage.eINSTANCE.getGenClass().getName().equals(eClassifier.getName()) ||
GenModelPackage.eINSTANCE.getGenClassifier().getName().equals(eClassifier.getName());
}
public GenClassifierContextAdapter(IModelExpression expression) {
super(expression, expression.getResultType());
if(!isGenClassifier(expression.getResultType())) {
// FIXME? created status is not assigned anywhere!
getIncompatibleTypesStatus(GenModelPackage.eINSTANCE.getGenClassifier(), expression.getResultType());
}
}
public EClassifier getContextClassifier(EObject resolutionContext) {
if(!getStatus().isOK()) {
return null;
}
GenClassifier genClassifier = (GenClassifier)super.evaluate(resolutionContext);
return genClassifier != null ? genClassifier.getEcoreClassifier() : null;
}
}
public static class ExpressionContextProvider extends ExpressionBasedProvider implements ContextProvider {
public ExpressionContextProvider(IModelExpression expression) {
super(expression, EcorePackage.eINSTANCE.getEClassifier());
}
public EClassifier getContextClassifier(EObject resolutionContext) {
return getStatus().isOK() ? (EClassifier)super.evaluate(resolutionContext) : null;
}
}
public static class ReferencedContextProvider extends AbstractProvider implements ContextProvider {
private EReference contextRef;
private Map<EClass, ContextProvider> referencedContexts = Collections.emptyMap();
public ReferencedContextProvider(EClass context, String referenceName, IModelExpressionProvider oclExprProvider, EPackage.Registry registry) {
if(context == null) {
throw new IllegalArgumentException("null context EClass"); //$NON-NLS-1$
}
if(referenceName != null) {
EStructuralFeature eFeature = context.getEStructuralFeature(referenceName);
if(eFeature instanceof EReference) {
this.contextRef = (EReference) eFeature;
} else {
String message = NLS.bind(Messages.def_NoEReferenceFoundByName,
referenceName, LabelProvider.INSTANCE.getObjectLabel(context));
setStatus(GMFValidationPlugin.createStatus(IStatus.ERROR, -1, message, null));
}
} else {
String message = Messages.def_NoEReferenceInCtxBinding;
setStatus(GMFValidationPlugin.createStatus(IStatus.ERROR, -1, message, null));
}
if(contextRef != null) {
EClass referencedClass = contextRef.getEReferenceType();
List<EClass> subTypes = getSubTypes(getRootEPackage(referencedClass.getEPackage()), referencedClass, new LinkedList<EClass>());
referencedContexts = new HashMap<EClass, ContextProvider>(5);
for (Iterator<EClass> it = subTypes.iterator(); it.hasNext();) {
EClass nextClass = it.next();
ContextProvider referencedContext = DefUtils.getContextClass(nextClass, oclExprProvider, null, registry);
if(referencedContext != null) {
referencedContexts.put(nextClass, referencedContext);
it.remove();
}
}
// perform coverage check
List<IStatus> statuses = new ArrayList<IStatus>();
for (Iterator<EClass> it = subTypes.iterator(); it.hasNext();) {
EClass nextClass = it.next();
if(getProvider(nextClass) == null && !(nextClass.isInterface() || nextClass.isAbstract())) {
String message = NLS.bind(Messages.def_NoCtxInProviderForCtxBinding,
LabelProvider.INSTANCE.getObjectLabel(nextClass),
LabelProvider.INSTANCE.getFeatureLabel(contextRef));
statuses.add(GMFValidationPlugin.createStatus(IStatus.ERROR, -1, message, null));
}
}
if(statuses.size() == 1) {
setStatus(statuses.get(0));
} else {
setStatus(new MultiStatus(GMFValidationPlugin.getPluginId(), -1,
statuses.toArray(new IStatus[statuses.size()]),
Messages.def_MissingCtxDefInReferencedCtxProviders, null));
}
}
}
public EClassifier getContextClassifier(EObject resolutionContext) {
if(getStatus().isOK()) {
if(!contextRef.getEContainingClass().isSuperTypeOf(resolutionContext.eClass())) {
throw new IllegalArgumentException("Requires instance of :" + contextRef.getEContainingClass()); //$NON-NLS-1$
}
assert resolutionContext.eClass().getEStructuralFeature(contextRef.getName()) != null;
Object referencedEntity = resolutionContext.eGet(contextRef);
if(referencedEntity instanceof EObject) {
EObject eObject = (EObject)referencedEntity;
ContextProvider provider = getProvider(eObject.eClass());
return (provider != null && provider.getStatus().isOK()) ?
provider.getContextClassifier(eObject) : null;
}
}
return null;
}
private ContextProvider getProvider(EClass contextProviderEClass) {
ContextProvider provider = referencedContexts.get(contextProviderEClass);
if(provider == null) {
for (EClass nextClass : contextProviderEClass.getESuperTypes()) {
ContextProvider nextProvider = referencedContexts.get(nextClass);
if(nextProvider != null) {
return nextProvider;
}
}
}
return provider;
}
}
public static class ExpressionTypeProvider extends ExpressionBasedProvider implements TypeProvider {
public ExpressionTypeProvider(IModelExpression expression) {
super(expression, getRequiredResultType(expression));
}
public EClassifier getType(EObject context) {
if(!getStatus().isOK()) {
return null;
}
Object val = evaluate(context);
if(val instanceof ETypedElement) {
return ((ETypedElement)val).getEType();
} else if(val instanceof EClassifier) {
return (EClassifier)val;
}
assert false;
return null;
}
public boolean isAssignable(EObject context, IModelExpression expression) {
if (hasTypedElement()) {
// [artem] not sure I see connection between #hasTypedElement and #getTypedElement
return expression.isAssignableToElement(getTypedElement(context));
} else {
return expression.isAssignableTo(getType(context));
}
}
private boolean hasTypedElement() {
if(getStatus().isOK()) {
EClassifier requiredType = getCanonicalEClassifier(getRequiredType());
return requiredType instanceof EClass &&
EcorePackage.eINSTANCE.getETypedElement().isSuperTypeOf((EClass)requiredType);
}
return false;
}
private ETypedElement getTypedElement(EObject context) {
if(!getStatus().isOK()) {
return null;
}
Object val = evaluate(context);
if(val instanceof ETypedElement) {
return (ETypedElement)val;
}
return null;
}
private static EClassifier getRequiredResultType(IModelExpression expression) {
EClassifier type = expression.getResultType();
if(type instanceof EClass) {
if(DefUtils.isEcorePackageClassifier(type)) {
EClassifier canonicalClassifier = DefUtils.getCanonicalEcorePackageClassifier(type);
assert canonicalClassifier instanceof EClass;
EClass canonicalClass = (EClass)canonicalClassifier;
if(EcorePackage.eINSTANCE.getETypedElement().isSuperTypeOf(canonicalClass) ||
EcorePackage.eINSTANCE.getEClassifier().isSuperTypeOf(canonicalClass)) {
return type;
}
}
}
return EcorePackage.eINSTANCE.getEClassifier();
}
}
public static class TypedElementProvider extends AbstractProvider implements TypeProvider {
private EStructuralFeature feature;
public TypedElementProvider(EStructuralFeature feature) {
this.feature = feature;
}
public EClassifier getType(EObject context) {
return getTypedElement(context).getEType();
}
public boolean isAssignable(EObject context, IModelExpression expression) {
return expression.isAssignableToElement(getTypedElement(context));
}
private ETypedElement getTypedElement(EObject context) {
// [artem] has no idea why return value is certainly ETypedElement
return (ETypedElement)context.eGet(feature);
}
}
public static class ContextTypeAdapter extends AbstractProvider implements TypeProvider {
private ContextProvider ctxProvider;
public ContextTypeAdapter(ContextProvider contextProvider) {
if(contextProvider == null) {
throw new IllegalArgumentException("null contextProvider"); //$NON-NLS-1$
}
this.ctxProvider = contextProvider;
}
public EClassifier getType(EObject resolutionContext) {
return ctxProvider.getContextClassifier(resolutionContext);
}
public boolean isAssignable(EObject context, IModelExpression expression) {
return expression.isAssignableTo(getType(context));
}
}
public static Diagnostic statusToDiagnostic(IStatus status, String diagSource, Object destObj) {
return statusToDiagnostic(status, diagSource, destObj, null);
}
public static DiagnosticChain mergeAndFlatten(Diagnostic diagnostic, DiagnosticChain diagnosticChain) {
List<Diagnostic> children = diagnostic.getChildren();
if(children == null || children.isEmpty()) {
diagnosticChain.add(diagnostic);
} else {
for (Diagnostic next : children) {
mergeAndFlatten(next, diagnosticChain);
}
}
return diagnosticChain;
}
public static DiagnosticChain mergeAndFlatten(IStatus status, String diagSource, Object destObj, DiagnosticChain diagnosticChain) {
Diagnostic diagnostic = statusToDiagnostic(status, diagSource, destObj);
return mergeAndFlatten(diagnostic, diagnosticChain);
}
public static Diagnostic statusToDiagnostic(IStatus status, String diagSource, Object destObj, String prefixMessage) {
int severity = IStatus.INFO;
switch (status.getSeverity()) {
case IStatus.ERROR:
severity = Diagnostic.ERROR;
break;
case IStatus.WARNING:
severity = Diagnostic.WARNING;
break;
case IStatus.INFO:
severity = Diagnostic.INFO;
break;
case IStatus.OK:
severity = Diagnostic.OK;
break;
case IStatus.CANCEL:
severity = Diagnostic.CANCEL;
break;
}
Object[] data = (destObj != null) ? new Object[] { destObj } : new Object[0];
String message = (prefixMessage != null) ? prefixMessage + status.getMessage() : status.getMessage();
BasicDiagnostic diagnostic = new BasicDiagnostic(severity, diagSource, status.getCode(), message, data);
if(status.isMultiStatus()) {
IStatus[] children = status.getChildren();
for (int i = 0; i < children.length; i++) {
diagnostic.add(statusToDiagnostic(children[i], diagSource, destObj, prefixMessage));
}
}
return diagnostic;
}
public static boolean checkTypeAssignmentCompatibility(EClassifier leftClassifier, EClassifier rightClassifier) {
EClassifier left = getCanonicalEClassifier(leftClassifier);
EClassifier right = getCanonicalEClassifier(rightClassifier);
if(left == right) {
return true;
}
if(left instanceof EClass) {
if(right instanceof EClass &&
((EClass)left).isSuperTypeOf(((EClass)right))) {
return true;
}
} else {
Class<?> rightClass = right.getInstanceClass();
Class<?> leftClass = left.getInstanceClass();
if(leftClass != null && rightClass != null && leftClass.isAssignableFrom(rightClass)) {
return true;
}
}
return false;
}
public static IStatus getIncompatibleTypesStatus(EClassifier leftClassifier, EClassifier rightClassifier) {
String message = MessageFormat.format(
Messages.incompatibleTypes,
new Object[] {
LabelProvider.INSTANCE.getObjectLabel(leftClassifier),
LabelProvider.INSTANCE.getObjectLabel(rightClassifier) } );
return GMFValidationPlugin.createStatus(IStatus.ERROR,
StatusCodes.INVALID_EXPRESSION_TYPE, message, null);
}
public static Map.Entry<String, String> findAnnotationDetailEntry(EModelElement eModelElement, String sourceURI, String key, String val) {
for (EAnnotation nextAnnotation : eModelElement.getEAnnotations()) {
if(sourceURI.equals(nextAnnotation.getSource()) && nextAnnotation.getDetails().containsKey(key)) {
for (Map.Entry<String, String> nextEntry : nextAnnotation.getDetails()) {
if(nextEntry.getValue() == val || nextEntry.getKey().equals(key)) {
return nextEntry;
}
}
}
}
return null;
}
public static Map.Entry<String, String> getKeyPrefixAnnotation(EAnnotation annotation, String keyPrefix) {
for (Map.Entry<String, String> nextEntry : annotation.getDetails()) {
if(nextEntry.getKey().startsWith(keyPrefix)) {
return nextEntry;
}
}
return null;
}
public static List<EAnnotation> getAnnotationsWithKeyAndValue(EModelElement eModelElement, String sourceURI, String key, String value) {
ArrayList<EAnnotation> annotations = null;
for (EAnnotation nextAnnotation : eModelElement.getEAnnotations()) {
if(sourceURI.equals(nextAnnotation.getSource())) {
Object detailVal = nextAnnotation.getDetails().get(key);
if((value != null && value.equals(detailVal)) || value == detailVal) {
if(annotations == null) {
annotations = new ArrayList<EAnnotation>(eModelElement.getEAnnotations().size());
}
annotations.add(nextAnnotation);
}
}
}
if (annotations != null) {
return annotations;
}
return Collections.emptyList();
}
private static boolean isEcorePackageClassifier(EClassifier classifier) {
EPackage classifierPackage = classifier.getEPackage();
return EcorePackage.eINSTANCE == classifierPackage ||
(classifierPackage != null && EcorePackage.eINSTANCE.getNsURI().equals(classifierPackage.getNsURI()));
}
public static EClassifier getCanonicalEcorePackageClassifier(EClassifier classifier) {
if(!isEcorePackageClassifier(classifier)) {
return null;
}
return EcorePackage.eINSTANCE.getEClassifier(classifier.getName());
}
public static EClassifier getCanonicalEClassifier(EClassifier classifier) {
EClassifier eCoreCanonical = getCanonicalEcorePackageClassifier(classifier);
return (eCoreCanonical == null) ? classifier : eCoreCanonical;
}
public static ContextProvider getContextClass(EClass resolutionContext, IModelExpressionProvider oclExprProvider, EStructuralFeature bindFeature, EPackage.Registry registry) {
assert bindFeature == null || bindFeature.getEContainingClass().isSuperTypeOf(resolutionContext);
EModelElement annotationTarget = (bindFeature != null) ? (EModelElement) bindFeature : resolutionContext;
EAnnotation ctxAnnotation = annotationTarget.getEAnnotation(Annotations.CONSTRAINTS_META_URI);
if (ctxAnnotation != null && Annotations.Meta.CONTEXT.equals(ctxAnnotation.getDetails().get(Annotations.Meta.DEF_KEY))) {
for (Map.Entry<String, String> nextDetail : ctxAnnotation.getDetails()) {
String key = nextDetail.getKey();
String value = nextDetail.getValue() != null ? nextDetail.getValue() : ""; //$NON-NLS-1$
if (Annotations.Meta.OCL_KEY.equals(key)) {
if (value != null) {
IModelExpression contextEpression = oclExprProvider.createExpression(value, resolutionContext);
EClassifier resultType = contextEpression.getResultType();
if (String.class.equals(resultType.getInstanceClass())) {
return new LookupByNameContextProvider(contextEpression, registry);
}
else if(GenClassifierContextAdapter.isGenClassifier(resultType)) {
return new GenClassifierContextAdapter(contextEpression);
}
// TODO - support in general EClassifier, GenClassifier, or String typeName
// report problem for expressions with any other result type
return new ExpressionContextProvider(contextEpression);
}
} else if (Annotations.Meta.REF.equals(key)) {
return new ReferencedContextProvider(resolutionContext, value, oclExprProvider, registry);
}
}
}
// no context found
return null;
}
/**
* @param ePackage
* scope for searching the sub-types, including sub-packages
* @param superType
* the type of which the sub-types are to be found
* @param foundSubTypes
* placeholder for the collected sub-types
* @return passed <code>foundSubTypes</code> list for convenience
*/
static List<EClass> getSubTypes(EPackage ePackage, EClass superType, List<EClass> foundSubTypes) {
for (EClassifier classifier : ePackage.getEClassifiers()) {
if(classifier instanceof EClass && (superType).isSuperTypeOf((EClass)classifier)) {
foundSubTypes.add((EClass) classifier);
}
}
for (EPackage next : ePackage.getESubpackages()) {
getSubTypes(next, superType, foundSubTypes);
}
return foundSubTypes;
}
static EPackage getRootEPackage(EPackage ePackage) {
EPackage root = ePackage;
for (EPackage parent = ePackage; parent != null; parent = parent.getESuperPackage()) {
root = parent;
}
return root;
}
}