/**
* Optimus, framework for Model Transformation
*
* Copyright (C) 2013 Worldline or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.atos.optimus.m2m.engine.core;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import net.atos.optimus.m2m.engine.core.adapters.AbstractOptimusAdapter;
import net.atos.optimus.m2m.engine.core.adapters.EObjectChildAdditionAdapter;
import net.atos.optimus.m2m.engine.core.adapters.EObjectLockAdapter;
import net.atos.optimus.m2m.engine.core.exceptions.TransformationFailedException;
import net.atos.optimus.m2m.engine.core.hooks.TransformationExecutionHookManager;
import net.atos.optimus.m2m.engine.core.logging.EObjectLabelProvider;
import net.atos.optimus.m2m.engine.core.logging.OptimusM2MEngineMessages;
import net.atos.optimus.m2m.engine.core.masks.ITransformationMask;
import net.atos.optimus.m2m.engine.core.masks.TransformationMaskDataSource;
import net.atos.optimus.m2m.engine.core.masks.TransformationMaskDataSourceManager;
import net.atos.optimus.m2m.engine.core.masks.TransformationMaskReference;
import net.atos.optimus.m2m.engine.core.requirements.AbstractRequirement;
import net.atos.optimus.m2m.engine.core.transformations.AbstractTransformation;
import net.atos.optimus.m2m.engine.core.transformations.ITransformationContext;
import net.atos.optimus.m2m.engine.core.transformations.ITransformationFactory;
import net.atos.optimus.m2m.engine.core.transformations.TransformationDataSource;
import net.atos.optimus.m2m.engine.core.transformations.TransformationDataSourceManager;
import net.atos.optimus.m2m.engine.core.transformations.TransformationReference;
import net.atos.optimus.m2m.engine.core.transformations.TransformationSet;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.viewers.LabelProvider;
/**
* The purpose of this class is to propose a chaining process to execute the
* registered transformations, that follows the user's defined requirements.
*
* @author Maxence Vanbésien (mvaawl@gmail.com)
* @since 1.0
*
*/
public class OptimusM2MEngine {
/**
* Internal object, to be provided to objects that can have access to this
* API of this instance.
*/
private Object password = new Object();
/**
* Internal wrapper class, that links a pending transformation and the
* object it should be applied on.
*
* @author Maxence Vanbésien (mvaawl@gmail.com)
* @since 1.0
*
*/
private static class PendingTransformation {
/**
* Pending Transformation Reference
*/
TransformationReference reference;
/**
* EObject on which the pending Transformation should be applied.
*/
EObject eObject;
}
/**
* Set to true if transformations are filtered according to their
* transformation set
*/
private boolean transformationsSetLimited = false;
/**
* Array of transformation set ids. If boolean above set to true, a given
* transformation eligibility will be conditionned whether its
* transformation set is available from this array
*/
private String[] allowedTransformationSetsID = new String[0];
/**
* Set of Objects, from the user selection
*/
private Set<EObject> selectedElements = new LinkedHashSet<EObject>();
/**
* Set of Objects, corresponding to the objects that will be eligible for
* transitions. Set is computed from user selected elements.
*/
private Set<EObject> resolvedElements = new LinkedHashSet<EObject>();
/**
* Map of transformations sorted by EObject. This structure acts as a cache
*
*/
private Map<EObject, Set<AbstractTransformation<?>>> transformationsCache = new HashMap<EObject, Set<AbstractTransformation<?>>>();
/**
* Map that contains the transformations that can be executed in this
* transformation set limitation context.
*/
private Map<String, TransformationReference> accessibleTransformationReferences = new LinkedHashMap<String, TransformationReference>();
/**
* Queue containing the EObjects to transform, that have been added as
* children of resolved elements during transformations.
*/
private Queue<EObject> pendingEObjects = new ConcurrentLinkedQueue<EObject>();
/**
* Queue containing the transformations that have been scheduled by other
* transformations.
*/
private Queue<PendingTransformation> pendingTransformations = new ConcurrentLinkedQueue<PendingTransformation>();
/**
* Context implementation to be provided to the transformations.
*/
private ITransformationContext context;
/**
* List of adapters that are used during Optimus execution
*/
private Set<AbstractOptimusAdapter> optimusAdapters = new HashSet<AbstractOptimusAdapter>();
/**
* Label Provider which purpose is to provide naming system for EObjects.
*/
private LabelProvider eObjectLabelProvider = new EObjectLabelProvider();
/**
* Instance of transformation data source. Default implementation heads to
* extension points management
*/
protected List<TransformationDataSource> transformationDataSources = TransformationDataSourceManager.INSTANCE
.getTransformationDataSources();
/**
* Instance of transformation mask data source.
*/
protected List<TransformationMaskDataSource> transformationMaskDataSources = TransformationMaskDataSourceManager.INSTANCE
.getTransformationMaskDataSources();
/**
* Instance of Mask used to filter the transformations
*/
private TransformationMaskReference userTransformationMaskReference = null;
/**
* Creates new transformation engine, with provided context implementation
*
* This default constructor will allow the input elements to be modified or
* removed during the transformation and added elements won't be considered
* by transformation
*
* @param context
*/
public OptimusM2MEngine(ITransformationContext context) {
this(context, false, false);
}
/**
* Creates new transformation engine, with provided context implementation
*
* @param context
* @param lockInput
* : true if all the resolved elements should be locked in
* modification & deletion during transformation
* @param trackAddition
* : true if all the children of input, created during process,
* have to be considered as input of the process.
*/
public OptimusM2MEngine(ITransformationContext context, boolean lockInput, boolean trackAddition) {
this.context = context;
if (lockInput)
this.optimusAdapters.add(new EObjectLockAdapter());
if (trackAddition)
this.optimusAdapters.add(new EObjectChildAdditionAdapter(this, this.password));
}
/**
* Limits the possible transformation sets. If this method is called, A
* given transformation will be executed only if its transformation set is
* referenced in the provided ones.
*
* @param transformationSetIDs
* @return
*/
public OptimusM2MEngine limitTransformationSetsTo(String... transformationSetIDs) {
this.allowedTransformationSetsID = transformationSetIDs;
this.transformationsSetLimited = true;
OptimusM2MEngineMessages.TE03.log(Arrays.toString(transformationSetIDs));
return this;
}
/**
* Applies a mask on the transformations, to prevent them from being
* enabled. Applying a mask will NOT go over the user configuration
* specified in the Optimus preferences...
*
* @param transformationMask
* @return
* @see applyTransformationMask(ITransformationMask, String)
*/
@Deprecated
public OptimusM2MEngine applyTransformationMask(ITransformationMask transformationMask) {
return applyTransformationMask(transformationMask,
String.valueOf("<UserTransformationMask-" + System.currentTimeMillis() + ">"));
}
/**
* Applies a mask on the transformations, to prevent them from being
* enabled. Applying a mask will NOT go over the user configuration
* specified in the Optimus preferences...
*
* @param transformationMask
* @param name
* @return
*/
public OptimusM2MEngine applyTransformationMask(ITransformationMask transformationMask, String name) {
this.userTransformationMaskReference = new TransformationMaskReference(name, null, transformationMask);
return this;
}
/**
* Sets user selection
*
* @param selectedElements
* @return
*/
public OptimusM2MEngine setElements(Set<? extends EObject> selectedElements) {
this.selectedElements.clear();
this.selectedElements.addAll(selectedElements);
return this;
}
/**
* Sets user selection
*
* @param selectedElement
* @return
*/
public OptimusM2MEngine setElement(EObject selectedElement) {
this.selectedElements.clear();
this.selectedElements.add(selectedElement);
return this;
}
/**
* Executes the transformations for the user selection
*
* @throws TransformationFailedException
*/
public IStatus execute() {
OptimusM2MEngineMessages.TE01.log();
long begin = System.currentTimeMillis();
this.resolveTransformations();
this.resolveElements();
try {
this.executeAllTransformations();
OptimusM2MEngineMessages.TE02.log(System.currentTimeMillis() - begin);
return Status.OK_STATUS;
} catch (TransformationFailedException tfe) {
OptimusM2MEngineMessages.TE23
.log(tfe.getTransformationID(), eObjectLabelProvider.getText(tfe.getEObject()));
IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, OptimusM2MEngineMessages.TFE.message(tfe
.getTransformationID()), tfe);
Activator.getDefault().getLog().log(status);
return status;
} finally {
for (AbstractOptimusAdapter optimusAdapter : optimusAdapters)
optimusAdapter.release();
}
}
/**
* The purpose of this method is to compute the subset of transformation
* references that are accessible through this instance, according to
* transformation sets limitations and/or their privacy.
*/
private void resolveTransformations() {
OptimusM2MEngineMessages.TE24.log();
for (final TransformationDataSource transformationDataSource : this.transformationDataSources) {
for (final TransformationReference reference : transformationDataSource.getAll()) {
String transformationSetID = reference.getTransformationSet().getId();
if (this.transformationsSetLimited) {
boolean found = false;
for (int i = 0; i < this.allowedTransformationSetsID.length && !found; i++) {
if (this.allowedTransformationSetsID[i].equals(transformationSetID))
found = true;
}
if (found) {
OptimusM2MEngineMessages.TE26.log(reference.getId(), transformationSetID);
this.accessibleTransformationReferences.put(reference.getId(), reference);
} else {
OptimusM2MEngineMessages.TE27.log(reference.getId(), transformationSetID);
}
} else {
if (!reference.getTransformationSet().isPrivate()) {
OptimusM2MEngineMessages.TE28.log(reference.getId(), transformationSetID);
this.accessibleTransformationReferences.put(reference.getId(), reference);
} else {
OptimusM2MEngineMessages.TE29.log(reference.getId(), transformationSetID);
}
}
}
}
OptimusM2MEngineMessages.TE25.log();
}
/**
* Executes all transformations for all resolved elements.
*
* @throws TransformationFailedException
*/
private void executeAllTransformations() throws TransformationFailedException {
OptimusM2MEngineMessages.TE04.log();
long begin = System.currentTimeMillis();
OptimusM2MEngineMessages.TE30.log();
for (EObject eObject : this.resolvedElements) {
for (TransformationReference reference : this.accessibleTransformationReferences.values()) {
executeTransformation(eObject, reference, this.password);
}
}
OptimusM2MEngineMessages.TE31.log();
while (!pendingEObjects.isEmpty()) {
EObject eObject = pendingEObjects.poll();
for (TransformationReference reference : this.accessibleTransformationReferences.values()) {
executeTransformation(eObject, reference, this.password);
}
}
OptimusM2MEngineMessages.TE05.log(System.currentTimeMillis() - begin);
}
/**
* Executes a transformation identified by the provided reference, on the
* eobject. Password object has to be provided, if external process wants to
* invoke this method from the outside.
*
* @param eObject
* @param reference
* @param password
* @return
* @throws TransformationFailedException
*/
public boolean executeTransformation(EObject eObject, TransformationReference reference, Object password)
throws TransformationFailedException {
if (this.password != password) {
OptimusM2MEngineMessages.TE07.log(reference.getId());
return false;
}
ITransformationFactory factory = reference.getTransformationFactory();
TransformationSet transformationSet = reference.getTransformationSet();
if (transformationsSetLimited || transformationSet.isPrivate()) {
boolean found = false;
for (int i = 0; i < this.allowedTransformationSetsID.length && !found; i++) {
if (transformationSet != null && allowedTransformationSetsID[i].equals(transformationSet.getId()))
found = true;
}
if (!found) {
OptimusM2MEngineMessages.TE08.log(reference.getId());
return false;
}
}
if (checkAlreadyExecuted(eObject, reference)) {
OptimusM2MEngineMessages.TE09.log(reference.getId(), reference.getDescription(),
eObjectLabelProvider.getText(eObject));
return true;
}
if (factory.isEligible(eObject) && (transformationSet == null || transformationSet.isEligible(eObject))) {
OptimusM2MEngineMessages.TE06.log(reference.getId(), reference.getDescription(),
eObjectLabelProvider.getText(eObject));
if (!reference.isEnabled(this.getTransformationMask())) {
OptimusM2MEngineMessages.TE10.log(reference.getId());
return false;
}
OptimusM2MEngineMessages.TE11.log();
for (AbstractRequirement requirement : reference.getRequirements()) {
TransformationReference requiredReference = this.accessibleTransformationReferences.get(requirement
.getId());
if (requiredReference == null) {
OptimusM2MEngineMessages.TE22.log(requirement.getId());
} else
for (EObject requiredEObject : requirement.getMatchingEObjects(eObject, this.context)) {
boolean executed = executeTransformation(requiredEObject, requiredReference, password);
if (!executed) {
OptimusM2MEngineMessages.TE12.log(requiredReference.getId());
return false;
}
}
}
AbstractTransformation<?> transformation = factory.create(eObject, reference.getId());
transformation.setPassword(password);
transformation.setTransformationEngine(this);
Set<AbstractTransformation<?>> cachedTransformationsForEObject = this.transformationsCache.get(eObject);
if (cachedTransformationsForEObject == null) {
cachedTransformationsForEObject = new LinkedHashSet<AbstractTransformation<?>>();
this.transformationsCache.put(eObject, cachedTransformationsForEObject);
}
cachedTransformationsForEObject.add(transformation);
try {
TransformationExecutionHookManager.INSTANCE.beforeExecution(transformation, this.context);
transformation.execute(this.context);
OptimusM2MEngineMessages.TE13.log(reference.getId(), this.eObjectLabelProvider.getText(eObject));
TransformationExecutionHookManager.INSTANCE.afterExecution(transformation, this.context);
} catch (Exception e) {
throw new TransformationFailedException(reference.getId(), eObject, e);
}
while (!pendingTransformations.isEmpty()) {
PendingTransformation pendingTransformation = pendingTransformations.poll();
OptimusM2MEngineMessages.TE14.log(pendingTransformation.reference,
this.eObjectLabelProvider.getText(pendingTransformation.eObject));
this.executeTransformation(pendingTransformation.eObject, pendingTransformation.reference, password);
}
}
return true;
}
/**
* Returns the transformation mask to apply for this execution.
*
* If none is defined by the user, it returns the one selected through
* preferences.
*
* @return
*/
private ITransformationMask getTransformationMask() {
TransformationMaskReference tmr = this.userTransformationMaskReference != null ? this.userTransformationMaskReference
: TransformationMaskDataSourceManager.INSTANCE.getPreferredTransformationMask();
ITransformationMask transformationMask = tmr.getImplementation();
if (transformationMask == null) {
OptimusM2MEngineMessages.TE32.log();
} else {
OptimusM2MEngineMessages.TE33.log(tmr.getName());
}
return transformationMask;
}
/**
* Checks if a transformation instance (identified by reference) has already
* been executed for the given EObject.
*
* @param eObject
* @param reference
* @return
*/
private boolean checkAlreadyExecuted(EObject eObject, TransformationReference reference) {
Set<AbstractTransformation<?>> transformations = this.transformationsCache.get(eObject);
if (transformations == null || transformations.size() == 0)
return false;
String id = reference.getId();
for (AbstractTransformation<?> transformation : transformations)
if (id.equals(transformation.getId()))
return true;
return false;
}
/**
* Resolves all the eligible elements from the user selection
*/
private void resolveElements() {
OptimusM2MEngineMessages.TE15.log();
this.resolvedElements.clear();
// For each selected element
for (EObject selectedElement : this.selectedElements) {
// Add the selected element itself
this.resolvedElements.add(selectedElement);
this.adapt(selectedElement);
OptimusM2MEngineMessages.TE16.log(eObjectLabelProvider.getText(selectedElement));
// Add all the parents of the selected object
EObject parent = selectedElement.eContainer();
while (parent != null) {
this.adapt(parent);
this.resolvedElements.add(parent);
OptimusM2MEngineMessages.TE17.log(eObjectLabelProvider.getText(parent));
parent = parent.eContainer();
}
// Add all the children of the selected object
TreeIterator<EObject> allChildren = selectedElement.eAllContents();
while (allChildren.hasNext()) {
EObject child = allChildren.next();
this.resolvedElements.add(child);
this.adapt(child);
OptimusM2MEngineMessages.TE18.log(eObjectLabelProvider.getText(child));
}
}
}
/**
* Adds Optimus Adapters to eObject
*
* @param eObject
*/
private void adapt(EObject eObject) {
for (AbstractOptimusAdapter optimusAdapter : this.optimusAdapters)
optimusAdapter.adaptEObject(eObject);
}
/**
* Schedules a transformation (identified by reference) for the EObject
* provided. Password object has to be provided, if external process wants
* to invoke this method from the outside.
*
* @param eObject
* @param reference
* @param password
*/
public void scheduleTransformation(EObject eObject, TransformationReference reference, Object password) {
OptimusM2MEngineMessages.TE19.log(reference.getId(), eObjectLabelProvider.getText(eObject));
if (this.password != password) {
OptimusM2MEngineMessages.TE20.log();
return;
}
this.adapt(eObject);
PendingTransformation pendingTransformation = new PendingTransformation();
pendingTransformation.eObject = eObject;
pendingTransformation.reference = reference;
this.pendingTransformations.offer(pendingTransformation);
OptimusM2MEngineMessages.TE21.log();
}
public void scheduleEObject(EObject eObject, Object password) {
if (this.password != password) {
OptimusM2MEngineMessages.TE20.log();
return;
}
if (pendingEObjects.contains(eObject) || this.resolvedElements.contains(eObject)) {
// Add message
return;
}
this.adapt(eObject);
this.pendingEObjects.offer(eObject);
}
/**
* Returns internal transformation datasource implementation
*
* @return implementation
*/
@Deprecated
public TransformationDataSource getTransformationDataSource() {
return transformationDataSources.size() > 0 ? transformationDataSources.iterator().next() : null;
}
/**
* Returns a transformation reference from its id. It tries to locate it in
* all the transformation data sources identified in this engine.
*
* @param transformationReferenceId
* @return
*/
public TransformationReference getTransformationReference(String transformationReferenceId) {
if (this.transformationDataSources == null) {
return null;
}
for (Iterator<TransformationDataSource> iterator = this.transformationDataSources.iterator(); iterator
.hasNext();) {
TransformationDataSource transformationDataSource = iterator.next();
TransformationReference reference = transformationDataSource != null ? transformationDataSource
.getById(transformationReferenceId) : null;
if (reference != null) {
return reference;
}
}
return null;
}
}