1 /*******************************************************************************
 
   2  * Copyright (c) 2006, 2008 IBM Corporation and others.
 
   3  * All rights reserved. This program and the accompanying materials
 
   4  * are made available under the terms of the Eclipse Public License v1.0
 
   5  * which accompanies this distribution, and is available at
 
   6  * http://www.eclipse.org/legal/epl-v10.html
 
   9  *     IBM Corporation - initial API and implementation
 
  10  *******************************************************************************/
 
  12 package net.sourceforge.phpdt.core.dom;
 
  14 import java.util.List;
 
  15 import java.util.Vector;
 
  17 import org.eclipse.jdt.core.compiler.CategorizedProblem;
 
  18 import net.sourceforge.phpdt.core.compiler.CharOperation;
 
  19 import net.sourceforge.phpdt.core.compiler.IProblem;
 
  20 import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner;
 
  21 import org.eclipse.jdt.internal.compiler.parser.RecoveryScannerData;
 
  22 import net.sourceforge.phpdt.internal.compiler.parser.TerminalTokens;
 
  23 import org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToIntArray;
 
  26  * Internal AST visitor for propagating syntax errors.
 
  28 class ASTRecoveryPropagator extends DefaultASTVisitor {
 
  29         private static final int NOTHING = -1;
 
  30         HashtableOfObjectToIntArray endingTokens = new HashtableOfObjectToIntArray();
 
  32                 this.endingTokens.put(AnonymousClassDeclaration.class, new int[]{TerminalTokens.TokenNameRBRACE});
 
  33                 this.endingTokens.put(ArrayAccess.class, new int[]{TerminalTokens.TokenNameRBRACKET});
 
  34                 this.endingTokens.put(ArrayCreation.class, new int[]{NOTHING, TerminalTokens.TokenNameRBRACKET});
 
  35                 this.endingTokens.put(ArrayInitializer.class, new int[]{TerminalTokens.TokenNameRBRACE});
 
  36                 this.endingTokens.put(ArrayType.class, new int[]{TerminalTokens.TokenNameRBRACKET});
 
  37                 this.endingTokens.put(AssertStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  38                 this.endingTokens.put(Block.class, new int[]{TerminalTokens.TokenNameRBRACE});
 
  39                 this.endingTokens.put(BooleanLiteral.class, new int[]{TerminalTokens.TokenNamefalse, TerminalTokens.TokenNametrue});
 
  40                 this.endingTokens.put(BreakStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  41                 this.endingTokens.put(CharacterLiteral.class, new int[]{TerminalTokens.TokenNameCharacterLiteral});
 
  42                 this.endingTokens.put(ClassInstanceCreation.class, new int[]{TerminalTokens.TokenNameRBRACE, TerminalTokens.TokenNameRPAREN});
 
  43                 this.endingTokens.put(ConstructorInvocation.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  44                 this.endingTokens.put(ContinueStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  45                 this.endingTokens.put(DoStatement.class, new int[]{TerminalTokens.TokenNameRPAREN});
 
  46                 this.endingTokens.put(EmptyStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  47                 this.endingTokens.put(ExpressionStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  48                 this.endingTokens.put(FieldDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  49                 this.endingTokens.put(ImportDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  50                 this.endingTokens.put(Initializer.class, new int[]{TerminalTokens.TokenNameRBRACE});
 
  51                 this.endingTokens.put(MethodDeclaration.class, new int[]{NOTHING, TerminalTokens.TokenNameSEMICOLON});
 
  52                 this.endingTokens.put(MethodInvocation.class, new int[]{TerminalTokens.TokenNameRPAREN});
 
  53                 this.endingTokens.put(NullLiteral.class, new int[]{TerminalTokens.TokenNamenull});
 
  54                 this.endingTokens.put(NumberLiteral.class, new int[]{TerminalTokens.TokenNameIntegerLiteral, TerminalTokens.TokenNameLongLiteral, TerminalTokens.TokenNameFloatingPointLiteral, TerminalTokens.TokenNameDoubleLiteral});
 
  55                 this.endingTokens.put(PackageDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  56                 this.endingTokens.put(ParenthesizedExpression.class, new int[]{TerminalTokens.TokenNameRPAREN});
 
  57                 this.endingTokens.put(PostfixExpression.class, new int[]{TerminalTokens.TokenNamePLUS_PLUS, TerminalTokens.TokenNameMINUS_MINUS});
 
  58                 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});
 
  59                 this.endingTokens.put(ReturnStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  60                 this.endingTokens.put(SimpleName.class, new int[]{TerminalTokens.TokenNameIdentifier});
 
  61                 this.endingTokens.put(SingleVariableDeclaration.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  62                 this.endingTokens.put(StringLiteral.class, new int[]{TerminalTokens.TokenNameStringLiteral});
 
  63                 this.endingTokens.put(SuperConstructorInvocation.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  64                 this.endingTokens.put(SuperMethodInvocation.class, new int[]{TerminalTokens.TokenNameRPAREN});
 
  65                 this.endingTokens.put(SwitchCase.class, new int[]{TerminalTokens.TokenNameCOLON});
 
  66                 this.endingTokens.put(SwitchStatement.class, new int[]{TerminalTokens.TokenNameRBRACE});
 
  67                 this.endingTokens.put(SynchronizedStatement.class, new int[]{TerminalTokens.TokenNameRBRACE});
 
  68                 this.endingTokens.put(ThisExpression.class, new int[]{TerminalTokens.TokenNamethis});
 
  69                 this.endingTokens.put(ThrowStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  70                 this.endingTokens.put(TypeDeclaration.class, new int[]{TerminalTokens.TokenNameRBRACE});
 
  71                 this.endingTokens.put(TypeLiteral.class, new int[]{TerminalTokens.TokenNameclass});
 
  72                 this.endingTokens.put(VariableDeclarationStatement.class, new int[]{TerminalTokens.TokenNameSEMICOLON});
 
  75         private CategorizedProblem[] problems;
 
  76         private boolean[] usedOrIrrelevantProblems;
 
  78         private RecoveryScannerData data;
 
  79         private int blockDepth = 0;
 
  82         private int[] insertedTokensKind;
 
  83         private int[] insertedTokensPosition;
 
  84         private boolean[] insertedTokensFlagged;
 
  86         private boolean[] removedTokensFlagged;
 
  87         private boolean[] replacedTokensFlagged;
 
  89         private Vector stack = new Vector();
 
  92          * @noreference This method is not intended to be referenced by clients.
 
  94         ASTRecoveryPropagator(CategorizedProblem[] problems, RecoveryScannerData data) {
 
  95                 // visit Javadoc.tags() as well
 
  96                 this.problems = problems;
 
  97                 this.usedOrIrrelevantProblems = new boolean[problems.length];
 
 101                 if(this.data != null) {
 
 104                         for (int i = 0; i < data.insertedTokensPtr + 1; i++) {
 
 105                                 length += data.insertedTokens[i].length;
 
 107                         this.insertedTokensKind = new int[length];
 
 108                         this.insertedTokensPosition = new int[length];
 
 109                         this.insertedTokensFlagged = new boolean[length];
 
 111                         for (int i = 0; i < data.insertedTokensPtr + 1; i++) {
 
 112                                 for (int j = 0; j < data.insertedTokens[i].length; j++) {
 
 113                                         this.insertedTokensKind[tokenCount] = data.insertedTokens[i][j];
 
 114                                         this.insertedTokensPosition[tokenCount] = data.insertedTokensPosition[i];
 
 119                         if(data.removedTokensPtr != -1) {
 
 120                                 this.removedTokensFlagged = new boolean[data.removedTokensPtr + 1];
 
 122                         if(data.replacedTokensPtr != -1) {
 
 123                                 this.replacedTokensFlagged = new boolean[data.replacedTokensPtr + 1];
 
 128         public void endVisit(Block node) {
 
 130                 if(this.blockDepth <= 0) {
 
 131                         flagNodeWithInsertedTokens();
 
 133                 super.endVisit(node);
 
 138         public boolean visit(Block node) {
 
 139                 boolean visitChildren = super.visit(node);
 
 141                 return visitChildren;
 
 144         protected boolean visitNode(ASTNode node) {
 
 145                 if(this.blockDepth > 0) {
 
 146                         int start = node.getStartPosition();
 
 147                         int end = start + node.getLength() - 1;
 
 149                         // continue to visit the node only if it contains tokens modifications
 
 151                         if(this.insertedTokensFlagged != null) {
 
 152                                 for (int i = 0; i < this.insertedTokensFlagged.length; i++) {
 
 153                                         if(this.insertedTokensPosition[i] >= start &&
 
 154                                                         this.insertedTokensPosition[i] <= end) {
 
 160                         if(this.removedTokensFlagged != null) {
 
 161                                 for (int i = 0; i <= this.data.removedTokensPtr; i++) {
 
 162                                         if(this.data.removedTokensStart[i] >= start &&
 
 163                                                         this.data.removedTokensEnd[i] <= end) {
 
 169                         if(this.replacedTokensFlagged != null) {
 
 170                                 for (int i = 0; i <= this.data.replacedTokensPtr; i++) {
 
 171                                         if(this.data.replacedTokensStart[i] >= start &&
 
 172                                                         this.data.replacedTokensEnd[i] <= end) {
 
 183         protected void endVisitNode(ASTNode node) {
 
 184                 int start = node.getStartPosition();
 
 185                 int end = start + node.getLength() - 1;
 
 187                 // is inside diet part of the ast
 
 188                 if(this.blockDepth < 1) {
 
 189                         switch (node.getNodeType()) {
 
 190                                 case ASTNode.ANNOTATION_TYPE_DECLARATION:
 
 191                                 case ASTNode.COMPILATION_UNIT:
 
 192                                 case ASTNode.ENUM_DECLARATION:
 
 193                                 case ASTNode.FIELD_DECLARATION:
 
 194                                 case ASTNode.IMPORT_DECLARATION:
 
 195                                 case ASTNode.INITIALIZER:
 
 196                                 case ASTNode.METHOD_DECLARATION:
 
 197                                 case ASTNode.PACKAGE_DECLARATION:
 
 198                                 case ASTNode.TYPE_DECLARATION:
 
 199                                 case ASTNode.MARKER_ANNOTATION:
 
 200                                 case ASTNode.NORMAL_ANNOTATION:
 
 201                                 case ASTNode.SINGLE_MEMBER_ANNOTATION:
 
 203                                         if(this.markIncludedProblems(start, end)) {
 
 204                                                 node.setFlags(node.getFlags() | ASTNode.RECOVERED);
 
 209                         this.markIncludedProblems(start, end);
 
 211                         if(this.insertedTokensFlagged != null) {
 
 212                                 if(this.lastEnd != end) {
 
 213                                         flagNodeWithInsertedTokens();
 
 215                                 this.stack.add(node);
 
 218                         if(this.removedTokensFlagged != null) {
 
 219                                 for (int i = 0; i <= this.data.removedTokensPtr; i++) {
 
 220                                         if(!this.removedTokensFlagged[i] &&
 
 221                                                         this.data.removedTokensStart[i] >= start &&
 
 222                                                         this.data.removedTokensEnd[i] <= end) {
 
 223                                                 node.setFlags(node.getFlags() | ASTNode.RECOVERED);
 
 224                                                 this.removedTokensFlagged[i] = true;
 
 229                         if(this.replacedTokensFlagged != null) {
 
 230                                 for (int i = 0; i <= this.data.replacedTokensPtr; i++) {
 
 231                                         if(!this.replacedTokensFlagged[i] &&
 
 232                                                         this.data.replacedTokensStart[i] >= start &&
 
 233                                                         this.data.replacedTokensEnd[i] <= end) {
 
 234                                                 node.setFlags(node.getFlags() | ASTNode.RECOVERED);
 
 235                                                 this.replacedTokensFlagged[i] = true;
 
 243         private void flagNodeWithInsertedTokens() {
 
 244                 if(this.insertedTokensKind != null && this.insertedTokensKind.length > 0) {
 
 245                         int s = this.stack.size();
 
 246                         for (int i = s - 1; i > -1; i--) {
 
 247                                 flagNodesWithInsertedTokensAtEnd((ASTNode)this.stack.get(i));
 
 249                         for (int i = 0; i < s; i++) {
 
 250                                 flagNodesWithInsertedTokensInside((ASTNode)this.stack.get(i));
 
 252                         this.stack = new Vector();
 
 256         private boolean flagNodesWithInsertedTokensAtEnd(ASTNode node) {
 
 257                 int[] expectedEndingToken = this.endingTokens.get(node.getClass());
 
 258                 if (expectedEndingToken != null) {
 
 259                         int start = node.getStartPosition();
 
 260                         int end = start + node.getLength() - 1;
 
 262                         boolean flagParent = false;
 
 263                         done : for (int i = this.insertedTokensKind.length - 1; i > -1 ; i--) {
 
 264                                 if(!this.insertedTokensFlagged[i] &&
 
 265                                                 this.insertedTokensPosition[i] == end){
 
 266                                         this.insertedTokensFlagged[i] = true;
 
 267                                         for (int j = 0; j < expectedEndingToken.length; j++) {
 
 268                                                 if(expectedEndingToken[j] == this.insertedTokensKind[i]) {
 
 269                                                         node.setFlags(node.getFlags() | ASTNode.RECOVERED);
 
 278                                 ASTNode parent = node.getParent();
 
 279                                 while (parent != null) {
 
 280                                         parent.setFlags(node.getFlags() | ASTNode.RECOVERED);
 
 281                                         if((parent.getStartPosition() + parent.getLength() - 1) != end) {
 
 284                                                 parent = parent.getParent();
 
 292         private boolean flagNodesWithInsertedTokensInside(ASTNode node) {
 
 293                 int start = node.getStartPosition();
 
 294                 int end = start + node.getLength() - 1;
 
 295                 for (int i = 0; i < this.insertedTokensKind.length; i++) {
 
 296                         if(!this.insertedTokensFlagged[i] &&
 
 297                                         start <= this.insertedTokensPosition[i] &&
 
 298                                         this.insertedTokensPosition[i] < end){
 
 299                                 node.setFlags(node.getFlags() | ASTNode.RECOVERED);
 
 300                                 this.insertedTokensFlagged[i] = true;
 
 306         private boolean markIncludedProblems(int start, int end) {
 
 307                 boolean foundProblems = false;
 
 308                 next: for (int i = 0, max = this.problems.length; i < max; i++) {
 
 309                         CategorizedProblem problem = this.problems[i];
 
 311                         if(this.usedOrIrrelevantProblems[i]) continue next;
 
 313                         switch(problem.getID()) {
 
 314                                 case IProblem.ParsingErrorOnKeywordNoSuggestion :
 
 315                                 case IProblem.ParsingErrorOnKeyword :
 
 316                                 case IProblem.ParsingError :
 
 317                                 case IProblem.ParsingErrorNoSuggestion :
 
 318                                 case IProblem.ParsingErrorInsertTokenBefore :
 
 319                                 case IProblem.ParsingErrorInsertTokenAfter :
 
 320                                 case IProblem.ParsingErrorDeleteToken :
 
 321                                 case IProblem.ParsingErrorDeleteTokens :
 
 322                                 case IProblem.ParsingErrorMergeTokens :
 
 323                                 case IProblem.ParsingErrorInvalidToken :
 
 324                                 case IProblem.ParsingErrorMisplacedConstruct :
 
 325                                 case IProblem.ParsingErrorReplaceTokens :
 
 326                                 case IProblem.ParsingErrorNoSuggestionForTokens :
 
 327                                 case IProblem.ParsingErrorUnexpectedEOF :
 
 328                                 case IProblem.ParsingErrorInsertToComplete :
 
 329                                 case IProblem.ParsingErrorInsertToCompleteScope :
 
 330                                 case IProblem.ParsingErrorInsertToCompletePhrase :
 
 331                                 case IProblem.EndOfSource :
 
 332                                 case IProblem.InvalidHexa :
 
 333                                 case IProblem.InvalidOctal :
 
 334                                 case IProblem.InvalidCharacterConstant :
 
 335                                 case IProblem.InvalidEscape :
 
 336                                 case IProblem.InvalidInput :
 
 337                                 case IProblem.InvalidUnicodeEscape :
 
 338                                 case IProblem.InvalidFloat :
 
 339                                 case IProblem.NullSourceString :
 
 340                                 case IProblem.UnterminatedString :
 
 341                                 case IProblem.UnterminatedComment :
 
 342                                 case IProblem.InvalidDigit :
 
 345                                         this.usedOrIrrelevantProblems[i] = true;
 
 350                         int problemStart = problem.getSourceStart();
 
 351                         int problemEnd = problem.getSourceEnd();
 
 352                         if ((start <= problemStart) && (problemStart <= end) ||
 
 353                                         (start <= problemEnd) && (problemEnd <= end)) {
 
 354                                 this.usedOrIrrelevantProblems[i] = true;
 
 355                                 foundProblems = true;
 
 358                 return foundProblems;
 
 361         public void endVisit(ExpressionStatement node) {
 
 363                 if ((node.getFlags() & ASTNode.RECOVERED) == 0) return; 
 
 364                 Expression expression = node.getExpression();
 
 365                 if (expression.getNodeType() == ASTNode.ASSIGNMENT) {
 
 366                         Assignment assignment = (Assignment) expression;
 
 367                         Expression rightHandSide = assignment.getRightHandSide();
 
 368                         if (rightHandSide.getNodeType() == ASTNode.SIMPLE_NAME) {
 
 369                                 SimpleName simpleName = (SimpleName) rightHandSide;
 
 370                                 if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) {
 
 371                                         Expression expression2 =  assignment.getLeftHandSide();
 
 372                                         // unparent the expression to add it in the expression stateemnt
 
 373                                         expression2.setParent(null, null);
 
 374                                         expression2.setFlags(expression2.getFlags() | ASTNode.RECOVERED);
 
 375                                         node.setExpression(expression2);
 
 381         public void endVisit(ForStatement node) {
 
 383                 List initializers = node.initializers();
 
 384                 if (initializers.size() == 1) {
 
 385                         Expression expression = (Expression) initializers.get(0);
 
 386                         if (expression.getNodeType() == ASTNode.VARIABLE_DECLARATION_EXPRESSION) {
 
 387                                 VariableDeclarationExpression variableDeclarationExpression = (VariableDeclarationExpression) expression;
 
 388                                 List fragments = variableDeclarationExpression.fragments();
 
 389                                 for (int i = 0, max = fragments.size(); i <max; i++) {
 
 390                                         VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragments.get(i);
 
 391                                         SimpleName simpleName = fragment.getName();
 
 392                                         if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) {
 
 393                                                 fragments.remove(fragment);
 
 394                                                 variableDeclarationExpression.setFlags(variableDeclarationExpression.getFlags() | ASTNode.RECOVERED);
 
 401         public void endVisit(VariableDeclarationStatement node) {
 
 403                 List fragments = node.fragments();
 
 404                 for (int i = 0, max = fragments.size(); i <max; i++) {
 
 405                         VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragments.get(i);
 
 406                         Expression expression = fragment.getInitializer();
 
 407                         if (expression == null) continue;
 
 408                         if ((expression.getFlags() & ASTNode.RECOVERED) == 0) continue;
 
 409                         if (expression.getNodeType() == ASTNode.SIMPLE_NAME) {
 
 410                                 SimpleName simpleName = (SimpleName) expression;
 
 411                                 if (CharOperation.equals(RecoveryScanner.FAKE_IDENTIFIER, simpleName.getIdentifier().toCharArray())) {
 
 412                                         fragment.setInitializer(null);
 
 413                                         fragment.setFlags(fragment.getFlags() | ASTNode.RECOVERED);
 
 419         public void endVisit(NormalAnnotation node) {
 
 421                 // is inside diet part of the ast
 
 422                 if(this.blockDepth < 1) {
 
 423                         List values = node.values();
 
 424                         int size = values.size();
 
 426                                 MemberValuePair lastMemberValuePair = (MemberValuePair)values.get(size - 1);
 
 428                                 int annotationEnd = node.getStartPosition() + node.getLength();
 
 429                                 int lastMemberValuePairEnd = lastMemberValuePair.getStartPosition() + lastMemberValuePair.getLength();
 
 430                                 if (annotationEnd == lastMemberValuePairEnd) {
 
 431                                         node.setFlags(node.getFlags() | ASTNode.RECOVERED);
 
 437         public void endVisit(SingleMemberAnnotation node) {
 
 439                 // is inside diet part of the ast
 
 440                 if(this.blockDepth < 1) {
 
 441                         Expression value = node.getValue();
 
 442                         int annotationEnd = node.getStartPosition() + node.getLength();
 
 443                         int valueEnd = value.getStartPosition() + value.getLength();
 
 444                         if (annotationEnd == valueEnd) {
 
 445                                 node.setFlags(node.getFlags() | ASTNode.RECOVERED);