/** * 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.mmint; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Stream; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.RegistryFactory; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.ui.PlatformUI; import org.osgi.service.prefs.BackingStoreException; import edu.toronto.cs.se.mmint.extensions.EditorExtensionPointListener; import edu.toronto.cs.se.mmint.extensions.ExtensionPointType; import edu.toronto.cs.se.mmint.extensions.ModelExtensionPointListener; import edu.toronto.cs.se.mmint.extensions.ModelRelExtensionPointListener; import edu.toronto.cs.se.mmint.extensions.OperatorExtensionPointListener; import edu.toronto.cs.se.mmint.mid.EMFInfo; import edu.toronto.cs.se.mmint.mid.ExtendibleElement; import edu.toronto.cs.se.mmint.mid.GenericElement; import edu.toronto.cs.se.mmint.mid.MID; import edu.toronto.cs.se.mmint.mid.MIDFactory; import edu.toronto.cs.se.mmint.mid.MIDLevel; import edu.toronto.cs.se.mmint.mid.MIDPackage; import edu.toronto.cs.se.mmint.mid.Model; import edu.toronto.cs.se.mmint.mid.ModelElement; import edu.toronto.cs.se.mmint.mid.ModelEndpoint; import edu.toronto.cs.se.mmint.mid.editor.Editor; import edu.toronto.cs.se.mmint.mid.operator.ConversionOperator; import edu.toronto.cs.se.mmint.mid.operator.GenericEndpoint; import edu.toronto.cs.se.mmint.mid.operator.Operator; import edu.toronto.cs.se.mmint.mid.operator.OperatorConstraint; import edu.toronto.cs.se.mmint.mid.operator.OperatorConstraintRule; import edu.toronto.cs.se.mmint.mid.operator.OperatorPackage; import edu.toronto.cs.se.mmint.mid.operator.WorkflowOperator; import edu.toronto.cs.se.mmint.mid.reasoning.IReasoningEngine; import edu.toronto.cs.se.mmint.mid.relationship.BinaryModelRel; import edu.toronto.cs.se.mmint.mid.relationship.MappingReference; import edu.toronto.cs.se.mmint.mid.relationship.BinaryMappingReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementEndpointReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelEndpointReference; 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.MIDEcoreUtil; import edu.toronto.cs.se.mmint.mid.utils.MIDRegistry; import edu.toronto.cs.se.mmint.mid.utils.MIDTypeFactory; /** * Model Management is the management of collections of related models. It * provides tools and techniques for dealing with large collections of models * (mega-modeling), defining relationships among models, automatically * manipulating models and relationships using operators (i.e., * transformations). * * The Model Management Tool Framework (MMINT) is an Eclipse-based framework for * rapidly developing model management tools emphasizing graphical, interactive * model management with strong typing. * * MMINT facilitates interactive model management both at the type and instance * levels. It provides a graphical form of mega-model called a Model * Interconnection Diagram (MID) as the interface through which to perform * actions. The type MID allows the user to view and modify the * model/relationship/operator type hierarchy at runtime, while an instance MID * is used to interactively create instances of model/relationship types and to * apply operators to them. * * @author Alessio Di Sandro * */ public class MMINT implements MMINTConstants { /** The singleton instance. */ static final MMINT INSTANCE = new MMINT(); /** The default "heavy" type factory. */ static MIDHeavyTypeFactory typeFactory; /** The Type MID in memory. */ static MID cachedTypeMID; /** The table for subtyping in the repository. */ static Map<String, Set<String>> subtypes; /** The table for model type conversion in the repository. */ static Map<String, Map<String, Set<List<String>>>> conversions; /** The table for subtyping in the Type MID. */ static Map<String, Set<String>> subtypesMID; /** The table for model type conversion in the Type MID. */ static Map<String, Map<String, Set<List<String>>>> conversionsMID; /** The table to map type uris to their bundle name. */ static Map<String, String> bundleTable; /** The reasoners table. */ static Map<String, Map<String, IReasoningEngine>> languageReasoners; /** The cache of runtime types. */ static Map<ExtendibleElement, List<? extends ExtendibleElement>> cachedRuntimeTypes; /** The file containing the active instance MID (i.e. the one that triggered an operation) */ static IFile activeInstanceMIDFile; /** * The table to have some very poor sort of multiple inheritance, * i.e. to have UML_MAVO properly recognized. * TODO MMINT[MID] redo when needed! */ static Map<String, Set<String>> multipleInheritanceTable; /** The type MID filename. */ public static final String TYPEMID_FILENAME = "types" + MMINT.MODEL_FILEEXTENSION_SEPARATOR + MIDPackage.eNAME; /* TODO MMINT[IN PROGRESS WORKFLOW] * - Add the ability to exclude arbitrary intermediate results from output (because they could be not used as inputs to subsequent operators) * - Add control flow operators: If and Loop * - Support conversions in workflows? * - Export an instance mid as workflow? * - Create scripting language */ /* TODO MMINT[IN PROGRESS OPERATOR] Unify operator type behavior with other types: * - Set root Operator as supertype and add filter in gmfmap to avoid drawing the inheritance link (for root Model and ModelRel too) * - Add 2 model type endpoints for operators to Model with cardinality 0..n, and the corresponding operator api * - Add operator support in hierarchy tables and apis * - Review operator constraint heavy apis * - Differentiate between input and output constraints, use output constraint to validate output in normal operators, convert all operators to use them * - Add various apis: createOutputsByName() + make a workflow version for all apis used in startInstance * - Rethink ConversionOperator to be a simple workflow * - Rewrite ExperimentDriver to be a workflow * - Review and rationalize MIDOper and MIDRel, introduce MIDWorkflow? */ /* TODO MMINT[USABILITY] * - Change uris into ids * - There should be different classes rather than MID levels, e.g. Model <- ModelType, ModelInstance (although it brings heaps of gmf complexity if we want customized parts as well) * - Similarly, there should be a ModelRelModelEndpoint and an OperatorModelEndpoint * - Simplify the type system of model rels * - Handle optional uris/ids for subelements of model/modelrel/operator * - Use defaults and optionals in extension points as much as possible * - Move modelepedia types/operators into the examples directory together with papers + create appropriate features * - Transform various labels into toString() functions (helps debug too) * - Turn updateMID into global option and use it directly into apis * - Address the issue of not inheriting Editor and Diagram types; this will fix a model type without editor, which is still able to use the reflective ecore * - Review and unify all mid apis (e.g. add possibility to pass name) * - Create apis to create model and file (createInstance() + createModelFile()) * - Add createBinaryInstance/Subtype() to Mapping * - Rethink link icons in mids with/without MAVO (problem is they're detached from the label) * - Add documentation -> * a) Prerequisite: review apis for mid construction and destruction * b) Take every api function * c) Check usage and delete if unnecessary * d) Add @NonNull/@Nullable annotations + javadoc * e) Make a proper user guide online */ /* TODO MMINT[MATURITY] * - Use workflows to create test cases, and run tests at every commit * - Address todos * - Resource change listeners to track changes in model elements * - Formalize override semantics * - Formalize type introspection * - Unify libz3java if library load problems are fixed (z3java vs libz3java) * - Separate libz3java into own repo? * - Introduce intermediate level between model element and model element reference, contained in model endpoints * - Support standalone model rels (in their own file) * - Can operators detect input/output by code inspection rather than plugin.xml? * - Replace ocl constraints with java in gmfmap * - Refactor functions in mid.ecore like getMetatype() using generics (and rename that to getType()) * - ExtendibleElementReference.containedObject is completely useless */ private static void createTypeConstraint(IConfigurationElement extensionConfig, ExtendibleElement constrainedType, MIDHeavyTypeFactory typeFactory) { IConfigurationElement[] constraintConfig = extensionConfig.getChildren(CHILD_CONSTRAINT); String constraintLanguage = (constraintConfig.length == 0) ? null : constraintConfig[0].getAttribute(CONSTRAINT_ATTR_LANGUAGE); String constraintImplementation = (constraintConfig.length == 0) ? null : constraintConfig[0].getAttribute(CONSTRAINT_ATTR_IMPLEMENTATION); if (constraintLanguage != null && constraintImplementation != null) { typeFactory.createHeavyTypeConstraint(constraintLanguage, constraintImplementation, constrainedType); } } /** * Creates and adds a model type to the repository from a registered * edu.toronto.cs.se.mmint.models extension. Requires the model type package * to be registered through a org.eclipse.emf.ecore.generated_package * extension. * * @param extensionConfig * The edu.toronto.cs.se.mmint.models extension configuration. * @return The created model type, null if the model type can't be created. * @throws MMINTException * If the model type can't be created. */ public static Model createModelType(IConfigurationElement extensionConfig) throws MMINTException { ExtensionPointType extensionType = new ExtensionPointType(extensionConfig, multipleInheritanceTable, typeFactory); if (extensionType.getUri() == null) { throw new MMINTException("Model type " + extensionType.getName() + " must have a uri"); } Model newModelType = extensionType.getFactory().createHeavyModelType(extensionType); createTypeConstraint(extensionConfig, newModelType, extensionType.getFactory()); return newModelType; } /** * Creates and adds a model relationship type to the repository from a * registered edu.toronto.cs.se.mmint.relationships extension. Requires the * model relationship type package to be registered through a * org.eclipse.emf.ecore.generated_package extension. * * @param extensionConfig * The edu.toronto.cs.se.mmint.relationships extension * configuration. * @return The created model relationship type, null if the model * relationship type can't be created. * @throws Exception * If the model relationship type can't be created. */ public static ModelRel createModelRelType(IConfigurationElement extensionConfig) throws Exception { IConfigurationElement modelTypeConfig = extensionConfig.getChildren(MODELS_CHILD_MODELTYPE)[0]; ExtensionPointType modelRelExtensionType = new ExtensionPointType(modelTypeConfig, typeFactory); ExtensionPointType extensionType; if (modelRelExtensionType.getUri() == null) { throw new MMINTException("Model relationship type " + modelRelExtensionType.getName() + " must have a uri"); } IConfigurationElement[] binaryTypeConfigs = extensionConfig.getChildren(CHILD_BINARYTYPE); boolean isBinary = (binaryTypeConfigs.length == 0) ? false : true; ModelRel newModelRelType = modelRelExtensionType.getFactory().createHeavyModelRelType( modelRelExtensionType, isBinary ); createTypeConstraint(modelTypeConfig, newModelRelType, modelRelExtensionType.getFactory()); // binary model rel type String srcModelTypeUri = null, tgtModelTypeUri = null; if (isBinary) { srcModelTypeUri = binaryTypeConfigs[0].getAttribute(BINARYTYPE_ATTR_SOURCETYPEURI); ((BinaryModelRel) newModelRelType).addModelType(MIDTypeRegistry.<Model>getType(srcModelTypeUri), true); tgtModelTypeUri = binaryTypeConfigs[0].getAttribute(BINARYTYPE_ATTR_TARGETTYPEURI); ((BinaryModelRel) newModelRelType).addModelType(MIDTypeRegistry.<Model>getType(tgtModelTypeUri), false); } // model type endpoints IConfigurationElement[] modelTypeEndpointConfigs = extensionConfig.getChildren(MODELRELS_CHILD_MODELTYPEENDPOINT); for (IConfigurationElement modelTypeEndpointConfig : modelTypeEndpointConfigs) { extensionType = new ExtensionPointType(modelTypeEndpointConfig, typeFactory); IConfigurationElement modelTypeEndpointSubconfig = modelTypeEndpointConfig.getChildren(CHILD_TYPEENDPOINT)[0]; String targetModelTypeUri = modelTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_TARGETTYPEURI); Model targetModelType = MIDTypeRegistry.getType(targetModelTypeUri); if (targetModelType == null) { MMINTException.print(IStatus.WARNING, "Target model type " + targetModelTypeUri + " can't be found, skipping it", null); continue; } boolean isBinarySrc = (isBinary && srcModelTypeUri.equals(targetModelTypeUri)); ModelEndpointReference newModelTypeEndpointRef = modelRelExtensionType.getFactory().createHeavyModelTypeEndpointAndModelTypeEndpointReference( extensionType, targetModelType, isBinarySrc, newModelRelType ); String lowerBoundString = modelTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_LOWERBOUND); int lowerBound = (lowerBoundString == null) ? 1 : Integer.parseInt(lowerBoundString); String upperBoundString = modelTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_UPPERBOUND); int upperBound = (upperBoundString == null) ? 1 : Integer.parseInt(upperBoundString); MIDTypeFactory.addTypeEndpointCardinality( newModelTypeEndpointRef.getObject(), lowerBound, upperBound ); EPackage rootModelTypeObj = newModelTypeEndpointRef.getObject().getTarget().getEMFTypeRoot(); // model element types IConfigurationElement[] modelElemTypeConfigs = modelTypeEndpointConfig.getChildren(MODELRELS_MODELTYPEENDPOINT_CHILD_MODELELEMTYPE); for (IConfigurationElement modelElemTypeConfig : modelElemTypeConfigs) { extensionType = new ExtensionPointType(modelElemTypeConfig, typeFactory); ModelElement newModelElemType = MIDTypeRegistry.getType(extensionType.getUri()); if (newModelElemType == null) { // create new model element type EObject modelElemTypeObj = FileUtils.readModelObject(extensionType.getUri(), rootModelTypeObj.eResource()); EMFInfo eInfo = MIDRegistry.getModelElementEMFInfo(modelElemTypeObj, MIDLevel.TYPES); try { newModelElemType = modelRelExtensionType.getFactory().createHeavyModelElementType( extensionType, eInfo, targetModelType ); } catch (Exception e) { MMINTException.print(IStatus.WARNING, "Model element type " + extensionType.getUri() + " can't be created, skipping it", e); continue; } } ModelElementReference modelElemTypeRef = (extensionType.getSupertypeUri() == null) ? null : MIDRegistry.getReference(extensionType.getSupertypeUri(), newModelTypeEndpointRef.getModelElemRefs()); newModelElemType.createTypeReference(modelElemTypeRef, true, newModelTypeEndpointRef); } } // link types IConfigurationElement[] mappingTypeConfigs = extensionConfig.getChildren(MODELRELS_CHILD_MAPPINGTYPE); for (IConfigurationElement mappingTypeConfig : mappingTypeConfigs) { binaryTypeConfigs = mappingTypeConfig.getChildren(CHILD_BINARYTYPE); isBinary = (binaryTypeConfigs.length == 0) ? false : true; extensionType = new ExtensionPointType(mappingTypeConfig, typeFactory); MappingReference newMappingTypeRef; try { newMappingTypeRef = modelRelExtensionType.getFactory().createHeavyMappingTypeAndMappingTypeReference( extensionType, isBinary, newModelRelType ); } catch (Exception e) { MMINTException.print(IStatus.WARNING, "Link type " + extensionType.getUri() + " can't be created, skipping it", e); continue; } // binary link type String srcModelElemTypeUri = null, tgtModelElemTypeUri = null; if (isBinary) { srcModelElemTypeUri = binaryTypeConfigs[0].getAttribute(BINARYTYPE_ATTR_SOURCETYPEURI); ModelEndpointReference containerModelTypeEndpointRef = MIDRegistry.getEndpointReferences( ((Model) MIDTypeRegistry.<ModelElement>getType(srcModelElemTypeUri).eContainer()).getUri(), newModelRelType.getModelEndpointRefs() ).get(0); ((BinaryMappingReference) newMappingTypeRef).addModelElementTypeReference(MIDRegistry.getReference(srcModelElemTypeUri, containerModelTypeEndpointRef.getModelElemRefs()), true); tgtModelElemTypeUri = binaryTypeConfigs[0].getAttribute(BINARYTYPE_ATTR_TARGETTYPEURI); containerModelTypeEndpointRef = MIDRegistry.getEndpointReferences( ((Model) MIDTypeRegistry.<ModelElement>getType(tgtModelElemTypeUri).eContainer()).getUri(), newModelRelType.getModelEndpointRefs() ).get(0); ((BinaryMappingReference) newMappingTypeRef).addModelElementTypeReference(MIDRegistry.getReference(tgtModelElemTypeUri, containerModelTypeEndpointRef.getModelElemRefs()), false); } // model element type endpoints IConfigurationElement[] modelElemTypeEndpointConfigs = mappingTypeConfig.getChildren(MODELRELS_MAPPINGTYPE_CHILD_MODELELEMTYPEENDPOINT); for (IConfigurationElement modelElemTypeEndpointConfig : modelElemTypeEndpointConfigs) { extensionType = new ExtensionPointType(modelElemTypeEndpointConfig, typeFactory); IConfigurationElement modelElemTypeEndpointSubconfig = modelElemTypeEndpointConfig.getChildren(CHILD_TYPEENDPOINT)[0]; String targetModelElemTypeUri = modelElemTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_TARGETTYPEURI); ModelElement modelElemType = MIDTypeRegistry.getType(targetModelElemTypeUri); if (modelElemType == null) { MMINTException.print(IStatus.WARNING, "Target model element type " + targetModelElemTypeUri + " can't be found, skipping it", null); continue; } //TODO MMINT[MODELENDPOINT] well model elements should *really* be contained in the model endpoint now that they exist ModelEndpointReference modelTypeEndpointRef = MIDRegistry.getEndpointReferences(((Model) modelElemType.eContainer()).getUri(), newModelRelType.getModelEndpointRefs()).get(0); ModelElementReference newModelElemTypeRef = MIDRegistry.getReference(targetModelElemTypeUri, modelTypeEndpointRef.getModelElemRefs()); boolean isBinarySrc = (isBinary && srcModelElemTypeUri.equals(targetModelElemTypeUri)); ModelElementEndpointReference newModelElemTypeEndpointRef = modelRelExtensionType.getFactory().createHeavyModelElementTypeEndpointAndModelElementTypeEndpointReference( extensionType, newModelElemTypeRef, isBinarySrc, newMappingTypeRef ); String lowerBoundString = modelElemTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_LOWERBOUND); int lowerBound = (lowerBoundString == null) ? 1 : Integer.parseInt(lowerBoundString); String upperBoundString = modelElemTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_UPPERBOUND); int upperBound = (upperBoundString == null) ? 1 : Integer.parseInt(upperBoundString); MIDTypeFactory.addTypeEndpointCardinality( newModelElemTypeEndpointRef.getObject(), lowerBound, upperBound ); } } return newModelRelType; } /** * Creates and adds an editor type to the repository from a registered * edu.toronto.cs.se.mmint.editors extension. Requires the corresponding * Eclipse editor to be registered through a org.eclipse.ui.editors * extension. * * @param extensionConfig * The edu.toronto.cs.se.mmint.editors extension configuration. * @return The created editor type, null if the editor type can't be * created. * @throws MMINTException * If the editor type can't be created. */ public static Editor createEditorType(IConfigurationElement extensionConfig) throws MMINTException { ExtensionPointType extensionType = new ExtensionPointType(extensionConfig, typeFactory); String modelTypeUri = extensionConfig.getAttribute(EDITORS_ATTR_MODELTYPEURI); String editorId = extensionConfig.getAttribute(EDITORS_ATTR_ID); String wizardId = extensionConfig.getAttribute(EDITORS_ATTR_WIZARDID); String wizardDialogClassName = extensionConfig.getAttribute(EDITORS_ATTR_WIZARDDIALOGCLASS); Editor newEditorType = extensionType.getFactory().createHeavyEditorType( extensionType, modelTypeUri, editorId, wizardId, wizardDialogClassName, Boolean.parseBoolean(extensionConfig.getAttribute(EDITORS_ATTR_ISDIAGRAM)) ); return newEditorType; } /** * Creates and adds parameter types to an operator type. * * @param extensionConfig * The edu.toronto.cs.se.mmint.operators extension subconfiguration for the parameter types. * @param containerOperatorType * The operator type that will contain the new parameter types. * @param containerFeatureName * The name of the feature in the operator type that will contain the new parameter types. * @throws MMINTException * If the parameter types can't be created. */ private static void createOperatorTypeParameters(IConfigurationElement extensionConfig, Operator containerOperatorType, String containerFeatureName) throws MMINTException { IConfigurationElement[] paramTypeConfigs = extensionConfig.getChildren(OPERATORS_GENINOUT_CHILD_PARAMETER); for (int i = 0; i < paramTypeConfigs.length; i++) { IConfigurationElement paramTypeConfig = paramTypeConfigs[i]; ExtensionPointType extensionType = new ExtensionPointType(paramTypeConfig, typeFactory); IConfigurationElement modelTypeEndpointSubconfig = paramTypeConfig.getChildren(CHILD_TYPEENDPOINT)[0]; String targetModelTypeUri = modelTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_TARGETTYPEURI); Model targetModelType = MIDTypeRegistry.getType(targetModelTypeUri); if (targetModelType == null) { throw new MMINTException("Target model type " + targetModelTypeUri + " can't be found"); } ModelEndpoint newModelTypeEndpoint = extensionType.getFactory().createHeavyModelTypeEndpoint( extensionType, targetModelType, containerOperatorType, containerFeatureName ); String lowerBoundString = modelTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_LOWERBOUND); int lowerBound = (lowerBoundString == null) ? 1 : Integer.parseInt(lowerBoundString); String upperBoundString = modelTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_UPPERBOUND); int upperBound = (upperBoundString == null) ? 1 : Integer.parseInt(upperBoundString); if (upperBound > 1 && i != paramTypeConfigs.length-1) { MMINTException.print(IStatus.WARNING, "Only the last input parameter can have an upper bound > 1, setting it to 1", null); upperBound = 1; } MIDTypeFactory.addTypeEndpointCardinality( newModelTypeEndpoint, lowerBound, upperBound ); IConfigurationElement[] endpointConstraintConfigs = paramTypeConfig.getChildren(CHILD_ENDPOINTCONSTRAINT); if (endpointConstraintConfigs.length != 0 && targetModelType instanceof ModelRel) { OperatorConstraint constraint = (OperatorConstraint) containerOperatorType.getConstraint(); if (constraint == null) { // create empty constraint first constraint = (OperatorConstraint) typeFactory.createHeavyTypeConstraint("JAVA", "", containerOperatorType); } OperatorConstraintRule constraintRule = typeFactory.createHeavyOperatorTypeConstraintRule(constraint, newModelTypeEndpoint); for (IConfigurationElement endpointConstraintConfig : endpointConstraintConfigs) { String parameterName = endpointConstraintConfig.getAttribute(ENDPOINTCONSTRAINT_ATTR_PARAMETERNAME); String endpointIndex = endpointConstraintConfig.getAttribute(ENDPOINTCONSTRAINT_ATTR_ENDPOINTINDEX); ModelEndpoint ruleModelTypeEndpoint; int ruleEndpointIndex; try { ruleModelTypeEndpoint = Stream.concat(containerOperatorType.getInputs().stream(), containerOperatorType.getOutputs().stream()) .filter(inputModelTypeEndpoint -> inputModelTypeEndpoint.getName().equals(parameterName)) .findFirst() .get(); ruleEndpointIndex = (endpointIndex == null) ? -1 : Integer.valueOf(endpointIndex); } catch (Exception e) { throw new MMINTException("Bad operator constraint format", e); } typeFactory.createHeavyOperatorTypeConstraintRuleEndpoint( constraintRule, ruleModelTypeEndpoint, ruleEndpointIndex, OperatorPackage.eINSTANCE.getOperatorConstraintRule_EndpointModels().getName()); } } } } /** * Creates and adds generic types to an operator type. * * @param extensionConfig * The edu.toronto.cs.se.mmint.operators extension subconfiguration for the generic types. * @param containerOperatorType * The operator type that will contain the new generic types. * @throws MMINTException * If the generic types can't be created. */ private static void createOperatorTypeGenerics(IConfigurationElement extensionConfig, Operator containerOperatorType) throws MMINTException { IConfigurationElement[] paramTypeConfigs = extensionConfig.getChildren(OPERATORS_GENINOUT_CHILD_PARAMETER); for (IConfigurationElement paramTypeConfig : paramTypeConfigs) { ExtensionPointType extensionType = new ExtensionPointType(paramTypeConfig, typeFactory); IConfigurationElement genericTypeEndpointSubconfig = paramTypeConfig.getChildren(CHILD_TYPEENDPOINT)[0]; String targetGenericTypeUri = genericTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_TARGETTYPEURI); GenericElement targetGenericType = MIDTypeRegistry.getType(targetGenericTypeUri); if (targetGenericType == null) { throw new MMINTException("Target generic type " + targetGenericTypeUri + " can't be found"); } GenericEndpoint newGenericTypeEndpoint = extensionType.getFactory().createHeavyGenericTypeEndpoint( extensionType, targetGenericType, containerOperatorType ); String lowerBoundString = genericTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_LOWERBOUND); int lowerBound = (lowerBoundString == null) ? 1 : Integer.parseInt(lowerBoundString); String upperBoundString = genericTypeEndpointSubconfig.getAttribute(TYPEENDPOINT_ATTR_UPPERBOUND); int upperBound = (upperBoundString == null) ? 1 : Integer.parseInt(upperBoundString); MIDTypeFactory.addTypeEndpointCardinality( newGenericTypeEndpoint, lowerBound, upperBound ); } } /** * Creates and adds an editor type to the repository from a registered * edu.toronto.cs.se.mmint.operators extension. * * @param extensionConfig * The edu.toronto.cs.se.mmint.operators extension configuration. * @return The created operator type, null if the operator type can't be * created. * @throws MMINTException * If the operator type can't be created. */ public static Operator createOperatorType(IConfigurationElement extensionConfig) throws MMINTException { ExtensionPointType extensionType = new ExtensionPointType(extensionConfig, typeFactory); if (extensionType.getUri() == null) { throw new MMINTException("Operator type " + extensionType.getName() + " must have a uri"); } Operator newOperatorType = extensionType.getFactory().createHeavyOperatorType(extensionType); MMINT.createTypeConstraint(extensionConfig, newOperatorType, extensionType.getFactory()); IConfigurationElement[] genericsParamTypeConfigs = extensionConfig.getChildren(OPERATORS_CHILD_GENERICS); if (genericsParamTypeConfigs.length > 0) { MMINT.createOperatorTypeGenerics(genericsParamTypeConfigs[0], newOperatorType); } IConfigurationElement[] inputsParamTypeConfigs = extensionConfig.getChildren(OPERATORS_CHILD_INPUTS); if (inputsParamTypeConfigs.length > 0) { MMINT.createOperatorTypeParameters(inputsParamTypeConfigs[0], newOperatorType, OperatorPackage.eINSTANCE.getOperator_Inputs().getName()); } IConfigurationElement[] outputsParamTypeConfigs = extensionConfig.getChildren(OPERATORS_CHILD_OUTPUTS); if (outputsParamTypeConfigs.length > 0) { MMINT.createOperatorTypeParameters(outputsParamTypeConfigs[0], newOperatorType, OperatorPackage.eINSTANCE.getOperator_Outputs().getName()); } if (newOperatorType instanceof ConversionOperator) { MIDTypeFactory.addOperatorTypeConversion((ConversionOperator) newOperatorType); } return newOperatorType; } /** * Creates the necessary structures to support subtyping for a type. * * @param type * The type being evaluated. * @param subtype * The subtype that is being added in the subtyping table for the * supertypes of the type. * @param subtypeTable * The table for subtyping. */ private static void createSubtypeHierarchy(ExtendibleElement type, ExtendibleElement subtype, Map<String, Set<String>> subtypeTable) { // add subtype to supertypes String subtypeUri = subtype.getUri(); ExtendibleElement supertype = type.getSupertype(); if (supertype != null) { String supertypeUri = supertype.getUri(); Set<String> subtypes = subtypeTable.get(supertypeUri); if (!subtypes.contains(subtypeUri)) { subtypes.add(subtypeUri); // recursion createSubtypeHierarchy(supertype, subtype, subtypeTable); } } } /** * Creates the necessary structures to support type conversion for a type. * * @param type * The type being evaluated. * @param conversionTypes * The subtable for type conversion. */ private static void createConversionHierarchy(ExtendibleElement currentType, List<String> prevConversionPath, Map<String, Set<List<String>>> conversionsTo) { // add conversions for (ConversionOperator conversionOperatorType : ((Model) currentType).getConversionOperators()) { if (prevConversionPath.contains(conversionOperatorType.getUri())) { // cycle continue; } Model conversionType = conversionOperatorType.getOutputs().get(0).getTarget(); Set<List<String>> conversionPaths = conversionsTo.get(conversionType.getUri()); // handles multiple paths if (conversionPaths == null) { conversionPaths = new HashSet<>(); conversionsTo.put(conversionType.getUri(), conversionPaths); } // keep track of conversion operator used List<String> conversionPath = new ArrayList<>(prevConversionPath); conversionPath.add(conversionOperatorType.getUri()); conversionPaths.add(conversionPath); // recursion createConversionHierarchy(conversionType, conversionPath, conversionsTo); } } /** * Creates the necessary structures to support the type hierarchy for the Type MID. * * @param typeMID * The Type MID from which to extract the type hierarchy. * @param subtypeTable * The table for subtyping in the multimodel. * @param conversionTable * The table for model type conversion in the multimodel. */ private static void createTypeHierarchy(MID typeMID, Map<String, Set<String>> subtypeTable, Map<String, Map<String, Set<List<String>>>> conversionTable) { //TODO MMINT[OO] this should be called directly by the various createSubtype(), when the mechanism will be less expensive subtypeTable.clear(); conversionTable.clear(); for (ExtendibleElement type : typeMID.getExtendibleTable().values()) { subtypeTable.put(type.getUri(), new HashSet<>()); if (!(type instanceof Model)) { continue; } conversionTable.put(type.getUri(), new HashMap<>()); } for (ExtendibleElement type : typeMID.getExtendibleTable().values()) { createSubtypeHierarchy(type, type, subtypeTable); } for (Model modelType : typeMID.getModels()) { createConversionHierarchy(modelType, new ArrayList<String>(), conversionTable.get(modelType.getUri())); for (Model modelSubtype : MIDTypeHierarchy.getSubtypes(modelType, typeMID)) { createConversionHierarchy(modelType, new ArrayList<String>(), conversionTable.get(modelSubtype.getUri())); } } } /** * Creates the necessary structures to support the type hierarchy of the repository. */ public static void createTypeHierarchy() { createTypeHierarchy(cachedTypeMID, subtypes, conversions); } /** * Creates the necessary structures to support the type hierarchy of the Type MID. * * @param typeMID * The Type MID. */ public static void createTypeHierarchy(MID typeMID) { if (typeMID == cachedTypeMID) { createTypeHierarchy(); } else { createTypeHierarchy(typeMID, subtypesMID, conversionsMID); } } /** * Creates and adds a model type to the repository from a dynamic ("light") * model type created at runtime before the last shutdown. * * @param dynamicType * The dynamic model type from the last shutdown. * @return The created model type, null if the model type can't be created. */ private ExtendibleElement createDynamicType(ExtendibleElement dynamicType) { ExtendibleElement type = MIDTypeRegistry.getType(dynamicType.getSupertype().getUri()); if (type == null && dynamicType.getSupertype().isDynamic()) { type = createDynamicType(dynamicType.getSupertype()); } if (type == null) { return null; } ExtendibleElement newType = null; try { if (dynamicType instanceof ModelRel) { newType = ((ModelRel) type).copySubtype((ModelRel) dynamicType); } else if (dynamicType instanceof Model) { newType = ((Model) type).createSubtype( dynamicType.getName(), (MIDTypeRegistry.getExtendedMetamodelPath((Model) dynamicType) != null) ); newType.addTypeConstraint(dynamicType.getConstraint().getLanguage(), dynamicType.getConstraint().getImplementation()); } else if (dynamicType instanceof WorkflowOperator) { newType = ((Operator) type).createSubtype(dynamicType.getName(), ((WorkflowOperator) dynamicType).getMidUri()); } } catch (MMINTException e) { MMINTException.print(IStatus.WARNING, "Dynamic type " + dynamicType.getName() + " can't be recreated", e); } return newType; } /** * Creates and adds types to the repository from all the dynamic ("light") types created at runtime before the last * shutdown. */ private void createDynamicTypes() { MID typeMID; try { typeMID = (MID) FileUtils.readModelFileInState(TYPEMID_FILENAME); } catch (Exception e) { MMINTException.print(IStatus.WARNING, "No previous Type MID found, skipping dynamic types", e); return; } // do model types first //TODO MMINT[MISC] this probably explains the todo in type hierarchy (are type and type ref iterators really needed, or are the lists already ordered by construction?) typeMID.getModels().stream() .filter(modelType -> !(modelType instanceof ModelRel)) .filter(modelType -> modelType.isDynamic()) .filter(modelType -> MIDTypeRegistry.getType(modelType.getUri()) == null) .forEach(dynamicModelType -> this.createDynamicType(dynamicModelType)); typeMID.getModelRels().stream() .filter(modelRelType -> modelRelType.isDynamic()) .filter(modelRelType -> MIDTypeRegistry.getType(modelRelType.getUri()) == null) .forEach(dynamicModelRelType -> this.createDynamicType(dynamicModelRelType)); typeMID.getOperators().stream() .filter(operatorType -> operatorType.isDynamic()) .filter(operatorType -> MIDTypeRegistry.getType(operatorType.getUri()) == null) .forEach(dynamicOperatorType -> this.createDynamicType(dynamicOperatorType)); } public static IReasoningEngine createReasoner(IConfigurationElement extensionConfig) throws CoreException { String reasonerName = extensionConfig.getAttribute(REASONERS_REASONER_ATTR_NAME); IReasoningEngine reasoner = (IReasoningEngine) extensionConfig.createExecutableExtension(REASONERS_REASONER_ATTR_CLASS); IConfigurationElement[] languageConfigs = extensionConfig.getChildren(REASONERS_REASONER_CHILD_LANGUAGE); for (IConfigurationElement languageConfig : languageConfigs) { String languageId = languageConfig.getAttribute(REASONERS_REASONER_LANGUAGE_ATTR_ID).toUpperCase(); Map<String, IReasoningEngine> reasoners = languageReasoners.get(languageId); if (reasoners == null) { reasoners = new HashMap<String, IReasoningEngine>(); languageReasoners.put(languageId, reasoners); } reasoners.put(reasonerName, reasoner); } return reasoner; } /** * Initializes the Type MID from the registered extensions and the dynamic * types created at runtime before the last shutdown, then stores it. * * @param registry * The Eclipse extension registry. */ private void initTypeMID(IExtensionRegistry registry) { cachedTypeMID = MIDFactory.eINSTANCE.createMID(); cachedTypeMID.setLevel(MIDLevel.TYPES); bundleTable = new HashMap<>(); multipleInheritanceTable = new HashMap<>(); typeFactory = new MIDHeavyTypeFactory(); languageReasoners = new HashMap<>(); activeInstanceMIDFile = null; IConfigurationElement[] configs; Iterator<IConfigurationElement> extensionsIter; IConfigurationElement config; // model types configs = registry.getConfigurationElementsFor(MODELS_EXT_POINT); extensionsIter = MIDTypeHierarchy.getExtensionHierarchyIterator(configs, null, ROOT_MODEL_URI); while (extensionsIter.hasNext()) { config = extensionsIter.next(); try { Model modelType = createModelType(config); bundleTable.put(modelType.getUri(), config.getContributor().getName()); } catch (MMINTException e) { MMINTException.print(IStatus.ERROR, "Model type can't be created in " + config.getContributor().getName(), e); } } // model relationship types configs = registry.getConfigurationElementsFor(MODELRELS_EXT_POINT); extensionsIter = MIDTypeHierarchy.getExtensionHierarchyIterator(configs, MODELS_CHILD_MODELTYPE, ROOT_MODELREL_URI); while (extensionsIter.hasNext()) { config = extensionsIter.next(); try { ModelRel modelRelType = createModelRelType(config); bundleTable.put(modelRelType.getUri(), config.getContributor().getName()); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Model relationship type can't be created in " + config.getContributor().getName(), e); } } // editor types configs = registry.getConfigurationElementsFor(EDITORS_EXT_POINT); extensionsIter = MIDTypeHierarchy.getExtensionHierarchyIterator(configs, null, ROOT_EDITOR_URI); while (extensionsIter.hasNext()) { config = extensionsIter.next(); try { Editor editorType = createEditorType(config); bundleTable.put(editorType.getUri(), config.getContributor().getName()); MIDHeavyTypeFactory.addHeavyModelTypeEditor(editorType, editorType.getModelUri()); } catch (MMINTException e) { MMINTException.print(IStatus.ERROR, "Editor type can't be created in " + config.getContributor().getName(), e); } } // operator types configs = registry.getConfigurationElementsFor(OPERATORS_EXT_POINT); extensionsIter = MIDTypeHierarchy.getExtensionHierarchyIterator(configs, null, null); while (extensionsIter.hasNext()) { config = extensionsIter.next(); try { Operator operatorType = createOperatorType(config); bundleTable.put(operatorType.getUri(), config.getContributor().getName()); } catch (MMINTException e) { MMINTException.print(IStatus.ERROR, "Operator type can't be created in " + config.getContributor().getName(), e); } } // dynamic types from last shutdown createDynamicTypes(); // reasoners configs = registry.getConfigurationElementsFor(REASONERS_EXT_POINT); for (int i = 0; i < configs.length; i++) { config = configs[i]; try { createReasoner(config); } catch (CoreException e) { MMINTException.print(IStatus.ERROR, "Reasoner can't be created in " + config.getContributor().getName(), e); } } // type hierarchy subtypes = new HashMap<>(); conversions = new HashMap<>(); subtypesMID = new HashMap<>(); conversionsMID = new HashMap<>(); cachedRuntimeTypes = new HashMap<>(); storeTypeMID(); } /** * Copies one subtype table into another (src -> tgt). * * @param srcTable * The source subtype table. * @param tgtTable * The target subtype table. */ private static void copySubtypeTable(Map<String, Set<String>> srcTable, Map<String, Set<String>> tgtTable) { for (Map.Entry<String, Set<String>> entry : srcTable.entrySet()) { Set<String> newValue = new HashSet<>(entry.getValue()); tgtTable.put(entry.getKey(), newValue); } } /** * Copies one conversion table into another (src -> tgt). * * @param srcTable * The source conversion table. * @param tgtTable * The target conversion table. */ private static void copyConversionTable(Map<String, Map<String, Set<List<String>>>> srcTable, Map<String, Map<String, Set<List<String>>>> tgtTable) { for (Map.Entry<String, Map<String, Set<List<String>>>> entry : srcTable.entrySet()) { Map<String, Set<List<String>>> newValue = new HashMap<>(); tgtTable.put(entry.getKey(), newValue); for (Map.Entry<String, Set<List<String>>> nestedEntry : entry.getValue().entrySet()) { Set<List<String>> newNestedValue = new HashSet<>(); newValue.put(nestedEntry.getKey(), newNestedValue); for (List<String> nestedNestedValue : nestedEntry.getValue()) { List<String> newNestedNestedValue = new ArrayList<>(nestedNestedValue); newNestedValue.add(newNestedNestedValue); } } } } /** * Stores the repository into the Type MID file (repository -> Type MID). */ public static void storeTypeMID() { createTypeHierarchy(); copySubtypeTable(subtypes, subtypesMID); copyConversionTable(conversions, conversionsMID); try { FileUtils.writeModelFileInState(cachedTypeMID, TYPEMID_FILENAME); } catch (Exception e) { MMINTException.print(IStatus.ERROR, "Error creating Type MID file", e); } } /** * Syncs the Type MID with its cache in memory (TypeMID -> cache). * * @param typeMID * The Type MID. */ public static void syncTypeMID(MID typeMID) { //TODO MMINT[OO] review the copy-on-sync mechanism and find an alternative cachedTypeMID = MIDEcoreUtil.copy(typeMID); copySubtypeTable(subtypesMID, subtypes); copyConversionTable(conversionsMID, conversions); } /** * Initializes a preference. * * @param preferences * The preferences. * @param preferenceName * The preference name. * @param preferenceValue * The preferenceValue. * @param override * True if the preference should always be initialized with its * value, false if it should be initialized only when not found. */ private void initPreference(IEclipsePreferences preferences, String preferenceName, String preferenceValue, boolean override) { if (override || preferences.get(preferenceName, null) == null) { preferences.put(preferenceName, preferenceValue); } } /** * Initializes the preferences. */ private void initPreferences() { IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(MMINTActivator.PLUGIN_ID); initPreference(preferences, PREFERENCE_MENU_ICONS_ENABLED, "true", false); initPreference(preferences, PREFERENCE_MENU_MODELRELS_ENABLED, "true", false); initPreference(preferences, PREFERENCE_MENU_MODELRELENDPOINTS_ENABLED, "true", false); initPreference(preferences, PREFERENCE_MENU_OPERATORS_ENABLED, "true", false); initPreference(preferences, PREFERENCE_MENU_OPERATORENDPOINTS_ENABLED, "true", false); initPreference(preferences, PREFERENCE_MENU_OPENMODELEDITORS_ENABLED, "true", false); initPreference(preferences, PREFERENCE_MENU_POLYMORPHISM_ENABLED, "true", false); initPreference(preferences, PREFERENCE_MENU_DELETEMODELFILE_ENABLED, "true", false); initPreference(preferences, PREFERENCE_TESTS_ENABLED, "false", true); for (String languageId : languageReasoners.keySet()) { String reasonerName = preferences.get(PREFERENCE_MENU_LANGUAGE_REASONER + languageId, null); if (reasonerName != null) { IReasoningEngine reasoner = languageReasoners.get(languageId).get(reasonerName); if (reasoner != null) { continue; } } reasonerName = languageReasoners.get(languageId).keySet().iterator().next(); initPreference(preferences, PREFERENCE_MENU_LANGUAGE_REASONER + languageId, reasonerName, true); } } /** * Gets a preference. * * @param preferenceName * The preference name. * @return The preference value, null if the preference name could not be * found. */ public static String getPreference(String preferenceName) { IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(MMINTActivator.PLUGIN_ID); return preferences.get(preferenceName, null); } /** * Sets a preference. * * @param preferenceName * The preference name. * @param preferenceValue * The new preference value. * @return True if the preference was set, false if the preference name * could not be found. */ public static boolean setPreference(String preferenceName, String preferenceValue) { IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(MMINTActivator.PLUGIN_ID); if (preferences.get(preferenceName, null) == null) { return false; } preferences.put(preferenceName, preferenceValue); try { preferences.flush(); return true; } catch (BackingStoreException e) { return false; } } public static Map<String, IReasoningEngine> getLanguageReasoners(String languageId) { return languageReasoners.get(languageId.toUpperCase()); } public static Set<String> getReasonerLanguages() { return languageReasoners.keySet(); } public static boolean isInitialized() { return INSTANCE != null; } public static @Nullable IFile getActiveInstanceMIDFile() { return activeInstanceMIDFile; } public static void storeActiveInstanceMIDFile() { try { IFile instanceMIDFile = (IFile) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().getEditorInput().getAdapter(IFile.class); activeInstanceMIDFile = instanceMIDFile; } catch (Exception e) { MMINTException.print(IStatus.WARNING, "An instance MID is not active and can't be stored", e); activeInstanceMIDFile = null; } } /** * Constructor: initializes the settings, initializes the repository and * registers listeners for dynamic installation/unistallation of extensions. */ private MMINT() { IExtensionRegistry registry = RegistryFactory.getRegistry(); if (registry != null) { initTypeMID(registry); registry.addListener(new ModelExtensionPointListener(), MODELS_EXT_POINT); registry.addListener(new ModelRelExtensionPointListener(), MODELRELS_EXT_POINT); registry.addListener(new EditorExtensionPointListener(), EDITORS_EXT_POINT); registry.addListener(new OperatorExtensionPointListener(), OPERATORS_EXT_POINT); } initPreferences(); } }