package org.eclipse.xtend.typesystem.uml2.profile; | |
/******************************************************************************* | |
* Copyright (c) 2005 - 2009 committers of openArchitectureWare 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: | |
* committers of openArchitectureWare - initial API and implementation | |
*******************************************************************************/ | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
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.emf.ecore.ENamedElement; | |
import org.eclipse.emf.ecore.util.EcoreUtil; | |
import org.eclipse.emf.mwe.core.ConfigurationException; | |
import org.eclipse.internal.xtend.expression.parser.SyntaxConstants; | |
import org.eclipse.internal.xtend.util.Cache; | |
import org.eclipse.internal.xtend.util.Pair; | |
import org.eclipse.uml2.uml.Element; | |
import org.eclipse.uml2.uml.Enumeration; | |
import org.eclipse.uml2.uml.EnumerationLiteral; | |
import org.eclipse.uml2.uml.NamedElement; | |
import org.eclipse.uml2.uml.Package; | |
import org.eclipse.uml2.uml.Profile; | |
import org.eclipse.uml2.uml.Stereotype; | |
import org.eclipse.uml2.uml.UMLPackage; | |
import org.eclipse.xtend.expression.TypeSystem; | |
import org.eclipse.xtend.typesystem.MetaModel; | |
import org.eclipse.xtend.typesystem.Type; | |
import org.eclipse.xtend.typesystem.uml2.UML2MetaModelBase; | |
import org.eclipse.xtend.typesystem.uml2.UML2Util2; | |
/** | |
* The ProfileMetaModel maps Stereotypes defined in UML profiles to virtual | |
* types that extend the base UML metaclasses. Use this {@link MetaModel} | |
* implementation when using profiled UML models. | |
* | |
* <h2>Workflow configuration</h2> Instantiate one ProfileMetaModel instance in | |
* your workflow and add one or more <tt>profile</tt> URIs. | |
* | |
* <pre> | |
* <bean id="mm_profile" class="org.eclipse.xtend.typesystem.uml2.profile.ProfileMetaModel"> | |
* <profile value="platform:/resource/yourproject/path/to/The.profile.uml"/> | |
* <profile value="platform:/resource/yourproject/path/to/Another.profile.uml"/> | |
* </bean> | |
* ... | |
* <component class="org.eclipse.xpand2.Generator"> | |
* <metaModel idRef="mm_profile"/> | |
* ... | |
* </component> | |
* </pre> | |
* | |
* Use the ProfileMetaModel always as first metaModel registration for expression using workflow components (Generator, XtendComponent, etc.). | |
* It is <i>not necessary</i> to add an instance of {@link org.eclipse.xtend.typesystem.uml2.UML2MetaModel UML2MetaModel} or | |
* {@link org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel EmfRegistryMetaModel} additionally, since the ProfileMetaModel delegates | |
* to those metamodel implementations when it is not responsible for resolving a type. | |
* | |
* @author karsten.thoms@itemis.de | |
* @author Moritz@Eysholdt.de | |
* | |
*/ | |
public class ProfileMetaModel implements MetaModel { | |
public List<Profile> profiles = new ArrayList<Profile>(1); | |
private TypeSystem typeSystem; | |
private class InternaleProfileMetaModel extends UML2MetaModelBase { | |
private final Cache<String, Type> typeForNameCache = new Cache<String, Type>() { | |
@Override | |
protected Type createNew(final String typeName) { | |
final NamedElement[] profilesAsNE = profiles.toArray(new NamedElement[0]); | |
final NamedElement ele = getNamedElementRec(profilesAsNE, typeName); | |
if (ele != null) { | |
final Type result = getTypeForEClassifier(ele.eClass()); | |
return result; | |
} else { | |
return null; | |
} | |
} | |
}; | |
@Override | |
public Type getTypeForName(final String typeName) { | |
Type result = typeForNameCache.get(typeName); | |
if (result == null) { | |
result = super.getTypeForName(typeName); | |
} | |
return result; | |
} | |
private NamedElement getNamedElementRec(final NamedElement[] elements, final String name) { | |
final String[] frags = name.split(SyntaxConstants.NS_DELIM); | |
final String firstFrag = frags[0]; | |
for (final NamedElement ele : elements) { | |
if (ele.getName() != null && ele.getName().equals(firstFrag)) { | |
if (frags.length > 1) { | |
final Collection<ENamedElement> children = EcoreUtil.getObjectsByType(ele.eContents(), | |
UMLPackage.eINSTANCE.getNamedElement()); | |
return getNamedElementRec(children.toArray(new NamedElement[children.size()]), name | |
.substring(name.indexOf(SyntaxConstants.NS_DELIM) + SyntaxConstants.NS_DELIM.length())); | |
} | |
else | |
return ele; | |
} | |
} | |
return null; | |
} | |
} | |
private InternaleProfileMetaModel internalProfileMetaModel; | |
/** | |
* Flag, if an exception should be thrown, if stereotypes, assigned to the | |
* model element, are not loaded. If set to 'false', stereotypes not loaded | |
* are skipped. Default is 'true'. | |
*/ | |
private boolean errorIfStereotypeMissing = true; | |
private final Set<String> namespaces = new TreeSet<String>(); | |
public void setErrorIfStereotypeMissing(final boolean errorIfStereotypeMissing) { | |
this.errorIfStereotypeMissing = errorIfStereotypeMissing; | |
} | |
public ProfileMetaModel() { | |
} | |
public ProfileMetaModel(final Profile... profiles) { | |
assert profiles != null; | |
this.profiles = Arrays.asList(profiles); | |
init(); | |
} | |
public void addProfile(final String profile) { | |
assert profile != null; | |
final Profile p = UML2Util2.loadProfile(profile); | |
if (p == null) | |
throw new ConfigurationException("Couldn't load profile from " + profile); | |
this.profiles.add(p); | |
init(); | |
} | |
private Map<String, Type> stereoTypes = null; | |
private Map<Pair<String, Type>, StereotypeType> stereoTypesWithUmlType = null; | |
/** | |
* Initializes the metamodel. All stereotypes in the profile are mapped to | |
* StereotypeType instances and all Enumerations to EnumType instances. | |
*/ | |
private void init() { | |
if (stereoTypes != null || profiles.isEmpty() || typeSystem == null) | |
return; | |
internalProfileMetaModel = new InternaleProfileMetaModel(); | |
internalProfileMetaModel.setTypeSystem(typeSystem); | |
stereoTypes = new HashMap<String, Type>(); | |
List<org.eclipse.uml2.uml.Type> sts = new ArrayList<org.eclipse.uml2.uml.Type>(); | |
for (Profile p : profiles) { | |
sts.addAll(getAllOwnedTypes(p)); | |
namespaces.add(normalizedName(p.getName())); | |
} | |
for (final Iterator<org.eclipse.uml2.uml.Type> iter = sts.iterator(); iter.hasNext();) { | |
final Object o = iter.next(); | |
if (o instanceof Stereotype) { | |
final Stereotype st = (Stereotype) o; | |
final String typeName = getFullName(st); | |
final Type t = new StereotypeType(getTypeSystem(), typeName, st); | |
stereoTypes.put(typeName, t); | |
} | |
else if (o instanceof Enumeration) { | |
final Enumeration en = (Enumeration) o; | |
final String typeName = getFullName(en); | |
final Type t = new EnumType(getTypeSystem(), typeName, en); | |
stereoTypes.put(typeName, t); | |
} | |
} | |
} | |
private List<org.eclipse.uml2.uml.Type> getAllOwnedTypes(final Package pck) { | |
final List<org.eclipse.uml2.uml.Type> result = new ArrayList<org.eclipse.uml2.uml.Type>(); | |
result.addAll(pck.getOwnedTypes()); | |
for (final Package nested : pck.getNestedPackages()) { | |
result.addAll(getAllOwnedTypes(nested)); | |
} | |
return result; | |
} | |
/** | |
* It is not allowed to have non-word characters in profile, stereotype or | |
* tagged value names. All non-word characters are replaced by underscore. | |
* | |
* @param name | |
* An element's name | |
* @return All non-word characters are replaced by underscores except for | |
* occurances of the namespace delimiter '::' | |
*/ | |
private static String normalizedName(String name) { | |
String[] fragments = name.split(SyntaxConstants.NS_DELIM); | |
StringBuffer result = new StringBuffer(name.length()); | |
result.append(fragments[0].replaceAll("\\W", "_")); | |
for (int i = 1; i < fragments.length; i++) { | |
result.append(SyntaxConstants.NS_DELIM); | |
result.append(fragments[i].replaceAll("\\W", "_")); | |
} | |
return result.toString(); | |
} | |
public String getFullName(final org.eclipse.uml2.uml.Type type) { | |
return normalizedName(type.getQualifiedName()); | |
} | |
public Type getTypeForName(final String typeName) { | |
Type result = stereoTypes.get(typeName); | |
if (result == null) { | |
result = internalProfileMetaModel.getTypeForName(typeName); | |
} | |
return result; | |
} | |
/* | |
* getType() tries to return a type for every object according to it's | |
* stereotypes. If it fails, getType returns null and it is up to other | |
* MetaModel implementations to define a type for that object. | |
* | |
* EnumerationLiterals are a special case. If there is no stereotype | |
* defined, getType tries to return the type of the enumeration that | |
* contains the literal. So, enumerations' literals become instances of the | |
* enumerations' type. It is important to notice that enumerations are only | |
* types if they are defined in the uml-profile and not in the uml-model. | |
* | |
* This code should be able to handle the following two scenarios: - | |
* enumerations+literals without stereotypes defined in the uml-profile, | |
* with literals that have the enumeration as type. - enumerations+literals | |
* with stereotypes defined in the uml-model. Here it is the user's | |
* responsibility to maintain the type-compatibility of the literals. | |
* | |
*/ | |
public Type getType(final Object obj) { | |
// Get actual UML type | |
Type umlType = internalProfileMetaModel.getType(obj); | |
if (obj instanceof Element) { | |
if (obj instanceof EnumerationLiteral) { | |
EnumerationLiteral el = (EnumerationLiteral) obj; | |
String fqn = getFullName(el.getEnumeration()); | |
Type enumType = getTypeSystem().getTypeForName(fqn); | |
if (enumType != null) { | |
return enumType; | |
} | |
} | |
Element element = (Element) obj; | |
List<Stereotype> stereotypes = element.getAppliedStereotypes(); | |
// if no stereotype is found, the stereotype is skipped or an | |
// Exception is thrown | |
if (stereotypes.isEmpty()) { | |
// collection will be empty if the required profile is not | |
// loaded | |
if (errorIfStereotypeMissing && !stereotypes.toString().equals("[]")) | |
throw new RuntimeException("Stereotype could not be loaded! Possible hint: '" + stereotypes); | |
else | |
return umlType; | |
} | |
List<StereotypeType> types = new ArrayList<StereotypeType>(stereotypes.size()); | |
// collect StereotypeTypes | |
for (Iterator<Stereotype> iter = stereotypes.iterator(); iter.hasNext();) { | |
Stereotype st = iter.next(); | |
Type theType = getTypeSystem().getTypeForName(getFullName(st)); | |
if (theType != null && theType instanceof StereotypeType) { | |
StereotypeType stType = (StereotypeType) theType; | |
// Find out whether the actual type is more specific than any of the StereotypeTypes superTypes | |
if(umlType != null && !umlType.isAssignableFrom(stType)) { | |
// Try to find a fitting combination in the cache | |
Pair<String, Type> key = new Pair<String, Type>(getFullName(st), umlType); | |
if(stereoTypesWithUmlType == null) { | |
stereoTypesWithUmlType = new HashMap<Pair<String, Type>, StereotypeType>(); | |
} | |
stType = stereoTypesWithUmlType.get(key); | |
// Create new StereotypeType and a reference to the actual type and put it into cache if not yet contained. | |
if(stType == null) { | |
stType = new StereotypeType(getTypeSystem(), getFullName(st), st, umlType); | |
stereoTypesWithUmlType.put(key, stType); | |
} | |
} | |
types.add(stType); | |
} | |
} | |
switch (types.size()) { | |
case 0: | |
return umlType; | |
case 1: | |
return types.get(0); | |
// when more than one stereotype is applied we return a | |
// MultipleStereotypeType instance | |
// containing all applied stereotypes | |
default: | |
return new MultipleStereotypeType(getTypeSystem(), types, umlType); | |
} | |
} else { | |
return umlType; | |
} | |
} | |
public Set<Type> getKnownTypes() { | |
return new HashSet<Type>(stereoTypes.values()); | |
} | |
public TypeSystem getTypeSystem() { | |
return typeSystem; | |
} | |
public void setTypeSystem(final TypeSystem typeSystem) { | |
this.typeSystem = typeSystem; | |
init(); | |
} | |
public String getName() { | |
if (profiles.size()==1) { | |
return profiles.get(0).getName(); | |
} else { | |
List<String> names = new ArrayList<String>(profiles.size()); | |
for (Profile p : profiles) names.add(p.getName()); | |
return names.toString(); | |
} | |
} | |
/** | |
* @see MetaModel#getNamespaces() | |
*/ | |
public Set<String> getNamespaces() { | |
return namespaces; | |
} | |
} |