blob: 0d7dbaafd1c1131cc0e52fb8fc72edda5862d3d3 [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 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.Maps;
import com.google.common.collect.Sets;
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.ast.Expression;
import org.eclipse.acceleo.query.parser.AstEvaluator;
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.IQueryBuilderEngine;
import org.eclipse.acceleo.query.runtime.IQueryBuilderEngine.AstResult;
import org.eclipse.acceleo.query.runtime.IQueryEnvironment;
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.impl.EvaluationServices;
import org.eclipse.acceleo.query.runtime.impl.Nothing;
import org.eclipse.acceleo.query.runtime.impl.QueryBuilderEngine;
import org.eclipse.acceleo.query.runtime.impl.QueryEnvironment;
import org.eclipse.acceleo.query.runtime.impl.QueryValidationEngine;
import org.eclipse.acceleo.query.validation.type.IType;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.sirius.common.acceleo.aql.business.AQLSiriusPlugin;
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.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.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;
/**
* 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.EMPTY_SET;
}
}
};
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(), "Error loading Java extension class " + qualifiedName + " :" + e.getMessage(), e));
}
}
@Override
public void notFound(String qualifiedName) {
AQLSiriusPlugin.INSTANCE.log(new Status(IStatus.WARNING, AQLSiriusPlugin.INSTANCE.getSymbolicName(), "Could not find Java extension class " + qualifiedName));
}
@Override
public void unloaded(String qualifiedName, Class<?> clazz) {
// TODO implement the un-register once it is available in AQL.
// see Bug 461072
}
};
/**
* Create a new interpreter supporting the AQL evaluation engine.
*/
public AQLSiriusInterpreter() {
super();
this.queryEnvironment = new QueryEnvironment(xRef);
this.javaExtensions.addClassLoadingCallBack(callback);
final IQueryBuilderEngine builder = new QueryBuilderEngine(queryEnvironment);
this.parsedExpressions = CacheBuilder.newBuilder().maximumSize(500).build(new CacheLoader<String, AstResult>() {
@Override
public AstResult load(String key) throws Exception {
return builder.build(key);
}
});
this.queryEnvironment.registerEPackage(EcorePackage.eINSTANCE);
registerEcoreModels(EPackage.Registry.INSTANCE);
}
@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);
}
}
@Override
public void dispose() {
super.dispose();
this.javaExtensions.removeClassLoadingCallBack(callback);
}
@Override
public Object evaluate(EObject target, String fullExpression) throws EvaluationException {
String expression = new ExpressionTrimmer(fullExpression).getExpression();
Map<String, Object> variables = Maps.newLinkedHashMap(getVariables());
variables.put("self", target);
if (target != null && target.eResource() != null && target.eResource().getResourceSet() != null) {
registerEcoreModels(target.eResource().getResourceSet().getPackageRegistry());
}
AstResult build;
try {
build = parsedExpressions.get(expression);
Expression ast = build.getAst();
AstEvaluator evaluator = new AstEvaluator(new EvaluationServices(queryEnvironment, false));
Object result = evaluator.eval(variables, ast);
if (result instanceof Nothing) {
result = null;
}
return result;
} catch (ExecutionException e) {
throw new EvaluationException(e.getCause());
}
}
private void registerEcoreModels(Registry packageRegistry) {
for (String nsURI : packageRegistry.keySet()) {
EPackage pak = packageRegistry.getEPackage(nsURI);
if (pak != null && this.queryEnvironment.getEPackageProvider().getEPackage(pak.getNsPrefix()) == null) {
this.queryEnvironment.registerEPackage(pak);
}
}
}
@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 Collection<IInterpreterStatus> validateExpression(IInterpreterContext context, String fullExpression) {
String trimmedExpression = new ExpressionTrimmer(fullExpression).getExpression();
for (EPackage pak : context.getAvailableEPackages()) {
if (pak != null) {
queryEnvironment.registerEPackage(pak);
}
}
Map<String, Set<IType>> variableTypes = TypesUtil.createAQLVariableTypesFromInterpreterContext(context, queryEnvironment);
List<IInterpreterStatus> statuses = Lists.newArrayList();
QueryValidationEngine validator = new QueryValidationEngine(this.queryEnvironment);
try {
IValidationResult validationResult = validator.validate(trimmedExpression, variableTypes);
for (IValidationMessage message : validationResult.getMessages()) {
IInterpreterStatus status = InterpreterStatusFactory.createInterpreterStatus(context, IInterpreterStatus.WARNING, message.getMessage());
statuses.add(status);
}
} catch (AcceleoQueryValidationException e) {
statuses.add(InterpreterStatusFactory.createInterpreterStatus(context, IInterpreterStatus.ERROR, e.getMessage()));
AQLSiriusPlugin.getPlugin().log(new Status(IStatus.ERROR, AQLSiriusPlugin.getPlugin().getSymbolicName(), e.getMessage(), e));
} catch (AcceleoQueryEvaluationException e) {
statuses.add(InterpreterStatusFactory.createInterpreterStatus(context, IInterpreterStatus.ERROR, e.getMessage()));
}
if (statuses.size() == 0) {
// TODO check the return type of the expression matches or will be
// adapted to the expected type
}
return statuses;
}
/**
* 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() {
return this.queryEnvironment;
}
}