| /******************************************************************************* |
| * Copyright (c) 2006, 2008 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.jdt.core.dom; |
| |
| import java.util.List; |
| import java.util.Vector; |
| |
| import org.eclipse.jdt.core.compiler.CategorizedProblem; |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; |
| import org.eclipse.jdt.internal.compiler.parser.RecoveryScannerData; |
| import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; |
| import org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToIntArray; |
| |
| /** |
| * Internal AST visitor for propagating syntax errors. |
| */ |
| class ASTRecoveryPropagator extends DefaultASTVisitor { |
| private static final int NOTHING = -1; |
| HashtableOfObjectToIntArray endingTokens = new HashtableOfObjectToIntArray(); |
| { |
| this.endingTokens.put(AnonymousClassDeclaration.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
| this.endingTokens.put(ArrayAccess.class, new int[]{TerminalTokens.TokenNameRBRACKET}); |
| this.endingTokens.put(ArrayCreation.class, new int[]{NOTHING, TerminalTokens.TokenNameRBRACKET}); |
| this.endingTokens.put(ArrayInitializer.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
| this.endingTokens.put(ArrayType.class, new int[]{TerminalTokens.TokenNameRBRACKET}); |
| this.endingTokens.put(AssertStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(Block.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
| this.endingTokens.put(BooleanLiteral.class, new int[]{TerminalTokens.TokenNamefalse, TerminalTokens.TokenNametrue}); |
| this.endingTokens.put(BreakStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(CharacterLiteral.class, new int[]{TerminalTokens.TokenNameCharacterLiteral}); |
| this.endingTokens.put(ClassInstanceCreation.class, new int[]{TerminalTokens.TokenNameRBRACE, TerminalTokens.TokenNameRPAREN}); |
| this.endingTokens.put(ConstructorInvocation.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(ContinueStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(DoStatement.class, new int[]{TerminalTokens.TokenNameRPAREN}); |
| this.endingTokens.put(EmptyStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(ExpressionStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(FieldDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(ImportDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(Initializer.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
| this.endingTokens.put(MethodDeclaration.class, new int[]{NOTHING, TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(MethodInvocation.class, new int[]{TerminalTokens.TokenNameRPAREN}); |
| this.endingTokens.put(NullLiteral.class, new int[]{TerminalTokens.TokenNamenull}); |
| this.endingTokens.put(NumberLiteral.class, new int[]{TerminalTokens.TokenNameIntegerLiteral, TerminalTokens.TokenNameLongLiteral, TerminalTokens.TokenNameFloatingPointLiteral, TerminalTokens.TokenNameDoubleLiteral}); |
| this.endingTokens.put(PackageDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(ParenthesizedExpression.class, new int[]{TerminalTokens.TokenNameRPAREN}); |
| this.endingTokens.put(PostfixExpression.class, new int[]{TerminalTokens.TokenNamePLUS_PLUS, TerminalTokens.TokenNameMINUS_MINUS}); |
| this.endingTokens.put(PrimitiveType.class, new int[]{TerminalTokens.TokenNamebyte, TerminalTokens.TokenNameshort, TerminalTokens.TokenNamechar, TerminalTokens.TokenNameint, TerminalTokens.TokenNamelong, TerminalTokens.TokenNamefloat, TerminalTokens.TokenNameboolean, TerminalTokens.TokenNamedouble, TerminalTokens.TokenNamevoid}); |
| this.endingTokens.put(ReturnStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(SimpleName.class, new int[]{TerminalTokens.TokenNameIdentifier}); |
| this.endingTokens.put(SingleVariableDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(StringLiteral.class, new int[]{TerminalTokens.TokenNameStringLiteral}); |
| this.endingTokens.put(SuperConstructorInvocation.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(SuperMethodInvocation.class, new int[]{TerminalTokens.TokenNameRPAREN}); |
| this.endingTokens.put(SwitchCase.class, new int[]{TerminalTokens.TokenNameCOLON}); |
| this.endingTokens.put(SwitchStatement.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
| this.endingTokens.put(SynchronizedStatement.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
| this.endingTokens.put(ThisExpression.class, new int[]{TerminalTokens.TokenNamethis}); |
| this.endingTokens.put(ThrowStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| this.endingTokens.put(TypeDeclaration.class, new int[]{TerminalTokens.TokenNameRBRACE}); |
| this.endingTokens.put(TypeLiteral.class, new int[]{TerminalTokens.TokenNameclass}); |
| this.endingTokens.put(VariableDeclarationStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON}); |
| } |
| |
| private CategorizedProblem[] problems; |
| private boolean[] usedOrIrrelevantProblems; |
| |
| private RecoveryScannerData data; |
| private int blockDepth = 0; |
| private int lastEnd; |
| |
| private int[] insertedTokensKind; |
| private int[] insertedTokensPosition; |
| private boolean[] insertedTokensFlagged; |
| |
| private boolean[] removedTokensFlagged; |
| private boolean[] replacedTokensFlagged; |
| |
| private Vector stack = new Vector(); |
| |
| /** |
| * @noreference This method is not intended to be referenced by clients. |
| */ |
| ASTRecoveryPropagator(CategorizedProblem[] problems, RecoveryScannerData data) { |
| // visit Javadoc.tags() as well |
| this.problems = problems; |
| this.usedOrIrrelevantProblems = new boolean[problems.length]; |
| |
| this.data = data; |
| |
| if(this.data != null) { |
| |
| int length = 0; |
| for (int i = 0; i < data.insertedTokensPtr + 1; i++) { |
| length += data.insertedTokens[i].length; |
| } |
| this.insertedTokensKind = new int[length]; |
| this.insertedTokensPosition = new int[length]; |
| this.insertedTokensFlagged = new boolean[length]; |
| int tokenCount = 0; |
| for (int i = 0; i < data.insertedTokensPtr + 1; i++) { |
| for (int j = 0; j < data.insertedTokens[i].length; j++) { |
| this.insertedTokensKind[tokenCount] = data.insertedTokens[i][j]; |
| this.insertedTokensPosition[tokenCount] = data.insertedTokensPosition[i]; |
| tokenCount++; |
| } |
| } |
| |
| if(data.removedTokensPtr != -1) { |
| this.removedTokensFlagged = new boolean[data.removedTokensPtr + 1]; |
| } |
| if(data.replacedTokensPtr != -1) { |
| this.replacedTokensFlagged = new boolean[data.replacedTokensPtr + 1]; |
| } |
| } |
| } |
| |
| public void endVisit(Block node) { |
| this.blockDepth--; |
| if(this.blockDepth <= 0) { |
| flagNodeWithInsertedTokens(); |
| } |
| super.endVisit(node); |
| } |
| |
| |
| |
| public boolean visit(Block node) { |
| boolean visitChildren = super.visit(node); |
| this.blockDepth++; |
| return visitChildren; |
| } |
| |
| protected boolean visitNode(ASTNode node) { |
| if(this.blockDepth > 0) { |
| int start = node.getStartPosition(); |
| int end = start + node.getLength() - 1; |
| |
| // continue to visit the node only if it contains tokens modifications |
| |
| if(this.insertedTokensFlagged != null) { |
| for (int i = 0; i < this.insertedTokensFlagged.length; i++) { |
| if(this.insertedTokensPosition[i] >= start && |
| this.insertedTokensPosition[i] <= end) { |
| return true; |
| } |
| } |
| } |
| |
| if(this.removedTokensFlagged != null) { |
| for (int i = 0; i <= this.data.removedTokensPtr; i++) { |
| if(this.data.removedTokensStart[i] >= start && |
| this.data.removedTokensEnd[i] <= end) { |
| return true; |
| } |
| } |
| } |
| |
| if(this.replacedTokensFlagged != null) { |
| for (int i = 0; i <= this.data.replacedTokensPtr; i++) { |
| if(this.data.replacedTokensStart[i] >= start && |
| this.data.replacedTokensEnd[i] <= end) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| return true; |
| } |
| |
| protected void endVisitNode(ASTNode node) { |
| int start = node.getStartPosition(); |
| int end = start + node.getLength() - 1; |
| |
| // is inside diet part of the ast |
| if(this.blockDepth < 1) { |
| switch (node.getNodeType()) { |
| case ASTNode.ANNOTATION_TYPE_DECLARATION: |
| case ASTNode.COMPILATION_UNIT: |
| case ASTNode.ENUM_DECLARATION: |
| case ASTNode.FIELD_DECLARATION: |
| case ASTNode.IMPORT_DECLARATION: |
| case ASTNode.INITIALIZER: |
| case ASTNode.METHOD_DECLARATION: |
| case ASTNode.PACKAGE_DECLARATION: |
| case ASTNode.TYPE_DECLARATION: |
| case ASTNode.MARKER_ANNOTATION: |
| case ASTNode.NORMAL_ANNOTATION: |
| case ASTNode.SINGLE_MEMBER_ANNOTATION: |
| case ASTNode.BLOCK: |
| if(markIncludedProblems(start, end)) { |
| node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
| } |
| break; |
| } |
| } else { |
| markIncludedProblems(start, end); |
| |
| if(this.insertedTokensFlagged != null) { |
| if(this.lastEnd != end) { |
| flagNodeWithInsertedTokens(); |
| } |
| this.stack.add(node); |
| } |
| |
| if(this.removedTokensFlagged != null) { |
| for (int i = 0; i <= this.data.removedTokensPtr; i++) { |
| if(!this.removedTokensFlagged[i] && |
| this.data.removedTokensStart[i] >= start && |
| this.data.removedTokensEnd[i] <= end) { |
| node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
| this.removedTokensFlagged[i] = true; |
| } |
| } |
| } |
| |
| if(this.replacedTokensFlagged != null) { |
| for (int i = 0; i <= this.data.replacedTokensPtr; i++) { |
| if(!this.replacedTokensFlagged[i] && |
| this.data.replacedTokensStart[i] >= start && |
| this.data.replacedTokensEnd[i] <= end) { |
| node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
| this.replacedTokensFlagged[i] = true; |
| } |
| } |
| } |
| } |
| this.lastEnd = end; |
| } |
| |
| private void flagNodeWithInsertedTokens() { |
| if(this.insertedTokensKind != null && this.insertedTokensKind.length > 0) { |
| int s = this.stack.size(); |
| for (int i = s - 1; i > -1; i--) { |
| flagNodesWithInsertedTokensAtEnd((ASTNode)this.stack.get(i)); |
| } |
| for (int i = 0; i < s; i++) { |
| flagNodesWithInsertedTokensInside((ASTNode)this.stack.get(i)); |
| } |
| this.stack = new Vector(); |
| } |
| } |
| |
| private boolean flagNodesWithInsertedTokensAtEnd(ASTNode node) { |
| int[] expectedEndingToken = this.endingTokens.get(node.getClass()); |
| if (expectedEndingToken != null) { |
| int start = node.getStartPosition(); |
| int end = start + node.getLength() - 1; |
| |
| boolean flagParent = false; |
| done : for (int i = this.insertedTokensKind.length - 1; i > -1 ; i--) { |
| if(!this.insertedTokensFlagged[i] && |
| this.insertedTokensPosition[i] == end){ |
| this.insertedTokensFlagged[i] = true; |
| for (int j = 0; j < expectedEndingToken.length; j++) { |
| if(expectedEndingToken[j] == this.insertedTokensKind[i]) { |
| node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
| break done; |
| } |
| } |
| flagParent = true; |
| } |
| } |
| |
| if(flagParent) { |
| ASTNode parent = node.getParent(); |
| while (parent != null) { |
| parent.setFlags(node.getFlags() | ASTNode.RECOVERED); |
| if((parent.getStartPosition() + parent.getLength() - 1) != end) { |
| parent = null; |
| } else { |
| parent = parent.getParent(); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| private boolean flagNodesWithInsertedTokensInside(ASTNode node) { |
| int start = node.getStartPosition(); |
| int end = start + node.getLength() - 1; |
| for (int i = 0; i < this.insertedTokensKind.length; i++) { |
| if(!this.insertedTokensFlagged[i] && |
| start <= this.insertedTokensPosition[i] && |
| this.insertedTokensPosition[i] < end){ |
| node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
| this.insertedTokensFlagged[i] = true; |
| } |
| } |
| return true; |
| } |
| |
| private boolean markIncludedProblems(int start, int end) { |
| boolean foundProblems = false; |
| next: for (int i = 0, max = this.problems.length; i < max; i++) { |
| CategorizedProblem problem = this.problems[i]; |
| |
| if(this.usedOrIrrelevantProblems[i]) continue next; |
| |
| switch(problem.getID()) { |
| case IProblem.ParsingErrorOnKeywordNoSuggestion : |
| case IProblem.ParsingErrorOnKeyword : |
| case IProblem.ParsingError : |
| case IProblem.ParsingErrorNoSuggestion : |
| case IProblem.ParsingErrorInsertTokenBefore : |
| case IProblem.ParsingErrorInsertTokenAfter : |
| case IProblem.ParsingErrorDeleteToken : |
| case IProblem.ParsingErrorDeleteTokens : |
| case IProblem.ParsingErrorMergeTokens : |
| case IProblem.ParsingErrorInvalidToken : |
| case IProblem.ParsingErrorMisplacedConstruct : |
| case IProblem.ParsingErrorReplaceTokens : |
| case IProblem.ParsingErrorNoSuggestionForTokens : |
| case IProblem.ParsingErrorUnexpectedEOF : |
| case IProblem.ParsingErrorInsertToComplete : |
| case IProblem.ParsingErrorInsertToCompleteScope : |
| case IProblem.ParsingErrorInsertToCompletePhrase : |
| case IProblem.EndOfSource : |
| case IProblem.InvalidHexa : |
| case IProblem.InvalidOctal : |
| case IProblem.InvalidCharacterConstant : |
| case IProblem.InvalidEscape : |
| case IProblem.InvalidInput : |
| case IProblem.InvalidUnicodeEscape : |
| case IProblem.InvalidFloat : |
| case IProblem.NullSourceString : |
| case IProblem.UnterminatedString : |
| case IProblem.UnterminatedComment : |
| case IProblem.InvalidDigit : |
| break; |
| default: |
| this.usedOrIrrelevantProblems[i] = true; |
| continue next; |
| |
| } |
| |
| int problemStart = problem.getSourceStart(); |
| int problemEnd = problem.getSourceEnd(); |
| if ((start <= problemStart) && (problemStart <= end) || |
| (start <= problemEnd) && (problemEnd <= end)) { |
| this.usedOrIrrelevantProblems[i] = true; |
| foundProblems = true; |
| } |
| } |
| return foundProblems; |
| } |
| |
| public void endVisit(ExpressionStatement node) { |
| endVisitNode(node); |
| if ((node.getFlags() & ASTNode.RECOVERED) == 0) return; |
| Expression expression = node.getExpression(); |
| if (expression.getNodeType() == ASTNode.ASSIGNMENT) { |
| Assignment assignment = (Assignment) expression; |
| Expression rightHandSide = assignment.getRightHandSide(); |
| if (rightHandSide.getNodeType() == ASTNode.SIMPLE_NAME) { |
| SimpleName simpleName = (SimpleName) rightHandSide; |
| if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) { |
| Expression expression2 = assignment.getLeftHandSide(); |
| // unparent the expression to add it in the expression stateemnt |
| expression2.setParent(null, null); |
| expression2.setFlags(expression2.getFlags() | ASTNode.RECOVERED); |
| node.setExpression(expression2); |
| } |
| } |
| } |
| } |
| |
| public void endVisit(ForStatement node) { |
| endVisitNode(node); |
| List initializers = node.initializers(); |
| if (initializers.size() == 1) { |
| Expression expression = (Expression) initializers.get(0); |
| if (expression.getNodeType() == ASTNode.VARIABLE_DECLARATION_EXPRESSION) { |
| VariableDeclarationExpression variableDeclarationExpression = (VariableDeclarationExpression) expression; |
| List fragments = variableDeclarationExpression.fragments(); |
| for (int i = 0, max = fragments.size(); i <max; i++) { |
| VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragments.get(i); |
| SimpleName simpleName = fragment.getName(); |
| if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) { |
| fragments.remove(fragment); |
| variableDeclarationExpression.setFlags(variableDeclarationExpression.getFlags() | ASTNode.RECOVERED); |
| } |
| } |
| } |
| } |
| } |
| |
| public void endVisit(VariableDeclarationStatement node) { |
| endVisitNode(node); |
| List fragments = node.fragments(); |
| for (int i = 0, max = fragments.size(); i <max; i++) { |
| VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragments.get(i); |
| Expression expression = fragment.getInitializer(); |
| if (expression == null) continue; |
| if ((expression.getFlags() & ASTNode.RECOVERED) == 0) continue; |
| if (expression.getNodeType() == ASTNode.SIMPLE_NAME) { |
| SimpleName simpleName = (SimpleName) expression; |
| if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) { |
| fragment.setInitializer(null); |
| fragment.setFlags(fragment.getFlags() | ASTNode.RECOVERED); |
| } |
| } |
| } |
| } |
| |
| public void endVisit(NormalAnnotation node) { |
| endVisitNode(node); |
| // is inside diet part of the ast |
| if(this.blockDepth < 1) { |
| List values = node.values(); |
| int size = values.size(); |
| if (size > 0) { |
| MemberValuePair lastMemberValuePair = (MemberValuePair)values.get(size - 1); |
| |
| int annotationEnd = node.getStartPosition() + node.getLength(); |
| int lastMemberValuePairEnd = lastMemberValuePair.getStartPosition() + lastMemberValuePair.getLength(); |
| if (annotationEnd == lastMemberValuePairEnd) { |
| node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
| } |
| } |
| } |
| } |
| |
| public void endVisit(SingleMemberAnnotation node) { |
| endVisitNode(node); |
| // is inside diet part of the ast |
| if(this.blockDepth < 1) { |
| Expression value = node.getValue(); |
| int annotationEnd = node.getStartPosition() + node.getLength(); |
| int valueEnd = value.getStartPosition() + value.getLength(); |
| if (annotationEnd == valueEnd) { |
| node.setFlags(node.getFlags() | ASTNode.RECOVERED); |
| } |
| } |
| } |
| } |