diff --git a/src/main/java/spoon/support/compiler/jdt/ContextBuilder.java b/src/main/java/spoon/support/compiler/jdt/ContextBuilder.java index 39363923a4e..329404e8070 100644 --- a/src/main/java/spoon/support/compiler/jdt/ContextBuilder.java +++ b/src/main/java/spoon/support/compiler/jdt/ContextBuilder.java @@ -62,7 +62,12 @@ public class ContextBuilder { Deque annotationValueName = new ArrayDeque<>(); - List> casts = new ArrayList<>(CASTS_CONTAINER_DEFAULT_CAPACITY); + public static class CastInfo { + int nrOfBrackets; + CtTypeReference typeRef; + } + + List casts = new ArrayList<>(CASTS_CONTAINER_DEFAULT_CAPACITY); CompilationUnitDeclaration compilationunitdeclaration; @@ -72,6 +77,8 @@ public class ContextBuilder { boolean isBuildLambda = false; + boolean isBuildTypeCast = false; + boolean ignoreComputeImports = false; /** @@ -99,7 +106,7 @@ void enter(CtElement e, ASTNode node) { if (current instanceof CtExpression) { while (!casts.isEmpty()) { - ((CtExpression) current).addTypeCast(casts.remove(0)); + ((CtExpression) current).addTypeCast(casts.remove(0).typeRef); } } if (current instanceof CtStatement && !this.label.isEmpty()) { diff --git a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java index d22a89d8f5f..785e7b25674 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java @@ -152,6 +152,7 @@ import spoon.reflect.reference.CtReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.reference.CtUnboundVariableReference; +import spoon.support.compiler.jdt.ContextBuilder.CastInfo; import spoon.support.reflect.CtExtendedModifier; import java.util.HashSet; @@ -935,7 +936,11 @@ public boolean visit(BreakStatement breakStatement, BlockScope scope) { @Override public boolean visit(CastExpression castExpression, BlockScope scope) { - context.casts.add(this.references.buildTypeReference(castExpression.type, scope)); + CastInfo ci = new CastInfo(); + //the 8 bits from 21 to 28 represents number of enclosing brackets + ci.nrOfBrackets = ((castExpression.bits >>> 21) & 0xF); + ci.typeRef = this.references.buildTypeReference(castExpression.type, scope, true); + context.casts.add(ci); castExpression.expression.traverse(this, scope); return false; } diff --git a/src/main/java/spoon/support/compiler/jdt/PositionBuilder.java b/src/main/java/spoon/support/compiler/jdt/PositionBuilder.java index 7eb42843d71..573f0cf6246 100644 --- a/src/main/java/spoon/support/compiler/jdt/PositionBuilder.java +++ b/src/main/java/spoon/support/compiler/jdt/PositionBuilder.java @@ -34,6 +34,7 @@ import org.eclipse.jdt.internal.compiler.ast.TypeReference; import spoon.SpoonException; import spoon.reflect.code.CtCatchVariable; +import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtStatementList; import spoon.reflect.cu.CompilationUnit; import spoon.reflect.cu.SourcePosition; @@ -42,9 +43,12 @@ import spoon.reflect.declaration.CtModifiable; import spoon.reflect.declaration.CtPackage; import spoon.reflect.factory.CoreFactory; +import spoon.reflect.reference.CtTypeReference; +import spoon.support.compiler.jdt.ContextBuilder.CastInfo; import spoon.support.reflect.CtExtendedModifier; import java.util.Iterator; +import java.util.List; import java.util.Set; import static spoon.support.compiler.jdt.JDTTreeBuilderQuery.getModifiers; @@ -73,7 +77,6 @@ SourcePosition buildPositionCtElement(CtElement e, ASTNode node) { int[] lineSeparatorPositions = cr.lineSeparatorPositions; char[] contents = cr.compilationUnit.getContents(); - int sourceStart = node.sourceStart; int sourceEnd = node.sourceEnd; if ((node instanceof Annotation)) { @@ -90,6 +93,55 @@ SourcePosition buildPositionCtElement(CtElement e, ASTNode node) { if (statementEnd > 0) { sourceEnd = statementEnd; } + + if (this.jdtTreeBuilder.getContextBuilder().isBuildTypeCast && e instanceof CtTypeReference) { + //the type cast reference must be enclosed with brackets + int declarationSourceStart = sourceStart; + int declarationSourceEnd = sourceEnd; + declarationSourceStart = findPrevNonWhitespace(contents, 0, declarationSourceStart - 1); + if (contents[declarationSourceStart] != '(') { + throw new SpoonException("Unexpected character \'" + contents[declarationSourceStart] + "\' at start of cast expression on offset: " + declarationSourceStart); + } + declarationSourceEnd = findNextNonWhitespace(contents, contents.length, declarationSourceEnd + 1); + if (contents[declarationSourceEnd] != ')') { + throw new SpoonException("Unexpected character \'" + contents[declarationSourceStart] + "\' at end of cast expression on offset: " + declarationSourceEnd); + } + return cf.createCompoundSourcePosition(cu, + sourceStart, sourceEnd, + declarationSourceStart, declarationSourceEnd, + lineSeparatorPositions); + } + + List casts = this.jdtTreeBuilder.getContextBuilder().casts; + + if (casts.size() > 0 && e instanceof CtExpression) { + int declarationSourceStart = sourceStart; + int declarationSourceEnd = sourceEnd; + SourcePosition pos = casts.get(0).typeRef.getPosition(); + if (pos.isValidPosition()) { + declarationSourceStart = pos.getSourceStart(); + int nrOfBrackets = getNrOfFirstCastExpressionBrackets(); + while (nrOfBrackets > 0) { + declarationSourceStart = findPrevNonWhitespace(contents, 0, declarationSourceStart - 1); + if (contents[declarationSourceStart] != '(') { + throw new SpoonException("Unexpected character \'" + contents[declarationSourceStart] + "\' at start of expression on offset: " + declarationSourceStart); + } + nrOfBrackets--; + } + nrOfBrackets = getNrOfCastExpressionBrackets(); + while (nrOfBrackets > 0) { + declarationSourceEnd = findNextNonWhitespace(contents, contents.length, declarationSourceEnd + 1); + if (contents[declarationSourceEnd] != ')') { + throw new SpoonException("Unexpected character \'" + contents[declarationSourceStart] + "\' at end of expression on offset: " + declarationSourceEnd); + } + nrOfBrackets--; + } + } + return cf.createCompoundSourcePosition(cu, + sourceStart, sourceEnd, + declarationSourceStart, declarationSourceEnd, + lineSeparatorPositions); + } } if (node instanceof TypeParameter) { @@ -285,6 +337,17 @@ SourcePosition buildPositionCtElement(CtElement e, ASTNode node) { return cf.createSourcePosition(cu, sourceStart, sourceEnd, lineSeparatorPositions); } + private int getNrOfFirstCastExpressionBrackets() { + return this.jdtTreeBuilder.getContextBuilder().casts.get(0).nrOfBrackets; + } + + private int getNrOfCastExpressionBrackets() { + int nr = 0; + for (CastInfo castInfo : this.jdtTreeBuilder.getContextBuilder().casts) { + nr += castInfo.nrOfBrackets; + } + return nr; + } private void setModifiersPosition(CtModifiable e, int start, int end) { CoreFactory cf = this.jdtTreeBuilder.getFactory().Core(); diff --git a/src/main/java/spoon/support/compiler/jdt/ReferenceBuilder.java b/src/main/java/spoon/support/compiler/jdt/ReferenceBuilder.java index b1641afa723..7158a33832a 100644 --- a/src/main/java/spoon/support/compiler/jdt/ReferenceBuilder.java +++ b/src/main/java/spoon/support/compiler/jdt/ReferenceBuilder.java @@ -131,11 +131,14 @@ private CtTypeReference getBoundedTypeReference(TypeBinding binding) { * @return a type reference. */ CtTypeReference buildTypeReference(TypeReference type, Scope scope) { + return buildTypeReference(type, scope, false); + } + CtTypeReference buildTypeReference(TypeReference type, Scope scope, boolean isTypeCast) { if (type == null) { return null; } CtTypeReference typeReference = this.getTypeReference(type.resolvedType, type); - return buildTypeReferenceInternal(typeReference, type, scope); + return buildTypeReferenceInternal(typeReference, type, scope, isTypeCast); } /** @@ -173,11 +176,11 @@ private CtTypeParameterReference buildTypeParameterReference(TypeReference type, if (type == null) { return null; } - return (CtTypeParameterReference) this.buildTypeReferenceInternal(this.getTypeParameterReference(type.resolvedType, type), type, scope); + return (CtTypeParameterReference) this.buildTypeReferenceInternal(this.getTypeParameterReference(type.resolvedType, type), type, scope, false); } - private CtTypeReference buildTypeReferenceInternal(CtTypeReference typeReference, TypeReference type, Scope scope) { + private CtTypeReference buildTypeReferenceInternal(CtTypeReference typeReference, TypeReference type, Scope scope, boolean isTypeCast) { if (type == null) { return null; } @@ -187,7 +190,9 @@ private CtTypeReference buildTypeReferenceInternal(CtTypeReference typ if (currentReference == null) { break; } + this.jdtTreeBuilder.getContextBuilder().isBuildTypeCast = isTypeCast; this.jdtTreeBuilder.getContextBuilder().enter(currentReference, type); + this.jdtTreeBuilder.getContextBuilder().isBuildTypeCast = false; if (type.annotations != null && type.annotations.length - 1 <= position && type.annotations[position] != null && type.annotations[position].length > 0) { for (Annotation annotation : type.annotations[position]) { if (scope instanceof ClassScope) { diff --git a/src/test/java/spoon/test/position/PositionTest.java b/src/test/java/spoon/test/position/PositionTest.java index 0235d6a75c0..6bef4bb900b 100644 --- a/src/test/java/spoon/test/position/PositionTest.java +++ b/src/test/java/spoon/test/position/PositionTest.java @@ -6,6 +6,7 @@ import spoon.reflect.code.*; import spoon.reflect.cu.SourcePosition; import spoon.reflect.cu.position.BodyHolderSourcePosition; +import spoon.reflect.cu.position.CompoundSourcePosition; import spoon.reflect.cu.position.DeclarationSourcePosition; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtConstructor; @@ -768,4 +769,51 @@ public void testArrayArgParameter() throws Exception { assertEquals("String arg[]", contentAtPosition(classContent, param.getType().getPosition())); } } + + @Test + public void testExpressions() throws Exception { + //contract: the expression including type casts has correct position which includes all brackets too + final CtType foo = ModelUtils.buildClass(Expressions.class); + String classContent = getClassContent(foo); + List> statements = (List) foo.getMethodsByName("method").get(0).getBody().getStatements(); + + int idx = 0; + assertEquals("\"x\"", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition())); + assertEquals("(\"x\")", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition())); + assertEquals("(String)null", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition())); + assertEquals("( String) ( (Serializable)(( (null ))))", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition())); + assertEquals("(((String) null))", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition())); + assertEquals("( /*c2*/\n" + + " (\n" + + " /*c3*/ String\n" + + " /*c4*/) //c5\n" + + " null /*c6*/\n" + + " //c7\n" + + " )", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition())); + assertEquals("(List) null", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition())); + assertEquals("(List>>) null", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition())); + + //contract: check the position of expression without type casts + { + CtExpression expr = statements.get(1).getArguments().get(0); + assertEquals("(\"x\")", contentAtPosition(classContent, expr.getPosition())); + //if there is no expression, then it uses primitive SourcePosition + assertFalse(expr.getPosition() instanceof CompoundSourcePosition); + } + + //contract: check the position of children of the most complex expression + { + CtExpression expr = statements.get(3).getArguments().get(0); + assertEquals("( String) ( (Serializable)(( (null ))))", contentAtPosition(classContent, expr.getPosition())); + //if there is type cast in expression, then it uses CompoundSourcePosition + assertTrue(expr.getPosition() instanceof CompoundSourcePosition); + + //contract: check the position of type casts + assertEquals("( String)", contentAtPosition(classContent, expr.getTypeCasts().get(0).getPosition())); + assertEquals("(Serializable)", contentAtPosition(classContent, expr.getTypeCasts().get(1).getPosition())); + //contract: check the position of expression "name" + CompoundSourcePosition compoundSourcePosition = (CompoundSourcePosition) expr.getPosition(); + assertEquals("(( (null )))", contentAtPosition(classContent, compoundSourcePosition.getNameStart(), compoundSourcePosition.getNameEnd())); + } + } } diff --git a/src/test/java/spoon/test/position/testclasses/Expressions.java b/src/test/java/spoon/test/position/testclasses/Expressions.java new file mode 100644 index 00000000000..ff358feab27 --- /dev/null +++ b/src/test/java/spoon/test/position/testclasses/Expressions.java @@ -0,0 +1,24 @@ +package spoon.test.position.testclasses; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +public class Expressions { + void method() { + System.out.print("x"); + System.out.print(("x")); + System.out.print((String)null); + System.out.print(( String) ( (Serializable)(( (null ))))); + System.out.print((((String) null))); + System.out.print(/*c1*/ ( /*c2*/ + ( + /*c3*/ String + /*c4*/) //c5 + null /*c6*/ + //c7 + ) /*c8*/ ); + System.out.print((List) null); + System.out.print((List>>) null); + }; +} \ No newline at end of file