/*******************************************************************************
* Copyright (c) 2010 Michal Antkiewicz.
* 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:
* Michal Antkiewicz - initial API and implementation
******************************************************************************/
package ca.uwaterloo.gsd.fsml.core;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import ca.uwaterloo.gsd.fsml.ecore.FSMLEcoreUtil;
import ca.uwaterloo.gsd.fsml.stats.Stats;
import ca.uwaterloo.gsd.fsml.sync.StructuralFeatureSyncItem;
import ca.uwaterloo.gsd.fsml.sync.SyncItem;
public abstract class Mapping {
protected EObject element;
protected SyncItem syncItem;
protected EStructuralFeature feature;
protected EStructuralFeature essentialFeature;
protected EAnnotation annotation;
protected EClass concreteChildType;
protected FSMLMappingInterpreter interpreter;
protected IProgressMonitor progressMonitor;
protected Mode currentMode;
/**
* in reverse engineering, child is created for containment references in @see setFeature()
*/
protected EObject child;
/**
* Constructor used in reverse engineering.
* @param element
* @param feature
* @param annotation
* @param concreteChildType
* @param interpreter
* @param progressMonitor
* @throws FSMLMappingException
*/
public Mapping(EObject element, EStructuralFeature feature, EAnnotation annotation, EClass concreteChildType, FSMLMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
currentMode = Mode.REVERSE;
if (feature instanceof EReference && concreteChildType == null)
throw new FSMLMappingException(Cause.MISSING_CONCRETE_CHILD_TYPE, feature);
this.element = element;
this.syncItem = null;
this.feature = feature;
this.annotation = annotation;
this.concreteChildType = concreteChildType;
this.interpreter = interpreter;
this.progressMonitor = progressMonitor;
processFields();
}
/**
* Constructor used in reverse engineering using an essential feature.
* @param element
* @param feature
* @param annotation
* @param concreteChildType
* @param interpreter
* @param progressMonitor
* @throws FSMLMappingException
*/
public Mapping(EObject element, EStructuralFeature feature, EStructuralFeature essentialFeature, EAnnotation annotation, EClass concreteChildType, FSMLMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
currentMode = Mode.REVERSE_ESSENTIAL;
if (feature instanceof EReference && concreteChildType == null)
throw new FSMLMappingException(Cause.MISSING_CONCRETE_CHILD_TYPE, feature);
this.element = element;
this.syncItem = null;
this.feature = feature;
this.essentialFeature = essentialFeature;
this.annotation = annotation;
this.concreteChildType = concreteChildType;
this.interpreter = interpreter;
this.progressMonitor = progressMonitor;
processFields();
}
/**
* Constructor used in forward engineering.
* @param element
* @param syncItem
* @param feature
* @param annotation
* @param concreteChildType
* @param interpreter
* @param progressMonitor
* @throws FSMLMappingException
*/
public Mapping(EObject element, SyncItem syncItem, EAnnotation annotation, EClass concreteChildType, FSMLMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
currentMode = Mode.FORWARD;
if (feature instanceof EReference && concreteChildType == null)
throw new FSMLMappingException(Cause.MISSING_CONCRETE_CHILD_TYPE, feature);
this.element = element;
this.syncItem = syncItem;
this.feature = syncItem instanceof StructuralFeatureSyncItem ? ((StructuralFeatureSyncItem) syncItem).getStructuralFeature() : null;
this.annotation = annotation;
this.concreteChildType = concreteChildType;
this.interpreter = interpreter;
this.progressMonitor = progressMonitor;
processFields();
}
public boolean performForward() throws FSMLMappingException {
// check if the mode is correct
if (currentMode.compareTo(Mode.FORWARD) != 0)
throw new IllegalStateException("performForward() can only be executed in the Mode.FORWARD mode.");
// delegate to the actual forward method implemented in the concrete mapping.
return forward();
}
public boolean performReverse() throws FSMLMappingException {
// check if the mode is correct
if (currentMode.compareTo(Mode.REVERSE) != 0)
throw new IllegalStateException("performReverse() can only be executed in the Mode.REVERSE mode.");
// delegate to the actual reverse method implemented in the concrete mapping.
if (reverse()) {
createMarkers();
return true;
}
return false;
}
public boolean performReverseEssential() throws FSMLMappingException {
// check if the mode is correct
if (currentMode.compareTo(Mode.REVERSE_ESSENTIAL) != 0)
throw new IllegalStateException("performReverseEssential() can only be executed in the Mode.REVERSE_ESSENTIAL mode.");
// delegate to the actual reverse method implemented in the concrete mapping.
if (reverseEssential()) {
createMarkers();
return true;
}
return false;
}
/**
* Performs code transformation for a feature.
* @return true if the transformation was successful.
* @throws FSMLMappingException
*/
protected abstract boolean forward() throws FSMLMappingException;
/**
* Performs code query for a feature.
* @return true if the feature is present.
* @throws FSMLMappingException
*/
protected abstract boolean reverse() throws FSMLMappingException;
/**
* Performs code query for a feature and an essential subfeature.
* @return true if the feature is present.
* @throws FSMLMappingException
*/
protected boolean reverseEssential() throws FSMLMappingException {
// by default do nothing
return false;
}
/**
* Processes @Context and @Parameter annotations
*/
private void processFields() {
Field[] fields = this.getClass().getFields();
for (Field field : fields) {
Annotation fieldAnnotation = field.getAnnotation(Context.class);
if (fieldAnnotation != null) {
Context contextAnnotation = (Context) fieldAnnotation;
// check the dependency
String dependsOn = contextAnnotation.dependsOn();
if (!dependsOn.isEmpty()) {
try {
Field dependencyField = this.getClass().getField(dependsOn);
Object value = dependencyField.get(this);
if (value == null)
continue;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// check excludes
String excludes = contextAnnotation.excludes();
if (!excludes.isEmpty()) {
try {
Field excludesField = this.getClass().getField(excludes);
Object value = excludesField.get(this);
if (value != null)
continue;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
Mode mode = contextAnnotation.mode();
// the type of the field is the type of the context
try {
if (mode == Mode.ALL ||
mode == currentMode ||
(mode == Mode.REVERSE_AND_REVERSE_ESSENTIAL && (currentMode == Mode.REVERSE || currentMode == Mode.REVERSE_ESSENTIAL)) ||
(mode == Mode.FORWARD_AND_REVERSE && (currentMode == Mode.FORWARD || currentMode == Mode.REVERSE)) ||
(mode == Mode.FORWARD_AND_REVERSE_ESSENTIAL && (currentMode == Mode.FORWARD || currentMode == Mode.REVERSE_ESSENTIAL))) {
Object context = null;
boolean isRequired = contextAnnotation.required();
switch (currentMode) {
case REVERSE:
case REVERSE_ESSENTIAL:
context = Queries.INSTANCE.getContext(element, field.getType(), isRequired);
break;
case FORWARD:
// need to start from the parent because the element of the sync item can also correspond
// to the required context but it has not been created yet!
SyncItem parentSyncItem = (SyncItem) syncItem.eContainer();
context = Queries.INSTANCE.getContext(parentSyncItem, field.getType(), isRequired);
break;
}
field.set(this, context);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
continue;
}
fieldAnnotation = field.getAnnotation(Parameter.class);
if (fieldAnnotation != null) {
Parameter parameterAnnotation = (Parameter) fieldAnnotation;
Mode mode = parameterAnnotation.mode();
String name = parameterAnnotation.name();
if (mode == Mode.ALL
|| mode == currentMode
|| (mode == Mode.REVERSE && currentMode == Mode.REVERSE_ESSENTIAL)) { // REVERSE_ESSENTIAL is also REVERSE
try {
String value = FSMLEcoreUtil.retrieveParameterValue(annotation, name, parameterAnnotation.required());
if (value == null)
value = parameterAnnotation.defaultValue();
field.set(this, value);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (FSMLMappingException e) {
e.printStackTrace();
}
}
}
}
}
// support for markers
/**
* A list of marker descriptors added for creation.
*/
ArrayList<MarkerDescriptor> markerDescriptors = new ArrayList<MarkerDescriptor>();
/**
* Specify that a marker should be created in the @param resource at the given @param startOffset and @param endOffset.
* A @param value of the feature that the marker should correspond to can optionally be specified.
* The value should only be used for multivalue features, e.g., [0..*] f (String);
*/
protected void createMarkerDescriptor(IResource resource, int startOffset, int endOffset, Object value) {
MarkerDescriptor markerDescriptor = new MarkerDescriptor(resource);
markerDescriptor.setAttributes(feature, startOffset, endOffset);
if (value != null)
markerDescriptor.setValue(value);
addMarkerDescriptor(markerDescriptor);
}
protected void addMarkerDescriptor(MarkerDescriptor markerDescriptor) {
markerDescriptors.add(markerDescriptor);
}
/**
* Creates markers for all added items.
*/
protected void createMarkers() {
for (MarkerDescriptor descriptor : markerDescriptors)
descriptor.create(element);
}
protected boolean setFeature(boolean result) throws FSMLMappingException {
return setFeature(result, null);
}
protected boolean setFeature(boolean result, Object context) throws FSMLMappingException {
if (feature instanceof EAttribute && feature.getEType().getName().equals("EBoolean")) {
element.eSet(feature, result);
if (result)
Stats.INSTANCE.logFeatureInstance(element, feature, annotation);
}
else if (feature instanceof EReference && result) {
child = EcoreUtil.create(concreteChildType);
if (feature.isMany())
((EList)element.eGet(feature)).add(child);
else
element.eSet(feature, child);
if (context != null)
Queries.INSTANCE.associateContext(child, context);
if (!Queries.INSTANCE.reverseFeatureRepresentedAsClass(child, progressMonitor))
return false;
// so the feature is present.
Stats.INSTANCE.logFeatureInstance(element, feature, annotation);
}
return result;
}
protected boolean setFeature(String result) throws FSMLMappingException {
return setFeature(result, null);
}
protected boolean setFeature(String result, Object context) throws FSMLMappingException {
if (feature instanceof EAttribute && feature.getEType().getName().equals("EString")) {
if (feature.isMany())
((EList)element.eGet(feature)).add(result);
else
element.eSet(feature, result);
}
else if (feature instanceof EReference && result != null) {
child = EcoreUtil.create(concreteChildType);
if (feature.isMany())
((EList)element.eGet(feature)).add(child);
else
element.eSet(feature, child);
if (context != null)
Queries.INSTANCE.associateContext(child, context);
if (!Queries.INSTANCE.reverseFeatureRepresentedAsClass(child, progressMonitor))
return false;
}
if (result != null) {
Stats.INSTANCE.logFeatureInstance(element, feature, annotation);
return true;
}
return false;
}
}