/******************************************************************************* * Copyright (c) 2006, 2014 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. */ @SuppressWarnings({"rawtypes", "unchecked"}) 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(); 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); } } } }