StaticMethodCandidateCheck.java

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // checkstyle: Checks Java source code for adherence to a set of rules.
  3. // Copyright (C) 2001-2019 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. package com.github.sevntu.checkstyle.checks.design;

  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.LinkedList;
  23. import java.util.List;
  24. import java.util.Set;

  25. import com.google.common.base.Optional;
  26. import com.google.common.base.Predicate;
  27. import com.google.common.collect.Iterables;
  28. import com.google.common.collect.Sets;
  29. import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
  30. import com.puppycrawl.tools.checkstyle.api.DetailAST;
  31. import com.puppycrawl.tools.checkstyle.api.TokenTypes;
  32. import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;

  33. /**
  34.  * Checks whether {@code private} methods can be declared as {@code static}.
  35.  *
  36.  * <p>The check has option {@code skippedMethods} which allows to specify the
  37.  * list of comma separated names of methods to skip during the check. By default
  38.  * the private methods which a class can have when it implements
  39.  * <a href="https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html">
  40.  * Serializable</a> are skipped: "readObject, writeObject, readObjectNoData, readResolve,
  41.  * writeReplace".
  42.  *
  43.  * <p>The following configuration allows to skip method {@code foo} and {@code bar}:
  44.  * <pre>
  45.  *     &lt;module name=&quot;NestedSwitchCheck&quot;&gt;
  46.  *         &lt;property name=&quot;skippedMethods&quot; value=&quot;foo, bar&quot;/&gt;
  47.  *     &lt;/module&gt;
  48.  * </pre>
  49.  *
  50.  * <p>Limitations:
  51.  * <ul>
  52.  * <li>
  53.  * Due to limitation of Checkstyle, there is no ability to distinguish
  54.  * overloaded methods, so we skip them from candidates.
  55.  * </li>
  56.  * <li>
  57.  * Private methods called by reflection are not supported and have to be suppressed.
  58.  * </li>
  59.  * </ul>
  60.  * @author Vladislav Lisetskiy
  61.  * @since 1.17.0
  62.  */
  63. public class StaticMethodCandidateCheck extends AbstractCheck {

  64.     /** Warning message key. */
  65.     public static final String MSG_KEY = "static.method.candidate";

  66.     /** Comma literal. */
  67.     private static final String COMMA_SEPARATOR = ",";

  68.     /** Default method names to skip during the check. */
  69.     private static final String[] DEFAULT_SKIPPED_METHODS = new String[] {
  70.         "writeObject",
  71.         "readObject",
  72.         "readObjectNoData",
  73.         "readResolve",
  74.         "writeReplace",
  75.     };

  76.     /** Array of tokens which are frames. */
  77.     private static final int[] FRAME_TOKENS = new int[] {
  78.         TokenTypes.CLASS_DEF,
  79.         TokenTypes.METHOD_DEF,
  80.         TokenTypes.LITERAL_IF,
  81.         TokenTypes.LITERAL_FOR,
  82.         TokenTypes.LITERAL_WHILE,
  83.         TokenTypes.LITERAL_DO,
  84.         TokenTypes.LITERAL_CATCH,
  85.         TokenTypes.LITERAL_TRY,
  86.         TokenTypes.ENUM_DEF,
  87.         TokenTypes.ENUM_CONSTANT_DEF,
  88.         TokenTypes.STATIC_INIT,
  89.         TokenTypes.INSTANCE_INIT,
  90.         TokenTypes.CTOR_DEF,
  91.         TokenTypes.INTERFACE_DEF,
  92.     };

  93.     /** Method names to skip during the check. */
  94.     private List<String> skippedMethods = Arrays.asList(DEFAULT_SKIPPED_METHODS);

  95.     /** Stack of sets of field names, one for each class of a set of nested classes. */
  96.     private Frame currentFrame;

  97.     /**
  98.      * Sets custom skipped methods.
  99.      * @param skippedMethods user's skipped methods.
  100.      */
  101.     public void setSkippedMethods(String skippedMethods) {
  102.         final List<String> customSkippedMethods = new ArrayList<>();
  103.         final String[] splitSkippedMethods = skippedMethods.split(COMMA_SEPARATOR);
  104.         for (String skippedMethod : splitSkippedMethods) {
  105.             customSkippedMethods.add(skippedMethod.trim());
  106.         }
  107.         this.skippedMethods = customSkippedMethods;
  108.     }

  109.     @Override
  110.     public int[] getAcceptableTokens() {
  111.         return new int[] {
  112.             TokenTypes.CLASS_DEF,
  113.             TokenTypes.METHOD_DEF,
  114.             TokenTypes.LITERAL_IF,
  115.             TokenTypes.LITERAL_FOR,
  116.             TokenTypes.LITERAL_WHILE,
  117.             TokenTypes.LITERAL_DO,
  118.             TokenTypes.LITERAL_CATCH,
  119.             TokenTypes.LITERAL_TRY,
  120.             TokenTypes.VARIABLE_DEF,
  121.             TokenTypes.PARAMETER_DEF,
  122.             TokenTypes.ENUM_DEF,
  123.             TokenTypes.ENUM_CONSTANT_DEF,
  124.             TokenTypes.EXPR,
  125.             TokenTypes.STATIC_INIT,
  126.             TokenTypes.INSTANCE_INIT,
  127.             TokenTypes.LITERAL_NEW,
  128.             TokenTypes.LITERAL_THIS,
  129.             TokenTypes.CTOR_DEF,
  130.             TokenTypes.TYPE,
  131.             TokenTypes.TYPE_ARGUMENT,
  132.             TokenTypes.TYPE_PARAMETER,
  133.             TokenTypes.INTERFACE_DEF,
  134.             TokenTypes.LITERAL_SUPER,
  135.         };
  136.     }

  137.     @Override
  138.     public int[] getDefaultTokens() {
  139.         return getAcceptableTokens();
  140.     }

  141.     @Override
  142.     public int[] getRequiredTokens() {
  143.         return getAcceptableTokens();
  144.     }

  145.     @Override
  146.     public void beginTree(DetailAST rootAST) {
  147.         currentFrame = new Frame(null);

  148.         Arrays.sort(FRAME_TOKENS);
  149.     }

  150.     @Override
  151.     public void visitToken(final DetailAST ast) {
  152.         switch (ast.getType()) {
  153.             case TokenTypes.VARIABLE_DEF:
  154.             case TokenTypes.PARAMETER_DEF:
  155.                 currentFrame.addField(ast);
  156.                 break;
  157.             case TokenTypes.EXPR:
  158.                 currentFrame.addExpr(ast);
  159.                 break;
  160.             case TokenTypes.LITERAL_SUPER:
  161.             case TokenTypes.LITERAL_THIS:
  162.                 currentFrame.hasLiteralThisOrSuper = true;
  163.                 break;
  164.             case TokenTypes.TYPE:
  165.             case TokenTypes.TYPE_ARGUMENT:
  166.                 final Optional<DetailAST> firstChild = Optional.fromNullable(ast.getFirstChild());
  167.                 if (firstChild.isPresent()
  168.                         && firstChild.get().getType() == TokenTypes.IDENT) {
  169.                     currentFrame.addType(firstChild.get().getText());
  170.                 }
  171.                 break;
  172.             case TokenTypes.TYPE_PARAMETER:
  173.                 currentFrame.addTypeVariable(ast.getFirstChild().getText());
  174.                 break;
  175.             case TokenTypes.METHOD_DEF:
  176.                 Frame frame = createMethodFrame(currentFrame, ast);

  177.                 currentFrame.addMethod(ast);
  178.                 currentFrame.addChild(frame);
  179.                 currentFrame = frame;
  180.                 break;
  181.             case TokenTypes.LITERAL_NEW:
  182.                 if (isAnonymousClass(ast)) {
  183.                     frame = new Frame(currentFrame);
  184.                     // anonymous classes can't have static methods
  185.                     frame.isShouldBeChecked = false;

  186.                     currentFrame.addChild(frame);
  187.                     currentFrame = frame;
  188.                 }
  189.                 break;
  190.             case TokenTypes.ENUM_CONSTANT_DEF:
  191.                 frame = new Frame(currentFrame);
  192.                 // ENUM_CONSTANT_DEF can't have static methods
  193.                 frame.isShouldBeChecked = false;

  194.                 currentFrame.addEnumConst(ast);
  195.                 currentFrame.addChild(frame);
  196.                 currentFrame = frame;
  197.                 break;
  198.             default:
  199.                 frame = createFrame(currentFrame, ast);

  200.                 currentFrame.addChild(frame);
  201.                 currentFrame = frame;
  202.         }
  203.     }

  204.     @Override
  205.     public void leaveToken(DetailAST ast) {
  206.         if (isFrame(ast)
  207.                 || isAnonymousClass(ast)) {
  208.             currentFrame = currentFrame.parent;
  209.         }
  210.     }

  211.     @Override
  212.     public void finishTree(DetailAST ast) {
  213.         // result of checkFrame() is only used while checking methods and not needed here
  214.         // as we start from the root of the Frame tree
  215.         checkFrame(currentFrame);
  216.     }

  217.     /**
  218.      * Create a new Frame from METHOD_DEF ast.
  219.      * @param ast METHOD_DEF ast.
  220.      * @param parentFrame the parent frame for a new frame.
  221.      * @return a new frame with the set fields.
  222.      */
  223.     private Frame createMethodFrame(Frame parentFrame, DetailAST ast) {
  224.         final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
  225.         final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText();
  226.         final Frame frame = new Frame(parentFrame);
  227.         if (modifiersAst.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null
  228.                 && modifiersAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null
  229.                 && !skippedMethods.contains(methodName)) {
  230.             frame.isPrivateMethod = true;
  231.             frame.ast = ast;
  232.             frame.frameName = getIdentText(ast);
  233.         }
  234.         else {
  235.             // non-private or static methods are not checked and can't have static methods
  236.             // because local classes cannot be declared as static
  237.             frame.isShouldBeChecked = false;
  238.         }
  239.         return frame;
  240.     }

  241.     /**
  242.      * Check whether the ast is an anonymous class.
  243.      * @param ast the ast to check.
  244.      * @return if the checked ast is an anonymous class.
  245.      */
  246.     private static boolean isAnonymousClass(DetailAST ast) {
  247.         final int astType = ast.getType();
  248.         return astType == TokenTypes.LITERAL_NEW
  249.                 && ast.findFirstToken(TokenTypes.OBJBLOCK) != null;
  250.     }

  251.     /**
  252.      * Create a new Frame from CLASS_DEF, LITERAL_IF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO,
  253.      * LITERAL_CATCH, LITERAL_TRY, CTOR_DEF, ENUM_DEF.
  254.      * @param ast the processed ast.
  255.      * @param parentFrame the parent frame for a new frame.
  256.      * @return a new frame with the set fields.
  257.      */
  258.     private static Frame createFrame(Frame parentFrame, DetailAST ast) {
  259.         final Frame frame = new Frame(parentFrame);
  260.         final int astType = ast.getType();
  261.         if (astType == TokenTypes.CLASS_DEF
  262.                 || astType == TokenTypes.ENUM_DEF) {
  263.             if (astType == TokenTypes.CLASS_DEF
  264.                     && !ScopeUtil.isOuterMostType(ast)
  265.                     && !hasStaticModifier(ast)) {
  266.                 // local and inner classes can't have static methods
  267.                 frame.isShouldBeChecked = false;
  268.             }
  269.             frame.frameName = getIdentText(ast);
  270.             frame.isClassOrEnum = true;
  271.         }
  272.         else if (astType == TokenTypes.STATIC_INIT
  273.                 || astType == TokenTypes.INSTANCE_INIT
  274.                 || astType == TokenTypes.CTOR_DEF
  275.                 || astType == TokenTypes.INTERFACE_DEF) {
  276.             frame.isShouldBeChecked = false;
  277.         }
  278.         return frame;
  279.     }

  280.     /**
  281.      * Check whether the ast is a Frame.
  282.      * @param ast the ast to check.
  283.      * @return true if the checked ast is a Frame.
  284.      */
  285.     private static boolean isFrame(DetailAST ast) {
  286.         final int astType = ast.getType();
  287.         return Arrays.binarySearch(FRAME_TOKENS, astType) >= 0;
  288.     }

  289.     /**
  290.      * Check whether the frame or its parent, which is a private method,
  291.      * is a static method candidate.
  292.      * @param parentFrame the frame to check.
  293.      * @return true if the frame or its parent, which is a private method,
  294.      *     is a static method candidate.
  295.      */
  296.     private boolean checkFrame(Frame parentFrame) {
  297.         boolean isStaticCandidate = true;
  298.         for (Frame frame: parentFrame.children) {
  299.             if (frame.isShouldBeChecked) {
  300.                 isStaticCandidate = checkFrame(frame);
  301.                 if (!frame.isClassOrEnum) {
  302.                     isStaticCandidate = isStaticCandidate
  303.                             && !frame.hasLiteralThisOrSuper
  304.                             && isFrameExpressionsAcceptable(frame)
  305.                             && isFrameTypesAcceptable(frame);
  306.                     if (frame.isPrivateMethod) {
  307.                         if (isStaticCandidate) {
  308.                             log(frame.ast, MSG_KEY, frame.frameName);
  309.                         }
  310.                     }
  311.                     else if (!isStaticCandidate) {
  312.                         break;
  313.                     }
  314.                 }
  315.             }
  316.         }
  317.         return isStaticCandidate;
  318.     }

  319.     /**
  320.      * Get the name of the field.
  321.      * @param field to get the name from.
  322.      * @return name of the field.
  323.      */
  324.     private static String getIdentText(DetailAST field) {
  325.         return field.findFirstToken(TokenTypes.IDENT).getText();
  326.     }

  327.     /**
  328.      * Whether the ast has static modifier.
  329.      * @param ast the ast to check.
  330.      * @return true if the ast has static modifier.
  331.      */
  332.     private static boolean hasStaticModifier(DetailAST ast) {
  333.         return ast.findFirstToken(TokenTypes.MODIFIERS)
  334.             .findFirstToken(TokenTypes.LITERAL_STATIC) != null;
  335.     }

  336.     /**
  337.      * Check expressions in the given frame for being acceptable is static methods.
  338.      * @param frame the frame to check.
  339.      * @return true if the currently checked method
  340.      *     is still a static method candidate.
  341.      */
  342.     private static boolean isFrameExpressionsAcceptable(final Frame frame) {
  343.         final Predicate<DetailAST> predicate = new Predicate<DetailAST>() {
  344.             @Override
  345.             public boolean apply(DetailAST ast) {
  346.                 return !isExprAcceptable(frame, ast);
  347.             }
  348.         };
  349.         final Optional<DetailAST> result = Iterables.tryFind(frame.expressions, predicate);
  350.         return !result.isPresent();
  351.     }

  352.     /**
  353.      * Check types in the given frame for being acceptable in static methods.
  354.      * @param frame the frame to check.
  355.      * @return true if the currently checked method
  356.      *     is still a static method candidate.
  357.      */
  358.     private static boolean isFrameTypesAcceptable(final Frame frame) {
  359.         final Predicate<String> predicate = new Predicate<String>() {
  360.             @Override
  361.             public boolean apply(String type) {
  362.                 final Optional<Frame> typeFrame = findFrameByName(frame, type);
  363.                 return typeFrame.isPresent() && !typeFrame.get().isShouldBeChecked
  364.                         || findTypeVariable(frame, type);
  365.             }
  366.         };
  367.         final Optional<String> result = Iterables.tryFind(frame.types, predicate);
  368.         return !result.isPresent();
  369.     }

  370.     /**
  371.      * Check whether the expression only contains fields and method calls accepted
  372.      * in static methods (which can be checked).
  373.      * @param frame the frame where the expression is located.
  374.      * @param expr the expression to check.
  375.      * @return true if the currently checked method
  376.      *     is still a static method candidate.
  377.      */
  378.     private static boolean isExprAcceptable(Frame frame, DetailAST expr) {
  379.         boolean isStaticCandidate = true;
  380.         if (expr.branchContains(TokenTypes.IDENT)) {
  381.             DetailAST childAst = expr.getFirstChild();
  382.             while (childAst != null
  383.                     && isStaticCandidate) {
  384.                 if (childAst.getType() == TokenTypes.METHOD_CALL) {
  385.                     isStaticCandidate = isStaticMethod(frame, childAst)
  386.                             && isExprAcceptable(frame, childAst);
  387.                 }
  388.                 else if (childAst.getType() == TokenTypes.IDENT
  389.                         && isIdentShouldBeChecked(expr)) {
  390.                     isStaticCandidate = isStaticFieldOrLocalVariable(frame, childAst);
  391.                 }
  392.                 else if (childAst.getType() == TokenTypes.LITERAL_NEW) {
  393.                     final Optional<Frame> typeFrame = findFrameByName(
  394.                             frame, childAst.getFirstChild().getText());
  395.                     isStaticCandidate = isTypeFrameShouldBeChecked(typeFrame)
  396.                             && isExprAcceptable(frame, childAst);
  397.                 }
  398.                 else {
  399.                     isStaticCandidate = isExprAcceptable(frame, childAst);
  400.                 }
  401.                 childAst = childAst.getNextSibling();
  402.             }
  403.         }
  404.         return isStaticCandidate;
  405.     }

  406.     /**
  407.      * Find a frame with the specified name among the current frame and its parents.
  408.      * @param frame the frame to start searching from.
  409.      * @param frameName the specified name.
  410.      * @return search result.
  411.      */
  412.     private static Optional<Frame> findFrameByName(Frame frame, String frameName) {
  413.         Optional<Frame> result = Optional.absent();
  414.         Optional<Frame> parentFrame = Optional.of(frame.parent);
  415.         while (parentFrame.isPresent() && !result.isPresent()) {
  416.             for (Frame child: parentFrame.get().children) {
  417.                 if (child.isClassOrEnum
  418.                         && frameName.equals(child.frameName)) {
  419.                     result = Optional.of(child);
  420.                     break;
  421.                 }
  422.             }
  423.             parentFrame = Optional.fromNullable(parentFrame.get().parent);
  424.         }
  425.         return result;
  426.     }

  427.     /**
  428.      * Find a type variable with the specified name.
  429.      * @param frame the frame to start searching from.
  430.      * @param type the name of the type variable to find.
  431.      * @return true if a type variable with the specified name is found.
  432.      */
  433.     private static boolean findTypeVariable(Frame frame, String type) {
  434.         boolean result = false;
  435.         Optional<Frame> searchFrame = Optional.of(frame);
  436.         while (!result && searchFrame.isPresent()) {
  437.             result = searchFrame.get().typeVariables.contains(type);
  438.             searchFrame = Optional.fromNullable(searchFrame.get().parent);
  439.         }
  440.         return result;
  441.     }

  442.     /**
  443.      * Check whether a {@code static} method is called.
  444.      * @param frame the frame where the method call is located.
  445.      * @param methodCallAst METHOD_CALL ast.
  446.      * @return true if a {@code static} method is called.
  447.      */
  448.     private static boolean isStaticMethod(Frame frame, DetailAST methodCallAst) {
  449.         boolean result = false;
  450.         final DetailAST firstChild = methodCallAst.getFirstChild();
  451.         if (firstChild.getType() == TokenTypes.DOT) {
  452.             final DetailAST objCalledOn = getTheLeftmostIdent(methodCallAst);
  453.             if (objCalledOn.getType() == TokenTypes.IDENT) {
  454.                 final Optional<DetailAST> field = findField(frame, objCalledOn);
  455.                 if (field.isPresent()) {
  456.                     result = isAcceptableField(field.get());
  457.                 }
  458.                 else if (findFrameByName(frame, objCalledOn.getText()).isPresent()) {
  459.                     result = true;
  460.                 }
  461.             }
  462.             else {
  463.                 result = true;
  464.             }
  465.         }
  466.         else {
  467.             result = findStaticMethod(frame, methodCallAst, firstChild.getText());
  468.         }
  469.         return result;
  470.     }

  471.     /**
  472.      * Determine whether the method call should be checked.
  473.      * @param parentAst parent ast of the ident.
  474.      * @return true, if LITERAL_THIS is used or the usage is too complex to check.
  475.      */
  476.     private static boolean isIdentShouldBeChecked(DetailAST parentAst) {
  477.         final int parentAstType = parentAst.getType();
  478.         return parentAstType != TokenTypes.LITERAL_NEW
  479.                 && parentAstType != TokenTypes.TYPE
  480.                 && parentAstType != TokenTypes.METHOD_DEF;
  481.     }

  482.     /**
  483.      * Check whether a {@code static} field or a local variable is used.
  484.      * @param frame the frame where the field is located.
  485.      * @param identAst the identifier ast of the checked field.
  486.      * @return true if the field is {@code static} or local.
  487.      */
  488.     private static boolean isStaticFieldOrLocalVariable(Frame frame, DetailAST identAst) {
  489.         final boolean result;
  490.         final int parentType = identAst.getParent().getType();
  491.         if (parentType == TokenTypes.DOT) {
  492.             if (identAst.getNextSibling() == null) {
  493.                 result = true;
  494.             }
  495.             else {
  496.                 final Optional<DetailAST> field = findField(frame, identAst);
  497.                 if (field.isPresent()) {
  498.                     result = isAcceptableField(field.get());
  499.                 }
  500.                 else {
  501.                     result = findFrameByName(frame, identAst.getText()).isPresent();
  502.                 }
  503.             }
  504.         }
  505.         else if (parentType == TokenTypes.METHOD_CALL) {
  506.             result = true;
  507.         }
  508.         else {
  509.             final Optional<DetailAST> field = findField(frame, identAst);
  510.             result = field.isPresent() && isAcceptableField(field.get());
  511.         }
  512.         return result;
  513.     }

  514.     /**
  515.      * Whether the type frame should be checked.
  516.      * @param typeFrame the frame of the type to check.
  517.      * @return true if the type frame should be checked.
  518.      */
  519.     private static boolean isTypeFrameShouldBeChecked(final Optional<Frame> typeFrame) {
  520.         return !typeFrame.isPresent()
  521.                     || typeFrame.get().isShouldBeChecked;
  522.     }

  523.     /**
  524.      * Get the leftmost ident of the method call.
  525.      * @param mCall METHOD_CALL to get ident from.
  526.      * @return the leftmost's ident DetailAST.
  527.      */
  528.     private static DetailAST getTheLeftmostIdent(DetailAST mCall) {
  529.         DetailAST result = mCall.getFirstChild();
  530.         while (result.getChildCount() != 0
  531.                 && result.getType() != TokenTypes.METHOD_CALL) {
  532.             result = result.getFirstChild();
  533.         }
  534.         return result;
  535.     }

  536.     /**
  537.      * Find a static field definition or local variable.
  538.      * @param startFrame the frame to start searching from.
  539.      * @param identAst the IDENT ast to check.
  540.      * @return search result.
  541.      */
  542.     private static Optional<DetailAST> findField(Frame startFrame, DetailAST identAst) {
  543.         Optional<DetailAST> result = Optional.absent();
  544.         Optional<Frame> frame = Optional.of(startFrame);
  545.         final String fieldName = identAst.getText();
  546.         while (frame.isPresent() && !result.isPresent()) {
  547.             final Optional<DetailAST> field = frame.get().findFieldInFrame(fieldName);
  548.             if (field.isPresent()) {
  549.                 if (!isLocalVariable(field.get())
  550.                         || checkFieldLocation(field.get(), identAst)) {
  551.                     result = field;
  552.                 }
  553.             }
  554.             else {
  555.                 result = frame.get().findEnumConstInFrame(fieldName);
  556.             }
  557.             frame = Optional.fromNullable(frame.get().parent);
  558.         }
  559.         return result;
  560.     }

  561.     /**
  562.      * Check whether the field is acceptable is a {@code static} method.
  563.      * @param field the checked field.
  564.      * @return true if the checked field is acceptable is a {@code static} method.
  565.      */
  566.     private static boolean isAcceptableField(DetailAST field) {
  567.         boolean result = false;
  568.         if (isLocalVariable(field)
  569.                 || field.getType() == TokenTypes.ENUM_CONSTANT_DEF
  570.                 || hasStaticModifier(field)) {
  571.             result = true;
  572.         }
  573.         return result;
  574.     }

  575.     /**
  576.      * Find a {@code static} method definition of the specified method call
  577.      * and ensure that there are no non-{@code static} methods with the same name and
  578.      * number of parameters in the current frame or its parents.
  579.      * @param startFrame the frame to start searching from.
  580.      * @param methodCall METHOD_CALL ast.
  581.      * @param checkedMethodName the name of the called method.
  582.      * @return true if a {@code static} method definition of the specified method call
  583.      *     is found and no non-{@code static} with the same name and number of parameters found.
  584.      */
  585.     private static boolean findStaticMethod(Frame startFrame, DetailAST methodCall,
  586.                                             String checkedMethodName) {
  587.         final int argsNumber = methodCall.findFirstToken(TokenTypes.ELIST).getChildCount();
  588.         Optional<Frame> frame = Optional.of(startFrame);

  589.         // if we do not find neither static nor non-static method, then we cannot claim
  590.         // that the checked method can be static
  591.         boolean hasNonStaticMethod = false;
  592.         boolean hasStaticMethod = false;
  593.         while (!hasNonStaticMethod
  594.                 && frame.isPresent()) {
  595.             for (DetailAST method: frame.get().methods) {
  596.                 final DetailAST parametersAst = method.findFirstToken(TokenTypes.PARAMETERS);

  597.                 if (checkedMethodName.equals(getIdentText(method))
  598.                         && (parametersAst.getChildCount() == argsNumber
  599.                             || parametersAst.branchContains(TokenTypes.ELLIPSIS))) {
  600.                     final DetailAST modifiersAst = method.findFirstToken(TokenTypes.MODIFIERS);

  601.                     if (modifiersAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
  602.                         // if a non-static method is found, then the checked method
  603.                         // cannot be static
  604.                         hasNonStaticMethod = true;
  605.                         break;
  606.                     }
  607.                     else {
  608.                         // if a static method is found, we keep searching for a similar
  609.                         // non-static one to the end of the frame and if a non-static
  610.                         // method is not found, then the checked method is still
  611.                         // a static method candidate
  612.                         hasStaticMethod = true;
  613.                     }
  614.                 }
  615.             }
  616.             frame = Optional.fromNullable(frame.get().parent);
  617.         }
  618.         return hasStaticMethod
  619.                 && !hasNonStaticMethod;
  620.     }

  621.     /**
  622.      * Check whether the field is a local variable.
  623.      * @param ast VARIABLE_DEF ast.
  624.      * @return true if the field is a local variable.
  625.      */
  626.     private static boolean isLocalVariable(DetailAST ast) {
  627.         final int parentType = ast.getParent().getParent().getType();
  628.         return parentType != TokenTypes.CLASS_DEF
  629.                 && parentType != TokenTypes.ENUM_DEF;
  630.     }

  631.     /**
  632.      * Check whether the field is declared before its usage in case of methods.
  633.      * @param field field to check.
  634.      * @param objCalledOn object equals method called on.
  635.      * @return true if the field is declared before the method call.
  636.      */
  637.     private static boolean checkFieldLocation(DetailAST field, DetailAST objCalledOn) {
  638.         boolean result = false;
  639.         if (field.getLineNo() < objCalledOn.getLineNo()
  640.                 || field.getLineNo() == objCalledOn.getLineNo()
  641.                     && field.getColumnNo() < objCalledOn.getColumnNo()) {
  642.             result = true;
  643.         }
  644.         return result;
  645.     }

  646.     /**
  647.      * Contains information about the frame.
  648.      */
  649.     private static class Frame {

  650.         /** Name of the class, enum or method. */
  651.         private String frameName;

  652.         /** Parent frame. */
  653.         private final Frame parent;

  654.         /** List of frame's children. */
  655.         private final List<Frame> children = new LinkedList<>();

  656.         /** List of fields. */
  657.         private final List<DetailAST> fields = new LinkedList<>();

  658.         /** List of methods. */
  659.         private final List<DetailAST> methods = new LinkedList<>();

  660.         /** List of typeVariables. */
  661.         private final List<String> typeVariables = new LinkedList<>();

  662.         /** List of method calls. */
  663.         private final List<DetailAST> expressions = new ArrayList<>();

  664.         /** List of types. */
  665.         private final Set<String> types = Sets.newHashSet();

  666.         /** Set of enumConstants. */
  667.         private final Set<DetailAST> enumConstants = Sets.newHashSet();

  668.         /** Whether the frame is CLASS_DEF or ENUM_DEF. */
  669.         private boolean isClassOrEnum;

  670.         /** Whether the frame is {@code private} METHOD_DEF. */
  671.         private boolean isPrivateMethod;

  672.         /**
  673.          * Whether the frame should be checked.
  674.          * It is used in order not to check non-private methods, static methods
  675.          * and frames, where static methods cannot be defined: local and inner classes,
  676.          * constructors, anonymous classes, enum constant definitions, initializers.
  677.          */
  678.         private boolean isShouldBeChecked = true;

  679.         /** Whether the frame has LITERAL_THIS or LITERAL_SUPER. */
  680.         private boolean hasLiteralThisOrSuper;

  681.         /** AST where the frame is declared. */
  682.         private DetailAST ast;

  683.         /**
  684.          * Creates new frame.
  685.          * @param parent parent frame.
  686.          */
  687.         /* package */ Frame(Frame parent) {
  688.             this.parent = parent;
  689.         }

  690.         /**
  691.          * Add method call to this Frame.
  692.          * @param exprAst EXPR ast.
  693.          */
  694.         public void addExpr(DetailAST exprAst) {
  695.             expressions.add(exprAst);
  696.         }

  697.         /**
  698.          * Add child frame to this frame.
  699.          * @param child frame to add.
  700.          */
  701.         public void addChild(Frame child) {
  702.             children.add(child);
  703.         }

  704.         /**
  705.          * Add field to this Frame.
  706.          * @param field the ast of the field.
  707.          */
  708.         public void addField(DetailAST field) {
  709.             fields.add(field);
  710.         }

  711.         /**
  712.          * Add method definition to this frame.
  713.          * @param method METHOD_DEF ast.
  714.          */
  715.         public void addMethod(DetailAST method) {
  716.             methods.add(method);
  717.         }

  718.         /**
  719.          * Add method call to this frame.
  720.          * @param enumConst ENUM_CONST_DEF ast.
  721.          */
  722.         public void addEnumConst(DetailAST enumConst) {
  723.             enumConstants.add(enumConst);
  724.         }

  725.         /**
  726.          * Add type variable name to this frame.
  727.          * @param typeVariable the type variable name.
  728.          */
  729.         public void addTypeVariable(String typeVariable) {
  730.             typeVariables.add(typeVariable);
  731.         }

  732.         /**
  733.          * Add type to this frame.
  734.          * @param type the type name.
  735.          */
  736.         public void addType(String type) {
  737.             types.add(type);
  738.         }

  739.         /**
  740.          * Determine whether this Frame contains the field.
  741.          * @param name the name of the field to check.
  742.          * @return search result.
  743.          */
  744.         public Optional<DetailAST> findFieldInFrame(final String name) {
  745.             final Predicate<DetailAST> predicate = new Predicate<DetailAST>() {
  746.                 @Override
  747.                 public boolean apply(DetailAST field) {
  748.                     return getIdentText(field).equals(name);
  749.                 }
  750.             };
  751.             return Iterables.tryFind(fields, predicate);
  752.         }

  753.         /**
  754.          * Determine whether this Frame contains the enum constant.
  755.          * @param name the name of the enum constant to check.
  756.          * @return search result.
  757.          */
  758.         public Optional<DetailAST> findEnumConstInFrame(final String name) {
  759.             final Predicate<DetailAST> predicate = new Predicate<DetailAST>() {
  760.                 @Override
  761.                 public boolean apply(DetailAST enumConstant) {
  762.                     return getIdentText(enumConstant).equals(name);
  763.                 }
  764.             };
  765.             return Iterables.tryFind(enumConstants, predicate);
  766.         }

  767.     }

  768. }