| /******************************************************************************* |
| * Copyright (c) 2020 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.acceleo.aql.parser; |
| |
| import java.util.Iterator; |
| import java.util.Objects; |
| import java.util.stream.Collectors; |
| |
| import org.eclipse.acceleo.ASTNode; |
| import org.eclipse.acceleo.AcceleoPackage; |
| import org.eclipse.acceleo.Expression; |
| import org.eclipse.acceleo.Metamodel; |
| import org.eclipse.acceleo.Module; |
| import org.eclipse.acceleo.TypedElement; |
| import org.eclipse.acceleo.query.ast.VariableDeclaration; |
| import org.eclipse.acceleo.query.parser.AstResult; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.util.EcoreUtil.EqualityHelper; |
| |
| /** |
| * Utility methods for navigating the {@link AcceleoAstResult Acceleo Abstract Syntax Tree (AST)}. The AST is |
| * obtained using the {@link AcceleoParser}. |
| * |
| * @author Florent Latombe |
| */ |
| public final class AcceleoAstUtils { |
| |
| private AcceleoAstUtils() { |
| // Utility class. |
| } |
| |
| /** |
| * Provides the Acceleo AST element that contains the given AQL AST element. |
| * |
| * @param aqlAstElement |
| * the (non-{@code null}) {@link EObject} that is an AQL AST element |
| * ({@link org.eclipse.acceleo.query.ast.Expression} or {@link VariableDeclaration}). |
| * @return the Acceleo {@link Expression} or {@link TypedElement} that contains the given AQL AST element. |
| */ |
| public static ASTNode getContainerOfAqlAstElement(EObject aqlAstElement) { |
| Expression containerAcceleoExpression = AcceleoAstUtils.getContainerAcceleoExpression(aqlAstElement); |
| if (containerAcceleoExpression == null) { |
| // If the AQL element is not in an Acceleo Expression, it is in a TypedElement. |
| TypedElement containerAcceleoTypedElement = AcceleoAstUtils.getContainerTypedElement( |
| aqlAstElement); |
| // In practice, all TypedElements are also ASTNodes so this cast should be safe. |
| return (ASTNode)containerAcceleoTypedElement; |
| } else { |
| return containerAcceleoExpression; |
| } |
| } |
| |
| /** |
| * Provides the AQL {@link AstResult} corresponding to an AQL AST element. |
| * |
| * @param aqlAstElement |
| * the (non-{@code null}) AQL AST element ({@link org.eclipse.acceleo.query.ast.Expression} or |
| * {@link VariableDeclaration}). |
| * @return the AQL {@link AstResult} corresponding to {@code aqlAstElement}. |
| */ |
| public static AstResult getAqlAstResultOfAqlAstElement(EObject aqlAstElement) { |
| ASTNode acceleoContainerOfAqlAstElement = getContainerOfAqlAstElement(aqlAstElement); |
| return getContainedAqlAstResultOf(acceleoContainerOfAqlAstElement); |
| } |
| |
| /** |
| * Provides the {@link AstResult} contained by an {@link ASTNode Acceleo AST Element}. |
| * |
| * @param acceleoAstElementContainingAqlAstElement |
| * the (non-{@code null}) {@link ASTNode}. Most of the times it should be either an |
| * {@link Expression} or a {@link TypedElement}. |
| * @return the contained {@link AstResult AQL AST}, or {@code null} otherwise. |
| */ |
| public static AstResult getContainedAqlAstResultOf(ASTNode acceleoAstElementContainingAqlAstElement) { |
| AstResult containedAstResult = null; |
| if (acceleoAstElementContainingAqlAstElement instanceof Expression) { |
| Expression acceleoContainerExpression = (Expression)acceleoAstElementContainingAqlAstElement; |
| containedAstResult = acceleoContainerExpression.getAst(); |
| } else if (acceleoAstElementContainingAqlAstElement instanceof TypedElement) { |
| TypedElement acceleoContainerTypedElement = (TypedElement)acceleoAstElementContainingAqlAstElement; |
| containedAstResult = acceleoContainerTypedElement.getType(); |
| } else { |
| // Other Acceleo types do not contain AQL elements. |
| } |
| return containedAstResult; |
| } |
| |
| /** |
| * Provides the containing {@link Module} of an {@link ASTNode}. |
| * |
| * @param astNode |
| * the (non-{@code null}) {@link ASTNode}. |
| * @return {@code astNode} if it is a {@link Module}, or the first non-{@code null} |
| * {@link ASTNode#eContainer() container} of {@code astNode} that is instance of {@link Module}. |
| */ |
| public static Module getContainerModule(ASTNode astNode) { |
| return getSelfOrFirstContainerOfType(astNode, Module.class); |
| } |
| |
| /** |
| * FIXME: temporary quick implementation of a resolution mecanism for Acceleo. |
| * |
| * @param module |
| * the (non-{@code null}) {@link Module}. |
| * @return the fully-qualified name of {@code module}. |
| */ |
| public static String getModuleFullyQualifiedName(Module module) { |
| Objects.requireNonNull(module); |
| return module.getName() + "_" + module.getMetamodels().stream().map(Metamodel::getReferencedPackage) |
| .map(EPackage::getNsURI).collect(Collectors.joining("[", ", ", "]")); |
| } |
| |
| /** |
| * Provides the container Acceleo {@link Expression} of the given {@link EObject} (in our cases most |
| * likely an AQL AST element). Note that in an Acceleo AST, not all AQL Expression are contained by an |
| * Acceleo Expression. It may be contained in a {@link TypedElement} as well. Use |
| * {@link #getContainerOfAqlAstElement(EObject)}. |
| * |
| * @param containedEObject |
| * the (non-{@code null}) {@link EObject}. |
| * @return the Acceleo {@link Expression} that contains {@code containedEObject}. |
| */ |
| public static Expression getContainerAcceleoExpression(EObject containedEObject) { |
| return getSelfOrFirstContainerOfType(containedEObject, Expression.class); |
| } |
| |
| /** |
| * Provides the container Acceleo {@link TypedElement} of the given {@link EObject} (in our cases most |
| * likely an AQL AST element). |
| * |
| * @param containedEObject |
| * the (non-{@code null}) {@link EObject}. |
| * @return the Acceleo {@link TypedElement} that contains {@code containedEObject}. |
| */ |
| public static TypedElement getContainerTypedElement(EObject containedEObject) { |
| return getSelfOrFirstContainerOfType(containedEObject, TypedElement.class); |
| } |
| |
| /** |
| * Provides the first container (in the EMF sense) of the given {@link EObject} that is an instance of the |
| * given {@link Class}. If the {@link EObject} is an instance of the specified type, then it is simply |
| * cast and returned. |
| * |
| * @param <T> |
| * the type of the container. |
| * @param containedEObject |
| * the (non-{@code null}) contained {@link EObject}. |
| * @param containerType |
| * the (non-{@code null}) {@link Class} of the container. |
| * @return the first container of {@code containedEObject} that is an instance of {@code containerType}. |
| */ |
| private static <T extends EObject> T getSelfOrFirstContainerOfType(EObject containedEObject, |
| Class<T> containerType) { |
| Objects.requireNonNull(containedEObject); |
| Objects.requireNonNull(containerType); |
| |
| EObject candidate = containedEObject; |
| while (candidate != null && !(containerType.isInstance(candidate))) { |
| candidate = candidate.eContainer(); |
| } |
| return containerType.cast(candidate); |
| } |
| |
| /** |
| * Provides the {@link ASTNode} in the given {@link AcceleoAstResult} corresponding to the given |
| * {@link ASTNode}. That is, either the {@link ASTNode} itself, or its equivalent {@link ASTNode}, if the |
| * {@link ASTNode} and the {@link AcceleoAstResult} come from two different parsings. |
| * |
| * @param acceleoAstNode |
| * the (non-{@code null}) {@link ASTNode}. |
| * @param inAcceleoAstResult |
| * the (non-{@code null}) {@link AcceleoAstResult}. |
| * @return {@code astNode} or its equivalent in {@code acceleoAstResult}. |
| * @param <T> |
| * the specific type of {@link ASTNode}. |
| */ |
| public static <T extends ASTNode> T getSelfOrEquivalentOf(T acceleoAstNode, |
| AcceleoAstResult inAcceleoAstResult) { |
| if (inAcceleoAstResult.getStartPosition(acceleoAstNode) == -1 && inAcceleoAstResult.getEndPosition( |
| acceleoAstNode) == -1) { |
| return getStructuralEqualOf(acceleoAstNode, inAcceleoAstResult); |
| } else { |
| return acceleoAstNode; |
| } |
| } |
| |
| /** |
| * Provides the equivalent {@link EObject AQL AST element} of an {@link EObject AQL AST element} found in |
| * the given {@link AcceleoAstResult Acceleo AST}. |
| * |
| * @param aqlAstElement |
| * the (non-{@code null}) {@link EObject AQL AST element}. |
| * @param inAcceleoAstResult |
| * the (non-{@code null}) {@link AcceleoAstResult Acceleo AST} to search in. |
| * @return {@code aqlAstElement} or its equivalent in {@code inAcceleoAstResult}. |
| * @param <T> |
| * the specific type of {@link EObject AQL AST element}. |
| */ |
| public static <T extends EObject> T getSelfOrEquivalentOf(T aqlAstElement, |
| AcceleoAstResult inAcceleoAstResult) { |
| // Step 1: navigate from the AQL AST element up to the container Acceleo AST element. |
| ASTNode acceleoContainerOfAqlAstElement = getContainerOfAqlAstElement(aqlAstElement); |
| |
| // Step 2: find the equivalent of the container Acceleo AST element. |
| ASTNode equivalentOfAcceleoContainerOfAqlAstElement = getSelfOrEquivalentOf( |
| acceleoContainerOfAqlAstElement, inAcceleoAstResult); |
| |
| // Step 3: find AQL AST in the container equivalent. |
| AstResult aqlAstToSearchIn = getContainedAqlAstResultOf(equivalentOfAcceleoContainerOfAqlAstElement); |
| |
| // Step 4: finally, search for the AQL AST element in the AQL AST contained by our original Acceleo |
| // AST. |
| return getSelfOrEquivalentOf(aqlAstElement, aqlAstToSearchIn); |
| } |
| |
| /** |
| * Provides the equivalent {@link EObject AQL AST element} of an {@link EObject AQL AST element} found in |
| * the given {@link AstResult AQL AST}. |
| * |
| * @param <T> |
| * the specific type of {@link EObject AQL AST element}. |
| * @param aqlAstElement |
| * the (non-{@code null}) {@link T AQL AST element}. |
| * @param inAqlAstResult |
| * the (non-{@code null}) {@link AstResult AQL AST} to search in. |
| * @return {@code aqlAstElement} or its equivalent in {@code inAqlAstResult}. |
| */ |
| public static <T extends EObject> T getSelfOrEquivalentOf(T aqlAstElement, AstResult inAqlAstResult) { |
| org.eclipse.acceleo.query.ast.Expression aqlRootElement = inAqlAstResult.getAst(); |
| if (isEqualStructurally(aqlAstElement, aqlRootElement)) { |
| return (T)aqlRootElement; |
| } else { |
| Iterator<EObject> candidatesIterator = aqlRootElement.eAllContents(); |
| while (candidatesIterator.hasNext()) { |
| EObject candidate = candidatesIterator.next(); |
| if (isEqualStructurally(candidate, aqlAstElement)) { |
| return (T)candidate; |
| } |
| } |
| } |
| throw new IllegalArgumentException("Equivalent of AQL ASTNode \"" + aqlAstElement.toString() |
| + "\" could not be found in AQL AST: " + inAqlAstResult); |
| } |
| |
| /** |
| * Provides the {@link ASTNode} in the given {@link AcceleoAstResult} that is structurally equal to the |
| * given {@link ASTNode}. |
| * |
| * @param astNode |
| * the (non-{@code null}) {@link ASTNode} whose equivalent we are looking for. |
| * @param inAcceleoAstResult |
| * the (non-{@code null}) {@link AcceleoAstResult} in which to search. |
| * @return the {@link ASTNode} of {@code inAcceleoAstResult} that is structurally equal to |
| * {@code astNode}. If there is none, an exception is thrown. |
| * @param <T> |
| * the specific type of {@link ASTNode}. |
| */ |
| private static <T extends ASTNode> T getStructuralEqualOf(T astNode, |
| AcceleoAstResult inAcceleoAstResult) { |
| Module inAcceleoModule = inAcceleoAstResult.getModule(); |
| if (isEqualStructurally(astNode, inAcceleoModule)) { |
| return (T)inAcceleoModule; |
| } else { |
| Iterator<EObject> candidatesIterator = inAcceleoModule.eAllContents(); |
| while (candidatesIterator.hasNext()) { |
| EObject candidate = candidatesIterator.next(); |
| if (candidate instanceof ASTNode) { |
| ASTNode candidateAstNode = (ASTNode)candidate; |
| if (isEqualStructurally(candidateAstNode, astNode)) { |
| return (T)candidateAstNode; |
| } |
| } |
| } |
| } |
| throw new IllegalArgumentException("Equivalent of Acceleo ASTNode \"" + astNode.toString() |
| + "\" could not be found in Acceleo AST: " + inAcceleoAstResult); |
| } |
| |
| /** |
| * Customized {@link EqualityHelper} necessary because the default behavior does not go well with the Java |
| * objects ASTResult and AcceleoAstResult we use via EDataTypes. |
| * |
| * @param left |
| * an {@link ASTNode}. |
| * @param right |
| * an {@link ASTNode}. |
| * @return {@code true} if {@code left} and {@code right} are structurally equal. |
| * @see EqualityHelper#equals(EObject, EObject) |
| */ |
| public static boolean isEqualStructurally(ASTNode left, ASTNode right) { |
| return new EqualityHelper() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected boolean haveEqualAttribute(EObject eObject1, EObject eObject2, |
| org.eclipse.emf.ecore.EAttribute attribute) { |
| boolean result; |
| if (attribute.getEAttributeType().equals(AcceleoPackage.eINSTANCE.getASTResult())) { |
| AstResult value1 = (AstResult)eObject1.eGet(attribute); |
| AstResult value2 = (AstResult)eObject2.eGet(attribute); |
| result = this.equals(value1.getAst(), value2.getAst()); |
| } else if (attribute.getEAttributeType().equals(AcceleoPackage.eINSTANCE |
| .getAcceleoAstResult())) { |
| AcceleoAstResult value1 = (AcceleoAstResult)eObject1.eGet(attribute); |
| AcceleoAstResult value2 = (AcceleoAstResult)eObject2.eGet(attribute); |
| result = this.equals(value1.getModule(), value2.getModule()); |
| } else { |
| result = super.haveEqualAttribute(eObject1, eObject2, attribute); |
| } |
| return result; |
| }; |
| }.equals(left, right); |
| } |
| |
| /** |
| * Customized {@link EqualityHelper} for AQL AST elements. |
| * |
| * @param left |
| * an {@link EObject AQL AST element}. |
| * @param right |
| * an {@link EObject AQL AST element}. |
| * @return {@code true} if {@code left} and {@code right} are structurally equal. |
| * @see EqualityHelper#equals(EObject, EObject) |
| */ |
| public static boolean isEqualStructurally(EObject left, EObject right) { |
| return new EqualityHelper() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected boolean haveEqualAttribute(EObject eObject1, EObject eObject2, |
| org.eclipse.emf.ecore.EAttribute attribute) { |
| return super.haveEqualAttribute(eObject1, eObject2, attribute); |
| }; |
| }.equals(left, right); |
| } |
| |
| } |