/*******************************************************************************
* Copyright (c) 2013, 2015 CEA LIST 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 (CEA LIST) - initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.pivot.uml.internal.validation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.validation.internal.util.XmlConstraintDescriptor;
import org.eclipse.emf.validation.model.Category;
import org.eclipse.emf.validation.model.IModelConstraint;
import org.eclipse.emf.validation.service.ConstraintExistsException;
import org.eclipse.emf.validation.service.IConstraintDescriptor;
import org.eclipse.emf.validation.xml.XmlConstraintProvider;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.ocl.pivot.Annotation;
import org.eclipse.ocl.pivot.Constraint;
import org.eclipse.ocl.pivot.Element;
import org.eclipse.ocl.pivot.utilities.EnvironmentFactory;
import org.eclipse.ocl.pivot.utilities.MetamodelManager;
import org.eclipse.ocl.pivot.utilities.OCL;
import org.eclipse.ocl.pivot.utilities.PivotUtil;
import org.eclipse.uml2.uml.Stereotype;
/**
* LoadableConstraintProvider supports loading of algorithmically derived
* constraints before a traversal of a particular nsURI starts.
* <p>
* Pending improvement of the EMFv API:
* <p>
* Two ConstraintProviders should be specified for the one Category for the
* org.eclipse.emf.validation.constraintProviders extension point.
* <p>
* The first ConstraintProvider default-classed XmlConstraintProvider may have a
* single placeholder constraint that provides the initial content of the
* Model Validation Constraints Preference page.
* <p>
* The second ConstraintProvider deriving from LoadableConstraintProvider
* should have a Path-languaged constraint with an XML path parameter locating the
* model source of the loadable constraints. These will be loaded by the first validation
* run and repopulate the Preference page with their content.
* <p>
* See org.eclipse.ocl.examples.xtext.tests/plugin.xml for an example.
* <p>
* Derived implementations such as UMLProfileConstraintProvider should implement load
* to load the modeled constraints.
*/
@SuppressWarnings("restriction")
public abstract class LoadableConstraintProvider extends XmlConstraintProvider
{
private static final Logger logger = Logger.getLogger(LoadableConstraintProvider.class);
private static OCL ocl = null; // FIXME use CG'd constraints to allow this to be weak
public static @NonNull OCL getOCL() {
OCL ocl2 = ocl;
if (ocl2 == null) {
synchronized (LoadableConstraintProvider.class) {
ocl2 = ocl;
if (ocl2 == null) {
ocl = ocl2 = OCL.newInstance();
}
}
}
return ocl2;
}
protected LoadableConstraintProvider() {
}
protected void installConstraint(@NonNull Constraint constraint, @NonNull Set<Category> categories) {
MetamodelManager metamodelManager = ocl.getMetamodelManager();
for (/*@NonNull*/ Element constrainedElement : constraint.getConstrainedElements()) {
if (constrainedElement != null) {
EModelElement targetElement = metamodelManager.getEcoreOfPivot(EModelElement.class, constrainedElement);
if (targetElement != null) {
int code = 99;
LoadableConstraintDescriptor<?> desc = null;
if (targetElement instanceof EClassifier) {
desc = new LoadableConstraintDescriptor.Ecore((EClassifier)targetElement, constraint, code);
}
else if (targetElement instanceof Stereotype) {
desc = new LoadableConstraintDescriptor.UML((Stereotype)targetElement, constraint, code);
}
else {
logger.error("Unknown constrainedElement type : " + targetElement);
}
if (desc != null) {
for (Category category : categories) {
category.addConstraint(desc);
}
Collection<IModelConstraint> constraints = getConstraints();
constraints.add(desc);
}
}
}
}
}
protected void installContents(Iterable<? extends EObject> eContents, @NonNull Set<Category> categories) {
for (EObject eObject : eContents) {
if (eObject instanceof Constraint) {
installConstraint((Constraint)eObject, categories);
}
if (!(eObject instanceof EAnnotation) && !(eObject instanceof Annotation)) {
installContents(eObject.eContents(), categories);
}
}
}
protected void installDescriptor(@NonNull XmlConstraintDescriptor descriptor, String namespaceIdentifier, @NonNull Set<Category> categories) {
String path = descriptor.getParameterValue("path");
@NonNull URI uri = URI.createPlatformPluginURI("/" + namespaceIdentifier + "/" + path, true);
load(getOCL().getEnvironmentFactory(), uri, categories);
}
protected boolean installResource(@NonNull Resource asResource, @NonNull Set<Category> categories) {
List<Resource.Diagnostic> errors = asResource.getErrors();
assert errors != null;
String message = PivotUtil.formatResourceDiagnostics(errors, "", "\n");
if (message != null) {
logger.error("Failed to load Pivot from '" + this + "': " + message);
return false;
}
installContents(asResource.getContents(), categories);
try {
registerConstraints(getConstraints());
} catch (ConstraintExistsException e) {
logger.error("Duplicate constraint for '" + this + "'", e);
}
return true;
}
protected abstract boolean load(@NonNull EnvironmentFactory environmentFactory, @NonNull URI uri, @NonNull Set<Category> categories);
@Override
public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException {
super.setInitializationData(config, propertyName, data);
Object cfg = config;
while (cfg instanceof IConfigurationElement) {
cfg = ((IConfigurationElement)cfg).getParent();
}
if (!(cfg instanceof IExtension)) {
logger.error("The ConstraintDescriptor for '" + config.getName() + "' has no IExtension parent", null);
return;
}
String namespaceIdentifier = ((IExtension)cfg).getNamespaceIdentifier();
List<IModelConstraint> oldConstraints = new ArrayList<IModelConstraint>(getConstraints());
Set<Category> categories = new HashSet<Category>();
for (IModelConstraint constraint : oldConstraints) {
IConstraintDescriptor descriptor = constraint.getDescriptor();
if (descriptor != null) {
Set<Category> descriptorCategories = descriptor.getCategories();
if (descriptorCategories != null) {
categories.addAll(descriptorCategories);
}
}
}
Set<IConstraintDescriptor> allDescriptors = new HashSet<IConstraintDescriptor>();
for (Category category : categories) {
Set<IConstraintDescriptor> constraints = category.getConstraints();
if (constraints != null) {
allDescriptors.addAll(constraints);
}
}
for (IConstraintDescriptor descriptor : allDescriptors) {
if (!(descriptor instanceof LoadableConstraintDescriptor)) {
for (Category category : categories) {
category.removeConstraint(descriptor);
}
}
}
for (IModelConstraint constraint : oldConstraints) {
IConstraintDescriptor descriptor = constraint.getDescriptor();
if (descriptor != null) {
installDescriptor((XmlConstraintDescriptor) descriptor, namespaceIdentifier, categories);
}
}
}
@Override
public String toString() {
return "LoadableConstraintProvider";
}
}