blob: 0860e7ddb84e78cfeeaf60433460666ecbc178bf [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2014-2015 itemis 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
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
* itemis - Initial API and implementation
* itemis - [457521] Check MethodWrapper should be null safe wrt instance.getFilter()
* itemis - [458976] Validators are not singleton when they implement checks for different EPackages
* itemis - [463895] org.eclipse.sphinx.emf.check.AbstractCheckValidator.validate(EClass, EObject, DiagnosticChain, Map<Object, Object>) throws NPE
* itemis - [473260] Progress indication of check framework
* itemis - [473261] Check Validation: Cancel button unresponsive
*
* </copyright>
*/
package org.eclipse.sphinx.emf.check;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
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.EClass;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.sphinx.emf.check.catalog.Catalog;
import org.eclipse.sphinx.emf.check.catalog.Severity;
import org.eclipse.sphinx.emf.check.internal.Activator;
import org.eclipse.sphinx.emf.check.internal.CheckMethodWrapper;
import org.eclipse.sphinx.emf.check.util.CheckValidationUtil;
import org.eclipse.sphinx.emf.check.util.CheckValidationContextHelper;
import org.eclipse.sphinx.emf.check.util.DiagnosticLocation;
import org.eclipse.sphinx.emf.check.util.ExtendedEObjectValidator;
import org.eclipse.sphinx.emf.check.util.SourceLocation;
import org.eclipse.sphinx.emf.util.IWrapper;
import org.eclipse.sphinx.platform.util.PlatformLogUtil;
/**
* An abstract implementation of a check-based validator. Depending on the type of the object under validation, the set
* of methods annotated with @Check, whose argument type equals (or is subclass of) the type of the object, are
* automatically triggered. To be sub-classed by custom check validators.
*/
public abstract class AbstractCheckValidator implements ICheckValidator {
private int NO_INDEX = -1;
private boolean initialized = false;
private final ThreadLocal<CheckValidatorState> state;
private ExtendedEObjectValidator eObjectValidator;
private Map<Class<?>, List<CheckMethodWrapper>> collectedModelObjectTypeToCheckMethodsMap;
private Map<Class<?>, List<CheckMethodWrapper>> actualModelObjectTypeToCheckMethodsMap;
private CheckValidatorRegistry checkValidatorRegistry;
protected static class CheckValidatorStateAccess {
private ICheckValidator validator;
private CheckValidatorStateAccess(ICheckValidator validator) {
this.validator = validator;
}
public CheckValidatorState getState() {
CheckValidatorState result = validator.getState().get();
if (result == null) {
result = new CheckValidatorState();
validator.getState().set(result);
}
return result;
}
}
public AbstractCheckValidator() {
this(CheckValidatorRegistry.INSTANCE);
}
public AbstractCheckValidator(CheckValidatorRegistry checkValidatorRegistry) {
state = new ThreadLocal<CheckValidatorState>();
eObjectValidator = new ExtendedEObjectValidator();
collectedModelObjectTypeToCheckMethodsMap = new HashMap<Class<?>, List<CheckMethodWrapper>>();
actualModelObjectTypeToCheckMethodsMap = new HashMap<Class<?>, List<CheckMethodWrapper>>();
this.checkValidatorRegistry = checkValidatorRegistry;
}
protected Class<?> getModelObjectType(EObject eObject) {
// Ensure backward compatibility
Class<?> parameterType = getMethodWrapperType(eObject);
if (parameterType != null) {
return parameterType;
}
Assert.isNotNull(eObject);
// Return eObject.eClass().getInstanceClass() which is the interface and it's used as the key in the
// modelObjectTypeToCheckMethodsMap
return eObject.eClass().getInstanceClass();
}
/**
* @deprecated Use {@link #getModelObjectType(EObject)} instead.
*/
@Deprecated
protected Class<?> getMethodWrapperType(EObject eObject) {
return null;
}
protected void setCurrentObject(CheckValidatorState state, Object object) {
Assert.isNotNull(state);
state.currentObject = object;
}
protected synchronized void initCheckMethods() {
if (!initialized) {
initialized = true;
Collection<Method> declaredCheckMethods = CheckValidationUtil.getDeclaredCheckMethods(getClass());
for (Method method : declaredCheckMethods) {
addCheckMethod(this, method);
}
}
}
protected void addCheckMethod(ICheckValidator validator, Method method) {
Class<?> modelObjectType = method.getParameterTypes()[0];
List<CheckMethodWrapper> value = collectedModelObjectTypeToCheckMethodsMap.get(modelObjectType);
if (value == null) {
value = new ArrayList<CheckMethodWrapper>();
collectedModelObjectTypeToCheckMethodsMap.put(modelObjectType, value);
}
value.add(createCheckMethodWrapper(validator, method));
}
protected CheckMethodWrapper createCheckMethodWrapper(ICheckValidator validator, Method method) {
return new CheckMethodWrapper(validator, method, checkValidatorRegistry);
}
@Override
public boolean validate(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
return validate(eObject.eClass(), eObject, diagnostics, context);
}
@Override
public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
CheckValidationContextHelper helper = new CheckValidationContextHelper(context);
initCheckMethods();
CheckValidationMode mode = CheckValidationMode.getFromContext(context);
CheckValidatorState state = new CheckValidatorState();
state.chain = diagnostics;
setCurrentObject(state, eObject);
state.checkValidationMode = mode;
state.context = context;
// Validate intrinsic model integrity constraints if required
boolean intrinsicModelIntegrityConstraintErrors = false;
if (helper.areIntrinsicModelIntegrityConstraintsEnabled()) {
intrinsicModelIntegrityConstraintErrors = !eObjectValidator.validate(eObject.eClass().getClassifierID(), eObject, diagnostics, context);
}
for (CheckMethodWrapper method : getCheckMethodsForModelObjectType(getModelObjectType(eObject))) {
try {
method.invoke(state, helper.getConstraintCategories());
} catch (Exception ex) {
PlatformLogUtil.logAsError(Activator.getPlugin(), ex);
}
}
return !state.hasErrors && !intrinsicModelIntegrityConstraintErrors;
}
protected List<CheckMethodWrapper> getCheckMethodsForModelObjectType(Class<?> modelObjectType) {
List<CheckMethodWrapper> actualCheckMethodsForModelObjectType = actualModelObjectTypeToCheckMethodsMap.get(modelObjectType);
if (actualCheckMethodsForModelObjectType == null) {
actualCheckMethodsForModelObjectType = new ArrayList<CheckMethodWrapper>();
// Add Check method for current model object type and its super types
for (Class<?> collectedModelObjectType : collectedModelObjectTypeToCheckMethodsMap.keySet()) {
if (collectedModelObjectType.isAssignableFrom(modelObjectType)) {
actualCheckMethodsForModelObjectType.addAll(collectedModelObjectTypeToCheckMethodsMap.get(collectedModelObjectType));
}
}
actualModelObjectTypeToCheckMethodsMap.put(modelObjectType, actualCheckMethodsForModelObjectType);
}
return actualCheckMethodsForModelObjectType;
}
@Override
public boolean validate(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context) {
return true;
}
protected void issue(Object object, EStructuralFeature feature, Object... messageArguments) {
if (object instanceof IWrapper<?>) {
Object eObject = ((IWrapper<?>) object).getTarget();
issue(eObject, feature, NO_INDEX, messageArguments);
} else if (object instanceof EObject) {
issue((EObject) object, feature, NO_INDEX, messageArguments);
} else {
throw new UnsupportedOperationException("Could not recognize type of " + object.toString()); //$NON-NLS-1$
}
}
/**
* Use this method only if a check catalog is contributed.
*
* @param object
* @param feature
* @param messageArguments
*/
protected void issue(EObject object, EStructuralFeature feature, Object... messageArguments) {
issue(object, feature, NO_INDEX, messageArguments);
}
/**
* Use this method only if a check catalog is contributed.
*
* @param object
* @param feature
* @param index
* @param messageArguments
*/
protected void issue(EObject object, EStructuralFeature feature, int index, Object... messageArguments) {
String constraint = getState().get().constraint;
Catalog checkCatalog = getCheckCatalog();
if (checkCatalog == null) {
return;
}
String message = MessageFormat.format(checkCatalog.getMessage(constraint), messageArguments);
Severity severity = checkCatalog.getSeverity(constraint);
switch (severity) {
case ERROR:
error(message, object, feature, index);
break;
case WARNING:
warning(message, object, feature, index);
break;
case INFO:
info(message, object, feature, index);
break;
default:
throw new IllegalArgumentException("Unknow severity " + severity); //$NON-NLS-1$
}
}
protected void error(String message, EObject object, EStructuralFeature feature) {
error(message, object, feature, NO_INDEX);
}
protected void error(String message, EObject object, EStructuralFeature feature, int index) {
getState().get().hasErrors = true;
getState().get().chain.add(createDiagnostic(Severity.ERROR, message, createLocationData(object, feature, index)));
}
protected void error(String message, EObject object, EStructuralFeature feature, int index, Object[] data) {
getState().get().hasErrors = true;
getState().get().chain.add(createDiagnostic(Severity.ERROR, message, insertLocationData(data, object, feature, index)));
}
protected void warning(String message, EObject object, EStructuralFeature feature) {
warning(message, object, feature, NO_INDEX);
}
protected void warning(String message, EObject object, EStructuralFeature feature, int index) {
getState().get().chain.add(createDiagnostic(Severity.WARNING, message, createLocationData(object, feature, index)));
}
protected void warning(String message, EObject object, EStructuralFeature feature, int index, Object[] data) {
getState().get().chain.add(createDiagnostic(Severity.WARNING, message, insertLocationData(data, object, feature, index)));
}
protected void info(String message, EObject object, EStructuralFeature feature) {
info(message, object, feature, NO_INDEX);
}
protected void info(String message, EObject object, EStructuralFeature feature, int index) {
getState().get().chain.add(createDiagnostic(Severity.INFO, message, createLocationData(object, feature, index)));
}
protected void info(String message, EObject object, EStructuralFeature feature, int index, Object[] data) {
getState().get().chain.add(createDiagnostic(Severity.INFO, message, insertLocationData(data, object, feature, index)));
}
protected Object[] insertLocationData(Object[] data, EObject object, EStructuralFeature feature, int index) {
Object[] locationData = createLocationData(object, feature, index);
Object[] newData = new Object[locationData.length + data.length];
System.arraycopy(locationData, 0, newData, 0, locationData.length);
System.arraycopy(data, 0, newData, locationData.length, data.length);
return newData;
}
protected Object[] createLocationData(EObject object, EStructuralFeature feature, int index) {
Object[] data = new Object[2];
data[0] = new DiagnosticLocation(object, feature, index);
data[1] = new SourceLocation(this.getClass(), getState().get().currentMethod, getState().get().constraint);
return data;
}
protected int toDiagnosticSeverity(Severity severity) {
int diagnosticSeverity = -1;
switch (severity) {
case ERROR:
diagnosticSeverity = Diagnostic.ERROR;
break;
case WARNING:
diagnosticSeverity = Diagnostic.WARNING;
break;
case INFO:
diagnosticSeverity = Diagnostic.INFO;
break;
default:
throw new IllegalArgumentException("Unknow severity " + severity); //$NON-NLS-1$
}
return diagnosticSeverity;
}
protected Diagnostic createDiagnostic(Severity severity, String message, Object[] data) {
int diagnosticSeverity = toDiagnosticSeverity(severity);
Diagnostic result = new BasicDiagnostic(diagnosticSeverity, this.getClass().getName(), 0, message, data);
return result;
}
@Override
public ThreadLocal<CheckValidatorState> getState() {
return state;
}
public Catalog getCheckCatalog() {
return checkValidatorRegistry.getCheckCatalog(this);
}
}