/**
* Copyright (c) 2012-2016 Marsha Chechik, Alessio Di Sandro, Michalis Famelis,
* Rick Salay.
* 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:
* Alessio Di Sandro - Implementation.
*/
package edu.toronto.cs.se.modelepedia.emftocsp.reasoning;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.osgi.framework.FrameworkUtil;
import com.parctechnologies.eclipse.CompoundTerm;
import edu.toronto.cs.se.mmint.MMINT;
import edu.toronto.cs.se.mmint.MMINTException;
import edu.toronto.cs.se.mmint.MIDTypeHierarchy;
import edu.toronto.cs.se.mmint.MIDTypeRegistry;
import edu.toronto.cs.se.mmint.mid.ExtendibleElementConstraint;
import edu.toronto.cs.se.mmint.mid.MIDLevel;
import edu.toronto.cs.se.mmint.mid.Model;
import edu.toronto.cs.se.mmint.mid.relationship.ModelRel;
import edu.toronto.cs.se.mmint.mid.utils.FileUtils;
import edu.toronto.cs.se.mmint.mid.utils.MIDTypeFactory;
import edu.toronto.cs.se.modelepedia.ocl.reasoning.OCLReasoningEngine;
import fr.inria.atlanmod.emftocsp.ICspSolver;
import fr.inria.atlanmod.emftocsp.IModelProperty;
import fr.inria.atlanmod.emftocsp.IModelReader;
import fr.inria.atlanmod.emftocsp.IModelToCspSolver;
import fr.inria.atlanmod.emftocsp.IModelToCspSolverFactory;
import fr.inria.atlanmod.emftocsp.ProcessingException;
import fr.inria.atlanmod.emftocsp.eclipsecs.EclipseSolver;
import fr.inria.atlanmod.emftocsp.emf.impl.EAssociation;
import fr.inria.atlanmod.emftocsp.emf.impl.EmfModelToCspSolverFactory;
import fr.inria.atlanmod.emftocsp.emftoecl.EmfToEclCodeGenerator;
import fr.inria.atlanmod.emftocsp.impl.StrongSatisfiabilityModelProperty;
import fr.inria.atlanmod.emftocsp.ui.main.Activator;
public class EMFtoCSPReasoningEngine extends OCLReasoningEngine {
private final static String ECORE_PIVOT_CONSISTENCYCONSTRAINT = "consistencyConstraint";
private final static String EMFTOCSP_PREFERENCE_ECLIPSEPATH = "EclipsePath";
private final static String EMFTOCSP_PREFERENCE_GRAPHVIZPATH = "GraphvizPath";
private final static String EMFTOCSP_TEMPPROJECT = "edu.toronto.cs.se.mmint.emftocsp";
private final static String EMFTOCSP_TEMPFOLDER = "consistency";
private void flattenEPackage(EPackage flatPackage) {
Map<String, EClass> flatClasses = new HashMap<String, EClass>();
// first pass: pre-populate classes in the package
for (EClassifier flatClassifier : flatPackage.getEClassifiers()) {
if (!(flatClassifier instanceof EClass)) {
continue;
}
flatClasses.put(flatClassifier.getName(), (EClass) flatClassifier);
}
// second pass: flatten
for (EClassifier flatClassifier : flatPackage.getEClassifiers()) {
if (!(flatClassifier instanceof EClass)) {
continue;
}
flattenEClass((EClass) flatClassifier, flatPackage, flatClasses);
}
flatPackage.getEClassifiers().addAll(flatClasses.values());
}
private void flattenEClass(EClass flatClass, EPackage flatPackage, Map<String, EClass> flatClasses) {
// first pass: flatten superclasses
for (EClass superClass : flatClass.getEAllSuperTypes()) {
for (EStructuralFeature flatFeature : superClass.getEStructuralFeatures()) {
flatClass.getEStructuralFeatures().add(EcoreUtil.copy(flatFeature));
}
checkAndReflatten(superClass, flatPackage, flatClasses);
}
flatClass.getESuperTypes().clear();
// second pass: flatten types
for (EStructuralFeature flatFeature : flatClass.getEStructuralFeatures()) {
if (!(flatFeature instanceof EReference)) {
continue;
}
EClassifier flatFeatureClassifier = flatFeature.getEType();
if (!(flatFeatureClassifier instanceof EClass)) {
continue;
}
EClass flatFeatureClass = checkAndReflatten((EClass) flatFeatureClassifier, flatPackage, flatClasses);
flatFeature.setEType(flatFeatureClass);
}
}
private EClass checkAndReflatten(EClass checkedClass, EPackage flatPackage, Map<String, EClass> flatClasses) {
if (checkedClass.getEPackage() != flatPackage && !flatClasses.containsKey(checkedClass.getName())) { // not in same package (assuming package == file)
EClass flatClass = EcoreUtil.copy(checkedClass);
flatClasses.put(flatClass.getName(), flatClass);
flattenEClass(flatClass, flatPackage, flatClasses);
return flatClass;
}
return flatClasses.get(checkedClass.getName());
}
private void cleanupCheckOCLConstraintConsistency(IProject tempProject) {
try {
if (tempProject != null) {
tempProject.delete(true, true, null);
}
}
catch (CoreException e) {
MMINTException.print(IStatus.WARNING, "Can't delete EMFtoCSP temporary project", e);
}
}
@Override
public boolean checkModelConstraintConsistency(Model modelType, String oclConstraint) {
EPackage modelTypeObj;
try {
modelTypeObj = (EPackage) getConstraintContext(modelType, oclConstraint, MIDLevel.TYPES);
}
catch (MMINTException e) {
MMINTException.print(IStatus.ERROR, "Can't get context for OCL constraint, evaluating to false", e);
return false;
}
String modelTypeName = modelType.getName();
//TODO MMINT[CONSTRAINT] find language to express more complex contraints on model rels
// create and-ed global constraint
//TODO MMINT[CONSTRAINT] when invoked from add/remove don't consider the constraint on itself
String oclConsistencyConstraint = (modelType instanceof ModelRel && oclConstraint.startsWith(OCL_MODELENDPOINT_VARIABLE)) ?
oclConstraint.substring(oclConstraint.indexOf(OCL_VARIABLE_SEPARATOR) + 1, oclConstraint.length()) :
oclConstraint;
while (!MIDTypeHierarchy.isRootType(modelType)) {
ExtendibleElementConstraint constraint = modelType.getConstraint();
if (constraint != null && constraint.getLanguage().equals("OCL") && constraint.getImplementation() != null && !constraint.getImplementation().equals("")) {
oclConsistencyConstraint += " and ";
oclConsistencyConstraint += (modelType instanceof ModelRel && oclConstraint.startsWith(OCL_MODELENDPOINT_VARIABLE)) ?
constraint.getImplementation().substring(constraint.getImplementation().indexOf(OCL_VARIABLE_SEPARATOR) + 1, constraint.getImplementation().length()) :
constraint.getImplementation();
}
modelType = modelType.getSupertype();
}
// a constraint on model rel must be consistent with endpoints
if (modelType instanceof ModelRel && oclConstraint.startsWith(OCL_MODELENDPOINT_VARIABLE)) {
return checkModelConstraintConsistency(MIDTypeRegistry.<Model>getType(modelTypeObj.getNsURI()), oclConsistencyConstraint);
}
// flatten hierarchy and add constraint as annotation into the metamodel
ResourceSet flatResourceSet = new ResourceSetImpl();
String flatUri = EMFTOCSP_TEMPPROJECT + IPath.SEPARATOR + EMFTOCSP_TEMPFOLDER + IPath.SEPARATOR + modelTypeName + MMINT.MODEL_FILEEXTENSION_SEPARATOR + EcorePackage.eNAME;
Resource flatResource = flatResourceSet.createResource(URI.createPlatformResourceURI(flatUri, true));
EPackage flatModelTypeObj = EcoreUtil.copy(modelTypeObj);
flatResource.getContents().add(flatModelTypeObj);
flattenEPackage(flatModelTypeObj);
EAnnotation newEAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
newEAnnotation.setSource(EcorePackage.eNS_URI);
EMap<String, String> newEAnnotationDetails = newEAnnotation.getDetails();
newEAnnotationDetails.put(MIDTypeFactory.ECORE_VALIDATION_DELEGATE, MIDTypeFactory.ECORE_PIVOT_URI);
flatModelTypeObj.getEAnnotations().add(newEAnnotation);
EClass modelTypeRootObj = (EClass) flatModelTypeObj.getEClassifiers().get(0);
newEAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
newEAnnotation.setSource(EcorePackage.eNS_URI);
newEAnnotationDetails = newEAnnotation.getDetails();
newEAnnotationDetails.put(MIDTypeFactory.ECORE_VALIDATION_CONSTRAINTS, ECORE_PIVOT_CONSISTENCYCONSTRAINT);
modelTypeRootObj.getEAnnotations().add(newEAnnotation);
newEAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
newEAnnotation.setSource(MIDTypeFactory.ECORE_PIVOT_URI);
newEAnnotationDetails = newEAnnotation.getDetails();
newEAnnotationDetails.put(ECORE_PIVOT_CONSISTENCYCONSTRAINT, oclConsistencyConstraint);
modelTypeRootObj.getEAnnotations().add(newEAnnotation);
// use EMFtoCSP to check strong satisfiability
EPackage modelTypeObjToRestore = EPackage.Registry.INSTANCE.getEPackage(flatModelTypeObj.getNsURI()); // EMFtoCSP will mess this up, save it to restore later
// EMFtoCSP ui screen 4 (preview)
IProject tempProject = ResourcesPlugin.getWorkspace().getRoot().getProject(EMFTOCSP_TEMPPROJECT);
IFolder resultLocation;
try {
if (!tempProject.exists()) {
tempProject.create(null);
}
if (!tempProject.isOpen()) {
tempProject.open(null);
}
resultLocation = tempProject.getFolder(EMFTOCSP_TEMPFOLDER);
if (!resultLocation.exists()) {
resultLocation.create(true, true, null);
}
FileUtils.writeModelFile(flatModelTypeObj, flatUri, true);
}
catch (Exception e) {
MMINTException.print(IStatus.WARNING, "Can't create EMFtoCSP temporary project, skipping consistency check", e);
cleanupCheckOCLConstraintConsistency(tempProject);
return true;
}
// EMFtoCSP init
String eclipsePath = Activator.getDefault().getPreferenceStore().getString(EMFTOCSP_PREFERENCE_ECLIPSEPATH);
String graphvizPath = Activator.getDefault().getPreferenceStore().getString(EMFTOCSP_PREFERENCE_GRAPHVIZPATH);
ICspSolver<?> solver = new EclipseSolver(eclipsePath, graphvizPath);
IModelToCspSolverFactory<Resource, CompoundTerm> modelSolverFactory = new EmfModelToCspSolverFactory();
IModelToCspSolver<Resource, CompoundTerm> modelSolver = modelSolverFactory.getModelToCspSolver();
modelSolver.setModelFileName(modelTypeName);
modelSolver.setModel(flatModelTypeObj.eResource());
modelSolver.setSolver(solver);
modelSolver.setCspCodeGenerator(new EmfToEclCodeGenerator(modelSolver));
modelSolver.getBuilder();
// EMFtoCSP ui screen 1
IFile oclFile = null;
modelSolver.setConstraintsDocument(oclFile);
// EMFtoCSP ui screen 2
Map<String, String> modelElementsDomain = new HashMap<String, String>();
modelSolver.setModelElementsDomain(modelElementsDomain);
@SuppressWarnings("unchecked")
IModelReader<Resource, EPackage, EClass, EAssociation, EAttribute, EOperation> modelReader = (IModelReader<Resource, EPackage, EClass, EAssociation, EAttribute, EOperation>) modelSolver.getModelReader();
for (EClass c : modelReader.getClasses()) {
modelElementsDomain.put(c.getEPackage().getName() + "." + c.getName(), "0..5");
for (EAttribute at : modelReader.getClassAttributes(c)) {
if (at.getEAttributeType().getName().equalsIgnoreCase("boolean") || at.getEAttributeType().getName().equalsIgnoreCase("boolean")) {
modelElementsDomain.put(at.getEContainingClass().getName() + "." + at.getName(), "0..1");
}
else if (at.getEAttributeType().getName().equalsIgnoreCase("string") || at.getEAttributeType().getName().equalsIgnoreCase("estring") ) {
modelElementsDomain.put(at.getEContainingClass().getName() + "." + at.getName() + ".length", "0..10");
modelElementsDomain.put(at.getEContainingClass().getName() + "." + at.getName() + ".domain", "");
}
else {
modelElementsDomain.put(at.getEContainingClass().getName() + "." + at.getName(), "[1,10,20]");
}
}
}
for (String asName : modelReader.getAssociationsNames()) {
modelElementsDomain.put(asName, "0..10");
}
// EMFtoCSP ui screen 3
List<IModelProperty> modelProperties = new ArrayList<IModelProperty>();
modelProperties.add(new StrongSatisfiabilityModelProperty());
modelSolver.setModelProperties(modelProperties);
// EMFtoCSP ui screen 4 (reprise)
modelSolver.setResultLocation(resultLocation);
// EMFtoCSP performFinish()
File importsFolder;
try {
importsFolder = new File(FileLocator.toFileURL(FrameworkUtil.getBundle(fr.inria.atlanmod.emftocsp.eclipsecs.EclipseSolver.class).getEntry("/libs")).getFile());
}
catch (Exception e) {
MMINTException.print(IStatus.WARNING, "Can't find EMFtoCSP libs, skipping consistency check", e);
cleanupCheckOCLConstraintConsistency(tempProject);
return true;
}
File[] libs = importsFolder.listFiles(
new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.matches(".*\\.ecl$");
}
}
);
ArrayList<File> libList = new ArrayList<File>();
for(int i = 0; i < libs.length; i++) {
libList.add(libs[i]);
}
boolean isConsistent = true;
try {
isConsistent = modelSolver.solveModel(libList);
}
catch (ProcessingException e) {
MMINTException.print(IStatus.WARNING, "EMFtoCSP processing error, skipping consistency check", e);
}
finally {
EPackage.Registry.INSTANCE.put(flatModelTypeObj.getNsURI(), modelTypeObjToRestore); // EMFtoCSP messed this up, restore it
cleanupCheckOCLConstraintConsistency(tempProject);
}
return isConsistent;
}
}