blob: 79f8a28e344508afdb5af9680c39e6e512544f66 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008, 2009 THALES GLOBAL SERVICES.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.query.legacy.business.internal.interpreter;
import java.io.File;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.sirius.common.tools.api.interpreter.EvaluationException;
import org.eclipse.sirius.common.tools.api.interpreter.IInterpreter;
import org.eclipse.sirius.common.tools.api.interpreter.IInterpreterContext;
import org.eclipse.sirius.common.tools.api.interpreter.IInterpreterStatus;
import org.eclipse.sirius.ecore.extender.business.api.accessor.ModelAccessor;
import org.eclipse.sirius.ext.base.collect.StackEx;
import org.eclipse.sirius.query.legacy.AcceleoInterpreterPlugin;
import org.eclipse.sirius.query.legacy.gen.AcceleoGenMessages;
import org.eclipse.sirius.query.legacy.gen.template.TemplateSyntaxExceptions;
import org.eclipse.sirius.query.legacy.gen.template.eval.ENode;
import org.eclipse.sirius.query.legacy.gen.template.eval.ENodeException;
import org.eclipse.sirius.query.legacy.gen.template.eval.log.EvalFailure;
import org.eclipse.sirius.query.legacy.gen.template.scripts.SpecificScript;
import org.eclipse.sirius.query.legacy.tools.classloaders.AcceleoClassLoader;
import org.eclipse.sirius.query.legacy.tools.plugins.AcceleoModuleProvider;
import org.eclipse.sirius.query.legacy.tools.resources.Resources;
import com.google.common.collect.Sets;
/**
* {@link AcceleoExtendedInterpreter} is able to evaluate Acceleo expression
* considering the possible metamodel extenders and using variables.
*
* @author cbrun
*/
public class AcceleoExtendedInterpreter extends AcceleoInterpreter {
private static final boolean LOG_WARNINGS = false;
private static final String ACCELEO_PLUGIN_NAME = AcceleoInterpreterPlugin.getDefault().getBundle().getSymbolicName();
/**
* Class used to access the model data.
*/
protected ModelAccessor accessor;
private ECrossReferenceAdapter crossReferencer;
/**
* All the representation description files (*.odesign) to to take into
* account.
*/
private final List<File> representationDescriptionFiles;
/**
* Create a new {@link AcceleoExtendedInterpreter}. Be careful instantiating
* it this way, you should better use the File parameterized initializer to
* avoid class loader issues when evaluating your scripts.
*/
public AcceleoExtendedInterpreter() {
super();
representationDescriptionFiles = new ArrayList<File>();
}
/**
* Create a new {@link AcceleoExtendedInterpreter} initialized with an
* accessor.. Be careful instantiating it this way, you should better use
* the File parameterized initializer to avoid class loader issues when
* evaluating your scripts.
*
* @param accessor
* a model accessor.
*/
public AcceleoExtendedInterpreter(final ModelAccessor accessor) {
this();
this.accessor = accessor;
}
/**
* Create a new {@link AcceleoExtendedInterpreter} from an .air file.
*
* @param representationDescriptionFile
* any .odesign file, this file is used to initialize the class
* loaders.
* @deprecated
*/
@Deprecated
public AcceleoExtendedInterpreter(final File representationDescriptionFile) {
this();
representationDescriptionFiles.add(representationDescriptionFile);
}
/**
* Factory method for the new {@link SpecificScript} instances. Clients may
* override this method to provide new behaviors while evaluating Acceleo
* scripts.
*
* @param current
* current {@link EObject} used to create the script.
* @param mmURI
* metamodel URI.
* @return the new instance of {@link SpecificScript}.
*/
@Override
protected SpecificScript createNewScript(final EObject current, final String mmURI) {
File moreSpecificRepresentationDescriptionFile = representationDescriptionFiles != null && !representationDescriptionFiles.isEmpty() ? representationDescriptionFiles.get(0) : null;
/*
* If the interpreter has been created with no AIR file, then let's use
* the model one but then we will probably encounter issues with the
* services if the model is not in the same project as the AIR file.
*/
if (moreSpecificRepresentationDescriptionFile == null && current != null && current.eResource() != null) {
// if the resource associated to current is a CDOResource
// we obviously can't get any IFile related to this element
// to avoid any dependency to CDO, we catch the exception raised by
// getIFile
try {
if (current.eResource().getURI().path() != null && Resources.getIFile(current.eResource()) != null && Resources.getIFile(current.eResource()).getLocation() != null) {
moreSpecificRepresentationDescriptionFile = Resources.getIFile(current.eResource()).getLocation().toFile();
}
} catch (IllegalArgumentException e) {
// The assertion in
// org.eclipse.core.internal.resources.Workspace.newResource()
// -> case IFIle (line 1801), can raise an error
// We don't have to treat this exception, the
// moreSpecificRepresentationDescriptionFile will be left to his
// default value
}
}
/*
* We need to set the preferred loader here in order to have Acceleo
* remember which class loader was used to create the script so as to
* avoid LinkageErrors. It will be reverted back to null at the end of
* this method. IMPORTANT : the preferred class loader MUST be set
* everywhere we instantiate or evaluate a SpecificScript. See
* AcceleoInterpreter#evaluate(EObject, String) and
* SiriusInformationAdapter#getAdapter(Object, Class).
*/
if (current != null) {
AcceleoClassLoader.setPreferredClassLoader(current.getClass().getClassLoader());
}
final SmartSpecificScript newScript = new SmartSpecificScript(moreSpecificRepresentationDescriptionFile, representationDescriptionFiles, this);
newScript.setCrossReferencer(crossReferencer);
try {
final String templateHeader = getScriptHeader(mmURI);
newScript.reset(templateHeader);
} catch (final TemplateSyntaxExceptions e) {
AcceleoInterpreterPlugin.getDefault().error("Syntax error in template while initializing the interpreter.", e);
}
AcceleoClassLoader.setPreferredClassLoader(null);
return newScript;
}
/**
* Evaluate the Acceleo expression.
*
* @param eObj
* instance on which the Expression should be evaluated.
* @param expression
* Acceleo expression to evaluate.
* @throws EvaluationException
* on error concerning ENode.
* @return the result of the Expression.
*/
@Override
public Object evaluate(final EObject eObj, final String expression) throws EvaluationException {
// Save the original output
final PrintStream originalOut = System.out;
ENode result = null;
result = doEvaluate(eObj, expression);
// Restore the original output (because it is catched by Acceleo)
if (originalOut != System.out) {
System.setOut(originalOut);
}
return getValue(result, expression);
}
@SuppressWarnings("unused")
private ENode doEvaluate(final EObject eObj, final String expression) throws EvaluationException {
ENodeException.disableRuntimeMarkersFor(eObj);
final ENode result = super.evaluateENode(eObj, expression);
try {
//
// log eNode error if the profiler is active.
if (AcceleoExtendedInterpreter.LOG_WARNINGS && !result.log().isOk()) {
final MultiStatus status = new MultiStatus(AcceleoExtendedInterpreter.ACCELEO_PLUGIN_NAME, IStatus.INFO, "variables info", null);
final Iterator<Entry<String, StackEx<ENode>>> iterVariables = variableTables.entrySet().iterator();
while (iterVariables.hasNext()) {
final Entry<String, StackEx<ENode>> entry = iterVariables.next();
final String variableName = entry.getKey();
final Object variableValue = getVariableValue(variableName);
final IStatus subStatus = new Status(IStatus.INFO, AcceleoExtendedInterpreter.ACCELEO_PLUGIN_NAME, variableName + " : " + String.valueOf(variableValue));
status.add(subStatus);
}
final IStatus evaluationStatus = new Status(IStatus.INFO, AcceleoExtendedInterpreter.ACCELEO_PLUGIN_NAME, "expression : " + expression);
status.add(evaluationStatus);
final IStatus contextStatus = new Status(IStatus.INFO, AcceleoExtendedInterpreter.ACCELEO_PLUGIN_NAME, "context : " + eObj);
status.add(contextStatus);
AcceleoInterpreterPlugin.getDefault().logENode(result, status);
}
} finally {
ENodeException.enableRuntimeMarkersFor(eObj);
}
if (result.log().allErrors().hasNext()) {
final EvalFailure evalFailure = (EvalFailure) result.log().allErrors().next();
if (isMessageToThrow(evalFailure.getMessage())) {
throw new EvaluationException(evalFailure.getMessage());
}
}
return result;
}
/**
* Throw all the message except those corresponding to
* EmptyFeatureEvaluation
*
* @param message
* The message to test
* @return false if the message corresponds to EmptyFeatureEvaluation, true
* otherwise
*/
private boolean isMessageToThrow(final String message) {
final String evalFormater = AcceleoGenMessages.getString("EvalFailure.FailureMessage");
final String evalPrefix = MessageFormat.format(evalFormater, "");
if (message != null && message.startsWith(evalPrefix)) {
final String endOfMessage = message.substring(evalPrefix.length());
if (endOfMessage.startsWith(AcceleoGenMessages.getString("ENodeError.EmptyEvaluation"))) {
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.sirius.query.legacy.business.internal.interpreter.AcceleoInterpreter#setModelAccessor(org.eclipse.sirius.ecore.extender.business.api.accessor.ModelAccessor)
*/
@Override
public void setModelAccessor(final ModelAccessor modelAccessor) {
this.accessor = modelAccessor;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.sirius.query.legacy.business.internal.interpreter.AcceleoInterpreter#createInterpreter()
*/
@Override
public IInterpreter createInterpreter() {
return new AcceleoExtendedInterpreter();
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.sirius.query.legacy.business.internal.interpreter.AcceleoInterpreter#setProperty(java.lang.Object,
* java.lang.Object)
*/
@Override
public void setProperty(final Object key, final Object value) {
if (IInterpreter.FILES.equals(key)) {
if (value instanceof List) {
@SuppressWarnings("unchecked")
final List<String> odesignPaths = (List<String>) value;
for (String odesignPath : odesignPaths) {
// Add this file to the list of odesignFiles
File file = AcceleoModuleProvider.getDefault().getFile(new Path(odesignPath));
if (file != null) {
this.representationDescriptionFiles.add(file);
}
}
} else if (value == null) {
this.representationDescriptionFiles.clear();
}
}
super.setProperty(key, value);
}
/**
*
* {@inheritDoc}
*/
@Override
public void setCrossReferencer(final ECrossReferenceAdapter crossReferencer) {
this.crossReferencer = crossReferencer;
if (crossReferencer != null) {
for (final SpecificScript script : mmToScript.values()) {
if (script instanceof SmartSpecificScript) {
((SmartSpecificScript) script).setCrossReferencer(crossReferencer);
}
}
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.sirius.query.legacy.business.internal.interpreter.AcceleoInterpreter#getCompletionEntry()
*/
@Override
protected AcceleoCompletionEntry getCompletionEntry() {
return new AcceleoCompletionEntry() {
@Override
protected void addModelElements(Object[] proposals, List<Object> contents) {
if (element != null && accessor != null && accessor.eInstanceOf(element, "tool.ModelOperation")) {
/*
* if we are on a tool, then we do not need to provide
* semantic model element in completion
*/
} else {
super.addModelElements(proposals, contents);
}
}
};
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.sirius.common.tools.api.interpreter.IInterpreter#validateExpression(java.lang.String,
* java.lang.String)
*/
public Collection<IInterpreterStatus> validateExpression(IInterpreterContext context, String expression) {
// Acceleo 2 is not able to analyse this expression if no target is
// given
return Sets.newLinkedHashSet();
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.sirius.common.tools.api.interpreter.IInterpreter#supportsValidation()
*/
public boolean supportsValidation() {
return false;
}
}