Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
EqualsAvoidNullCheck
100%
161/161
100%
129/129
0
EqualsAvoidNullCheck$FieldFrame
100%
29/29
100%
4/4
0
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.coding;
 21  
 
 22  
 import java.util.Collections;
 23  
 import java.util.HashSet;
 24  
 import java.util.Set;
 25  
 
 26  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 27  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 28  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 29  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 30  
 
 31  
 /**
 32  
  * Checks that any combination of String literals
 33  
  * is on the left side of an equals() comparison.
 34  
  * Also checks for String literals assigned to some field
 35  
  * (such as {@code someString.equals(anotherString = "text")}).
 36  
  *
 37  
  * <p>Rationale: Calling the equals() method on String literals
 38  
  * will avoid a potential NullPointerException.  Also, it is
 39  
  * pretty common to see null check right before equals comparisons
 40  
  * which is not necessary in the below example.
 41  
  *
 42  
  * <p>For example:
 43  
  *
 44  
  * <pre>
 45  
  *  {@code
 46  
  *    String nullString = null;
 47  
  *    nullString.equals(&quot;My_Sweet_String&quot;);
 48  
  *  }
 49  
  * </pre>
 50  
  * should be refactored to
 51  
  *
 52  
  * <pre>
 53  
  *  {@code
 54  
  *    String nullString = null;
 55  
  *    &quot;My_Sweet_String&quot;.equals(nullString);
 56  
  *  }
 57  
  * </pre>
 58  
  *
 59  
  * @author Travis Schneeberger
 60  
  * @author Vladislav Lisetskiy
 61  
  */
 62  
 @FileStatefulCheck
 63  12
 public class EqualsAvoidNullCheck extends AbstractCheck {
 64  
 
 65  
     /**
 66  
      * A key is pointing to the warning message text in "messages.properties"
 67  
      * file.
 68  
      */
 69  
     public static final String MSG_EQUALS_AVOID_NULL = "equals.avoid.null";
 70  
 
 71  
     /**
 72  
      * A key is pointing to the warning message text in "messages.properties"
 73  
      * file.
 74  
      */
 75  
     public static final String MSG_EQUALS_IGNORE_CASE_AVOID_NULL = "equalsIgnoreCase.avoid.null";
 76  
 
 77  
     /** Method name for comparison. */
 78  
     private static final String EQUALS = "equals";
 79  
 
 80  
     /** Type name for comparison. */
 81  
     private static final String STRING = "String";
 82  
 
 83  
     /** Whether to process equalsIgnoreCase() invocations. */
 84  
     private boolean ignoreEqualsIgnoreCase;
 85  
 
 86  
     /** Stack of sets of field names, one for each class of a set of nested classes. */
 87  
     private FieldFrame currentFrame;
 88  
 
 89  
     @Override
 90  
     public int[] getDefaultTokens() {
 91  17
         return getRequiredTokens();
 92  
     }
 93  
 
 94  
     @Override
 95  
     public int[] getAcceptableTokens() {
 96  5
         return getRequiredTokens();
 97  
     }
 98  
 
 99  
     @Override
 100  
     public int[] getRequiredTokens() {
 101  39
         return new int[] {
 102  
             TokenTypes.METHOD_CALL,
 103  
             TokenTypes.CLASS_DEF,
 104  
             TokenTypes.METHOD_DEF,
 105  
             TokenTypes.LITERAL_IF,
 106  
             TokenTypes.LITERAL_FOR,
 107  
             TokenTypes.LITERAL_WHILE,
 108  
             TokenTypes.LITERAL_DO,
 109  
             TokenTypes.LITERAL_CATCH,
 110  
             TokenTypes.LITERAL_TRY,
 111  
             TokenTypes.VARIABLE_DEF,
 112  
             TokenTypes.PARAMETER_DEF,
 113  
             TokenTypes.CTOR_DEF,
 114  
             TokenTypes.SLIST,
 115  
             TokenTypes.ENUM_DEF,
 116  
             TokenTypes.ENUM_CONSTANT_DEF,
 117  
             TokenTypes.LITERAL_NEW,
 118  
         };
 119  
     }
 120  
 
 121  
     /**
 122  
      * Whether to ignore checking {@code String.equalsIgnoreCase(String)}.
 123  
      * @param newValue whether to ignore checking
 124  
      *    {@code String.equalsIgnoreCase(String)}.
 125  
      */
 126  
     public void setIgnoreEqualsIgnoreCase(boolean newValue) {
 127  1
         ignoreEqualsIgnoreCase = newValue;
 128  1
     }
 129  
 
 130  
     @Override
 131  
     public void beginTree(DetailAST rootAST) {
 132  5
         currentFrame = new FieldFrame(null);
 133  5
     }
 134  
 
 135  
     @Override
 136  
     public void visitToken(final DetailAST ast) {
 137  688
         switch (ast.getType()) {
 138  
             case TokenTypes.VARIABLE_DEF:
 139  
             case TokenTypes.PARAMETER_DEF:
 140  119
                 currentFrame.addField(ast);
 141  119
                 break;
 142  
             case TokenTypes.METHOD_CALL:
 143  293
                 processMethodCall(ast);
 144  293
                 break;
 145  
             case TokenTypes.SLIST:
 146  98
                 processSlist(ast);
 147  98
                 break;
 148  
             case TokenTypes.LITERAL_NEW:
 149  58
                 processLiteralNew(ast);
 150  58
                 break;
 151  
             default:
 152  120
                 processFrame(ast);
 153  
         }
 154  688
     }
 155  
 
 156  
     @Override
 157  
     public void leaveToken(DetailAST ast) {
 158  688
         final int astType = ast.getType();
 159  688
         if (astType != TokenTypes.VARIABLE_DEF
 160  
                 && astType != TokenTypes.PARAMETER_DEF
 161  
                 && astType != TokenTypes.METHOD_CALL
 162  
                 && astType != TokenTypes.SLIST
 163  
                 && astType != TokenTypes.LITERAL_NEW
 164  
                 || astType == TokenTypes.LITERAL_NEW
 165  58
                     && ast.findFirstToken(TokenTypes.OBJBLOCK) != null) {
 166  122
             currentFrame = currentFrame.getParent();
 167  
         }
 168  566
         else if (astType == TokenTypes.SLIST) {
 169  98
             leaveSlist(ast);
 170  
         }
 171  688
     }
 172  
 
 173  
     @Override
 174  
     public void finishTree(DetailAST ast) {
 175  5
         traverseFieldFrameTree(currentFrame);
 176  5
     }
 177  
 
 178  
     /**
 179  
      * Determine whether SLIST begins static or non-static block and add it as
 180  
      * a frame in this case.
 181  
      * @param ast SLIST ast.
 182  
      */
 183  
     private void processSlist(DetailAST ast) {
 184  98
         final int parentType = ast.getParent().getType();
 185  98
         if (parentType == TokenTypes.SLIST
 186  
                 || parentType == TokenTypes.STATIC_INIT
 187  
                 || parentType == TokenTypes.INSTANCE_INIT) {
 188  7
             final FieldFrame frame = new FieldFrame(currentFrame);
 189  7
             currentFrame.addChild(frame);
 190  7
             currentFrame = frame;
 191  
         }
 192  98
     }
 193  
 
 194  
     /**
 195  
      * Determine whether SLIST begins static or non-static block.
 196  
      * @param ast SLIST ast.
 197  
      */
 198  
     private void leaveSlist(DetailAST ast) {
 199  98
         final int parentType = ast.getParent().getType();
 200  98
         if (parentType == TokenTypes.SLIST
 201  
                 || parentType == TokenTypes.STATIC_INIT
 202  
                 || parentType == TokenTypes.INSTANCE_INIT) {
 203  7
             currentFrame = currentFrame.getParent();
 204  
         }
 205  98
     }
 206  
 
 207  
     /**
 208  
      * Process CLASS_DEF, METHOD_DEF, LITERAL_IF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO,
 209  
      * LITERAL_CATCH, LITERAL_TRY, CTOR_DEF, ENUM_DEF, ENUM_CONSTANT_DEF.
 210  
      * @param ast processed ast.
 211  
      */
 212  
     private void processFrame(DetailAST ast) {
 213  120
         final FieldFrame frame = new FieldFrame(currentFrame);
 214  120
         final int astType = ast.getType();
 215  120
         if (astType == TokenTypes.CLASS_DEF
 216  
                 || astType == TokenTypes.ENUM_DEF
 217  
                 || astType == TokenTypes.ENUM_CONSTANT_DEF) {
 218  36
             frame.setClassOrEnumOrEnumConstDef(true);
 219  36
             frame.setFrameName(ast.findFirstToken(TokenTypes.IDENT).getText());
 220  
         }
 221  120
         currentFrame.addChild(frame);
 222  120
         currentFrame = frame;
 223  120
     }
 224  
 
 225  
     /**
 226  
      * Add the method call to the current frame if it should be processed.
 227  
      * @param methodCall METHOD_CALL ast.
 228  
      */
 229  
     private void processMethodCall(DetailAST methodCall) {
 230  293
         final DetailAST dot = methodCall.getFirstChild();
 231  293
         if (dot.getType() == TokenTypes.DOT) {
 232  271
             final String methodName = dot.getLastChild().getText();
 233  271
             if (EQUALS.equals(methodName)
 234  37
                     || !ignoreEqualsIgnoreCase && "equalsIgnoreCase".equals(methodName)) {
 235  236
                 currentFrame.addMethodCall(methodCall);
 236  
             }
 237  
         }
 238  293
     }
 239  
 
 240  
     /**
 241  
      * Determine whether LITERAL_NEW is an anonymous class definition and add it as
 242  
      * a frame in this case.
 243  
      * @param ast LITERAL_NEW ast.
 244  
      */
 245  
     private void processLiteralNew(DetailAST ast) {
 246  58
         if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) {
 247  2
             final FieldFrame frame = new FieldFrame(currentFrame);
 248  2
             currentFrame.addChild(frame);
 249  2
             currentFrame = frame;
 250  
         }
 251  58
     }
 252  
 
 253  
     /**
 254  
      * Traverse the tree of the field frames to check all equals method calls.
 255  
      * @param frame to check method calls in.
 256  
      */
 257  
     private void traverseFieldFrameTree(FieldFrame frame) {
 258  63
         for (FieldFrame child: frame.getChildren()) {
 259  129
             if (!child.getChildren().isEmpty()) {
 260  58
                 traverseFieldFrameTree(child);
 261  
             }
 262  129
             currentFrame = child;
 263  129
             child.getMethodCalls().forEach(this::checkMethodCall);
 264  129
         }
 265  63
     }
 266  
 
 267  
     /**
 268  
      * Check whether the method call should be violated.
 269  
      * @param methodCall method call to check.
 270  
      */
 271  
     private void checkMethodCall(DetailAST methodCall) {
 272  236
         DetailAST objCalledOn = methodCall.getFirstChild().getFirstChild();
 273  236
         if (objCalledOn.getType() == TokenTypes.DOT) {
 274  42
             objCalledOn = objCalledOn.getLastChild();
 275  
         }
 276  236
         final DetailAST expr = methodCall.findFirstToken(TokenTypes.ELIST).getFirstChild();
 277  236
         if (isObjectValid(objCalledOn)
 278  218
                 && containsOneArgument(methodCall)
 279  215
                 && containsAllSafeTokens(expr)
 280  187
                 && isCalledOnStringFieldOrVariable(objCalledOn)) {
 281  100
             final String methodName = methodCall.getFirstChild().getLastChild().getText();
 282  100
             if (EQUALS.equals(methodName)) {
 283  85
                 log(methodCall.getLineNo(), methodCall.getColumnNo(),
 284  
                     MSG_EQUALS_AVOID_NULL);
 285  
             }
 286  
             else {
 287  15
                 log(methodCall.getLineNo(), methodCall.getColumnNo(),
 288  
                     MSG_EQUALS_IGNORE_CASE_AVOID_NULL);
 289  
             }
 290  
         }
 291  236
     }
 292  
 
 293  
     /**
 294  
      * Check whether the object equals method is called on is not a String literal
 295  
      * and not too complex.
 296  
      * @param objCalledOn the object equals method is called on ast.
 297  
      * @return true if the object is valid.
 298  
      */
 299  
     private static boolean isObjectValid(DetailAST objCalledOn) {
 300  236
         boolean result = true;
 301  236
         final DetailAST previousSibling = objCalledOn.getPreviousSibling();
 302  236
         if (previousSibling != null
 303  42
                 && previousSibling.getType() == TokenTypes.DOT) {
 304  2
             result = false;
 305  
         }
 306  236
         if (isStringLiteral(objCalledOn)) {
 307  16
             result = false;
 308  
         }
 309  236
         return result;
 310  
     }
 311  
 
 312  
     /**
 313  
      * Checks for calling equals on String literal and
 314  
      * anon object which cannot be null.
 315  
      * @param objCalledOn object AST
 316  
      * @return if it is string literal
 317  
      */
 318  
     private static boolean isStringLiteral(DetailAST objCalledOn) {
 319  472
         return objCalledOn.getType() == TokenTypes.STRING_LITERAL
 320  230
                 || objCalledOn.getType() == TokenTypes.LITERAL_NEW;
 321  
     }
 322  
 
 323  
     /**
 324  
      * Verify that method call has one argument.
 325  
      *
 326  
      * @param methodCall METHOD_CALL DetailAST
 327  
      * @return true if method call has one argument.
 328  
      */
 329  
     private static boolean containsOneArgument(DetailAST methodCall) {
 330  218
         final DetailAST elist = methodCall.findFirstToken(TokenTypes.ELIST);
 331  218
         return elist.getChildCount() == 1;
 332  
     }
 333  
 
 334  
     /**
 335  
      * Looks for all "safe" Token combinations in the argument
 336  
      * expression branch.
 337  
      * @param expr the argument expression
 338  
      * @return - true if any child matches the set of tokens, false if not
 339  
      */
 340  
     private static boolean containsAllSafeTokens(final DetailAST expr) {
 341  215
         DetailAST arg = expr.getFirstChild();
 342  215
         arg = skipVariableAssign(arg);
 343  
 
 344  215
         boolean argIsNotNull = false;
 345  215
         if (arg.getType() == TokenTypes.PLUS) {
 346  32
             DetailAST child = arg.getFirstChild();
 347  122
             while (child != null
 348  
                     && !argIsNotNull) {
 349  90
                 argIsNotNull = child.getType() == TokenTypes.STRING_LITERAL
 350  64
                         || child.getType() == TokenTypes.IDENT;
 351  90
                 child = child.getNextSibling();
 352  
             }
 353  
         }
 354  
 
 355  430
         return argIsNotNull
 356  185
                 || !arg.branchContains(TokenTypes.IDENT)
 357  159
                     && !arg.branchContains(TokenTypes.LITERAL_NULL);
 358  
     }
 359  
 
 360  
     /**
 361  
      * Skips over an inner assign portion of an argument expression.
 362  
      * @param currentAST current token in the argument expression
 363  
      * @return the next relevant token
 364  
      */
 365  
     private static DetailAST skipVariableAssign(final DetailAST currentAST) {
 366  215
         DetailAST result = currentAST;
 367  215
         if (currentAST.getType() == TokenTypes.ASSIGN
 368  8
                 && currentAST.getFirstChild().getType() == TokenTypes.IDENT) {
 369  6
             result = currentAST.getFirstChild().getNextSibling();
 370  
         }
 371  215
         return result;
 372  
     }
 373  
 
 374  
     /**
 375  
      * Determine, whether equals method is called on a field of String type.
 376  
      * @param objCalledOn object ast.
 377  
      * @return true if the object is of String type.
 378  
      */
 379  
     private boolean isCalledOnStringFieldOrVariable(DetailAST objCalledOn) {
 380  
         final boolean result;
 381  187
         final DetailAST previousSiblingAst = objCalledOn.getPreviousSibling();
 382  187
         if (previousSiblingAst == null) {
 383  151
             result = isStringFieldOrVariable(objCalledOn);
 384  
         }
 385  
         else {
 386  36
             if (previousSiblingAst.getType() == TokenTypes.LITERAL_THIS) {
 387  10
                 result = isStringFieldOrVariableFromThisInstance(objCalledOn);
 388  
             }
 389  
             else {
 390  26
                 final String className = previousSiblingAst.getText();
 391  26
                 result = isStringFieldOrVariableFromClass(objCalledOn, className);
 392  
             }
 393  
         }
 394  187
         return result;
 395  
     }
 396  
 
 397  
     /**
 398  
      * Whether the field or the variable is of String type.
 399  
      * @param objCalledOn the field or the variable to check.
 400  
      * @return true if the field or the variable is of String type.
 401  
      */
 402  
     private boolean isStringFieldOrVariable(DetailAST objCalledOn) {
 403  151
         boolean result = false;
 404  151
         final String name = objCalledOn.getText();
 405  151
         FieldFrame frame = currentFrame;
 406  334
         while (frame != null) {
 407  310
             final DetailAST field = frame.findField(name);
 408  310
             if (field != null
 409  131
                     && (frame.isClassOrEnumOrEnumConstDef()
 410  93
                             || checkLineNo(field, objCalledOn))) {
 411  127
                 result = STRING.equals(getFieldType(field));
 412  127
                 break;
 413  
             }
 414  183
             frame = frame.getParent();
 415  183
         }
 416  151
         return result;
 417  
     }
 418  
 
 419  
     /**
 420  
      * Whether the field or the variable from THIS instance is of String type.
 421  
      * @param objCalledOn the field or the variable from THIS instance to check.
 422  
      * @return true if the field or the variable from THIS instance is of String type.
 423  
      */
 424  
     private boolean isStringFieldOrVariableFromThisInstance(DetailAST objCalledOn) {
 425  10
         boolean result = false;
 426  10
         final String name = objCalledOn.getText();
 427  10
         final DetailAST field = getObjectFrame(currentFrame).findField(name);
 428  10
         if (field != null) {
 429  8
             result = STRING.equals(getFieldType(field));
 430  
         }
 431  10
         return result;
 432  
     }
 433  
 
 434  
     /**
 435  
      * Whether the field or the variable from the specified class is of String type.
 436  
      * @param objCalledOn the field or the variable from the specified class to check.
 437  
      * @param className the name of the class to check in.
 438  
      * @return true if the field or the variable from the specified class is of String type.
 439  
      */
 440  
     private boolean isStringFieldOrVariableFromClass(DetailAST objCalledOn,
 441  
             final String className) {
 442  26
         boolean result = false;
 443  26
         final String name = objCalledOn.getText();
 444  26
         FieldFrame frame = getObjectFrame(currentFrame);
 445  56
         while (frame != null) {
 446  40
             if (className.equals(frame.getFrameName())) {
 447  10
                 final DetailAST field = frame.findField(name);
 448  10
                 if (field != null) {
 449  8
                     result = STRING.equals(getFieldType(field));
 450  
                 }
 451  
                 break;
 452  
             }
 453  30
             frame = getObjectFrame(frame.getParent());
 454  
         }
 455  26
         return result;
 456  
     }
 457  
 
 458  
     /**
 459  
      * Get the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF.
 460  
      * @param frame to start the search from.
 461  
      * @return the nearest parent frame which is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF.
 462  
      */
 463  
     private static FieldFrame getObjectFrame(FieldFrame frame) {
 464  66
         FieldFrame objectFrame = frame;
 465  128
         while (objectFrame != null && !objectFrame.isClassOrEnumOrEnumConstDef()) {
 466  62
             objectFrame = objectFrame.getParent();
 467  
         }
 468  66
         return objectFrame;
 469  
     }
 470  
 
 471  
     /**
 472  
      * Check whether the field is declared before the method call in case of
 473  
      * methods and initialization blocks.
 474  
      * @param field field to check.
 475  
      * @param objCalledOn object equals method called on.
 476  
      * @return true if the field is declared before the method call.
 477  
      */
 478  
     private static boolean checkLineNo(DetailAST field, DetailAST objCalledOn) {
 479  93
         boolean result = false;
 480  
         // Required for pitest coverage. We should specify columnNo passing condition
 481  
         // in such a way, so that the minimal possible distance between field and
 482  
         // objCalledOn will be the maximal condition to pass this check.
 483  
         // The minimal distance between objCalledOn and field (of type String) initialization
 484  
         // is calculated as follows:
 485  
         // String(6) + space(1) + variableName(1) + assign(1) +
 486  
         // anotherStringVariableName(1) + semicolumn(1) = 11
 487  
         // Example: length of "String s=d;" is 11 symbols.
 488  93
         final int minimumSymbolsBetween = 11;
 489  93
         if (field.getLineNo() < objCalledOn.getLineNo()
 490  11
                 || field.getLineNo() == objCalledOn.getLineNo()
 491  9
                     && field.getColumnNo() + minimumSymbolsBetween <= objCalledOn.getColumnNo()) {
 492  89
             result = true;
 493  
         }
 494  93
         return result;
 495  
     }
 496  
 
 497  
     /**
 498  
      * Get field type.
 499  
      * @param field to get the type from.
 500  
      * @return type of the field.
 501  
      */
 502  
     private static String getFieldType(DetailAST field) {
 503  143
         String fieldType = null;
 504  143
         final DetailAST identAst = field.findFirstToken(TokenTypes.TYPE)
 505  143
                 .findFirstToken(TokenTypes.IDENT);
 506  143
         if (identAst != null) {
 507  141
             fieldType = identAst.getText();
 508  
         }
 509  143
         return fieldType;
 510  
     }
 511  
 
 512  
     /**
 513  
      * Holds the names of fields of a type.
 514  
      */
 515  
     private static class FieldFrame {
 516  
         /** Parent frame. */
 517  
         private final FieldFrame parent;
 518  
 
 519  
         /** Set of frame's children. */
 520  134
         private final Set<FieldFrame> children = new HashSet<>();
 521  
 
 522  
         /** Set of fields. */
 523  134
         private final Set<DetailAST> fields = new HashSet<>();
 524  
 
 525  
         /** Set of equals calls. */
 526  134
         private final Set<DetailAST> methodCalls = new HashSet<>();
 527  
 
 528  
         /** Name of the class, enum or enum constant declaration. */
 529  
         private String frameName;
 530  
 
 531  
         /** Whether the frame is CLASS_DEF, ENUM_DEF or ENUM_CONST_DEF. */
 532  
         private boolean classOrEnumOrEnumConstDef;
 533  
 
 534  
         /**
 535  
          * Creates new frame.
 536  
          * @param parent parent frame.
 537  
          */
 538  134
         FieldFrame(FieldFrame parent) {
 539  134
             this.parent = parent;
 540  134
         }
 541  
 
 542  
         /**
 543  
          * Set the frame name.
 544  
          * @param frameName value to set.
 545  
          */
 546  
         public void setFrameName(String frameName) {
 547  36
             this.frameName = frameName;
 548  36
         }
 549  
 
 550  
         /**
 551  
          * Getter for the frame name.
 552  
          * @return frame name.
 553  
          */
 554  
         public String getFrameName() {
 555  40
             return frameName;
 556  
         }
 557  
 
 558  
         /**
 559  
          * Getter for the parent frame.
 560  
          * @return parent frame.
 561  
          */
 562  
         public FieldFrame getParent() {
 563  404
             return parent;
 564  
         }
 565  
 
 566  
         /**
 567  
          * Getter for frame's children.
 568  
          * @return children of this frame.
 569  
          */
 570  
         public Set<FieldFrame> getChildren() {
 571  192
             return Collections.unmodifiableSet(children);
 572  
         }
 573  
 
 574  
         /**
 575  
          * Add child frame to this frame.
 576  
          * @param child frame to add.
 577  
          */
 578  
         public void addChild(FieldFrame child) {
 579  129
             children.add(child);
 580  129
         }
 581  
 
 582  
         /**
 583  
          * Add field to this FieldFrame.
 584  
          * @param field the ast of the field.
 585  
          */
 586  
         public void addField(DetailAST field) {
 587  119
             fields.add(field);
 588  119
         }
 589  
 
 590  
         /**
 591  
          * Sets isClassOrEnum.
 592  
          * @param value value to set.
 593  
          */
 594  
         public void setClassOrEnumOrEnumConstDef(boolean value) {
 595  36
             classOrEnumOrEnumConstDef = value;
 596  36
         }
 597  
 
 598  
         /**
 599  
          * Getter for classOrEnumOrEnumConstDef.
 600  
          * @return classOrEnumOrEnumConstDef.
 601  
          */
 602  
         public boolean isClassOrEnumOrEnumConstDef() {
 603  243
             return classOrEnumOrEnumConstDef;
 604  
         }
 605  
 
 606  
         /**
 607  
          * Add method call to this frame.
 608  
          * @param methodCall METHOD_CALL ast.
 609  
          */
 610  
         public void addMethodCall(DetailAST methodCall) {
 611  236
             methodCalls.add(methodCall);
 612  236
         }
 613  
 
 614  
         /**
 615  
          * Determines whether this FieldFrame contains the field.
 616  
          * @param name name of the field to check.
 617  
          * @return true if this FieldFrame contains instance field field.
 618  
          */
 619  
         public DetailAST findField(String name) {
 620  330
             DetailAST resultField = null;
 621  330
             for (DetailAST field: fields) {
 622  434
                 if (getFieldName(field).equals(name)) {
 623  147
                     resultField = field;
 624  147
                     break;
 625  
                 }
 626  287
             }
 627  330
             return resultField;
 628  
         }
 629  
 
 630  
         /**
 631  
          * Getter for frame's method calls.
 632  
          * @return method calls of this frame.
 633  
          */
 634  
         public Set<DetailAST> getMethodCalls() {
 635  129
             return Collections.unmodifiableSet(methodCalls);
 636  
         }
 637  
 
 638  
         /**
 639  
          * Get the name of the field.
 640  
          * @param field to get the name from.
 641  
          * @return name of the field.
 642  
          */
 643  
         private static String getFieldName(DetailAST field) {
 644  434
             return field.findFirstToken(TokenTypes.IDENT).getText();
 645  
         }
 646  
     }
 647  
 }