/*
* Copyright 2009-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.codebrowsing.fragments;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.MethodPointerExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.syntax.Token;
import org.eclipse.core.runtime.Assert;
/**
* Factory class to create an AST Fragment for a given expression.
* Only works on expressions now, but consider expanding to statements in the
* future if required.
*
* @author andrew
* @created Jun 4, 2010
*/
public class ASTFragmentFactory {
public IASTFragment createFragment(ASTNode node) {
if (node instanceof Expression) {
return createFragment((Expression) node, node.getStart(), node.getEnd());
} else {
return new EnclosingASTNodeFragment(node);
}
}
public IASTFragment createFragment(Expression expression) {
return createFragment(expression, expression.getStart(), expression.getEnd());
}
public IASTFragment createFragment(Expression expression, int start, int end) {
Assert.isLegal(expression.getStart() <= start, "Invalid start position: " + start);
Assert.isLegal(expression.getEnd() >= end, "Invalid end position: " + end);
// do some unwrapping
if (expression instanceof BooleanExpression) {
Expression inner = ((BooleanExpression) expression).getExpression();
if (inner.getStart() == expression.getStart() && inner.getEnd() == expression.getEnd()) {
expression = inner;
}
} else if (expression instanceof TupleExpression && ((TupleExpression) expression).getExpressions().size() == 1) {
Expression inner = ((TupleExpression) expression).getExpression(0);
if (inner.getStart() == expression.getStart() && inner.getEnd() == expression.getEnd()) {
expression = inner;
}
}
if (expression instanceof BinaryExpression) {
IASTFragment fragment = createBinaryFragment((BinaryExpression) expression, start, end);
if (expression instanceof DeclarationExpression) {
// we have a declaration, must include the type in the start position
if (fragment.kind() == ASTFragmentKind.BINARY) {
((BinaryExpressionFragment) fragment).setActualStartPosition(((DeclarationExpression) expression).getStart());
} else if (fragment.kind() == ASTFragmentKind.SIMPLE_EXPRESSION) {
// declaration without an assignment
((SimpleExpressionASTFragment) fragment).setActualStartPosition(((DeclarationExpression) expression).getStart());
}
}
return fragment;
} else if (expression instanceof PropertyExpression) {
return createPropertyBasedFragment(expression, start, end);
} else if (expression instanceof MethodCallExpression) {
return createPropertyBasedFragment(expression, start, end);
} else if (expression instanceof MethodPointerExpression) {
return createPropertyBasedFragment(expression, start, end);
} else if (expression.getStart() == start && expression.getEnd() == end) {
return new SimpleExpressionASTFragment(expression);
} else {
return new EmptyASTFragment();
}
}
private IASTFragment createBinaryFragment(BinaryExpression expression, int start, int end) {
// tokens list will be one shorter than the exprs list
List<Expression> exprs = new ArrayList<Expression>();
List<Token> tokens = new ArrayList<Token>();
walkBinaryExpr(expression, exprs, tokens);
Expression firstExpr = exprs.get(exprs.size() - 1);
IASTFragment next = new EmptyASTFragment();
if (firstExpr.getStart() >= start && firstExpr.getEnd() <= end) {
next = new SimpleExpressionASTFragment(firstExpr);
}
for (int i = tokens.size() - 1; i >= 0; i -= 1) {
Expression nextExpr = exprs.get(i);
if (nextExpr.getStart() >= start && nextExpr.getEnd() <= end) {
if (next.kind() == ASTFragmentKind.EMPTY) {
next = new SimpleExpressionASTFragment(nextExpr);
} else {
next = new BinaryExpressionFragment(tokens.get(i), nextExpr, next);
}
}
}
return next;
}
/**
* Flattens a binary expression into components
*/
private void walkBinaryExpr(BinaryExpression expression, List<Expression> exprs, List<Token> tokens) {
if (expression.getLeftExpression() instanceof BinaryExpression) {
walkBinaryExpr((BinaryExpression) expression.getLeftExpression(), exprs, tokens);
} else {
exprs.add(expression.getLeftExpression());
}
tokens.add(expression.getOperation());
if (expression.getRightExpression() instanceof BinaryExpression) {
walkBinaryExpr((BinaryExpression) expression.getRightExpression(), exprs, tokens);
} else {
exprs.add(expression.getRightExpression());
}
}
private IASTFragment createPropertyBasedFragment(Expression expression, int start, int end) {
// kinds list will be same size as exprs list
// args list will contain nulls for items that are not method calls
List<ASTFragmentKind> kinds = new ArrayList<ASTFragmentKind>();
// all of the target expressions
List<Expression> exprs = new ArrayList<Expression>();
// ensure that method calls include their arguments
List<Expression> args = new ArrayList<Expression>();
// ensure that method calls include the closing paren
List<Integer> ends = new ArrayList<Integer>();
walkPropertyExpr(expression, exprs, args, kinds, ends);
// return an empty fragment if there are no complete matches
IASTFragment prev = new EmptyASTFragment();
// closure calls are implicitly converted into method calls with 'call' as the method name
// we need to handle this case specially and ignore this and make the next fragment a method call instead
boolean wasImplicitCall = false;
Expression firstExpr = exprs.get(exprs.size() - 1);
if (firstExpr.getStart() >= start && firstExpr.getEnd() <= end) {
if (kinds.get(kinds.size() - 1) == ASTFragmentKind.METHOD_CALL) {
MethodCallFragment frag = new MethodCallFragment(firstExpr, args.get(exprs.size() - 1), ends.get(exprs.size() - 1));
frag.callExpression = (MethodCallExpression) expression;
prev = frag;
} else {
prev = new SimpleExpressionASTFragment(firstExpr);
}
} else if (checkWasImplicitCall(firstExpr)) {
wasImplicitCall = true;
}
for (int i = exprs.size() - 2; i >= 0; i -= 1) {
Expression expr = exprs.get(i);
if (expr.getStart() >= start && expr.getEnd() <= end) {
if (kinds.get(i) == ASTFragmentKind.METHOD_CALL) {
prev = new MethodCallFragment(expr, args.get(i), prev.kind() == ASTFragmentKind.EMPTY ? null : prev, ends.get(i));
} else {
if (prev.kind() == ASTFragmentKind.EMPTY) {
if (!wasImplicitCall) {
prev = new SimpleExpressionASTFragment(expr);
} else {
prev = new MethodCallFragment(expr, args.get(i + 1), null, ends.get(i));
}
} else {
if (!wasImplicitCall) {
prev = new PropertyExpressionFragment(kinds.get(i), expr, prev);
} else {
prev = new MethodCallFragment(expr, args.get(i + 1), prev, ends.get(i));
}
}
}
wasImplicitCall = false;
} else if (checkWasImplicitCall(expr)) {
wasImplicitCall = true;
}
}
return prev;
}
private boolean checkWasImplicitCall(Expression firstExpr) {
return firstExpr.getEnd() == 0 &&
firstExpr instanceof ConstantExpression &&
((ConstantExpression) firstExpr).getText().equals("call");
}
private void walkPropertyExpr(Expression startExpression, List<Expression> targetExprs, List<Expression> targetArgs,
List<ASTFragmentKind> targetKinds, List<Integer> targetEnds) {
if (startExpression instanceof PropertyExpression) {
PropertyExpression propertyExpression = (PropertyExpression) startExpression;
ASTFragmentKind kind = ASTFragmentKind.toPropertyKind(propertyExpression);
walkPropertyExpr(propertyExpression.getObjectExpression(), targetExprs, targetArgs, targetKinds, targetEnds);
int toRemove = targetKinds.size() - 1;
if (targetKinds.get(toRemove) == ASTFragmentKind.SIMPLE_EXPRESSION) {
// need to replace with a property
targetKinds.set(toRemove, kind);
targetArgs.set(toRemove, null);
}
walkPropertyExpr(propertyExpression.getProperty(), targetExprs, targetArgs, targetKinds, targetEnds);
} else if (startExpression instanceof MethodCallExpression) {
MethodCallExpression methodCallExpression = (MethodCallExpression) startExpression;
walkPropertyExpr(methodCallExpression.getObjectExpression(), targetExprs, targetArgs, targetKinds, targetEnds);
int toRemove = targetKinds.size() - 1;
if (targetKinds.get(toRemove) == ASTFragmentKind.SIMPLE_EXPRESSION) {
// need to replace with a property
targetKinds.set(toRemove, ASTFragmentKind.PROPERTY);
targetArgs.set(toRemove, null);
}
walkPropertyExpr(methodCallExpression.getMethod(), targetExprs, targetArgs, targetKinds, targetEnds);
// replace the last with method call since we need to add arguments here
toRemove = targetKinds.size() - 1;
targetKinds.set(toRemove, ASTFragmentKind.METHOD_CALL);
targetArgs.set(toRemove, methodCallExpression.getArguments());
targetEnds.set(toRemove, methodCallExpression.getEnd());
} else if (startExpression instanceof MethodPointerExpression) {
MethodPointerExpression methodPointerExpression = (MethodPointerExpression) startExpression;
walkPropertyExpr(methodPointerExpression.getExpression(), targetExprs, targetArgs, targetKinds, targetEnds);
int toRemove = targetKinds.size() - 1;
if (targetKinds.get(toRemove) == ASTFragmentKind.SIMPLE_EXPRESSION) {
// need to replace with a method pointer
targetKinds.set(toRemove, ASTFragmentKind.METHOD_POINTER);
targetArgs.set(toRemove, null);
}
walkPropertyExpr(methodPointerExpression.getMethodName(), targetExprs, targetArgs, targetKinds, targetEnds);
} else {
// we should only have this kind of fragment as the last one
targetKinds.add(ASTFragmentKind.SIMPLE_EXPRESSION);
targetArgs.add(null);
targetExprs.add(startExpression);
targetEnds.add(startExpression.getEnd());
}
}
public static String spaces(int num) {
StringBuilder sb = new StringBuilder();
while (num-- > 0) {
sb.append(" ");
}
return sb.toString();
}
}