blob: d17e45b9bd8cde14a4f18b54c6dacb1806b2159f [file] [log] [blame]
/*
* Copyright (c) 2012 CEA 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:
* Christian W. Damus (CEA) - initial API and implementation
*/
package org.eclipse.uml2.uml.validation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.DiagnosticChain;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.validation.model.Category;
import org.eclipse.emf.validation.model.CategoryManager;
import org.eclipse.emf.validation.model.IModelConstraint;
import org.eclipse.emf.validation.service.AbstractConstraintProvider;
import org.eclipse.emf.validation.service.ConstraintExistsException;
import org.eclipse.emf.validation.service.IConstraintDescriptor;
import org.eclipse.uml2.common.util.UML2Util;
import org.eclipse.uml2.uml.UMLPlugin;
/**
* A provider of constraints that delegate to an {@link EPackage}'s constraints
* via its generated {@link EValidator}.
*/
public class DelegatingConstraintProvider
extends AbstractConstraintProvider {
private static final String E_CATEGORY = "category"; //$NON-NLS-1$
private static final String A_PATH = "path"; //$NON-NLS-1$
private static final String E_EVALIDATOR_PROVIDER = "eValidatorProvider"; //$NON-NLS-1$
private static final String A_CLASS = "class"; //$NON-NLS-1$
/**
* Initializes me.
*/
public DelegatingConstraintProvider() {
super();
}
@Override
public void setInitializationData(IConfigurationElement config,
String propertyName, Object data)
throws CoreException {
super.setInitializationData(config, propertyName, data);
// first, grab the categories that I'll be assigning to my constraints
final Set<Category> categories = getCategories(config);
// get an EValidator provider
IEValidatorProvider validatorProvider = getEValidatorProvider(config);
// then find the constraints that I need to adapt
for (String next : getNamespaceUris()) {
// find the EValidator for this package
EPackage epackage = EPackage.Registry.INSTANCE.getEPackage(next);
if (epackage == null) {
UMLPlugin.INSTANCE
.log(new Status(
IStatus.WARNING,
UMLPlugin.INSTANCE.getSymbolicName(),
"No such EPackage available for model validation: " + next)); //$NON-NLS-1$
} else {
EValidator validator = validatorProvider
.getEValidator(epackage);
if (validator == null) {
UMLPlugin.INSTANCE
.log(new Status(
IStatus.WARNING,
UMLPlugin.INSTANCE.getSymbolicName(),
"No generated validator available for package: " + next)); //$NON-NLS-1$
} else {
EValidator.SubstitutionLabelProvider labelProvider = validatorProvider
.getSubstitutionLabelProvider(epackage);
try {
Iterable<? extends IModelConstraint> constraints = createConstraints(
config.getNamespaceIdentifier(), epackage,
validator, labelProvider);
if (!categories.isEmpty()) {
Category[] cats = categories
.toArray(new Category[categories.size()]);
for (IModelConstraint constraint : constraints) {
IConstraintDescriptor desc = constraint
.getDescriptor();
for (int i = 0; i < cats.length; i++) {
desc.addCategory(cats[i]);
}
}
}
} catch (ConstraintExistsException e) {
throw new CoreException(new Status(IStatus.ERROR,
UMLPlugin.INSTANCE.getSymbolicName(),
"Failed to register model validation constraints.", //$NON-NLS-1$
e));
}
}
}
}
}
private Set<Category> getCategories(IConfigurationElement config) {
final Set<Category> result = new java.util.HashSet<Category>();
for (IConfigurationElement next : config.getChildren(E_CATEGORY)) {
String path = next.getAttribute(A_PATH);
if (!UML2Util.isEmpty(path)) {
// if the category doesn't already exist, it is implicitly
// created, so
// we won't get a null category
result.add(CategoryManager.getInstance().getCategory(path));
}
}
return result;
}
private IEValidatorProvider getEValidatorProvider(
IConfigurationElement config) {
IEValidatorProvider result = null;
IConfigurationElement[] vpConfig = config
.getChildren(E_EVALIDATOR_PROVIDER);
if (vpConfig.length > 0) {
Object ext;
try {
ext = vpConfig[0].createExecutableExtension(A_CLASS);
if (ext instanceof IEValidatorProvider) {
result = (IEValidatorProvider) ext;
}
} catch (CoreException e) {
UMLPlugin.INSTANCE.log(e.getStatus());
}
}
if (result == null) {
result = new IEValidatorProvider.Default();
}
return result;
}
private Iterable<? extends IModelConstraint> createConstraints(
final String namespace, final EPackage epackage,
final EValidator validator,
final EValidator.SubstitutionLabelProvider labelProvider)
throws ConstraintExistsException {
final List<IModelConstraint> result = new java.util.ArrayList<IModelConstraint>();
final Matcher m = Pattern.compile("validate\\w+_validate(\\w+)") //$NON-NLS-1$
.matcher(""); //$NON-NLS-1$
final Map<Class<?>, EClass> eclasses = new java.util.HashMap<Class<?>, EClass>();
for (Method next : validator.getClass().getDeclaredMethods()) {
if (Modifier.isPublic(next.getModifiers())) {
final Class<?>[] signature = next.getParameterTypes();
m.reset(next.getName());
if (m.matches()
&& isConstraintMethod(next.getReturnType(), signature)) {
EClass eclass = getEClass(eclasses, epackage, signature[0]);
// constraint methods could exist for EDataTypes; this
// framework doesn't handle them
if (eclass != null) {
result.add(new DelegatingModelConstraint(namespace,
validator, labelProvider, eclass, next));
}
}
}
}
getConstraints().addAll(result);
registerConstraints(result);
return result;
}
private static boolean isConstraintMethod(Class<?> returnType,
Class<?>[] parameterTypes) {
boolean result = false;
if ((returnType == boolean.class) && (parameterTypes.length == 3)) {
result = EObject.class.isAssignableFrom(parameterTypes[0])
&& (parameterTypes[1] == DiagnosticChain.class)
&& (parameterTypes[2] == Map.class);
}
return result;
}
/**
* Look up an {@link EClass} by instance-class, using a cache for
* performance of repeated queries.
*
* @param cache
* a cache of previous look-up results
* @param epackage
* the epackage in which to find the eclass
* @param interfaceType
* the Java interface type by which to look up the eclass
*
* @return the eclass, or {@code null} if not found
*/
private static EClass getEClass(Map<Class<?>, EClass> cache,
EPackage epackage, Class<?> interfaceType) {
EClass result = cache.get(interfaceType);
if (result == null) {
for (EClassifier next : epackage.getEClassifiers()) {
if ((next.getInstanceClass() == interfaceType)
&& (next instanceof EClass)) {
result = (EClass) next;
cache.put(interfaceType, result);
break;
}
}
}
return result;
}
}