package org.reuseware.air.comogen;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.reuseware.air.comogen.util.CMSLSpecificationUtil;
import org.reuseware.air.language.cmsl.Annotation;
import org.reuseware.air.language.cmsl.Arbitrary;
import org.reuseware.air.language.cmsl.BaseLanguageReference;
import org.reuseware.air.language.cmsl.BaseReference;
import org.reuseware.air.language.cmsl.Cardinality;
import org.reuseware.air.language.cmsl.Construct;
import org.reuseware.air.language.cmsl.ConstructReference;
import org.reuseware.air.language.cmsl.DefinitionChoice;
import org.reuseware.air.language.cmsl.DefinitionConstruct;
import org.reuseware.air.language.cmsl.DefinitionPart;
import org.reuseware.air.language.cmsl.ExtensionStatement;
import org.reuseware.air.language.cmsl.Injection;
import org.reuseware.air.language.cmsl.MoreThanOne;
import org.reuseware.air.language.cmsl.NonTerminal;
import org.reuseware.air.language.cmsl.NonTerminalIdentifier;
import org.reuseware.air.language.cmsl.Reference;
import org.reuseware.air.language.cmsl.Slotify;
import org.reuseware.air.language.cmsl.UpperLevelReference;
import org.reuseware.air.language.cmsl.ZeroOne;
/**
* Action class for performing a language extension as specified in
* a CMSL program.
*
* @author b0kaj
*/
public class LanguageExtensionGenerator {
private EcoreFactory ecoreFactory = EcoreFactory.eINSTANCE;
private static final String annotationSourceURI = "http://reuseware.org/annotation/location";
// name conventions
private String SLOT_SUFFIX = "SLOT";
private String ABSTRACT_SUFFIX = "ABSTRACT";
// maps: String -> EClassifier in extended language model
private Hashtable<String,EClassifier> extendedConstructsMap =
new Hashtable<String,EClassifier>();
// upper-level component model constructs
private List<String> upperLevelCompModelTerms = new LinkedList<String>();
public LanguageExtensionGenerator() {}
/**
* Process the file on which the ExtendGrammarAction was fired
* @param file
*/
public void process(IExtensionContext context) {
extendedConstructsMap.clear();
// introduce upper-level component model notions
upperLevelCompModelTerms.add("Composer");
upperLevelCompModelTerms.add("Location");
// slot
upperLevelCompModelTerms.add("Slot");
extendGrammar(context);
}
/**
* Extends a grammar according to CMSL specification
*
* @param baseLanguage
*/
@SuppressWarnings("unchecked")
private void extendGrammar(IExtensionContext context) {
/**
* TODO:
* 1) Parse each CMSL statement and extend metamodel accordingly
* (look at GeneratePluginAction.java, and GenerateReuseLanguagePlugin.java,
* and AbstractSyntaxMapping.mapToEcore() )
*/
EPackage extendedLanguageModelPackage = context.getExtensionLanguagePackage();
// get the extension statements
EList<ExtensionStatement> statements =
(EList<ExtensionStatement>)context.getExtensionProgram().getStatements();
// create all the needed new classes in the extended language model
// that is, classes for all the new constructs
cmslExtensionIntroduceNewConstructs(extendedLanguageModelPackage, statements);
// treat the extension statements one by one
for (Iterator i = statements.iterator(); i.hasNext(); ) {
ExtensionStatement statement = (ExtensionStatement)i.next();
// DEFINITION CHOICE
if (statement instanceof DefinitionChoice) {
cmslExtensionConstructChoice(extendedLanguageModelPackage, (DefinitionChoice)statement);
}
// DEFINITION CONSTRUCT
else if (statement instanceof DefinitionConstruct) {
cmslExtensionConstructDefine((DefinitionConstruct)statement,context);
}
// SLOTIFY
else if (statement instanceof Slotify) {
Slotify slotify = (Slotify)statement;
String constructName =
((NonTerminalIdentifier)((BaseLanguageReference)slotify.getConstruct()).getBase()).getValue();
EClass slotConstruct = (EClass)getClass(constructName + SLOT_SUFFIX);
// annotate with the upper-level Slot construct
cmslExtensionAnnotate(slotConstruct, (EClass)getClass("Slot"));
// inject
cmslExtensionInjection(slotConstruct, (EClass)getClass(slotify.getConstruct(),context),context);
}
// INJECTION
else if (statement instanceof Injection) {
cmslExtensionInjectionConstruct(extendedLanguageModelPackage, (Injection)statement, context);
}
// ANNOTATION
else if (statement instanceof Annotation) {
cmslExtensionAnnotationConstruct(extendedLanguageModelPackage, (Annotation)statement);
}
}
context.setExtensionDone(true);
}
/**
* Introduce all new constructs (classes) into the extended language model
*
* @param extendedLanguageModelPackage
* @param statements
*/
private void cmslExtensionIntroduceNewConstructs(
EPackage extendedLanguageModelPackage,
EList<ExtensionStatement> statements) {
// go through all statements in the program
for (Iterator<ExtensionStatement> iter = statements.iterator(); iter.hasNext(); ) {
ExtensionStatement statement = (ExtensionStatement)iter.next();
String constructName = null;
// DefinitionConstruct:s introduce new constructs
if (statement instanceof DefinitionConstruct) {
// name of the new construct
constructName =
((NonTerminalIdentifier) ((DefinitionConstruct)statement).getNewConstruct()).getValue();
}
// DefinitionChoice:s can also introduce new constructs
else if (statement instanceof DefinitionChoice) {
// name of the new construct
constructName =
((NonTerminalIdentifier) ((DefinitionChoice)statement).getNewConstruct()).getValue();
}
// but also annotations may introduce new constructs
else if (statement instanceof Annotation) {
// name of the new construct
constructName =
((NonTerminalIdentifier) ((Annotation)statement).getSource()).getValue();
}
// introduce a new construct for each slotify construct
else if (statement instanceof Slotify) {
// name of the new construct
BaseLanguageReference langRef = (BaseLanguageReference)((Slotify)statement).getConstruct();
constructName =
((NonTerminalIdentifier) langRef.getBase()).getValue() + SLOT_SUFFIX;
}
// check that we did not construct it already, and that we found the name
if ((constructName != null) && !extendedConstructsMap.containsKey(constructName)) {
// create a model class for the new construct
EClass eClass = ecoreFactory.createEClass();
eClass.setName(constructName);
// add it to the extended language model
extendedLanguageModelPackage.getEClassifiers().add(eClass);
// save the map between the construct name and the model element
extendedConstructsMap.put(constructName, eClass);
}
}
}
/**
* Introduces a new construct, and its definition, in a language model
*
*
* @param extendedLanguageModelPackage package for the extended language model
* @param definition definition statement
*/
@SuppressWarnings("unchecked")
private void cmslExtensionConstructDefine(DefinitionConstruct definition, IExtensionContext context) {
// get new construct being defined
EClass constructDefined =
(EClass)getClass(definition.getNewConstruct());
if (constructDefined == null) {
System.err.println("Referred construct does not exist");
return;
}
// make sure the construct has a definition
if (definition.getDefinition() != null) {
// iterate over the definition parts
for (Iterator<DefinitionPart> iter =
definition.getDefinition().getRefs().iterator(); iter.hasNext(); )
{
DefinitionPart part = (DefinitionPart)iter.next();
// the role name for the referred construct
String roleId = part.getRolename().getValue();
// cardinality, default
int lowerBound = 1;
int upperBound = 1;
// retrieve the cardinality of the referred construct
Cardinality cardinality = null;
if (part.getConstruct() instanceof Construct)
cardinality = ((Construct)part.getConstruct()).getCardinality();
else if (part.getConstruct() instanceof Reference)
cardinality = ((Reference)part.getConstruct()).getCardinality();
// calculate lower and upper bounds based on specified cardinality
if (cardinality instanceof MoreThanOne) {
lowerBound = 1;
upperBound = -1;
}
else if (cardinality instanceof Arbitrary) {
lowerBound = 0;
upperBound = -1;
}
else if (cardinality instanceof ZeroOne) {
lowerBound = 0;
upperBound = 1;
}
// populate defined model class
if (constructDefined.getEStructuralFeature(roleId) == null) {
// retrieve model element for referred part
EClassifier refClass = null;
EAnnotation annotation = null;
// check if the construct is annotated
if (part.getAnnotation() != null) {
// in that case, use the annotation as
// the construct (most likely Location)
refClass = getClass(part.getAnnotation(),context);
// annotate construct
annotation = ecoreFactory.createEAnnotation();
//ecoreFactory.create
// meta information
annotation.setSource(annotationSourceURI);
//annotation.getReferences().add(getClass(part.getConstruct()));
// annotation contents
EMap<String,String> map = annotation.getDetails();
if (map != null) {
EClass cls = (EClass)getClass(part.getConstruct(),context);
map.put("language", cls.getEPackage().getNsURI());
map.put("construct", cls.getName());
}
} else {
// if there is no annotation, use the specified
// construct instead
refClass = getClass(part.getConstruct(),context);
}
EStructuralFeature structFeature = null;
// differentiate between Classes and DataTypes
if (refClass instanceof EClass) {
EReference ref = ecoreFactory.createEReference();
ref.setContainment(true);
structFeature = ref;
}
else if (refClass instanceof EDataType) {
structFeature = ecoreFactory.createEAttribute();
}
// populate and set the feature information
if (structFeature != null) {
structFeature.setEType(refClass);
structFeature.setName(roleId);
structFeature.setLowerBound(lowerBound);
structFeature.setUpperBound(upperBound);
// check if we should create the annotation, if so, add it
if (annotation != null) {
structFeature.getEAnnotations().add(annotation);
}
constructDefined.getEStructuralFeatures().add(structFeature);
}
} else {
System.err.println("Role reference already exists.");
}
}
}
}
/**
*
*
* @param extendedLanguageModelPackage
* @param statement
*/
@SuppressWarnings("unchecked")
private void cmslExtensionConstructChoice(
EPackage extendedLanguageModelPackage, DefinitionChoice statement) {
// super class
EClassifier superClass = getClass(statement.getNewConstruct());
// get all the choices
EList<NonTerminal> choices = (EList<NonTerminal>)statement.getChoice();
// make each choice a subclass of the superclass
for (NonTerminal choice : choices) {
EClassifier choiceClass = getClass(choice);
((EClass)choiceClass).getESuperTypes().add((EClass)superClass);
}
return;
}
/**
* Handle a INJECT construct from CMSL
*
* @param extendedLanguageModelPackage
* @param statement
*/
private void cmslExtensionInjectionConstruct(
EPackage extendedLanguageModelPackage, Injection statement, IExtensionContext context) {
// get construct being injected
EClass constructInjected = (EClass)getClass(statement.getSource());
// get construct for injection point
EClass injectionPoint = (EClass)getClass(statement.getTarget(),context);
// inject the construct
cmslExtensionInjection(constructInjected, injectionPoint,context);
}
/**
* Injects a newly introduced construct into the base language
*
* @param extendedLanguageModelPackage
* @param statement
*/
private void cmslExtensionInjection(
EClass constructInjected, EClass injectionPoint, IExtensionContext context) {
// checks if a "complex" injection is required
// a complex injection is required if the injection point class is not
// an abstract, feature-less, class
EList<EStructuralFeature> features = injectionPoint.getEStructuralFeatures();
// make sure that the base language model package is accessible
if (!features.isEmpty() && (context.getBaseLanguagePackage() != null)) {
System.out.println("Complex injection probably needed.");
// check if the construct already has been injected, in which case
// we can do a simple injection, not a complex one
if (context.getBaseLanguagePackage().getEClassifier(injectionPoint.getName() + ABSTRACT_SUFFIX) != null) {
if (constructInjected != null && injectionPoint != null) {
System.out.println("However, the construct already injected, doing simple injection.");
// make the injected construct a sub-class of the already created
// abstract injection point construct
EClass newInjectionPoint =
(EClass)context.getBaseLanguagePackage().getEClassifier(injectionPoint.getName() + ABSTRACT_SUFFIX);
((EClass)constructInjected).getESuperTypes().add(newInjectionPoint);
}
}
// actually do the complex injection
else {
// STEP 1: re-wire the base language model such that it is possible
// to inject the construct
// the injection point index in the classifiers list
int index =
context.getBaseLanguagePackage().getEClassifiers().indexOf(injectionPoint);
// create an abstract class
EClass abstractClass = EcoreFactory.eINSTANCE.createEClass();
abstractClass.setName(injectionPoint.getName() + ABSTRACT_SUFFIX);
context.getBaseLanguagePackage().getEClassifiers().add(index, abstractClass);
// move all super types from the original class to the abstract class
abstractClass.getESuperTypes().addAll(injectionPoint.getESuperTypes());
// let the original class derive from the abstract class
injectionPoint.getESuperTypes().clear();
injectionPoint.getESuperTypes().add(abstractClass);
// replace the references to the original with references to the
// abstract class
Iterator<EClassifier> iterAllClasses =
context.getBaseLanguagePackage().getEClassifiers().iterator();
while (iterAllClasses.hasNext()) {
EClass c = (EClass) iterAllClasses.next();
Iterator<EReference> iterAllRefs = c.getEReferences().iterator();
while (iterAllRefs.hasNext()) {
EReference ref = (EReference) iterAllRefs.next();
if (ref.getEType().equals(injectionPoint)) {
ref.setEType(abstractClass);
}
}
}
// finally, make the injected construct a sub-class of the already created
// abstract injection point construct
((EClass)constructInjected).getESuperTypes().add(abstractClass);
}
System.out.println("Modification done.");
}
// just sub-class the injection point construct with the injected construct
else {
System.out.println("Going ahead with simple injection.");
if (constructInjected != null && injectionPoint != null) {
// make the injected construct a sub-class of the construct where the injection is made
((EClass)constructInjected).getESuperTypes().add(injectionPoint);
} else {
System.err.println("Class not found in referenced language, or construct not found");
}
}
return;
}
/**
* Annotates a newly introduced construct by referring to the
* upper-level component model
*
* @param extendedLanguageModelPackage package representing the extended language model
* @param statement annotation statement representing information for performing the annotation
*/
private void cmslExtensionAnnotationConstruct(
EPackage extendedLanguageModelPackage, Annotation statement) {
// get construct being annotated
EClass constructAnnotated = (EClass)getClass(statement.getSource());
// get annotation construct
EClass annotation = (EClass)getClass(statement.getTarget());
// do the annotation
cmslExtensionAnnotate(constructAnnotated, annotation);
return;
}
/**
* Annotate a construct
*
* @param constructAnnotated
* @param annotation
*/
private void cmslExtensionAnnotate(EClass constructAnnotated, EClass annotation) {
if (constructAnnotated != null && annotation != null) {
// make the annotation a super-class of the annotated construct
((EClass)constructAnnotated).getESuperTypes().add(annotation);
} else {
System.err.println("Class not found in upper-level component model, or construct not found");
}
return;
}
/**
* Return class from language extension
*
* @param classID
* @return
*/
private EClassifier getClass(ConstructReference classID, IExtensionContext context) {
// it is a construct (from CMSL language specification)
if (classID instanceof Construct) {
Construct construct = (Construct)classID;
// get the model class for the non-terminal
return getClass(construct.getName());
}
// it is a reference (from CMSL language specification)
else if (classID instanceof Reference) {
Reference reference = (Reference)classID;
// get the model class for the reference (BaseReference)
return getClass(reference.getBase(), context);
}
// it is an upper-level reference
else if (classID instanceof UpperLevelReference) {
UpperLevelReference upper = (UpperLevelReference)classID;
// get the model class for the upper-level construct
return getClass(upper.getUpper());
}
System.err.println("Should be unreachable code!");
// default (should never happen)
return null;
}
/**
* Return class from language extension
*
* @param classID
* @return
*/
private EClassifier getClass(NonTerminal classID) {
// name of the construct
String constructName =
((NonTerminalIdentifier) classID).getValue();
return getClass(constructName);
}
/**
* Return class from language extension
*
* @param constructName The name of the construct
* @return
*/
private EClassifier getClass(String constructName) {
// get the name from the saved map between construct
// names and model elements, that it, if it exists
EClassifier classifier = extendedConstructsMap.get(constructName);
// return if found
if (classifier != null)
return classifier;
// otherwise, check if it refers to the UPPER-LEVEL COMPONENT MODEL
if (upperLevelCompModelTerms.contains(constructName)) {
// try the upper-level component model
// load the referenced language model
EPackage componentModelPackage =
CMSLSpecificationUtil.loadLanguageModelFromRegistry(URI.createURI("http://www.reuseware.org/air/language/componentmodel"));
// return upper-level annotation construct, if it exists
EClassifier componentModelConstruct =
componentModelPackage.getEClassifier(constructName);
if (componentModelConstruct != null)
return componentModelConstruct;
// try the minimal composition language
// load the referenced language model
// EPackage minimalCompositionLangugePackage =
// loadLanguageModel("minimalcl");
// return minimal composition language construct, if it exists
// EClassifier minimalCompositionLanguageConstruct =
// minimalCompositionLangugePackage.getEClassifier(constructName);
// if (minimalCompositionLanguageConstruct != null)
// return minimalCompositionLanguageConstruct;
}
// default
return null;
}
/**
* Return class from extended language
*
* @param classID
* @return
*/
private EClassifier getClass(BaseReference classID, IExtensionContext context) {
BaseLanguageReference baseRef = (BaseLanguageReference)classID;
if (context.getBaseLanguagePackage() != null) {
String languagePrefix = baseRef.getLanguage();
EPackage basePackage = context.getPackageForLanguagePrefix(languagePrefix);
if (basePackage != null) {
String constructName =
((NonTerminalIdentifier) baseRef.getBase()).getValue();
EClassifier classifier = basePackage.getEClassifier(constructName);
return classifier;
} else {
System.err.println("Invalid prefix used, lanugage not defined");
}
}
return null;
}
}