/*
* 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) - [230418] non-containment contexts; refactoring
*/
package org.eclipse.gmf.internal.validate;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
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.common.util.EList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EObjectValidator;
import org.eclipse.gmf.internal.validate.Annotations.Meta;
import org.eclipse.gmf.internal.validate.DefUtils.ContextTypeAdapter;
import org.eclipse.gmf.internal.validate.DefUtils.ExpressionContextProvider;
import org.eclipse.gmf.internal.validate.DefUtils.ExpressionTypeProvider;
import org.eclipse.gmf.internal.validate.DefUtils.LookupByNameContextProvider;
import org.eclipse.gmf.internal.validate.DefUtils.TypedElementProvider;
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.EnvironmentProvider;
import org.eclipse.gmf.internal.validate.expressions.ExpressionProviderRegistry;
import org.eclipse.gmf.internal.validate.expressions.IModelExpression;
import org.eclipse.gmf.internal.validate.expressions.IModelExpressionProvider;
import org.eclipse.gmf.internal.validate.expressions.IParseEnvironment;
import org.eclipse.gmf.internal.validate.expressions.NoProviderExpression;
import org.eclipse.gmf.internal.validate.ocl.OCLExpressionProvider;
import org.eclipse.osgi.util.NLS;
// FIXME: replace all methods with Map<Object, Object> context with dedicated "ValidateSession" class
// that keeps track of all the caching stuff
public class AnnotatedDefinitionValidator extends AbstractValidator implements EValidator {
public AnnotatedDefinitionValidator() {
}
public boolean validate(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context) {
return true;
}
public boolean validate(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
return validate(eObject.eClass(), eObject, diagnostics, context);
}
/**
* @see EObjectValidator#validate(org.eclipse.emf.ecore.EClass, org.eclipse.emf.ecore.EObject, org.eclipse.emf.common.util.DiagnosticChain, java.util.Map)
*/
public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
if(eObject.eClass().getEPackage() == EcorePackage.eINSTANCE) {
if(eObject instanceof EModelElement) {
return validateMetaModel((EModelElement)eObject, diagnostics, context);
}
} else {
return validateModel(eObject, diagnostics, context);
}
return true;
}
protected boolean validateModel(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
EReference[] constrainedFeatures = collectConstrainedFeatures(eObject.eClass());
if (constrainedFeatures == null || constrainedFeatures.length == 0) {
return true;
}
boolean result = true;
for (EReference sf : constrainedFeatures) {
if (!eObject.eIsSet(sf)) {
continue;
}
if (sf.isMany()) {
@SuppressWarnings("unchecked")
List<EObject> constraintObject = (List<EObject>) eObject.eGet(sf, true);
for (EObject o : constraintObject) {
result &= validateInstance(eObject, sf, o, diagnostics, context);
}
} else {
EObject constraintObject = (EObject) eObject.eGet(sf, true);
result = constraintObject != null && validateInstance(eObject, sf, constraintObject, diagnostics, context);
}
}
return result;
}
private boolean validateInstance(EObject context, EReference contextFeature, EObject constraint, DiagnosticChain diag, Map<Object, Object> validationContext) {
ValueSpecDef def = getDefinition(constraint.eClass(), diag, null, validationContext);
if(def == null) {
SubstitutionLabelProvider lp = getLabelProvider(validationContext);
String message = String.format("Object '%s', supposed to be a constraint for feature '%s' of '%s', is missing essential constraint metainformation", lp.getObjectLabel(constraint), lp.getFeatureLabel(contextFeature), lp.getObjectLabel(context));
diag.add(new BasicDiagnostic(Diagnostic.ERROR, DIAGNOSTIC_SOURCE, StatusCodes.INVALID_CONSTRAINT_CONTEXT, message, new Object[] {context} ));
return false;
} else if(!def.isOK()) {
return false;
}
String lang = def.createLanguage(constraint);
if(!(Annotations.Meta.OCL_KEY.equals(lang) || Annotations.REGEXP_KEY.equals(lang) || Annotations.NEG_REGEXP_KEY.equals(lang))) {
// add support for other languages here
return true;
}
ContextData contextData = getContextBinding(context.eClass(), contextFeature, validationContext);
if(contextData == null) {
diag.add(new BasicDiagnostic(
Diagnostic.ERROR, DIAGNOSTIC_SOURCE,
StatusCodes.NO_VALUESPEC_CONTEXT_AVAILABLE,
NLS.bind(Messages.def_NoContextAvailable, getLabelProvider(validationContext).getObjectLabel(context)),
new Object[] { context }));
return false;
}
EClassifier evaluationContextClass = contextData.contextClass.getContextClassifier(context);
if(evaluationContextClass == null) {
String noCtxMessage = contextData.contextClass.getStatus().isOK() ?
NLS.bind(Messages.def_NoContextAvailable, getLabelProvider(validationContext).getObjectLabel(context))
: contextData.contextClass.getStatus().getMessage();
diag.add(new BasicDiagnostic(Diagnostic.ERROR, DIAGNOSTIC_SOURCE,
StatusCodes.NO_VALUESPEC_CONTEXT_AVAILABLE,
noCtxMessage, new Object[] { context } ));
}
String body = def.createBody(constraint);
if(body != null && evaluationContextClass != null) {
// get real environment
IParseEnvironment env = null;
if(contextData.environment != null) {
env = EnvironmentProvider.createParseEnv();
ExternModelImport modelImports = ExternModelImport.getImporter(validationContext);
env.setImportRegistry(modelImports.getPackageRegistry());
for (String varName : contextData.environment.keySet()) {
TypeProvider typeProvider = contextData.environment.get(varName);
EClassifier type = typeProvider.getType(context);
if(type != null) {
// TODO - produce error status as no variable type is available
env.setVariable(varName, type);
}
}
}
IModelExpression expression = getExpression(lang, body, evaluationContextClass, env);
if(!expression.getStatus().isOK()) {
String message = MessageFormat.format(
Messages.invalidExpressionBody,
new Object[] { expression.getBody(),
expression.getStatus().getMessage() });
diag.add(new BasicDiagnostic(
Diagnostic.ERROR, DIAGNOSTIC_SOURCE,
StatusCodes.INVALID_CONSTRAINT_EXPRESSION,
message, new Object[] { context }));
return false;
}
EObject typeResolutionContext = context;
// check type restriction on the given expression
TypeProvider typeProvider = def.getTypeRestriction();
if(typeProvider == null) {
typeProvider = getTypeInfo(contextFeature, context.eClass(), diag, validationContext);
}
if(typeProvider != null && typeProvider.getStatus().isOK() && expression.getResultType() != null) {
if(!typeProvider.isAssignable(typeResolutionContext, expression)) {
EClassifier type = typeProvider.getType(typeResolutionContext);
IStatus s = DefUtils.getIncompatibleTypesStatus(type, expression.getResultType());
diag.add(DefUtils.statusToDiagnostic(s, DIAGNOSTIC_SOURCE, context));
return false;
}
}
}
return true;
}
private static IModelExpression getExpression(String language, String body, EClassifier context, IParseEnvironment env) {
IModelExpressionProvider provider = ExpressionProviderRegistry.getInstance().getProvider(language);
if (provider == null) {
return new NoProviderExpression(language, body, context);
}
return provider.createExpression(body, context, env);
}
public ContextData getContextBinding(EClass contextClass, EStructuralFeature featureToConstraint, Map<Object, Object> validationContext) {
ContextData contextData = getCachedCtxBinding(featureToConstraint, validationContext);
if(contextData != null) {
return contextData;
}
ContextProvider contextProvider = getContextClass(featureToConstraint, validationContext);
if(contextProvider != null) {
ContextData newContextData = new ContextData(contextProvider, getEnvProvider(featureToConstraint, getExpressionFactory(validationContext)));
registerCtxBinding(featureToConstraint, newContextData, validationContext);
if(Trace.shouldTrace(DebugOptions.META_DEFINITIONS)) {
String msgPtn = "[context-def] {0} binding: {1}::{2}"; //$NON-NLS-1$
Trace.trace(MessageFormat.format(msgPtn,
new Object[] {
newContextData.contextClass.toString(),
LabelProvider.INSTANCE.getObjectLabel(contextClass),
LabelProvider.INSTANCE.getFeatureLabel(featureToConstraint)
}));
}
return newContextData;
}
return null;
}
protected boolean validateMetaModel(EModelElement modelElement, DiagnosticChain diagnostics, Map<Object, Object> context) {
EAnnotation annotation = (modelElement instanceof EAnnotation) ? (EAnnotation)modelElement : null;
if(annotation != null) {
if(!Annotations.CONSTRAINTS_META_URI.equals(annotation.getSource())) {
return true;
}
modelElement = annotation.getEModelElement();
if(modelElement == null) {
return true;
}
}
if(modelElement instanceof EStructuralFeature && annotation != null && Meta.CONTEXT.equals(annotation.getDetails().get(Meta.DEF_KEY))) {
EStructuralFeature sfeature = (EStructuralFeature)modelElement;
ContextProvider contextProvider = getContextClass(sfeature, context);
if(contextProvider != null) {
// check extended context environment
getEnvProvider(sfeature, getExpressionFactory(context));
if(!contextProvider.getStatus().isOK()) {
DefUtils.mergeAndFlatten(contextProvider.getStatus(), DIAGNOSTIC_SOURCE, annotation, diagnostics);
return false;
}
}
} else if(modelElement instanceof EClass) {
getDefinition((EClass)modelElement, diagnostics, null, context);
}
return true;
}
private static OCLExpressionProvider getExpressionFactory(Map<Object, Object> validationContext) {
OCLExpressionProvider p = (OCLExpressionProvider) validationContext.get(OCLExpressionProvider.class);
if (p == null) {
validationContext.put(OCLExpressionProvider.class, p = new OCLExpressionProvider());
}
return p;
}
public static ContextProvider getContextClass(EStructuralFeature bindFeature, Map<Object, Object> validationContext) {
EClass resolutionContext = bindFeature.getEContainingClass();
ExternModelImport modelImports = ExternModelImport.getImporter(validationContext);
return DefUtils.getContextClass(resolutionContext, getExpressionFactory(validationContext), bindFeature, modelImports.getPackageRegistry());
}
private static ValueSpecDef getDefinition(EClass metaClass, DiagnosticChain diagnostics, DefData data, Map<Object, Object> context) {
ValueSpecDef definition = findDefinition(metaClass, context);
if(definition != null) {
return definition;
}
if(data == null) {
for (EAnnotation nextAnnotation : metaClass.getEAnnotations()) {
if(Annotations.CONSTRAINTS_META_URI.equals(nextAnnotation.getSource())) {
String val = nextAnnotation.getDetails().get(Meta.DEF_KEY);
if(val != null && (val.equals(Meta.VALUESPEC) || val.equals(Meta.CONSTRAINT))) {
data = new DefData();
data.metaKey = val;
data.defClass = metaClass;
break;
}
}
}
}
EList<EClass> superTypes = metaClass.getESuperTypes();
if(data == null && superTypes.isEmpty()) {
return null;
}
if(data != null) {
OCLExpressionProvider expressionFactory = getExpressionFactory(context);
for (EStructuralFeature nextAttr : metaClass.getEStructuralFeatures()) {
for (EAnnotation annotation : nextAttr.getEAnnotations()) {
if(!Annotations.CONSTRAINTS_META_URI.equals(annotation.getSource())) {
continue;
}
String metaValue = annotation.getDetails().get(Meta.DEF_KEY);
if(data.body == null) {
if(Meta.BODY.equals(metaValue)) {
data.body = new DefUtils.FeatureValProvider(nextAttr);
checkAndReportProblems(data.body, annotation, diagnostics);
}
}
if(data.lang == null) {
if(Meta.LANG.equals(metaValue)) {
data.lang = new DefUtils.FeatureValProvider(nextAttr);
checkAndReportProblems(data.lang, annotation, diagnostics);
}
}
if(data.context == null) {
if(Meta.CONTEXT.equals(metaValue)) {
String ctxExpression = annotation.getDetails().get(Meta.OCL_KEY);
if(ctxExpression != null) {
data.context = new ExpressionContextProvider(expressionFactory.createExpression(ctxExpression, metaClass));
checkAndReportProblems(data.context, annotation, diagnostics);
}
}
}
if(data.type == null) {
if(Meta.TYPE.equals(metaValue)) {
String typeExpr = annotation.getDetails().get(Meta.OCL_KEY);
if(typeExpr != null) {
data.type = new ExpressionTypeProvider(expressionFactory.createExpression(typeExpr, metaClass));
} else {
data.type = new TypedElementProvider(nextAttr);
}
checkAndReportProblems(data.type, annotation, diagnostics);
}
}
} // end of EAttribute annotations iteration
}
if(data.type == null) {
data.type = getTypeInfo(metaClass, metaClass, diagnostics, context);
/* EAnnotation typeAnnotation = DefUtils.getAnnotationWithKey(metaClass, Annotations.CONSTRAINTS_META_URI, Annotations.Meta.OCL_KEY);
if(typeAnnotation != null && Meta.TYPE.equals(typeAnnotation.getDetails().get(Meta.DEF_KEY))) {
String typeExpr = (String)typeAnnotation.getDetails().get(Meta.OCL_KEY);
if(typeExpr != null) {
data.type = new ExpresssionTypeProvider(getExpression(Meta.OCL_KEY, typeExpr, metaClass, context));
checkAndReportProblems(data.type, typeAnnotation, diagnostics);
}
}*/
}
if(data.body != null) {
definition = data.createDefinition();
assert data.defClass != null;
registerDefinition(data.defClass, definition, context);
return definition;
}
}
for (EClass superClass : superTypes) {
ValueSpecDef inheritedDef = getDefinition(superClass, diagnostics, data, context);
if(inheritedDef != null) {
if(data == null) {
data = new DefData();
}
data.inheritFrom(metaClass, inheritedDef);
registerDefinition(data.defClass, data.createDefinition(), context);
return inheritedDef;
}
}
if(data != null) {
if(data.body == null) {
String message = MessageFormat.format(Messages.def_MissingBodyAnnotation, new Object[] { LabelProvider.INSTANCE.getObjectLabel(metaClass) });
// report missing body
diagnostics.add(new BasicDiagnostic(Diagnostic.ERROR, DIAGNOSTIC_SOURCE, StatusCodes.MISSING_VALUESPEC_BODY_ANNOTATION, message, new Object[] { metaClass }));
return null;
}
}
return null;
}
private static TypeProvider getTypeInfo(EModelElement typeAnnotationSource, EClass resolutionContext, DiagnosticChain diagnostics, Map<Object, Object> validationContext) {
TypeProvider typeProvider = null;
List<EAnnotation> annotations = DefUtils.getAnnotationsWithKeyAndValue(typeAnnotationSource, Annotations.CONSTRAINTS_META_URI, Annotations.Meta.DEF_KEY, Annotations.Meta.TYPE);
EAnnotation typeAnnotation = annotations.isEmpty() ? null : annotations.get(0);
final OCLExpressionProvider exprFactory = getExpressionFactory(validationContext);
final ExternModelImport modelImports = ExternModelImport.getImporter(validationContext);
if(typeAnnotation != null && Meta.TYPE.equals(typeAnnotation.getDetails().get(Meta.DEF_KEY))) {
String typeExprBody = typeAnnotation.getDetails().get(Meta.OCL_KEY);
if(typeExprBody != null) {
IModelExpression typeExpr = exprFactory.createExpression(typeExprBody, resolutionContext);
boolean usesTypeName = typeExpr.getStatus().isOK() && String.class.equals(typeExpr.getResultType().getInstanceClass());
typeProvider = (usesTypeName) ?
new ContextTypeAdapter(new LookupByNameContextProvider(typeExpr, modelImports.getPackageRegistry())) :
new ExpressionTypeProvider(typeExpr);
checkAndReportProblems(typeProvider, typeAnnotation, diagnostics);
}
}
return typeProvider;
}
private static boolean checkAndReportProblems(IDefElementProvider defElementProvider, EObject destination, DiagnosticChain diagnostics) {
if(!defElementProvider.getStatus().isOK()) {
diagnostics.add(DefUtils.statusToDiagnostic(defElementProvider.getStatus(), DIAGNOSTIC_SOURCE, destination));
return false;
}
return true;
}
private static Map<String, TypeProvider> getEnvProvider(EStructuralFeature contextBindFeature, OCLExpressionProvider exprFactory) {
List<EAnnotation> varDefs = DefUtils.getAnnotationsWithKeyAndValue(
contextBindFeature, Annotations.CONSTRAINTS_META_URI,
Annotations.Meta.DEF_KEY, Annotations.Meta.VARIABLE);
if(varDefs.isEmpty()) {
return null;
}
Map<String, TypeProvider> env = null;
for (EAnnotation nextVarAnnotation : varDefs) {
TypeProvider type = null;
String typePrefix = Annotations.Meta.TYPE + "."; //$NON-NLS-1$
Map.Entry<String, String> typeExpression = DefUtils.getKeyPrefixAnnotation(nextVarAnnotation, typePrefix);
if(typeExpression != null) {
String body = typeExpression.getValue();
if(body == null) {
// TODO - report missing var type status
} else {
IModelExpression expression = exprFactory.createExpression(body, contextBindFeature.getEContainingClass());
type = new DefUtils.ExpressionTypeProvider(expression);
}
} else {
// TODO - report missing var type status
}
String name = nextVarAnnotation.getDetails().get(Annotations.Meta.NAME);
if(name == null) {
//TODO - report missing var name status
continue;
}
if(env == null) {
env = new HashMap<String, TypeProvider>();
}
env.put(name, type);
}
return env;
}
private static void registerDefinition(EClass eClass, ValueSpecDef definition, Map<Object, Object> context) {
assert definition != null;
assert eClass != null;
@SuppressWarnings("unchecked")
Map<EClass, ValueSpecDef> defMap = (Map<EClass, ValueSpecDef>) context.get(ValueSpecDef.class);
if(defMap == null) {
defMap = new HashMap<EClass, ValueSpecDef>();
context.put(ValueSpecDef.class, defMap);
}
defMap.put(eClass, definition);
}
private static ValueSpecDef findDefinition(EClass eClass, Map<Object, Object> context) {
Map<?,?> defMap = (Map<?,?>)context.get(ValueSpecDef.class);
return (defMap != null) ? (ValueSpecDef) defMap.get(eClass) : null;
}
private static class DefData {
String metaKey;
EClass defClass;
StringValProvider body;
StringValProvider lang;
TypeProvider type;
ContextProvider context;
public DefData() {}
ValueSpecDef createDefinition() {
assert body != null;
ValueSpecDef valueSpecDef = Meta.CONSTRAINT.equals(metaKey) ?
new ConstraintDef(body, lang) :
new ValueSpecDef(body, lang, type);
if(Trace.shouldTrace(DebugOptions.META_DEFINITIONS)) {
String msgPtn = "[{0}] {1} type: {2}"; //$NON-NLS-1$
Trace.trace( MessageFormat.format(msgPtn, new Object[] { metaKey, defClass.getName(), type }));
}
return valueSpecDef;
}
void inheritFrom(EClass valueSpecEClass, ValueSpecDef superDef) {
defClass = valueSpecEClass;
if(body == null) {
body = superDef.getBody();
}
if(lang == null) {
lang = superDef.getLang();
}
if(type == null) {
type = superDef.getTypeRestriction();
}
}
}
private static class ContextData {
final ContextProvider contextClass;
final Map<String, TypeProvider> environment;
public ContextData(ContextProvider contextProvider, Map<String, TypeProvider> environment) {
this.contextClass = contextProvider;
this.environment = environment;
}
}
private static ContextData getCachedCtxBinding(EModelElement modelElement, Map<Object, Object> context) {
if(context != null) {
Map<?,?> bindMap = (Map<?,?>)context.get(ContextProvider.class);
if(bindMap != null) {
return (ContextData) bindMap.get(modelElement);
}
}
if(Trace.shouldTrace(DebugOptions.DEBUG)) {
Trace.trace("Performance warning: Validation should run in a context for caching"); //$NON-NLS-1$
}
return null;
}
private static void registerCtxBinding(EStructuralFeature contextDefOwner, ContextData contextData, Map<Object, Object> context) {
if(context != null) {
@SuppressWarnings("unchecked")
Map<EStructuralFeature, ContextData> bindMap = (Map<EStructuralFeature, ContextData>)context.get(ContextProvider.class);
if(bindMap == null) {
bindMap = new HashMap<EStructuralFeature, ContextData>();
context.put(ContextProvider.class, bindMap);
}
bindMap.put(contextDefOwner, contextData);
}
}
// may cache features per class, but doesn't seem too expensive to calculate 'em
private static EReference[] collectConstrainedFeatures(EClass eClass) {
LinkedList<EReference> result = new LinkedList<EReference>();
for (EReference sf : eClass.getEAllReferences()) {
for (EAnnotation a: sf.getEAnnotations()) {
if (Annotations.CONSTRAINTS_META_URI.equals(a.getSource()) && Annotations.Meta.CONTEXT.equals(a.getDetails().get(Annotations.Meta.DEF_KEY))) {
result.add(sf);
break; // recognize no more than one def="context"
}
}
}
if (result.isEmpty()) {
return null;
}
return result.toArray(new EReference[result.size()]);
}
}