blob: a7a76d0be1065502fafd2248a6ebd41816d53c3a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2015 Willink Transformations and others.
* 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:
* E.D.Willink - initial API and implementation
* E.D.Willink (Obeo) - Bug 416287 - tuple-valued constraints
* E.D.Willink. M. Rostren (Obeo) - Bug 425830 - single constraint API
*******************************************************************************/
package org.eclipse.ocl.pivot.internal.validation;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
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.Monitor;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EObjectValidator;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.Constraint;
import org.eclipse.ocl.pivot.ExpressionInOCL;
import org.eclipse.ocl.pivot.LanguageExpression;
import org.eclipse.ocl.pivot.Model;
import org.eclipse.ocl.pivot.Type;
import org.eclipse.ocl.pivot.Variable;
import org.eclipse.ocl.pivot.evaluation.AbstractConstraintEvaluator;
import org.eclipse.ocl.pivot.evaluation.EvaluationVisitor;
import org.eclipse.ocl.pivot.evaluation.ModelManager;
import org.eclipse.ocl.pivot.internal.manager.PivotMetamodelManager;
import org.eclipse.ocl.pivot.internal.messages.PivotMessagesInternal;
import org.eclipse.ocl.pivot.internal.utilities.EnvironmentFactoryInternal;
import org.eclipse.ocl.pivot.internal.utilities.PivotUtilInternal;
import org.eclipse.ocl.pivot.utilities.EnvironmentFactory;
import org.eclipse.ocl.pivot.utilities.LabelUtil;
import org.eclipse.ocl.pivot.utilities.ParserException;
import org.eclipse.ocl.pivot.utilities.PivotUtil;
import org.eclipse.ocl.pivot.utilities.StringUtil;
import org.eclipse.ocl.pivot.validation.ComposedEValidator;
import org.eclipse.ocl.pivot.values.InvalidValueException;
/**
* A PivotEObjectValidator augments EValidator.Registry.INSTANCE validation by validation of
* additional Pivot-defined invariants.
*
* Since there is no per-ResourceSet EValidator.Registry it is necessary for the additional
* functionality for a particular EPackage to be provided by displacing the global entry into
* PivotEObjectValidator.eValidators and installing PivotEObjectValidator.INSTANCE in its stead.
*
* When validation occurs, the static INSTANCE first invokes the displaced functionality and
* then looks for a ValidationAdapter in the ResourceSet for the object to be validated.
* This ValidationAdapter is only available if the ResourceSet is for an application for which
* Pivot invariants were defined. Other applications see only a small overhead in their
* processing time.
*/
public class PivotEObjectValidator implements EValidator
{
/**
* A ValidationAdapter is installed in the ResourceSet of applications that register for additional
* PIvot-defined constraints. The standard validation is performed by PivotEObjectValidator.INSTANCE
* before additional functionality is provided by the ValidationAdapter.
*
* For non-Pivot applications the ValidationAdapter adapts the ResourceSet containing the validatable
* (Ecore) instances. Validation is invoked for validatable (Ecore) instances.
*
* For Pivot applications the ValidationAdapter adapts the ResourceSet containing the validatable
* metamodel elements. Validation is invoked for validatable (Pivot) elements so a redirection via
* the MetamodelManager is needed to find the ValidationAdapter on the externalResourceSet.
*/
public static class ValidationAdapter extends AdapterImpl
{
public static ValidationAdapter findAdapter(@NonNull ResourceSet resourceSet) {
for (Adapter adapter : resourceSet.eAdapters()) {
if (adapter instanceof ValidationAdapter) {
return (ValidationAdapter)adapter;
}
}
EnvironmentFactory environmentFactory = PivotUtilInternal.findEnvironmentFactory(resourceSet);
if (environmentFactory != null) {
ResourceSet externalResourceSet = environmentFactory.getResourceSet();
for (Adapter adapter : externalResourceSet.eAdapters()) {
if (adapter instanceof ValidationAdapter) {
return (ValidationAdapter)adapter;
}
}
}
return null;
}
protected final @NonNull EnvironmentFactoryInternal environmentFactory;
public ValidationAdapter(@NonNull EnvironmentFactoryInternal environmentFactory) {
this.environmentFactory = environmentFactory;
}
public @NonNull EnvironmentFactoryInternal getEnvironmentFactory() {
return environmentFactory;
}
/**
* Validate all of eClassifier's constraints for object, appending warnings and at most one error to diagnostics
* using context to elaborate the validation context.
*/
@Deprecated // Temporary internal API preservation for Mars RC3
public boolean validate(@NonNull EClassifier eClassifier, @Nullable Object object, @Nullable DiagnosticChain diagnostics, @Nullable Map<Object, Object> context) {
return validate(eClassifier, object, null, diagnostics, context);
}
public boolean validate(@NonNull EClassifier eClassifier, @Nullable Object object, @Nullable List<Model> complementingModels,
@Nullable DiagnosticChain diagnostics, @Nullable Map<Object, Object> context) {
boolean allOk = true;
PivotMetamodelManager metamodelManager = environmentFactory.getMetamodelManager();
Type type = metamodelManager.getASOfEcore(Type.class, eClassifier);
if (type != null) {
for (Constraint constraint : metamodelManager.getAllInvariants(type)) {
if (constraint != null) {
if (complementingModels != null) {
Model containingModel = PivotUtil.getContainingModel(constraint);
if (!complementingModels.contains(containingModel)) {
continue;
}
}
Diagnostic diagnostic = validate(constraint, object, context);
if (diagnostic != null) {
if (diagnostics != null) {
diagnostics.add(diagnostic);
}
allOk = false;
if (diagnostic.getSeverity() == Diagnostic.ERROR) {
return allOk; // Generate many warnings but only one error
}
}
}
}
}
return allOk;
}
/**
* Validate constraint for object using context to elaborate the validation context.
* Returns null for no problem or a warning/error severity diagnostic for a problem.
*/
public @Nullable Diagnostic validate(final @NonNull Constraint constraint, final @Nullable Object object, final @Nullable Map<Object, Object> context) {
LanguageExpression specification = constraint.getOwnedSpecification();
if (specification == null) {
return null;
}
if (specification.getBody() == null) { // May be null for declations of hand coded Java
return null;
}
// if ((specification.getBodyExpression() == null) && (specification.getBody().size() <= 0)) { // May be null for declations of hand coded Java
// return null;
// }
final PivotMetamodelManager metamodelManager = environmentFactory.getMetamodelManager();
ExpressionInOCL query;
try {
query = metamodelManager.parseSpecification(specification);
} catch (ParserException e) {
String message = e.getLocalizedMessage();
return new BasicDiagnostic(Diagnostic.ERROR, EObjectValidator.DIAGNOSTIC_SOURCE, 0, message, new Object [] { object });
}
Variable contextVariable = query.getOwnedContext();
if (contextVariable == null) {
return null;
}
// OCLExpression bodyExpression = query.getBodyExpression();
// if (bodyExpression == null) { // May be null for declations of hand coded Java
// return null;
// }
ModelManager oldModelManager = null;
if (context != null) {
oldModelManager = (ModelManager) context.get(ModelManager.class);
}
EvaluationVisitor evaluationVisitor = environmentFactory.createEvaluationVisitor(object, query, oldModelManager);
if (context != null) {
ModelManager newModelManager = evaluationVisitor.getModelManager();
if (newModelManager != oldModelManager) {
context.put(ModelManager.class, newModelManager);
}
Object monitor = context.get(Monitor.class);
if (monitor instanceof Monitor) {
evaluationVisitor.setMonitor((Monitor) monitor);
}
}
AbstractConstraintEvaluator<Diagnostic> constraintEvaluator = new AbstractConstraintEvaluator<Diagnostic>(query)
{
@Override
protected String getObjectLabel() {
Type type = PivotUtil.getContainingType(constraint);
Type primaryType = type != null ? metamodelManager.getPrimaryType(type) : null;
EObject eTarget = primaryType != null ? primaryType.getESObject() : null;
EClassifier eClassifier = eTarget instanceof EClassifier ? (EClassifier)eTarget : null;
return LabelUtil.getLabel(eClassifier, object, context);
}
@Override
protected Diagnostic handleExceptionResult(@NonNull Throwable e) {
String message = StringUtil.bind(PivotMessagesInternal.ValidationConstraintException_ERROR_,
getConstraintTypeName(), getConstraintName(), getObjectLabel(), e);
return new BasicDiagnostic(Diagnostic.ERROR, EObjectValidator.DIAGNOSTIC_SOURCE, 0, message, new Object [] { object });
}
@Override
protected Diagnostic handleFailureResult(@Nullable Object result) {
String message = getConstraintResultMessage(result);
int severity = getConstraintResultSeverity(result);
return new BasicDiagnostic(severity, EObjectValidator.DIAGNOSTIC_SOURCE, 0, message, new Object [] { object });
}
@Override
protected Diagnostic handleInvalidExpression(@NonNull String message) {
return new BasicDiagnostic(Diagnostic.ERROR, EObjectValidator.DIAGNOSTIC_SOURCE, 0, message, new Object [] { object });
}
@Override
protected Diagnostic handleInvalidResult(@NonNull InvalidValueException e) {
String message = StringUtil.bind(PivotMessagesInternal.ValidationResultIsInvalid_ERROR_,
getConstraintTypeName(), getConstraintName(), getObjectLabel(), e.getLocalizedMessage());
return new BasicDiagnostic(Diagnostic.ERROR, EObjectValidator.DIAGNOSTIC_SOURCE, 0, message, new Object [] { object });
}
@Override
protected Diagnostic handleSuccessResult() {
return null;
}
};
Diagnostic diagnostic = constraintEvaluator.evaluate(evaluationVisitor);
// if (diagnostic != null) { // FIXME Debugging
// constraintEvaluator.evaluate(evaluationVisitor);
// }
return diagnostic;
}
}
/**
* The static instance that is installed in the EValidator.Registry.INSTANCE to compose
* Pivot validation with whatever other validation was installed.
*/
private static final @NonNull PivotEObjectValidator INSTANCE = new PivotEObjectValidator();
/**
* Install Complete OCL validation support in resourceSet for metamodelManager.
*/
public static @NonNull ValidationAdapter install(@NonNull ResourceSet resourceSet, @NonNull EnvironmentFactoryInternal environmentFactory) {
ValidationAdapter validationAdapter = ValidationAdapter.findAdapter(resourceSet);
if (validationAdapter != null) {
if (validationAdapter.getEnvironmentFactory() != environmentFactory) {
throw new IllegalArgumentException("Inconsistent EnvironmentFactory");
}
}
else {
validationAdapter = new ValidationAdapter(environmentFactory);
resourceSet.eAdapters().add(validationAdapter);
}
return validationAdapter;
}
/**
* Install Pivot-defined validation support for ePackage.
*/
@Deprecated // Temporary internal API preservation for Mars RC3
public static synchronized void install(@NonNull EPackage ePackage) {
install(ePackage, null);
}
public static synchronized void install(@NonNull EPackage ePackage, @Nullable List<Model> complementingModels) {
ComposedEValidator composedEValidator = ComposedEValidator.install(ePackage);
if ((complementingModels == null) || complementingModels.isEmpty()) {
composedEValidator.addChild(INSTANCE);
}
else {
composedEValidator.addChild(new PivotEObjectValidator(complementingModels));
}
}
/**
* Return the user's ResourceSet, preferably as a data element of the diagnostics, corresponding to
* the original validation context, else from the object else from the eClassifier.
*/
public static ResourceSet getResourceSet(@NonNull EClassifier eClassifier, @Nullable Object object, @Nullable DiagnosticChain diagnostics) {
ResourceSet resourceSet = null;
if (diagnostics instanceof BasicDiagnostic) {
for (Object dataObject : ((BasicDiagnostic)diagnostics).getData()) {
if (dataObject instanceof EObject) {
Resource resource = EcoreUtil.getRootContainer((EObject) dataObject).eResource();
if (resource != null) {
resourceSet = resource.getResourceSet();
if (resourceSet != null) {
break;
}
}
}
}
}
if (resourceSet == null) {
if (object instanceof EObject) {
Resource resource = EcoreUtil.getRootContainer((EObject) object).eResource();
if (resource != null) {
resourceSet = resource.getResourceSet();
}
}
if (resourceSet == null) {
Resource resource = EcoreUtil.getRootContainer(eClassifier).eResource();
if (resource != null) {
resourceSet = resource.getResourceSet();
}
}
}
return resourceSet;
}
protected final @Nullable List<Model> complementingModels;
@Deprecated // Temporary internal API preservation for Mars RC3
protected PivotEObjectValidator() {
this.complementingModels = null;
}
public PivotEObjectValidator(@Nullable List<Model> complementingModels) {
this.complementingModels = complementingModels;
}
@Override
public boolean validate(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
return validate(eObject.eClass(), eObject, diagnostics, context);
}
/**
* Overriden to intercept the validation of an EObject to add the additional Pivot-defined validation.
*/
@Override
public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
boolean allOk = true;
if ((eClass != null) && !eObject.eIsProxy()) {
allOk &= validatePivot(eClass, eObject, diagnostics, context);
}
return allOk;
}
/**
* Overriden to intercept the validation of an EDataType value to add the additional Pivot-defined validation.
*/
@Override
public boolean validate(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context) {
boolean allOk = true;
if (eDataType != null) {
allOk &= validatePivot(eDataType, value, diagnostics, context);
}
return allOk;
/* assert value != null;
boolean allOk = true;
EPackage ePackage = eDataType.getEPackage();
EValidator eValidator = eValidators.get(ePackage);
if (eValidator != null) {
allOk &= eValidator.validate(eDataType, value, diagnostics, context);
}
if ((allOk || (diagnostics != null)) && eDataType.isInstance(value)) {
allOk &= validatePivot(eDataType, value, diagnostics, context);
}
return allOk; */
}
/**
* Perform the additional Pivot-defined validation.
*/
protected boolean validatePivot(@NonNull EClassifier eClassifier, @Nullable Object object, @Nullable DiagnosticChain diagnostics, Map<Object, Object> context) {
ResourceSet resourceSet = getResourceSet(eClassifier, object, diagnostics);
if (resourceSet != null) {
ValidationAdapter validationAdapter = ValidationAdapter.findAdapter(resourceSet);
if (validationAdapter != null) {
boolean allOk = validationAdapter.validate(eClassifier, object, complementingModels, diagnostics, context);
return allOk || (diagnostics != null);
}
}
return true;
}
}