blob: b30b69db3db98833d45d240c50bd5b6269fe9fa5 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2008-2010 See4sys 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:
* See4sys - Initial API and implementation
* Elektrobit - [567814] The EMF validation framework reports multiple identical validation problems
*
* </copyright>
*/
package org.eclipse.sphinx.emf.validation.evalidator.adapter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.emf.common.util.BasicDiagnostic;
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.util.EObjectValidator;
import org.eclipse.emf.transaction.RunnableWithResult;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.emf.validation.model.EvaluationMode;
import org.eclipse.emf.validation.model.IConstraintStatus;
import org.eclipse.emf.validation.service.IBatchValidator;
import org.eclipse.emf.validation.service.IConstraintFilter;
import org.eclipse.emf.validation.service.ITraversalStrategy;
import org.eclipse.emf.validation.service.ModelValidationService;
import org.eclipse.sphinx.emf.validation.diagnostic.ExtendedDiagnostic;
import org.eclipse.sphinx.emf.validation.preferences.IValidationPreferences;
/**
* An adapter that plugs the EMF Model Validation Service API into the {@link org.eclipse.emf.ecore.EValidator} API.
*/
public class EValidatorAdapter extends EObjectValidator {
/**
* Model Validation Service interface for batch validation of EMF elements.
*/
private final IBatchValidator batchValidator;
private static Boolean emfIntrinsicConstraints = null;
/**
* Initializes me.
*/
public EValidatorAdapter() {
super();
batchValidator = (IBatchValidator) ModelValidationService.getInstance().newValidator(EvaluationMode.BATCH);
batchValidator.setIncludeLiveConstraints(true);
batchValidator.setReportSuccesses(false);
batchValidator.setTraversalStrategy(new ITraversalStrategy.Flat());
}
/**
* Return the value of the preference to check EMF default rules.
*
* @return
*/
protected static Boolean areEMFIntrinsicConstraintsEnabled() {
/*
* Performance optimization: Read preference value only once and cache it to avoid repeated reads at every
* validation constraint evaluation. Install a preference change listener to update cached preference value when
* it changes.
*/
if (emfIntrinsicConstraints == null) {
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(org.eclipse.sphinx.emf.validation.Activator.PLUGIN_ID);
if (prefs != null) {
emfIntrinsicConstraints = prefs.getBoolean(IValidationPreferences.PREF_ENABLE_EMF_DEFAULT_RULES,
IValidationPreferences.PREF_ENABLE_EMF_DEFAULT_RULES_DEFAULT);
prefs.addPreferenceChangeListener(new IEclipsePreferences.IPreferenceChangeListener() {
@Override
public void preferenceChange(org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent event) {
String key = event.getKey();
if (key.equals(IValidationPreferences.PREF_ENABLE_EMF_DEFAULT_RULES)) {
Object newValue = event.getNewValue();
if (newValue == null) {
emfIntrinsicConstraints = Boolean.FALSE;
} else {
emfIntrinsicConstraints = Boolean.valueOf(newValue.toString());
}
}
}
});
}
}
return emfIntrinsicConstraints;
}
@Override
public boolean validate(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
return validate(eObject.eClass(), eObject, diagnostics, context);
}
/**
* Validates the default rules from EMF, if the corresponding preference is enabled.
*/
protected void validateEMFRules(EClass eClass, final EObject eObject, final DiagnosticChain diagnostics, final Map<Object, Object> context) {
// Let's check if EMF default rules should be checked
if (areEMFIntrinsicConstraintsEnabled() == Boolean.TRUE) {
if (eClass.eContainer() == getEPackage()) {
validate(eClass.getClassifierID(), eObject, diagnostics, context);
} else {
List<EClass> eSuperTypes = eClass.getESuperTypes();
if (eSuperTypes.isEmpty()) {
validate_EveryDefaultConstraint(eObject, diagnostics, context);
} else {
validate(eSuperTypes.get(0), eObject, diagnostics, context);
}
}
}
}
/**
* Implements validation by delegation to the EMF validation framework using 'context' filter.
*/
public boolean validate(EClass eClass, final EObject eObject, final DiagnosticChain diagnostics, final Map<Object, Object> context,
final Set<IConstraintFilter> filters) {
// first, do whatever the basic EcoreValidator does
// former call to super.validate(eClass, eObject, diagnostics, context);
// Let's check if EMF default constraints should be checked
validateEMFRules(eClass, eObject, diagnostics, context);
// Ok, Now let's check our constraints.
IStatus status = Status.OK_STATUS;
// no point in validating if we can't report results
if (diagnostics != null) {
// if EMF Mode Validation Service already covered the sub-tree,
// which it does for efficient computation and error reporting,
// then don't repeat (the Diagnostician does the recursion
// externally). If there is no context map, then we can't
// help it
// now runs the custom/other validators
if (!hasProcessed(eObject, context)) {
// Add filters, if it exists (pre-process)
addFilters(filters);
TransactionalEditingDomain editingDomain = TransactionUtil.getEditingDomain(eObject);
if (editingDomain != null) {
final EObject tgt = eObject;
RunnableWithResult<IStatus> run = new RunnableWithResult.Impl<IStatus>() {
@Override
public void run() {
IStatus status = Status.OK_STATUS;
try {
status = batchValidator.validate(tgt, new NullProgressMonitor());
} finally {
// Remove the icf filter, if it exists
removeFilters(filters);
}
setResult(status);
}
};
try {
status = (IStatus) editingDomain.runExclusive(run);
} catch (InterruptedException ex) {
}
} else {
try {
status = batchValidator.validate(eObject, new NullProgressMonitor());
} finally {
// Remove the icf filter, if it exists
removeFilters(filters);
}
}
// post-process
processed(eObject, context, status);
appendDiagnostics(status, diagnostics);
}
}
return status.isOK();
}
/**
* Implements validation by delegation to the EMF validation framework.
*/
@Override
public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
return validate(eClass, eObject, diagnostics, context, null);
}
/**
* Direct validation of {@link EDataType}s is not supported by the EMF validation framework; they are validated
* indirectly via the {@link EObject}s that hold their values.
*/
@Override
public boolean validate(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context) {
return super.validate(eDataType, value, diagnostics, context);
}
/**
* If we have a context map, record this object's <code>status</code> in it so that we will know later that we have
* processed it and its sub-tree.
*
* @param eObject
* an element that we have validated
* @param context
* the context (may be <code>null</code>)
* @param status
* the element's validation status
*/
private void processed(EObject eObject, Map<Object, Object> context, IStatus status) {
if (context != null) {
context.put(eObject, status);
}
}
/**
* Determines whether we have processed this <code>eObject</code> before, by automatic recursion of the EMF Model
* Validation Service. This is only possible if we do, indeed, have a context.
*
* @param eObject
* an element to be validated
* @param context
* the context (may be <code>null</code>)
* @return <code>true</code> if the context is not <code>null</code> and the <code>eObject</code> has already been
* validated; <code>false</code>, otherwise
*/
private boolean hasProcessed(EObject eObject, Map<Object, Object> context) {
return context != null && context.containsKey(eObject);
}
/**
* Converts a status result from the EMF validation service to diagnostics.
*
* @param status
* the EMF validation service's status result
* @param diagnostics
* a diagnostic chain to accumulate results on
*/
private void appendDiagnostics(IStatus status, DiagnosticChain diagnostics) {
// Registry registry = EValidator.Registry.INSTANCE;
// registry.toString();
// MarkerFilter[] userFilters = super.getAllFilters();
// Collection declaredFilters = MarkerSupportRegistry.getInstance().getRegisteredFilters();
// Iterator iterator = declaredFilters.iterator();
//
// MarkerFilter[] allFilters = new MarkerFilter[userFilters.length + declaredFilters.size()];
// System.arraycopy(userFilters, 0, allFilters, 0, userFilters.length);
// int index = userFilters.length;
//
// while (iterator.hasNext()) {
// allFilters[index] = (MarkerFilter) iterator.next();
// index++;
// }
if (status.isMultiStatus()) {
IStatus[] children = status.getChildren();
for (IStatus element : children) {
appendDiagnostics(element, diagnostics);
}
} else if (status instanceof IConstraintStatus) {
/* <<< */
// diagnostics.add(new BasicDiagnostic(status.getSeverity(), status.getPlugin(), status.getCode(),
// status.getMessage(),
// ((IConstraintStatus) status).getResultLocus().toArray()));
/* --- */
diagnostics.add(new ExtendedDiagnostic(status.getSeverity(), status.getPlugin(), ((IConstraintStatus) status).getConstraint(),
status.getCode(), status.getMessage(), ((IConstraintStatus) status).getResultLocus().toArray()));
/* >>> */
} else {
diagnostics
.add(new BasicDiagnostic(status.getSeverity(), status.getPlugin(), status.getCode(), status.getMessage(), status.getChildren()));
}
}
/**
* add filters to the batchValidator
*
* @param filters
* @see IConstraintFilter
*/
private void addFilters(Set<IConstraintFilter> filters) {
if (filters != null) {
for (IConstraintFilter icf : filters) {
batchValidator.addConstraintFilter(icf);
}
}
return;
}
/**
* remove filters to the batch validator
*
* @param filters
* @see IConstraintFilter
*/
private void removeFilters(Set<IConstraintFilter> filters) {
if (filters != null) {
for (IConstraintFilter icf : filters) {
batchValidator.removeConstraintFilter(icf);
}
}
return;
}
}