/******************************************************************************* | |
* Copyright (c) 2006, 2012 David A Carlson 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: | |
* David A Carlson (XMLmodeling.com) - initial API and implementation | |
* Kenn Hussey - added utility to retrieve controlled (sub-)resources | |
* Kenn Hussey - added utilities for working with (model) properties files | |
* Christian W. Damus - factor out CDA base model dependencies (artf3350) | |
* | |
* $Id$ | |
*******************************************************************************/ | |
package org.eclipse.mdht.uml.common.util; | |
import java.io.BufferedInputStream; | |
import java.io.IOException; | |
import java.io.OutputStream; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.ArrayList; | |
import java.util.LinkedHashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.resources.IResourceVisitor; | |
import org.eclipse.core.resources.IWorkspace; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.emf.common.util.EList; | |
import org.eclipse.emf.common.util.TreeIterator; | |
import org.eclipse.emf.common.util.URI; | |
import org.eclipse.emf.common.util.UniqueEList; | |
import org.eclipse.emf.common.util.WrappedException; | |
import org.eclipse.emf.ecore.EAnnotation; | |
import org.eclipse.emf.ecore.EClass; | |
import org.eclipse.emf.ecore.EObject; | |
import org.eclipse.emf.ecore.InternalEObject; | |
import org.eclipse.emf.ecore.resource.Resource; | |
import org.eclipse.emf.ecore.resource.ResourceSet; | |
import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl; | |
import org.eclipse.emf.ecore.util.EcoreUtil; | |
import org.eclipse.emf.ecore.xmi.XMLResource; | |
import org.eclipse.mdht.uml.common.internal.Logger; | |
import org.eclipse.uml2.common.util.UML2Util; | |
import org.eclipse.uml2.uml.Artifact; | |
import org.eclipse.uml2.uml.Association; | |
import org.eclipse.uml2.uml.Class; | |
import org.eclipse.uml2.uml.Classifier; | |
import org.eclipse.uml2.uml.ClassifierTemplateParameter; | |
import org.eclipse.uml2.uml.Constraint; | |
import org.eclipse.uml2.uml.DataType; | |
import org.eclipse.uml2.uml.DirectedRelationship; | |
import org.eclipse.uml2.uml.Element; | |
import org.eclipse.uml2.uml.Enumeration; | |
import org.eclipse.uml2.uml.EnumerationLiteral; | |
import org.eclipse.uml2.uml.Generalization; | |
import org.eclipse.uml2.uml.Interface; | |
import org.eclipse.uml2.uml.NamedElement; | |
import org.eclipse.uml2.uml.Namespace; | |
import org.eclipse.uml2.uml.Operation; | |
import org.eclipse.uml2.uml.Package; | |
import org.eclipse.uml2.uml.PackageImport; | |
import org.eclipse.uml2.uml.ParameterableElement; | |
import org.eclipse.uml2.uml.PrimitiveType; | |
import org.eclipse.uml2.uml.Profile; | |
import org.eclipse.uml2.uml.Property; | |
import org.eclipse.uml2.uml.Signal; | |
import org.eclipse.uml2.uml.Stereotype; | |
import org.eclipse.uml2.uml.StructuredClassifier; | |
import org.eclipse.uml2.uml.TemplateBinding; | |
import org.eclipse.uml2.uml.TemplateParameterSubstitution; | |
import org.eclipse.uml2.uml.Type; | |
import org.eclipse.uml2.uml.UMLPackage; | |
import org.eclipse.uml2.uml.util.UMLSwitch; | |
/** | |
* | |
* @version $Id: $ | |
*/ | |
public class UMLUtil { | |
private static final String UML2REFLECTIONERROR = "UML2 Reflection Error"; | |
/** | |
* File encoding for properties files. | |
*/ | |
protected static final String PROPERTIES_ENCODING = "ISO-8859-1"; | |
/** | |
* Patterns for lines in a properties file. | |
*/ | |
protected static Pattern PROPERTY_LINE = Pattern.compile("\\s*(\\S+)\\s*=.*", Pattern.MULTILINE); | |
public static void addAliasName(Element element, String alias) { | |
if (alias != null && alias.length() > 0) { | |
EAnnotation annotation = element.createEAnnotation("uml2.alias"); | |
annotation.getDetails().put(alias, null); | |
} | |
} | |
public static void cloneStereotypes(Class first, Class second) { | |
cloneStereotypes((Element) first, (Element) second); | |
for (Property p1 : first.getOwnedAttributes()) { | |
Property p2 = second.getOwnedAttribute(p1.getName(), p1.getType()); | |
if (p2 != null) { | |
cloneStereotypes(p1, p2); | |
} | |
} | |
for (Constraint constraint1 : first.getOwnedRules()) { | |
Constraint constraint2 = second.getOwnedRule(constraint1.getName()); | |
if (constraint2 != null) { | |
cloneStereotypes(constraint1, constraint2); | |
} | |
} | |
for (Classifier nested1 : first.getNestedClassifiers()) { | |
Classifier nested2 = second.getNestedClassifier(nested1.getName()); | |
if (nested2 != null) { | |
if (nested1 instanceof Class && nested2 instanceof Class) { | |
cloneStereotypes((Class) nested1, (Class) nested2); | |
} else { | |
cloneStereotypes(nested1, nested2); | |
} | |
} | |
} | |
} | |
public static void cloneStereotypes(Element first, Element second) { | |
for (Stereotype s : first.getAppliedStereotypes()) { | |
if (second.isStereotypeApplicable(s) && !second.isStereotypeApplied(s)) { | |
second.applyStereotype(s); | |
for (Property sProperty : s.getAllAttributes()) { | |
if (sProperty.getName().startsWith("base_")) { | |
continue; | |
} | |
try { | |
Object value = first.getValue(s, sProperty.getName()); | |
if (value != null) { | |
second.setValue(s, sProperty.getName(), value); | |
} | |
} catch (IllegalArgumentException e) { | |
// ignore | |
} | |
} | |
} | |
} | |
/* | |
* TODO clone stereotypes for all matching owned elements. However | |
* getOwnedElements() is a union and may have problems with list order | |
* and getting matching owned elements in second element. | |
*/ | |
// for (Element ownedElement : first.getOwnedElements()) { | |
// | |
// } | |
} | |
public static void cloneStereotypes(Enumeration first, Enumeration second) { | |
cloneStereotypes((Element) first, (Element) second); | |
for (EnumerationLiteral literal1 : first.getOwnedLiterals()) { | |
EnumerationLiteral literal2 = second.getOwnedLiteral(literal1.getName()); | |
if (literal2 != null) { | |
cloneStereotypes(literal1, literal2); | |
} | |
} | |
} | |
/** | |
* Accumulate a list containing general classifiers of all generalizations | |
* for the given classifier, including the given classfier. | |
* | |
* @param classifier | |
* @return a List with zero or more classifiers | |
*/ | |
public static List<Classifier> getAllGeneralizations(Classifier classifier) { | |
List<Classifier> parents = new ArrayList<Classifier>(); | |
parents.add(classifier); | |
for (Classifier parent : classifier.getGenerals()) { | |
parents.addAll(getAllGeneralizations(parent)); | |
} | |
return parents; | |
} | |
/** | |
* Accumulate a list containing the unqualified names of all generalizations | |
* for the given classifier, including this classfier name. | |
* | |
* @param classifier | |
* @return a List with zero or more classifiers | |
*/ | |
public static List<String> getAllParentNames(Classifier classifier) { | |
List<String> parentNames = new ArrayList<String>(); | |
parentNames.add(classifier.getName()); | |
for (Classifier parent : classifier.getGenerals()) { | |
parentNames.addAll(getAllParentNames(parent)); | |
} | |
return parentNames; | |
} | |
/** | |
* Accumulate a list containing specific classifiers of direct subclass generalizations | |
* for the given classifier, including the given classfier. This list will include | |
* only classifier from models loaded into the current ResourceSet. | |
* | |
* @param classifier | |
* @return a List with zero or more classifiers | |
*/ | |
public static List<Classifier> getSpecializations(Classifier classifier) { | |
List<Classifier> subclasses = new ArrayList<Classifier>(); | |
List<DirectedRelationship> specializations = classifier.getTargetDirectedRelationships( | |
UMLPackage.Literals.GENERALIZATION); | |
for (DirectedRelationship relationship : specializations) { | |
Classifier specific = ((Generalization) relationship).getSpecific(); | |
if (specific != null) { | |
subclasses.add(specific); | |
} | |
} | |
return subclasses; | |
} | |
/** | |
* Accumulate a list containing specific classifiers of all subclass generalizations | |
* for the given classifier, including the given classfier. This list will include | |
* only classifier from models loaded into the current ResourceSet. | |
* | |
* @param classifier | |
* @return a List with zero or more classifiers | |
*/ | |
public static List<Classifier> getAllSpecializations(Classifier classifier) { | |
List<Classifier> allSpecializations = new ArrayList<Classifier>(); | |
List<DirectedRelationship> specializations = classifier.getTargetDirectedRelationships( | |
UMLPackage.Literals.GENERALIZATION); | |
for (DirectedRelationship relationship : specializations) { | |
Classifier specific = ((Generalization) relationship).getSpecific(); | |
if (specific != null) { | |
allSpecializations.add(specific); | |
allSpecializations.addAll(getAllSpecializations(specific)); | |
} | |
} | |
return allSpecializations; | |
} | |
/** | |
* Find applied profile to a Package container of element, or return null if | |
* profile is not applied. | |
* | |
* @param profileURI | |
* @param element | |
* @return | |
*/ | |
public static Profile getAppliedProfile(String profileURI, Element element) { | |
if (element == null) { | |
return null; | |
} | |
try { | |
ResourceSet resourceSet = element.eResource().getResourceSet(); | |
Resource profileResource = resourceSet.getResource(URI.createURI(profileURI), true); | |
if (profileResource != null) { | |
// is profile loaded into this resource set? | |
Profile profile = (Profile) EcoreUtil.getObjectByType( | |
profileResource.getContents(), UMLPackage.eINSTANCE.getProfile()); | |
if (profile == null) { | |
return null; | |
} | |
try { | |
Package pkg = element.getNearestPackage(); | |
while (pkg != null) { | |
if (pkg.isProfileApplied(profile)) { | |
return profile; | |
} else { | |
pkg = pkg.getNestingPackage(); | |
} | |
} | |
} catch (IllegalArgumentException e) { | |
Logger.logException(e); | |
} | |
} | |
} catch (WrappedException we) { | |
Logger.logException(we); | |
} | |
return null; | |
} | |
/** | |
* Search all nested packages for the given class name. This search does not | |
* consider qualified names, but only looks for a matching local name. | |
* | |
* @param basePackage | |
* base package for the library | |
* @param localName | |
* @return a Class, or null if not found | |
*/ | |
public static Class getClassByName(Package basePackage, final String localName) { | |
return (Class) getClassifierByName(basePackage, localName, UMLPackage.Literals.CLASS); | |
} | |
/** | |
* Search all nested packages for the given classifier name. This search | |
* does not consider qualified names, but only looks for a matching local | |
* name. | |
* | |
* @param basePackage | |
* base package for the library | |
* @param localName | |
* @return a Classifier, or null if not found | |
*/ | |
public static Classifier getClassifierByName(Package basePackage, final String localName) { | |
return getClassifierByName(basePackage, localName, null); | |
} | |
/** | |
* Search all nested packages for the given classifier name. This search | |
* does not consider qualified names, but only looks for a matching local | |
* classifer name. | |
* | |
* @param basePackage | |
* base package for the library | |
* @param localName | |
* @param eClass | |
* UML metaclass to search for | |
* @return a Classifier, or null if not found | |
*/ | |
public static Classifier getClassifierByName(Package basePackage, final String localName, final EClass eClass) { | |
if (basePackage == null || localName == null) { | |
return null; | |
} | |
Classifier classifier = null; | |
UMLSwitch<Object> umlSwitch = new UMLSwitch<Object>() { | |
@Override | |
public Object caseClassifier(Classifier classifier) { | |
if (localName.equals(classifier.getName())) { | |
if (eClass == null) { | |
return classifier; | |
} else { | |
return eClass == classifier.eClass() | |
? classifier | |
: null; | |
} | |
} else { | |
return null; | |
} | |
} | |
@Override | |
public Object casePackage(Package pkg) { | |
Object result = null; | |
for (NamedElement namedElement : pkg.getOwnedMembers()) { | |
result = doSwitch(namedElement); | |
if (result != null) { | |
break; | |
} | |
} | |
return result; | |
} | |
}; | |
classifier = (Classifier) umlSwitch.doSwitch(basePackage); | |
return classifier; | |
} | |
/** | |
* getConstrainingClassifier is a static utility method used to encapsulate | |
* UML 2.3 Migration in the code base. The goal is to use the same code and | |
* avoid multiple builds, features, sites to support both UML 2.2 and 2.3 | |
* api with the same code base. | |
* | |
* The difference in this case is that ConstrainingClassifier is scalar in | |
* 2.2 (0..1) and list (0..*) in 2.3 | |
* | |
* Implementation/Exception Handling Note - The reflection API currently | |
* does not have an "hasMethod" so the approach is to call getMethod on the | |
* target class starting with UML 2.2 method. If the method is not found or | |
* some exception has been encountered attempt to get the 2.3 method. If | |
* both attempts fail, throw a RuntimeException. The reasoning behind this | |
* approach is not to introduce a series of exception handling for | |
* reflection errors into the base code because the project will eventually | |
* migrate completely to 2.3 so the current logic captures and ignores all | |
* exceptions. | |
* | |
* http://www.eclipse.org/modeling/mdt/uml2/docs/guides/UML2_3.0 | |
* _Migration_Guide/guide.html | |
* | |
* @param classifierTemplateParameter | |
* @return | |
* @throws NoSuchMethodException | |
* | |
* | |
*/ | |
public static Classifier getConstrainingClassifier(ClassifierTemplateParameter classifierTemplateParameter) { | |
Classifier classifier = null; | |
boolean reflectionCompleted = false; | |
try { | |
// Attempt UML 2.2 API | |
Method getConstrainingClassifier = ClassifierTemplateParameter.class.getMethod( | |
"getConstrainingClassifier", (java.lang.Class<?>[]) null); | |
classifier = (Classifier) getConstrainingClassifier.invoke(classifierTemplateParameter, (Object[]) null); | |
reflectionCompleted = true; | |
} catch (IllegalArgumentException e) { | |
// Consume Exception | |
} catch (IllegalAccessException e) { | |
// Consume Exception | |
} catch (InvocationTargetException e) { | |
// Consume Exception | |
} catch (SecurityException e) { | |
// Consume Exception | |
} catch (NoSuchMethodException e) { | |
// Consume Exception | |
} | |
if (!reflectionCompleted) { | |
try { | |
// Attempt UML 2.3 API | |
Method getConstrainingClassifiers = ClassifierTemplateParameter.class.getMethod( | |
"getConstrainingClassifiers", (java.lang.Class<?>[]) null); | |
EList<Classifier> classifiers = (EList<Classifier>) getConstrainingClassifiers.invoke( | |
classifierTemplateParameter, (Object[]) null); | |
if (classifiers.size() > 0) { | |
classifier = classifiers.get(0); | |
} | |
reflectionCompleted = true; | |
} catch (IllegalArgumentException e) { | |
// Consume Exception | |
} catch (IllegalAccessException e) { | |
// Consume Exception | |
} catch (InvocationTargetException e) { | |
// Consume Exception | |
} catch (SecurityException e) { | |
// Consume Exception | |
} catch (NoSuchMethodException e) { | |
// Consume Exception | |
} | |
} | |
// If neither 2.2/2.3 or some other execution error a general purpose | |
// UML 2 Reflection Error | |
if (!reflectionCompleted) { | |
throw new RuntimeException(UML2REFLECTIONERROR); | |
} | |
return classifier; | |
} | |
public static List<Resource> getControlledResources(Resource resource) { | |
List<Resource> controlledResources = new UniqueEList.FastCompare<Resource>(); | |
if (resource != null) { | |
for (TreeIterator<EObject> allContents = resource.getAllContents(); allContents.hasNext();) { | |
Resource directResource = ((InternalEObject) allContents.next()).eDirectResource(); | |
if (directResource != null && directResource != resource) { | |
controlledResources.add(directResource); | |
} | |
} | |
} | |
return controlledResources; | |
} | |
/** | |
* Search all nested packages for the given data type name. This search does | |
* not consider qualified names, but only looks for a matching local name. | |
* | |
* @param basePackage | |
* base package for the library | |
* @param localName | |
* @return a DataType, or null if not found | |
*/ | |
public static DataType getDataTypeByName(Package basePackage, final String localName) { | |
return (DataType) getClassifierByName(basePackage, localName, UMLPackage.Literals.DATA_TYPE); | |
} | |
/** | |
* Search all nested packages for the given enumeration name. This search | |
* does not consider qualified names, but only looks for a matching local | |
* name. | |
* | |
* @param basePackage | |
* base package for the library | |
* @param localName | |
* @return an Enumeration, or null if not found | |
*/ | |
public static Enumeration getEnumerationByName(Package basePackage, final String localName) { | |
return (Enumeration) getClassifierByName(basePackage, localName, UMLPackage.Literals.ENUMERATION); | |
} | |
/** | |
* Get nearest UML namespace containing this model element. | |
*/ | |
public static Namespace getNearestNamespace(Element element) { | |
if (element.eIsProxy()) { | |
return null; | |
} | |
EObject eObject = element; | |
while (!(eObject instanceof Namespace)) { | |
eObject = eObject.eContainer(); | |
} | |
return eObject instanceof Namespace | |
? (Namespace) eObject | |
: null; | |
} | |
/** | |
* Delegates to the subclass specific getOwnedAttributes() method for type. | |
* | |
* @param type | |
* @return list of Property | |
*/ | |
public static List<Property> getOwnedAttributes(Type type) { | |
return new UMLSwitch<List<Property>>() { | |
@Override | |
public List<Property> caseArtifact(Artifact artifact) { | |
return artifact.getOwnedAttributes(); | |
} | |
@Override | |
public List<Property> caseDataType(DataType dataType) { | |
return dataType.getOwnedAttributes(); | |
} | |
@Override | |
public List<Property> caseInterface(Interface interface_) { | |
return interface_.getOwnedAttributes(); | |
} | |
@Override | |
public List<Property> caseSignal(Signal signal) { | |
return signal.getOwnedAttributes(); | |
} | |
@Override | |
public List<Property> caseStructuredClassifier(StructuredClassifier structuredClassifier) { | |
return structuredClassifier.getOwnedAttributes(); | |
} | |
@Override | |
public List<Property> doSwitch(EObject eObject) { | |
return null == eObject | |
? null | |
: super.doSwitch(eObject); | |
} | |
}.doSwitch(type); | |
} | |
/** | |
* Delegates to the subclass specific getOwnedOperations() method for type. | |
* | |
* @param type | |
* @return list of Operation | |
*/ | |
public static List<Operation> getOwnedOperations(Type type) { | |
return new UMLSwitch<List<Operation>>() { | |
@Override | |
public List<Operation> caseArtifact(Artifact artifact) { | |
return artifact.getOwnedOperations(); | |
} | |
@Override | |
public List<Operation> caseDataType(DataType dataType) { | |
return dataType.getOwnedOperations(); | |
} | |
@Override | |
public List<Operation> caseInterface(Interface interface_) { | |
return interface_.getOwnedOperations(); | |
} | |
@Override | |
public List<Operation> caseClass(Class clazz) { | |
return clazz.getOwnedOperations(); | |
} | |
@Override | |
public List<Operation> doSwitch(EObject eObject) { | |
return null == eObject | |
? null | |
: super.doSwitch(eObject); | |
} | |
}.doSwitch(type); | |
} | |
public static String getClassQualifiedName(Property property) { | |
if (property.eIsProxy() || property.getName() == null) { | |
return null; | |
} | |
StringBuffer qname = new StringBuffer(); | |
qname.append(property.getClass_().getName()); | |
qname.append(NamedElement.SEPARATOR); | |
qname.append(property.getName()); | |
return qname.toString(); | |
} | |
public static String getPackageQualifiedName(NamedElement namedElement) { | |
if (namedElement.eIsProxy() || namedElement.getName() == null) { | |
return null; | |
} | |
StringBuffer qname = new StringBuffer(namedElement.getName()); | |
Element container = namedElement.getOwner(); | |
while (container instanceof NamedElement) { | |
qname.insert(0, NamedElement.SEPARATOR); | |
qname.insert(0, ((NamedElement) container).getName()); | |
if (container instanceof Package) { | |
break; | |
} | |
container = container.getOwner(); | |
} | |
return qname.toString(); | |
} | |
/** | |
* setParameterableElement is a static utility method used to encapsulate | |
* UML 2.3 Migration in the code base. The goal is to use the same code and | |
* avoid multiple builds, features, sites to support both UML 2.2 and 2.3 | |
* api with the same code base. | |
* | |
* The difference in this case is that TemplateParameterSubstitution Actual | |
* Parameters a list (0..*) in 2.2 and scalar (0..1) in 2.3 | |
* | |
* Implementation/Exception Handling Note - The reflection API currently | |
* does not have an "hasMethod" so the approach is to call getMethod on the | |
* target class starting with UML 2.2 method. If the method is not found or | |
* some exception has been encountered attempt to get the 2.3 method. If | |
* both attempts fail, throw a RuntimeException. The reasoning behind this | |
* approach is not to introduce a series of exception handling for | |
* reflection errors into the base code because the project will eventually | |
* migrate completely to 2.3 so the current logic captures and ignores all | |
* exceptions. | |
* | |
* http://www.eclipse.org/modeling/mdt/uml2/docs/guides/UML2_3.0 | |
* _Migration_Guide/guide.html | |
* | |
* @param substitution | |
* @return | |
*/ | |
public static ParameterableElement getParameterableElement(TemplateParameterSubstitution substitution) { | |
ParameterableElement parameterableElement = null; | |
boolean reflectionCompleted = false; | |
try { | |
// Attempt UML 2.2 API | |
Method getAcuals = TemplateParameterSubstitution.class.getMethod("getActuals", null); | |
EList<ParameterableElement> actuals = (EList<ParameterableElement>) getAcuals.invoke(substitution, null); | |
reflectionCompleted = true; | |
if (actuals.size() > 0) { | |
parameterableElement = actuals.get(0); | |
} | |
} catch (IllegalArgumentException e) { | |
// Consume Exception | |
} catch (IllegalAccessException e) { | |
// Consume Exception | |
} catch (InvocationTargetException e) { | |
// Consume Exception | |
} catch (SecurityException e) { | |
// Consume Exception | |
} catch (NoSuchMethodException e) { | |
// Consume Exception | |
} | |
if (!reflectionCompleted) { | |
try { | |
// Attempt UML 2.3 API | |
Method getAcual = TemplateParameterSubstitution.class.getMethod("getActual", null); | |
parameterableElement = (ParameterableElement) getAcual.invoke(substitution, null); | |
reflectionCompleted = true; | |
} catch (IllegalArgumentException e) { | |
// Consume Exception | |
} catch (IllegalAccessException e) { | |
// Consume Exception | |
} catch (InvocationTargetException e) { | |
// Consume Exception | |
} catch (SecurityException e) { | |
// Consume Exception | |
} catch (NoSuchMethodException e) { | |
// Consume Exception | |
} | |
} | |
// If neither 2.2/2.3 or some other execution error a general purpose | |
// UML 2 Reflection Error | |
if (!reflectionCompleted) { | |
throw new RuntimeException(UML2REFLECTIONERROR); | |
} | |
return parameterableElement; | |
} | |
/** | |
* Returns a URI for the properties file corresponding to the specified | |
* resource; this will be essentially the same URI except with a properties | |
* file extension. | |
* | |
* @param resource | |
* The model resource. | |
* @return A properties file URI. | |
*/ | |
public static URI getPropertiesURI(Resource resource) { | |
// test added due to runtime NPE | |
if (resource != null && resource.getURI() != null) { | |
return resource.getURI().trimFileExtension().appendFileExtension("properties"); | |
} else { | |
return null; | |
} | |
} | |
/** | |
* Returns the navigable end of an assoiciation's member properties, | |
* or null if none or more than one navigable end. | |
* | |
* @param association | |
* @return Property, or null if none or more than one navigable end | |
*/ | |
public static Property getNavigableEnd(Association association) { | |
Property navigableEnd = null; | |
for (Property end : association.getMemberEnds()) { | |
if (end.isNavigable()) { | |
if (navigableEnd != null) { | |
return null; // multiple navigable ends | |
} | |
navigableEnd = end; | |
} | |
} | |
return navigableEnd; | |
} | |
public static List<Property> getRedefinedProperties(Property property) { | |
List<Property> redefinedProperties = new ArrayList<Property>(); | |
if (!property.getRedefinedProperties().isEmpty()) { | |
redefinedProperties.addAll(property.getRedefinedProperties()); | |
} else { | |
for (Classifier parent : property.getClass_().allParents()) { | |
Property p = parent.getAttribute(property.getName(), null); | |
if (p != null) { | |
redefinedProperties.add(p); | |
} | |
} | |
} | |
return redefinedProperties; | |
} | |
/** | |
* If classifier is a template binding and template is a Classifier, return | |
* the template, else return null. | |
* | |
* @param classifier | |
* @return template classifier, or null if not a template binding | |
*/ | |
public static Classifier getTemplate(Classifier classifier) { | |
Classifier template = null; | |
for (TemplateBinding binding : classifier.getTemplateBindings()) { | |
if (binding.getSignature().getTemplate() instanceof Classifier) { | |
template = (Classifier) binding.getSignature().getTemplate(); | |
} | |
} | |
return template; | |
} | |
/** | |
* If classifier is a template binding and template is a Classifier, return | |
* a list of template parameter substitutions. Only include | |
* ParameterableElement if it is a Classifier. | |
* | |
* @param classifier | |
* @return list of template binding parameter substitutions | |
*/ | |
public static List<Classifier> getTemplateBindingParameters(Classifier classifier) { | |
List<Classifier> params = new ArrayList<Classifier>(); | |
for (TemplateBinding binding : classifier.getTemplateBindings()) { | |
if (binding.getSignature().getTemplate() instanceof Classifier) { | |
for (TemplateParameterSubstitution substitution : binding.getParameterSubstitutions()) { | |
ParameterableElement element = getParameterableElement(substitution); | |
if (element instanceof Classifier) { | |
params.add((Classifier) element); | |
} | |
} | |
} | |
// only process one binding, why would there ever be more? | |
break; | |
} | |
return params; | |
} | |
/** | |
* Returns the outermost top package containing the given element. | |
* | |
* @param element | |
* @return a Package | |
*/ | |
public static Package getTopPackage(Element element) { | |
return element == null || element.eIsProxy() | |
? null | |
: getTopPackage(element.getNearestPackage()); | |
} | |
/** | |
* Returns the outermost top package containing the given package. | |
* | |
* @param pkg | |
* @return a Package | |
*/ | |
public static Package getTopPackage(Package pkg) { | |
if (pkg == null || pkg.eIsProxy()) { | |
return null; | |
} | |
EList<Package> allOwningPackages = pkg.allOwningPackages(); | |
int size = allOwningPackages.size(); | |
return size > 0 | |
? allOwningPackages.get(size - 1) | |
: pkg; | |
} | |
/** | |
* Find next unused nested classifier name, using 'name' as the base. | |
*/ | |
public static String getUniqueNestedClassifierName(Class owner, String name) { | |
int seqNo = 1; | |
String uniqueName = name; | |
while (null != owner.getNestedClassifier(uniqueName)) { | |
uniqueName = name + String.valueOf(seqNo++); | |
} | |
return uniqueName; | |
} | |
/** | |
* Find next unused type name, using 'name' as the base. | |
*/ | |
public static String getUniqueTypeName(Package owner, String name) { | |
int seqNo = 1; | |
String uniqueName = name; | |
while (null != owner.getOwnedType(uniqueName)) { | |
uniqueName = name + String.valueOf(seqNo++); | |
} | |
return uniqueName; | |
} | |
/** | |
* Import library into a model containing the given package. | |
* | |
* @param basePackage | |
* a package for the model into which library is imported | |
* @return root package of the imported library | |
*/ | |
public static Package importLibrary(Package basePackage, String libraryURI) { | |
Package library = null; | |
try { | |
ResourceSet resourceSet = basePackage.eResource().getResourceSet(); | |
Resource libraryResource = resourceSet.getResource(URI.createURI(libraryURI), true); | |
if (libraryResource != null) { | |
library = (Package) EcoreUtil.getObjectByType( | |
libraryResource.getContents(), UMLPackage.eINSTANCE.getPackage()); | |
// This must be called from within an EMFT Transaction. | |
if (library != null) { | |
PackageImport packageImport = null; | |
Package topPackage = getTopPackage(basePackage); | |
for (PackageImport pkgImport : topPackage.getPackageImports()) { | |
if (library.equals(pkgImport.getImportedPackage())) { | |
packageImport = pkgImport; | |
break; | |
} | |
} | |
if (packageImport == null) { | |
packageImport = topPackage.createPackageImport(library); | |
} | |
} | |
} | |
} catch (WrappedException we) { | |
Logger.logException(we); | |
} | |
return library; | |
} | |
public static boolean isSameModel(Element first, Element second) { | |
if (first == null || second == null) { | |
return false; | |
} | |
Package firstPackage = UMLUtil.getTopPackage(first); | |
Package secondPackage = UMLUtil.getTopPackage(second); | |
if (firstPackage != null || secondPackage != null) { | |
if (firstPackage != null) { | |
return firstPackage.equals(secondPackage); | |
} | |
} else if (first.eResource() != null && second.eResource() != null) { | |
return first.eResource().getURI().equals(second.eResource().getURI()); | |
} | |
return false; | |
} | |
/** | |
* FindResourcesByNameVisitor searches the resource for resources of a particular name | |
* You would think there was a method for this already but i could not find it | |
* | |
* @author seanmuir | |
* | |
*/ | |
public static class FindResourcesByNameVisitor implements IResourceVisitor { | |
private String resourceName; | |
private ArrayList<IResource> resources = new ArrayList<IResource>(); | |
/** | |
* @return the resources | |
*/ | |
public ArrayList<IResource> getResources() { | |
return resources; | |
} | |
/** | |
* @param resourceName | |
*/ | |
public FindResourcesByNameVisitor(String resourceName) { | |
super(); | |
this.resourceName = resourceName; | |
} | |
/* | |
* (non-Javadoc) | |
* | |
* @see org.eclipse.core.resources.IResourceVisitor#visit(org.eclipse.core.resources.IResource) | |
*/ | |
public boolean visit(IResource arg0) throws CoreException { | |
if (resourceName != null && resourceName.equals(arg0.getName())) { | |
resources.add(arg0); | |
} | |
return true; | |
} | |
} | |
public static boolean isSameProject(Element first, Element second) { | |
// TODO Move this to a plugin which supports ResourcePlugin | |
// This is not fool proof - if a resource with the same name exists in multiple projects | |
// we will return not the same however this is unlikely | |
if (first == null || first.eResource() == null || second == null || second.eResource() == null) { | |
return false; | |
} | |
try { | |
FindResourcesByNameVisitor firstVisitor = new FindResourcesByNameVisitor( | |
first.eResource().getURI().lastSegment()); | |
IWorkspace iw = org.eclipse.core.resources.ResourcesPlugin.getWorkspace(); | |
iw.getRoot().accept(firstVisitor); | |
FindResourcesByNameVisitor secondVisitor = new FindResourcesByNameVisitor( | |
second.eResource().getURI().lastSegment()); | |
iw.getRoot().accept(secondVisitor); | |
if ((!firstVisitor.getResources().isEmpty() && firstVisitor.getResources().size() == 1) && | |
(!secondVisitor.getResources().isEmpty() && secondVisitor.getResources().size() == 1)) { | |
IProject firstProject = firstVisitor.getResources().get(0).getProject(); | |
IProject secondProject = secondVisitor.getResources().get(0).getProject(); | |
if (firstProject.equals(secondProject)) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
} catch (CoreException e) { | |
} | |
return false; | |
} | |
/** | |
* @param classifier | |
* @return true if classifier contains template bindings. | |
*/ | |
public static boolean isTemplateBinding(Classifier classifier) { | |
return classifier != null && classifier.getTemplateBindings().size() > 0; | |
} | |
/** | |
* Parses the specified properties file contents into a map of key/value | |
* pairs, where the key is the key for a named element and the value is the | |
* corresponding line from the properties file. | |
* | |
* @param properties | |
* The string contents of a properties file as a string. | |
* @return A map containing key/value pairs for the specified properties | |
* file contents. | |
*/ | |
public static Map<String, String> parseProperties(String properties) { | |
Map<String, String> result = new LinkedHashMap<String, String>(); | |
int i = 0; | |
while (i < properties.length()) { | |
int eol; | |
for (int start = i;;) { | |
eol = properties.indexOf("\n", start); | |
if (eol != -1) { | |
if (eol + 1 < properties.length() && properties.charAt(eol + 1) == '\r') { | |
if (eol > start && properties.charAt(eol - 1) == '\\') { | |
start = eol + 2; | |
} else { | |
++eol; | |
break; | |
} | |
} else if (eol > start && properties.charAt(eol - 1) == '\\' || eol - 1 > start && | |
properties.charAt(eol - 1) == '\r' && properties.charAt(eol - 2) == '\\') { | |
start = eol + 1; | |
} else { | |
break; | |
} | |
} else { | |
eol = properties.indexOf("\r", start); | |
if (eol == -1) { | |
eol = properties.length() - 1; | |
break; | |
} else if (eol > start && properties.charAt(eol - 1) == '\\') { | |
start = eol + 1; | |
} else { | |
break; | |
} | |
} | |
} | |
String property = properties.substring(i, eol + 1); | |
Matcher matcher = PROPERTY_LINE.matcher(property); | |
if (matcher.find() && matcher.groupCount() >= 1) { | |
int begin = matcher.start(1); | |
int end = matcher.end(1); | |
String propertyName = property.substring(begin, end); | |
if (propertyName.indexOf("#") == -1) { | |
result.put(propertyName, property); | |
} else if (propertyName.startsWith("#")) { | |
result.put(propertyName.substring(1), property); | |
} | |
} | |
i = eol + 1; | |
} | |
return result; | |
} | |
/** | |
* Reads the properties file at the specified URI. | |
* | |
* @param uri | |
* The URI of the properties file. | |
* @return The contents of the properties file as a string, or <code>null</code> if an exception occurs. | |
*/ | |
public static String readProperties(URI uri) { | |
if (uri != null) { | |
try { | |
BufferedInputStream bufferedInputStream = new BufferedInputStream( | |
new ExtensibleURIConverterImpl().createInputStream(uri)); | |
byte[] input = new byte[bufferedInputStream.available()]; | |
bufferedInputStream.read(input); | |
bufferedInputStream.close(); | |
return new String(input, PROPERTIES_ENCODING); | |
} catch (IOException exception) { | |
// ignore | |
} | |
} | |
return null; | |
} | |
/** | |
* setConstrainingClassifier is a static utility method used to encapsulate | |
* UML 2.3 Migration in the code base. The goal is to use the same code and | |
* avoid multiple builds, features, sites to support both UML 2.2 and 2.3 | |
* api with the same code base. | |
* | |
* The difference in this case is that ConstrainingClassifier is scalar in | |
* 2.2 (0..1) and list (0..*) in 2.3 | |
* | |
* Implementation/Exception Handling Note - The reflection API currently | |
* does not have an "hasMethod" so the approach is to call getMethod on the | |
* target class starting with UML 2.2 method. If the method is not found or | |
* some exception has been encountered attempt to get the 2.3 method. If | |
* both attempts fail, throw a RuntimeException. The reasoning behind this | |
* approach is not to introduce a series of exception handling for | |
* reflection errors into the base code because the project will eventually | |
* migrate completely to 2.3 so the current logic captures and ignores all | |
* exceptions. | |
* | |
* http://www.eclipse.org/modeling/mdt/uml2/docs/guides/UML2_3.0 | |
* _Migration_Guide/guide.html | |
* | |
* @param classifierTemplateParameter | |
* @param constraint | |
*/ | |
public static void setConstrainingClassifier(ClassifierTemplateParameter classifierTemplateParameter, | |
Classifier constraint) { | |
boolean reflectionCompleted = false; | |
try { | |
// Attempt UML 2.2 API | |
Method setConstrainingClassifier = ClassifierTemplateParameter.class.getMethod( | |
"setConstrainingClassifier", new java.lang.Class<?>[] { Classifier.class }); | |
setConstrainingClassifier.invoke(classifierTemplateParameter, new Object[] { constraint }); | |
reflectionCompleted = true; | |
} catch (IllegalArgumentException e) { | |
// Consume Exception | |
} catch (IllegalAccessException e) { | |
// Consume Exception | |
} catch (InvocationTargetException e) { | |
// Consume Exception | |
} catch (SecurityException e) { | |
// Consume Exception | |
} catch (NoSuchMethodException e) { | |
// Consume Exception | |
} | |
if (!reflectionCompleted) { | |
try { | |
// Attempt UML 2.3 API | |
Method getConstrainingClassifiers = ClassifierTemplateParameter.class.getMethod( | |
"getConstrainingClassifiers", (java.lang.Class<?>[]) null); | |
EList<Classifier> classifiers = (EList<Classifier>) getConstrainingClassifiers.invoke( | |
classifierTemplateParameter, (Object[]) null); | |
classifiers.add(constraint); | |
reflectionCompleted = true; | |
} catch (IllegalArgumentException e) { | |
// Consume Exception | |
} catch (IllegalAccessException e) { | |
// Consume Exception | |
} catch (InvocationTargetException e) { | |
// Consume Exception | |
} catch (SecurityException e) { | |
// Consume Exception | |
} catch (NoSuchMethodException e) { | |
// Consume Exception | |
} | |
} | |
// If neither 2.2/2.3 or some other execution error a general purpose | |
// UML 2 Reflection Error | |
if (!reflectionCompleted) { | |
throw new RuntimeException(UML2REFLECTIONERROR); | |
} | |
return; | |
} | |
/* | |
* TODO this is not a reliable ID for association | |
*/ | |
public static String setEObjectID(Association association) { | |
return setEObjectID((Element) association); | |
// XMLResource resource = ((XMLResource)association.eResource()); | |
// StringBuffer xmiId = new StringBuffer(); | |
// for (Property end : association.getMemberEnds()) { | |
// if (xmiId.length() > 0) | |
// xmiId.append("_"); | |
// | |
// if (end.isNavigable()) | |
// xmiId.append(resource.getID(end)); | |
// else | |
// xmiId.append("anonymous"); | |
// } | |
// xmiId.append("_association"); | |
// | |
// return setEObjectID(association, xmiId.toString()); | |
} | |
public static String setEObjectID(Element element) { | |
String xmiId = UML2Util.getXMIIdentifier((InternalEObject) element); | |
return setEObjectID(element, xmiId); | |
} | |
public static String setEObjectID(Element element, String xmiId) { | |
XMLResource resource = ((XMLResource) element.eResource()); | |
if (xmiId != null && xmiId.length() > 0) { | |
int suffix = 1; | |
String firstId = xmiId; | |
while (resource.getEObject(xmiId) != null) { | |
xmiId = firstId + "." + suffix++; | |
} | |
resource.setID(element, xmiId); | |
} | |
return xmiId; | |
} | |
public static String setEObjectID(Generalization generalization) { | |
XMLResource resource = ((XMLResource) generalization.eResource()); | |
StringBuffer xmiId = new StringBuffer(); | |
xmiId.append(resource.getID(generalization.getGeneral())); | |
xmiId.append("_"); | |
xmiId.append(resource.getID(generalization.getSpecific())); | |
xmiId.append("_generalization"); | |
return setEObjectID(generalization, xmiId.toString()); | |
} | |
/** | |
* setParameterableElement is a static utility method used to encapsulate | |
* UML 2.3 Migration in the code base. The goal is to use the same code and | |
* avoid multiple builds, features, sites to support both UML 2.2 and 2.3 | |
* api with the same code base. | |
* | |
* The difference in this case is that TemplateParameterSubstitution Actual | |
* Parameters a list (0..*) in 2.2 and scalar (0..1) in 2.3 | |
* | |
* Implementation/Exception Handling Note - The reflection API currently | |
* does not have an "hasMethod" so the approach is to call getMethod on the | |
* target class starting with UML 2.2 method. If the method is not found or | |
* some exception has been encountered attempt to get the 2.3 method. If | |
* both attempts fail, throw a RuntimeException. The reasoning behind this | |
* approach is not to introduce a series of exception handling for | |
* reflection errors into the base code because the project will eventually | |
* migrate completely to 2.3 so the current logic captures and ignores all | |
* exceptions. | |
* | |
* http://www.eclipse.org/modeling/mdt/uml2/docs/guides/UML2_3.0 | |
* _Migration_Guide/guide.html | |
* | |
* @param substitution | |
* @param parameterableElement | |
*/ | |
public static void setParameterableElement(TemplateParameterSubstitution substitution, | |
ParameterableElement parameterableElement) { | |
boolean reflectionCompleted = false; | |
try { | |
// Attempt UML 2.2 API | |
Method getAcuals = TemplateParameterSubstitution.class.getMethod("getActuals", (java.lang.Class<?>[]) null); | |
EList<ParameterableElement> actuals = (EList<ParameterableElement>) getAcuals.invoke( | |
substitution, (Object[]) null); | |
reflectionCompleted = true; | |
actuals.add(parameterableElement); | |
} catch (IllegalArgumentException e) { | |
// Consume Exception | |
} catch (IllegalAccessException e) { | |
// Consume Exception | |
} catch (InvocationTargetException e) { | |
// Consume Exception | |
} catch (SecurityException e) { | |
// Consume Exception | |
} catch (NoSuchMethodException e) { | |
// Consume Exception | |
} | |
if (!reflectionCompleted) { | |
try { | |
// Attempt UML 2.3 API | |
Method setAcual = TemplateParameterSubstitution.class.getMethod( | |
"setActual", new java.lang.Class<?>[] { ParameterableElement.class }); | |
setAcual.invoke(substitution, new Object[] { parameterableElement }); | |
reflectionCompleted = true; | |
} catch (IllegalArgumentException e) { | |
// Consume Exception | |
} catch (IllegalAccessException e) { | |
// Consume Exception | |
} catch (InvocationTargetException e) { | |
// Consume Exception | |
} catch (SecurityException e) { | |
// Consume Exception | |
} catch (NoSuchMethodException e) { | |
// Consume Exception | |
} | |
} | |
// If neither 2.2/2.3 or some other execution error a general purpose | |
// UML 2 Reflection Error | |
if (!reflectionCompleted) { | |
throw new RuntimeException(UML2REFLECTIONERROR); | |
} | |
return; | |
} | |
/** | |
* Returns the text that is used for anchors in PDF files linking to the given element. | |
*/ | |
public static String getAnchorText(NamedElement element) { | |
String businessName = NamedElementUtil.getBusinessName(element); | |
if (businessName != null && !businessName.isEmpty() && !businessName.equals(element.getName())) { | |
// return preferably the business name | |
return businessName; | |
} | |
return splitName(element); | |
} | |
/** | |
* This method breaks element's name into words delimited by mixed-case | |
* naming and returns a string with name words separated by space. If model | |
* element name already contains spaces, return unchanged. | |
*/ | |
public static String splitName(NamedElement element) { | |
String modelName = element.getName(); | |
if (modelName != null && modelName.indexOf(' ') > 0) { | |
return modelName; | |
} | |
StringBuffer buffer = new StringBuffer(); | |
for (String token : UMLUtil.splitName(modelName)) { | |
buffer.append(buffer.length() > 0 | |
? " " | |
: ""); | |
buffer.append(token); | |
} | |
return buffer.toString(); | |
} | |
/** | |
* This method breaks sourceName into words delimited by mixed-case naming. | |
* Copied from org.eclipse.emf.codegen.util.CodeGenUtil.parseName() | |
*/ | |
public static List<String> splitName(String sourceName) { | |
List<String> result = new ArrayList<String>(); | |
if (sourceName != null) { | |
StringBuilder currentWord = new StringBuilder(); | |
boolean lastIsLower = false; | |
for (int index = 0, length = sourceName.length(); index < length; ++index) { | |
char curChar = sourceName.charAt(index); | |
if (curChar == '_') { | |
result.add(currentWord.toString()); | |
currentWord = new StringBuilder(); | |
lastIsLower = false; | |
} else if (Character.isUpperCase(curChar) || (!lastIsLower && Character.isDigit(curChar))) { | |
if (lastIsLower && currentWord.length() > 1) { | |
result.add(currentWord.toString()); | |
currentWord = new StringBuilder(); | |
} | |
lastIsLower = false; | |
} else { | |
if (!lastIsLower) { | |
int currentWordLength = currentWord.length(); | |
if (currentWordLength > 1) { | |
char lastChar = currentWord.charAt(--currentWordLength); | |
currentWord.setLength(currentWordLength); | |
result.add(currentWord.toString()); | |
currentWord = new StringBuilder(); | |
currentWord.append(lastChar); | |
} | |
} | |
lastIsLower = true; | |
} | |
if (curChar != '_') { | |
currentWord.append(curChar); | |
} | |
} | |
result.add(currentWord.toString()); | |
} | |
return result; | |
} | |
/** | |
* Writes the specified properties to a file with the specified URI. | |
* | |
* @param uri | |
* The URI of the properties file. | |
* @param properties | |
* The properties to be written. | |
* @return Whether the properties were successfully written. | |
*/ | |
public static boolean writeProperties(URI uri, Map<String, String> properties) { | |
StringBuilder result = new StringBuilder(); | |
for (String property : properties.values()) { | |
result.append(property); | |
} | |
try { | |
OutputStream output = new ExtensibleURIConverterImpl().createOutputStream(uri); | |
output.write(result.toString().getBytes(PROPERTIES_ENCODING)); | |
output.close(); | |
return true; | |
} catch (IOException ioe) { | |
return false; | |
} | |
} | |
/** | |
* isTypeString returns true if primitive type and is a String | |
* | |
* @param type | |
* @return | |
*/ | |
public static boolean isTypeString(Type type) { | |
if (type instanceof PrimitiveType) { | |
if ("EString".equals(type.getName()) || "String".equals(type.getName())) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Returns the nearest inherited property with the same name, or null if not found. | |
*/ | |
public static Property getInheritedProperty(Property property) { | |
if (!(property.getOwner() instanceof Classifier)) { | |
return null; | |
} | |
Classifier owner = (Classifier) property.getOwner(); | |
for (Classifier parent : owner.allParents()) { | |
for (Property inherited : parent.getAttributes()) { | |
if (inherited.getName().equals(property.getName())) { | |
return inherited; | |
} | |
} | |
} | |
if (property.getRedefinedProperties().size() == 1) { | |
return property.getRedefinedProperties().get(0); | |
} | |
if (property.getSubsettedProperties().size() == 1) { | |
return property.getSubsettedProperties().get(0); | |
} | |
return null; | |
} | |
/** | |
* Returns the nearest inherited property with the given name, or null if not found. | |
*/ | |
public static Property getInheritedProperty(Class childClass, String propertyName) { | |
for (Classifier parent : childClass.allParents()) { | |
for (Property inherited : parent.getAttributes()) { | |
if (inherited.getName().equals(propertyName)) { | |
return inherited; | |
} | |
} | |
} | |
return null; | |
} | |
} |