blob: 9092d6d309c9fc325ec24f4872b32e9908aabe92 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 Obeo.
* 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.common.acceleo.aql.business.internal;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.eclipse.acceleo.query.runtime.AcceleoQueryEvaluationException;
import org.eclipse.acceleo.query.runtime.AcceleoQueryValidationException;
import org.eclipse.acceleo.query.runtime.CrossReferenceProvider;
import org.eclipse.acceleo.query.runtime.EvaluationResult;
import org.eclipse.acceleo.query.runtime.IQueryBuilderEngine;
import org.eclipse.acceleo.query.runtime.IQueryBuilderEngine.AstResult;
import org.eclipse.acceleo.query.runtime.IQueryEnvironment;
import org.eclipse.acceleo.query.runtime.IQueryEvaluationEngine;
import org.eclipse.acceleo.query.runtime.IQueryValidationEngine;
import org.eclipse.acceleo.query.runtime.IValidationMessage;
import org.eclipse.acceleo.query.runtime.IValidationResult;
import org.eclipse.acceleo.query.runtime.InvalidAcceleoPackageException;
import org.eclipse.acceleo.query.runtime.Query;
import org.eclipse.acceleo.query.runtime.QueryEvaluation;
import org.eclipse.acceleo.query.runtime.QueryParsing;
import org.eclipse.acceleo.query.runtime.QueryValidation;
import org.eclipse.acceleo.query.validation.type.EClassifierType;
import org.eclipse.acceleo.query.validation.type.IType;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.impl.EStringToStringMapEntryImpl;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.sirius.common.acceleo.aql.business.AQLSiriusPlugin;
import org.eclipse.sirius.common.acceleo.aql.business.Messages;
import org.eclipse.sirius.common.acceleo.aql.business.api.AQLConstants;
import org.eclipse.sirius.common.acceleo.aql.business.api.ExpressionTrimmer;
import org.eclipse.sirius.common.acceleo.aql.business.api.TypesUtil;
import org.eclipse.sirius.common.tools.api.interpreter.ClassLoadingCallback;
import org.eclipse.sirius.common.tools.api.interpreter.EPackageLoadingCallback;
import org.eclipse.sirius.common.tools.api.interpreter.EvaluationException;
import org.eclipse.sirius.common.tools.api.interpreter.IInterpreterContext;
import org.eclipse.sirius.common.tools.api.interpreter.IInterpreterStatus;
import org.eclipse.sirius.common.tools.api.interpreter.InterpreterStatusFactory;
import org.eclipse.sirius.common.tools.api.interpreter.ValidationResult;
import org.eclipse.sirius.common.tools.api.interpreter.VariableType;
import org.eclipse.sirius.ecore.extender.business.api.accessor.EcoreMetamodelDescriptor;
import org.eclipse.sirius.ecore.extender.business.api.accessor.MetamodelDescriptor;
import org.eclipse.sirius.ecore.extender.business.api.accessor.ModelAccessor;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* A Sirius interpreter using the Acceleo Query Language. It only supports
* expressions which are not using implicit variables.
*
* @author cedric
*/
public class AQLSiriusInterpreter extends AcceleoAbstractInterpreter {
private LoadingCache<String, AstResult> parsedExpressions;
private IQueryEnvironment queryEnvironment;
private ECrossReferenceAdapter siriusXref;
private CrossReferenceProvider xRef = new CrossReferenceProvider() {
@Override
public Collection<Setting> getInverseReferences(EObject self) {
if (siriusXref != null) {
return siriusXref.getInverseReferences(self);
} else {
return Collections.emptySet();
}
}
};
private final ClassLoadingCallback callback = new ClassLoadingCallback() {
@Override
public void loaded(String qualifiedName, Class<?> clazz) {
try {
queryEnvironment.registerServicePackage(clazz);
} catch (InvalidAcceleoPackageException e) {
AQLSiriusPlugin.INSTANCE.log(
new Status(IStatus.WARNING, AQLSiriusPlugin.INSTANCE.getSymbolicName(), MessageFormat.format(Messages.AQLInterpreter_errorLoadingJavaClass, qualifiedName, e.getMessage()), e));
}
}
@Override
public void notFound(String qualifiedName) {
AQLSiriusPlugin.INSTANCE.log(new Status(IStatus.WARNING, AQLSiriusPlugin.INSTANCE.getSymbolicName(), MessageFormat.format(Messages.AQLInterpreter_javaClassNotFound, qualifiedName)));
}
@Override
public void unloaded(String qualifiedName, Class<?> clazz) {
queryEnvironment.removeServicePackage(clazz);
}
};
private final EPackageLoadingCallback ePackageCallBack;
/**
* Create a new interpreter supporting the AQL evaluation engine.
*/
public AQLSiriusInterpreter() {
super();
this.queryEnvironment = Query.newEnvironmentWithDefaultServices(xRef);
this.ePackageCallBack = new EPackageLoadingCallback() {
@Override
public void loaded(String nsURI, EPackage pak) {
queryEnvironment.registerEPackage(pak);
}
@Override
public void unloaded(String nsURI, EPackage pak) {
queryEnvironment.removeEPackage(pak.getName());
}
};
this.javaExtensions.addClassLoadingCallBack(callback);
this.javaExtensions.addEPackageCallBack(ePackageCallBack);
this.queryEnvironment.registerEPackage(EcorePackage.eINSTANCE);
this.queryEnvironment.registerCustomClassMapping(EcorePackage.eINSTANCE.getEStringToStringMapEntry(), EStringToStringMapEntryImpl.class);
initExpressionsCache();
}
private void initExpressionsCache() {
final IQueryBuilderEngine builder = QueryParsing.newBuilder(queryEnvironment);
this.parsedExpressions = CacheBuilder.newBuilder().maximumSize(500).build(new CacheLoader<String, AstResult>() {
@Override
public AstResult load(String key) throws Exception {
return builder.build(key);
}
});
}
@Override
public void activateMetamodels(Collection<MetamodelDescriptor> metamodels) {
Set<EPackage> additionalEPackages = Sets.newLinkedHashSet();
for (MetamodelDescriptor descriptor : metamodels) {
if (descriptor instanceof EcoreMetamodelDescriptor) {
EPackage pkg = ((EcoreMetamodelDescriptor) descriptor).resolve();
if (pkg != null) {
additionalEPackages.add(pkg);
}
}
}
for (EPackage ePackage : additionalEPackages) {
this.queryEnvironment.registerEPackage(ePackage);
}
if (additionalEPackages.size() > 0) {
this.initExpressionsCache();
}
}
@Override
public void dispose() {
super.dispose();
this.javaExtensions.removeClassLoadingCallBack(callback);
this.javaExtensions.removeEPackageCallBack(ePackageCallBack);
}
@Override
public Object evaluate(EObject target, String fullExpression) throws EvaluationException {
IEvaluationResult evaluationResult = this.evaluateExpression(target, fullExpression);
// We fire the exception to keep the old behavior
if (evaluationResult.getDiagnostic().getSeverity() == Diagnostic.ERROR) {
throw new EvaluationException(evaluationResult.getDiagnostic().getMessage(), evaluationResult.getDiagnostic().getException());
}
return evaluationResult.getValue();
}
@Override
public IEvaluationResult evaluateExpression(final EObject target, final String fullExpression) throws EvaluationException {
this.javaExtensions.reloadIfNeeded();
String expression = new ExpressionTrimmer(fullExpression).getExpression();
Map<String, Object> variables = getVariables();
variables.put("self", target); //$NON-NLS-1$
try {
AstResult build = parsedExpressions.get(expression);
IQueryEvaluationEngine evaluationEngine = QueryEvaluation.newEngine(queryEnvironment);
final EvaluationResult evalResult = evaluationEngine.eval(build, variables);
return new IEvaluationResult() {
@Override
public Object getValue() {
return evalResult.getResult();
}
@Override
public Diagnostic getDiagnostic() {
return evalResult.getDiagnostic();
}
};
} catch (ExecutionException e) {
throw new EvaluationException(e.getCause());
}
}
@Override
public String getVariablePrefix() {
/*
* no variable prefix for this interpreter.
*/
return null;
}
@Override
public void setCrossReferencer(ECrossReferenceAdapter crossReferencer) {
this.siriusXref = crossReferencer;
}
@Override
public void setModelAccessor(ModelAccessor modelAccessor) {
/*
* AQL does not support the ModelAccessor yet.
*/
}
@Override
public boolean supportsValidation() {
return true;
}
@Override
public ValidationResult analyzeExpression(IInterpreterContext context, String fullExpression) {
this.javaExtensions.reloadIfNeeded();
String trimmedExpression = new ExpressionTrimmer(fullExpression).getExpression();
ValidationResult result = new ValidationResult();
Map<String, Set<IType>> variableTypes = TypesUtil.createAQLVariableTypesFromInterpreterContext(context, queryEnvironment);
IQueryValidationEngine validator = QueryValidation.newEngine(this.queryEnvironment);
try {
IValidationResult validationResult = validator.validate(trimmedExpression, variableTypes);
for (IValidationMessage message : validationResult.getMessages()) {
result.addStatus(InterpreterStatusFactory.createInterpreterStatus(context, IInterpreterStatus.WARNING, message.getMessage()));
}
List<String> classifierNames = Lists.newArrayList();
for (IType type : validationResult.getPossibleTypes(validationResult.getAstResult().getAst())) {
if (type instanceof EClassifierType) {
EClassifierType eClassifierType = (EClassifierType) type;
if (eClassifierType.getType() != null && eClassifierType.getType().getName() != null) {
String typeName = eClassifierType.getType().getName();
if (eClassifierType.getType().getEPackage() != null && eClassifierType.getType().getEPackage().getName() != null) {
typeName = eClassifierType.getType().getEPackage().getName() + "." + typeName; //$NON-NLS-1$
}
classifierNames.add(typeName);
}
}
result.setReturnType(VariableType.fromStrings(classifierNames));
}
} catch (AcceleoQueryValidationException e) {
result.addStatus(InterpreterStatusFactory.createInterpreterStatus(context, IInterpreterStatus.ERROR, e.getMessage()));
AQLSiriusPlugin.INSTANCE.log(new Status(IStatus.ERROR, AQLSiriusPlugin.INSTANCE.getSymbolicName(), e.getMessage(), e));
} catch (AcceleoQueryEvaluationException e) {
result.addStatus(InterpreterStatusFactory.createInterpreterStatus(context, IInterpreterStatus.ERROR, e.getMessage()));
}
return result;
}
/**
* return the cross reference provider used by this interpreter instance.
*
* @return the cross reference provider used by this interpreter instance.
*/
public CrossReferenceProvider getCrossReferenceProvider() {
return xRef;
}
@Override
public String getPrefix() {
return AQLConstants.AQL_PREFIX;
}
/**
* {@inheritDoc}
*/
@Override
public boolean provides(String expression) {
return expression != null && expression.startsWith(AQLConstants.AQL_PREFIX);
}
/**
* The query environment currently used by this interpreter.
*
* @return The query environment currently used by this interpreter.
*/
public IQueryEnvironment getQueryEnvironment() {
/*
* The JavaExtensionManager might impact the query environment when
* loading classes. We trigger the reload before returning the
* IQueryEnvironment so that it is properly configured with EPackages
* and imports.
*/
this.javaExtensions.reloadIfNeeded();
return this.queryEnvironment;
}
}