/** * 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.Comparator; 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.TreeSet; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.emf.common.util.EList; import edu.toronto.cs.se.mmint.extensions.ExtensionPointType; 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.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.Operator; import edu.toronto.cs.se.mmint.mid.relationship.BinaryMappingReference; import edu.toronto.cs.se.mmint.mid.relationship.BinaryModelRel; import edu.toronto.cs.se.mmint.mid.relationship.ExtendibleElementReference; import edu.toronto.cs.se.mmint.mid.relationship.Mapping; import edu.toronto.cs.se.mmint.mid.relationship.MappingReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementEndpoint; import edu.toronto.cs.se.mmint.mid.relationship.ModelElementReference; import edu.toronto.cs.se.mmint.mid.relationship.ModelRel; import edu.toronto.cs.se.mmint.mid.ui.MIDDialogs; /** * Utilities to deal with the hierarchy of types. * * @author Alessio Di Sandro * */ public class MIDTypeHierarchy { //TODO MMINT[MISC] are type and type ref iterators really needed, or are the lists already ordered by construction? /** * The comparator for a hierarchy of types from registered extensions. * * @author Alessio Di Sandro * */ private static class ExtensionHierarchyComparator implements Comparator<IConfigurationElement> { /** The map from a uri to its supertype uri within the hierarchy of extensions. */ private Map<String, String> extensionUris; /** The name of the xml child to be used in the extensions. */ private String childName; /** The root uri in the hierarchy. */ private String rootUri; /** * Constructor: initializes the comparator. * * @param extensionUris * The map from a uri to its supertype uri within the * hierarchy of extensions. * @param childName * The name of the xml child to be used in the extensions. * @param rootUri * The root uri in the hierarchy. */ public ExtensionHierarchyComparator(Map<String, String> extensionUris, String childName, String rootUri) { this.extensionUris = extensionUris; this.childName = childName; this.rootUri = rootUri; } /** * Compares two extensions based on their position in the type * hierarchy, i.e. by counting the number of supertypes of the type * declared in the extension. {@inheritDoc} */ @Override public int compare(IConfigurationElement extension1, IConfigurationElement extension2) { if (childName != null) { extension1 = extension1.getChildren(childName)[0]; extension2 = extension2.getChildren(childName)[0]; } ExtensionPointType type1 = new ExtensionPointType(extension1); String uri1 = type1.getUri(); String tempUri1 = uri1; String tempSupertypeUri1 = extensionUris.get(uri1); int supertypes1 = (rootUri != null && uri1.equals(rootUri)) ? -1 : 0; while (tempSupertypeUri1 != null) { supertypes1++; tempUri1 = tempSupertypeUri1; tempSupertypeUri1 = extensionUris.get(tempUri1); } ExtensionPointType type2 = new ExtensionPointType(extension2); String uri2 = type2.getUri(); String tempUri2 = uri2; String tempSupertypeUri2 = extensionUris.get(uri2); int supertypes2 = (rootUri != null && uri2.equals(rootUri)) ? -1 : 0; while (tempSupertypeUri2 != null) { supertypes2++; tempUri2 = tempSupertypeUri2; tempSupertypeUri2 = extensionUris.get(tempUri2); } int relativeOrder = supertypes1 - supertypes2; if (relativeOrder == 0) { relativeOrder = uri1.compareTo(uri2); } return relativeOrder; } } /** * The comparator for a hierarchy of types. * * @author Alessio Di Sandro * */ private static class TypeHierarchyComparator implements Comparator<ExtendibleElement> { private List<? extends ExtendibleElement> types; public TypeHierarchyComparator(EList<? extends ExtendibleElement> types) { this.types = types; } /** * Compares two types based on their position in the type hierarchy, * i.e. by counting the number of their supertypes. */ @Override public int compare(ExtendibleElement type1, ExtendibleElement type2) { ExtendibleElement initialType1 = type1; int supertypes1 = 0; while (type1.getSupertype() != null) { supertypes1++; type1 = type1.getSupertype(); } ExtendibleElement initialType2 = type2; int supertypes2 = 0; while (type2.getSupertype() != null) { supertypes2++; type2 = type2.getSupertype(); } int relativeOrder = supertypes1 - supertypes2; if (relativeOrder == 0) { relativeOrder = types.indexOf(initialType1) - types.indexOf(initialType2); } return relativeOrder; } } /** * The comparator for a hierarchy of references to types. * * @author Alessio Di Sandro * */ private static class TypeRefHierarchyComparator implements Comparator<ExtendibleElementReference> { private List<? extends ExtendibleElementReference> typeRefs; public TypeRefHierarchyComparator(EList<? extends ExtendibleElementReference> typeRefs) { this.typeRefs = typeRefs; } /** * Compares two references to types based on their position in the type * hierarchy, i.e. by counting the number of references to their * supertypes. {@inheritDoc} */ @Override public int compare(ExtendibleElementReference typeRef1, ExtendibleElementReference typeRef2) { ExtendibleElementReference initialTypeRef1 = typeRef1; int supertypes1 = 0; while (typeRef1.getSupertypeRef() != null) { supertypes1++; typeRef1 = typeRef1.getSupertypeRef(); } ExtendibleElementReference initialTypeRef2 = typeRef2; int supertypes2 = 0; while (typeRef2.getSupertypeRef() != null) { supertypes2++; typeRef2 = typeRef2.getSupertypeRef(); } int relativeOrder = supertypes1 - supertypes2; if (relativeOrder == 0) { relativeOrder = typeRefs.indexOf(initialTypeRef1) - typeRefs.indexOf(initialTypeRef2); } return relativeOrder; } } /** * Gets an iterator to loop an array of extensions sorted according to their * type hierarchy, starting from the one that declares a type with the least * number of supertypes. * * @param extensions * The array of extensions to be looped. * @param childName * The name of the xml child to be used in the extensions. * @param rootUri * The root uri in the hierarchy. * @return The iterator. */ public static Iterator<IConfigurationElement> getExtensionHierarchyIterator(IConfigurationElement[] extensions, String childName, String rootUri) { Map<String, String> extensionUris = new HashMap<>(); for (IConfigurationElement extension : extensions) { if (childName != null) { extension = extension.getChildren(childName)[0]; } ExtensionPointType type = new ExtensionPointType(extension); extensionUris.put(type.getUri(), type.getSupertypeUri()); } TreeSet<IConfigurationElement> hierarchy = new TreeSet<>( new ExtensionHierarchyComparator(extensionUris, childName, rootUri) ); for (IConfigurationElement extension : extensions) { hierarchy.add(extension); } return hierarchy.iterator(); } /** * Gets a tree set representing the type hierarchy of an array of types. * * @param types * The list of types. * @return The tree set. */ private static <T extends ExtendibleElement> TreeSet<T> getTypeHierarchy(EList<T> types) { TreeSet<T> hierarchy = new TreeSet<T>( new TypeHierarchyComparator(types) ); for (T type : types) { hierarchy.add(type); } return hierarchy; } /** * Gets an iterator to loop an array of types according to their type * hierarchy, starting from the one with the least number of supertypes. * * @param types * The list of types to be looped. * @return The iterator. */ public static <T extends ExtendibleElement> Iterator<T> getTypeHierarchyIterator(EList<T> types) { TreeSet<T> hierarchy = getTypeHierarchy(types); return hierarchy.iterator(); } /** * Gets an iterator to loop an array of types according to their type * hierarchy, starting from the one with the most number of supertypes. * * @param types * The list of types to be looped. * @return The iterator. */ public static <T extends ExtendibleElement> Iterator<T> getInverseTypeHierarchyIterator(EList<T> types) { TreeSet<T> hierarchy = getTypeHierarchy(types); return hierarchy.descendingIterator(); } /** * Gets a tree set representing the type hierarchy of an array of references * to types. * * @param types * The list of references to types. * @return The tree set. */ private static <T extends ExtendibleElementReference> TreeSet<T> getTypeRefHierarchy(EList<T> typeRefs) { TreeSet<T> hierarchy = new TreeSet<T>( new TypeRefHierarchyComparator(typeRefs) ); for (T typeRef : typeRefs) { hierarchy.add(typeRef); } return hierarchy; } /** * Gets an iterator to loop an array of references to types according to * their type hierarchy, starting from the one with the least number of * references to supertypes. * * @param types * The list of references to types to be looped. * @return The iterator. */ public static <T extends ExtendibleElementReference> Iterator<T> getTypeRefHierarchyIterator(EList<T> typeRefs) { TreeSet<T> hierarchy = getTypeRefHierarchy(typeRefs); return hierarchy.iterator(); } /** * Gets an iterator to loop an array of references to types according to * their type hierarchy, starting from the one with the most number of * references to supertypes. * * @param types * The list of references to types to be looped. * @return The iterator. */ public static <T extends ExtendibleElementReference> Iterator<T> getInverseTypeRefHierarchyIterator(EList<T> typeRefs) { TreeSet<T> hierarchy = getTypeRefHierarchy(typeRefs); return hierarchy.descendingIterator(); } /** * Gets the table for subtyping in the Type MID. * * @param typeMID * The Type MID. * @return The table for subtyping. */ private static Map<String, Set<String>> getSubtypeTable(MID typeMID) { return (typeMID == MMINT.cachedTypeMID) ? MMINT.subtypes : MMINT.subtypesMID; } /** * Gets the table for model type conversion in the Type * MID. * * @param typeMID * The Type MID. * @return The table for model type conversion. */ private static Map<String, Map<String, Set<List<String>>>> getConversionTable(MID typeMID) { return (typeMID == MMINT.cachedTypeMID) ? MMINT.conversions : MMINT.conversionsMID; } public static Map<Model, Set<List<ConversionOperator>>> getMultiplePathConversions(String srcModelTypeUri) { Map<String, Set<List<String>>> srcModelTypeConversionUris = getConversionTable(MMINT.cachedTypeMID).get(srcModelTypeUri); Map<Model, Set<List<ConversionOperator>>> multiplePathConversions = new HashMap<Model, Set<List<ConversionOperator>>>(); for (Map.Entry<String, Set<List<String>>> srcModelTypeConversionUrisEntry : srcModelTypeConversionUris.entrySet()) { if (srcModelTypeConversionUrisEntry.getValue().size() == 1) { continue; } Set<List<ConversionOperator>> conversionPaths = new HashSet<List<ConversionOperator>>(); multiplePathConversions.put(MIDTypeRegistry.<Model>getType(srcModelTypeConversionUrisEntry.getKey()), conversionPaths); for (List<String> conversionPathUris : srcModelTypeConversionUrisEntry.getValue()) { List<ConversionOperator> conversionPath = new ArrayList<ConversionOperator>(); conversionPaths.add(conversionPath); for (String conversionUri : conversionPathUris) { conversionPath.add(MIDTypeRegistry.<ConversionOperator>getType(conversionUri)); } } } return multiplePathConversions; } /** * Determines if a subtype-supertype relationship holds for two types. * * @param subtypeUri * The uri of the subtype. * @param supertypeUri * The uri of the supertype. * @param typeMID * The Type MID. * @return True if the subtype-supertype relationship holds, false * otherwise. */ public static boolean isSubtypeOf(String subtypeUri, String supertypeUri, MID typeMID) { Map<String, Set<String>> subtypeTable = getSubtypeTable(typeMID); if (subtypeTable == null) { return false; } Set<String> subtypes = subtypeTable.get(supertypeUri); if (subtypes == null) { return false; } return subtypes.contains(subtypeUri); } /** * Determines if a subtype-supertype relationship holds for two types in the * repository. * * @param subtypeUri * The uri of the subtype. * @param supertypeUri * The uri of the supertype. * @return True if the subtype-supertype relationship holds, false * otherwise. */ public static boolean isSubtypeOf(String subtypeUri, String supertypeUri) { return isSubtypeOf(subtypeUri, supertypeUri, MMINT.cachedTypeMID); } /** * Determines if an element is an instance of a type (conversions included). * * @param element * The element. * @param typeUri * The uri of the type. * @return An empty list if the element is an instance of the type or one of its subtypes, a list of conversion * operators to be run to convert the element into an equivalent one which in turn is an instance of the * type or one of its subtypes, or null otherwise. */ public static List<ConversionOperator> instanceOf(ExtendibleElement element, String typeUri) { List<ConversionOperator> conversionOperatorTypes = new ArrayList<>(); // static check if (element.getMetatypeUri().equals(typeUri) || isSubtypeOf(element.getMetatypeUri(), typeUri)) { return conversionOperatorTypes; } // polymorphic check List<ExtendibleElement> runtimeTypes; try { runtimeTypes = element.getRuntimeTypes(); } catch (MMINTException e) { return null; } for (ExtendibleElement runtimeType : runtimeTypes) { if (runtimeType.getUri().equals(typeUri) || isSubtypeOf(runtimeType.getUri(), typeUri)) { return conversionOperatorTypes; } } // conversion check for (ExtendibleElement runtimeType : runtimeTypes) { Map<String, Set<List<String>>> conversions = getConversionTable(MMINT.cachedTypeMID).get(runtimeType.getUri()); for (Map.Entry<String, Set<List<String>>> conversion : conversions.entrySet()) { String convertedRuntimeTypeUri = conversion.getKey(); if (typeUri.equals(convertedRuntimeTypeUri) || isSubtypeOf(convertedRuntimeTypeUri, typeUri)) { for (List<String> conversionOperatorPath : conversion.getValue()) { for (String conversionOperatorTypeUri : conversionOperatorPath) { ConversionOperator conversionOperatorType = MIDTypeRegistry.getType(conversionOperatorTypeUri); conversionOperatorTypes.add(conversionOperatorType); } break; // use first conversion found } return conversionOperatorTypes; } } } return null; } /** * Determines if an element is an instance of a type. * * @param element * The element. * @param typeUri * The uri of the type. * @param includeConversions * True if conversions should be included, false otherwise. * @return True if the element is an instance of the type or one of its subtypes. */ public static boolean instanceOf(ExtendibleElement element, String typeUri, boolean includeConversions) { List<ConversionOperator> conversionOperatorTypes = instanceOf(element, typeUri); return (conversionOperatorTypes == null || (!includeConversions && !conversionOperatorTypes.isEmpty())) ? false : true; } /** * Gets all the subtypes of a type. * * @param type * The type. * @param typeMID * The Type MID. * @return The list of subtypes of the type. */ public static <T extends ExtendibleElement> List<T> getSubtypes(T type, MID typeMID) { List<T> subtypes = new ArrayList<>(); Map<String, Set<String>> subtypeTable = getSubtypeTable(typeMID); if (subtypeTable == null) { return subtypes; } for (String subtypeUri : subtypeTable.get(type.getUri())) { T subtype = typeMID.getExtendibleElement(subtypeUri); if (subtype != null) { subtypes.add(subtype); } } return subtypes; } /** * Gets all the direct subtypes of a type. * * @param type * The type. * @param typeMID * The Type MID. * @return The list of direct subtypes of the type. */ public static <T extends ExtendibleElement> List<T> getDirectSubtypes(T type, MID typeMID) { List<T> subtypes = new ArrayList<T>(); Map<String, Set<String>> subtypeTable = getSubtypeTable(typeMID); if (subtypeTable == null) { return subtypes; } for (String subtypeUri : subtypeTable.get(type.getUri())) { T subtype = typeMID.getExtendibleElement(subtypeUri); if (subtype != null && subtype.getSupertype() == type) { subtypes.add(subtype); } } return subtypes; } /** * Gets all the subtypes of a type in the repository. * * @param type * The type. * @return The list of subtypes of the type. */ public static <T extends ExtendibleElement> List<T> getSubtypes(T type) { return getSubtypes(type, MMINT.cachedTypeMID); } public static List<GenericElement> getGenericSubtypes(GenericElement type) { /*TODO MMINT[OPERATOR] * This should really be unified with the above one, which is easy if we assign Operator as root of all operators * (and avoid to show inheritance arrows for it in the Type MID) */ if (type instanceof Operator && isRootType(type)) { // wildcard to select all operators List<GenericElement> allOperatorTypes = new ArrayList<>(MIDTypeRegistry.getOperatorTypes()); allOperatorTypes.remove(type); return allOperatorTypes; } return getSubtypes(type, MMINT.cachedTypeMID); } /** * Gets something hacky related to the multiple inheritance of a type in the * repository. * * @param typeUri * The uri of the type. * @return That something hacky. */ public static Set<String> getMultipleInheritanceUris(String typeUri) { Set<String> uris = MMINT.multipleInheritanceTable.get(typeUri); if (uris == null) { uris = new HashSet<String>(); } return uris; } /** * Gets the uri of the root type for a certain class of types. * * @param type * The type from which to understand the class of types. * @return The uri of the root type. */ public static String getRootTypeUri(ExtendibleElement type) { String rootUri = ""; if (type instanceof ModelRel) { rootUri = MMINT.ROOT_MODELREL_URI; } else if (type instanceof Model) { rootUri = MMINT.ROOT_MODEL_URI; } else if (type instanceof ModelEndpoint) { rootUri = MMINT.ROOT_MODELENDPOINT_URI; } else if (type instanceof ModelElement) { rootUri = MMINT.ROOT_MODELELEM_URI; } else if (type instanceof Mapping) { rootUri = MMINT.ROOT_MAPPING_URI; } else if (type instanceof ModelElementEndpoint) { rootUri = MMINT.ROOT_MODELELEMENDPOINT_URI; } else if (type instanceof Operator) { rootUri = MMINT.ROOT_OPERATOR_URI; } else if (type instanceof Editor) { rootUri = MMINT.ROOT_EDITOR_URI; } return rootUri; } public static Model getRootModelType() { return MIDTypeRegistry.getType(MMINT.ROOT_MODEL_URI); } public static ModelRel getRootModelRelType() { return MIDTypeRegistry.getType(MMINT.ROOT_MODELREL_URI); } public static ModelEndpoint getRootModelTypeEndpoint() { return MIDTypeRegistry.getType(MMINT.ROOT_MODELENDPOINT_URI); } public static ModelElement getRootModelElementType() { return MIDTypeRegistry.getType(MMINT.ROOT_MODELELEM_URI); } public static Mapping getRootMappingType() { return MIDTypeRegistry.getType(MMINT.ROOT_MAPPING_URI); } public static ModelElementEndpoint getRootModelElementTypeEndpoint() { return MIDTypeRegistry.getType(MMINT.ROOT_MODELELEMENDPOINT_URI); } public static Operator getRootOperatorType() { return MIDTypeRegistry.getType(MMINT.ROOT_OPERATOR_URI); } public static Editor getRootEditorType() { return MIDTypeRegistry.getType(MMINT.ROOT_EDITOR_URI); } /** * Checks if the given type is the root type for its class. * * @param type * The type to be checked. * @return True if the given type is the root type, false otherwise. */ public static boolean isRootType(ExtendibleElement type) { return type.getUri().equals(getRootTypeUri(type)); } //TODO[MODELENDPOINT] returns null for non-overriding, or overridden model type endpoint (which can be the root one) public static ModelEndpoint getOverriddenModelTypeEndpoint(ModelRel modelRelType, Model targetModelType) { boolean isBinary = (modelRelType instanceof BinaryModelRel); MID typeMID = modelRelType.getMIDContainer(); modelRelType = (ModelRel) modelRelType.getSupertype(); while (!isRootType(modelRelType)) { for (ModelEndpoint modelTypeEndpoint : modelRelType.getModelEndpoints()) { if (isBinary && targetModelType.getUri().equals(modelTypeEndpoint.getTargetUri())) { return null; } if (MIDTypeHierarchy.isSubtypeOf(targetModelType.getUri(), modelTypeEndpoint.getTargetUri(), typeMID)) { if (!isBinary) { if (!MIDDialogs.getBooleanInput("Override model type endpoint", "Override " + modelTypeEndpoint.getName() + "?")) { continue; } } return modelTypeEndpoint; } } modelRelType = (ModelRel) modelRelType.getSupertype(); } return typeMID.getExtendibleElement(MMINT.ROOT_MODELENDPOINT_URI); } public static ModelElementEndpoint getOverriddenModelElementTypeEndpoint(MappingReference mappingTypeRef, ModelElementReference targetModelElemTypeRef) { boolean isBinary = (mappingTypeRef instanceof BinaryMappingReference); MID typeMID = mappingTypeRef.getMIDContainer(); Mapping mappingType = mappingTypeRef.getObject().getSupertype(); while (!isRootType(mappingType)) { for (ModelElementEndpoint modelElemTypeEndpoint : mappingType.getModelElemEndpoints()) { if (isBinary && targetModelElemTypeRef.getUri().equals(modelElemTypeEndpoint.getTargetUri())) { return null; } if (MIDTypeHierarchy.isSubtypeOf(targetModelElemTypeRef.getUri(), modelElemTypeEndpoint.getTargetUri(), typeMID)) { if (!isBinary) { if (!MIDDialogs.getBooleanInput("Override model element type endpoint", "Override " + modelElemTypeEndpoint.getName() + "?")) { continue; } } return modelElemTypeEndpoint; } } mappingType = mappingType.getSupertype(); } return typeMID.getExtendibleElement(MMINT.ROOT_MODELELEMENDPOINT_URI); } public static List<? extends ExtendibleElement> getCachedRuntimeTypes(ExtendibleElement instance) { return MMINT.cachedRuntimeTypes.get(instance); } public static void addCachedRuntimeTypes(ExtendibleElement instance, List<? extends ExtendibleElement> cachedTypes) { MMINT.cachedRuntimeTypes.put(instance, cachedTypes); } //TODO MMINT[OO] Make it a safe caching mechanism, not to be invalidated explicitly public static void clearCachedRuntimeTypes() { MMINT.cachedRuntimeTypes.clear(); } }