StaticMethodCandidateCheck.java
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2019 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.github.sevntu.checkstyle.checks.design;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
/**
* Checks whether {@code private} methods can be declared as {@code static}.
*
* <p>The check has option {@code skippedMethods} which allows to specify the
* list of comma separated names of methods to skip during the check. By default
* the private methods which a class can have when it implements
* <a href="https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html">
* Serializable</a> are skipped: "readObject, writeObject, readObjectNoData, readResolve,
* writeReplace".
*
* <p>The following configuration allows to skip method {@code foo} and {@code bar}:
* <pre>
* <module name="NestedSwitchCheck">
* <property name="skippedMethods" value="foo, bar"/>
* </module>
* </pre>
*
* <p>Limitations:
* <ul>
* <li>
* Due to limitation of Checkstyle, there is no ability to distinguish
* overloaded methods, so we skip them from candidates.
* </li>
* <li>
* Private methods called by reflection are not supported and have to be suppressed.
* </li>
* </ul>
* @author Vladislav Lisetskiy
* @since 1.17.0
*/
public class StaticMethodCandidateCheck extends AbstractCheck {
/** Warning message key. */
public static final String MSG_KEY = "static.method.candidate";
/** Comma literal. */
private static final String COMMA_SEPARATOR = ",";
/** Default method names to skip during the check. */
private static final String[] DEFAULT_SKIPPED_METHODS = new String[] {
"writeObject",
"readObject",
"readObjectNoData",
"readResolve",
"writeReplace",
};
/** Array of tokens which are frames. */
private static final int[] FRAME_TOKENS = new int[] {
TokenTypes.CLASS_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.LITERAL_IF,
TokenTypes.LITERAL_FOR,
TokenTypes.LITERAL_WHILE,
TokenTypes.LITERAL_DO,
TokenTypes.LITERAL_CATCH,
TokenTypes.LITERAL_TRY,
TokenTypes.ENUM_DEF,
TokenTypes.ENUM_CONSTANT_DEF,
TokenTypes.STATIC_INIT,
TokenTypes.INSTANCE_INIT,
TokenTypes.CTOR_DEF,
TokenTypes.INTERFACE_DEF,
};
/** Method names to skip during the check. */
private List<String> skippedMethods = Arrays.asList(DEFAULT_SKIPPED_METHODS);
/** Stack of sets of field names, one for each class of a set of nested classes. */
private Frame currentFrame;
/**
* Sets custom skipped methods.
* @param skippedMethods user's skipped methods.
*/
public void setSkippedMethods(String skippedMethods) {
final List<String> customSkippedMethods = new ArrayList<>();
final String[] splitSkippedMethods = skippedMethods.split(COMMA_SEPARATOR);
for (String skippedMethod : splitSkippedMethods) {
customSkippedMethods.add(skippedMethod.trim());
}
this.skippedMethods = customSkippedMethods;
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.CLASS_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.LITERAL_IF,
TokenTypes.LITERAL_FOR,
TokenTypes.LITERAL_WHILE,
TokenTypes.LITERAL_DO,
TokenTypes.LITERAL_CATCH,
TokenTypes.LITERAL_TRY,
TokenTypes.VARIABLE_DEF,
TokenTypes.PARAMETER_DEF,
TokenTypes.ENUM_DEF,
TokenTypes.ENUM_CONSTANT_DEF,
TokenTypes.EXPR,
TokenTypes.STATIC_INIT,
TokenTypes.INSTANCE_INIT,
TokenTypes.LITERAL_NEW,
TokenTypes.LITERAL_THIS,
TokenTypes.CTOR_DEF,
TokenTypes.TYPE,
TokenTypes.TYPE_ARGUMENT,
TokenTypes.TYPE_PARAMETER,
TokenTypes.INTERFACE_DEF,
TokenTypes.LITERAL_SUPER,
};
}
@Override
public int[] getDefaultTokens() {
return getAcceptableTokens();
}
@Override
public int[] getRequiredTokens() {
return getAcceptableTokens();
}
@Override
public void beginTree(DetailAST rootAST) {
currentFrame = new Frame(null);
Arrays.sort(FRAME_TOKENS);
}
@Override
public void visitToken(final DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.VARIABLE_DEF:
case TokenTypes.PARAMETER_DEF:
currentFrame.addField(ast);
break;
case TokenTypes.EXPR:
currentFrame.addExpr(ast);
break;
case TokenTypes.LITERAL_SUPER:
case TokenTypes.LITERAL_THIS:
currentFrame.hasLiteralThisOrSuper = true;
break;
case TokenTypes.TYPE:
case TokenTypes.TYPE_ARGUMENT:
final Optional<DetailAST> firstChild = Optional.fromNullable(ast.getFirstChild());
if (firstChild.isPresent()
&& firstChild.get().getType() == TokenTypes.IDENT) {
currentFrame.addType(firstChild.get().getText());
}
break;
case TokenTypes.TYPE_PARAMETER:
currentFrame.addTypeVariable(ast.getFirstChild().getText());
break;
case TokenTypes.METHOD_DEF:
Frame frame = createMethodFrame(currentFrame, ast);
currentFrame.addMethod(ast);
currentFrame.addChild(frame);
currentFrame = frame;
break;
case TokenTypes.LITERAL_NEW:
if (isAnonymousClass(ast)) {
frame = new Frame(currentFrame);
// anonymous classes can't have static methods
frame.isShouldBeChecked = false;
currentFrame.addChild(frame);
currentFrame = frame;
}
break;
case TokenTypes.ENUM_CONSTANT_DEF:
frame = new Frame(currentFrame);
// ENUM_CONSTANT_DEF can't have static methods
frame.isShouldBeChecked = false;
currentFrame.addEnumConst(ast);
currentFrame.addChild(frame);
currentFrame = frame;
break;
default:
frame = createFrame(currentFrame, ast);
currentFrame.addChild(frame);
currentFrame = frame;
}
}
@Override
public void leaveToken(DetailAST ast) {
if (isFrame(ast)
|| isAnonymousClass(ast)) {
currentFrame = currentFrame.parent;
}
}
@Override
public void finishTree(DetailAST ast) {
// result of checkFrame() is only used while checking methods and not needed here
// as we start from the root of the Frame tree
checkFrame(currentFrame);
}
/**
* Create a new Frame from METHOD_DEF ast.
* @param ast METHOD_DEF ast.
* @param parentFrame the parent frame for a new frame.
* @return a new frame with the set fields.
*/
private Frame createMethodFrame(Frame parentFrame, DetailAST ast) {
final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText();
final Frame frame = new Frame(parentFrame);
if (modifiersAst.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null
&& modifiersAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null
&& !skippedMethods.contains(methodName)) {
frame.isPrivateMethod = true;
frame.ast = ast;
frame.frameName = getIdentText(ast);
}
else {
// non-private or static methods are not checked and can't have static methods
// because local classes cannot be declared as static
frame.isShouldBeChecked = false;
}
return frame;
}
/**
* Check whether the ast is an anonymous class.
* @param ast the ast to check.
* @return if the checked ast is an anonymous class.
*/
private static boolean isAnonymousClass(DetailAST ast) {
final int astType = ast.getType();
return astType == TokenTypes.LITERAL_NEW
&& ast.findFirstToken(TokenTypes.OBJBLOCK) != null;
}
/**
* Create a new Frame from CLASS_DEF, LITERAL_IF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO,
* LITERAL_CATCH, LITERAL_TRY, CTOR_DEF, ENUM_DEF.
* @param ast the processed ast.
* @param parentFrame the parent frame for a new frame.
* @return a new frame with the set fields.
*/
private static Frame createFrame(Frame parentFrame, DetailAST ast) {
final Frame frame = new Frame(parentFrame);
final int astType = ast.getType();
if (astType == TokenTypes.CLASS_DEF
|| astType == TokenTypes.ENUM_DEF) {
if (astType == TokenTypes.CLASS_DEF
&& !ScopeUtil.isOuterMostType(ast)
&& !hasStaticModifier(ast)) {
// local and inner classes can't have static methods
frame.isShouldBeChecked = false;
}
frame.frameName = getIdentText(ast);
frame.isClassOrEnum = true;
}
else if (astType == TokenTypes.STATIC_INIT
|| astType == TokenTypes.INSTANCE_INIT
|| astType == TokenTypes.CTOR_DEF
|| astType == TokenTypes.INTERFACE_DEF) {
frame.isShouldBeChecked = false;
}
return frame;
}
/**
* Check whether the ast is a Frame.
* @param ast the ast to check.
* @return true if the checked ast is a Frame.
*/
private static boolean isFrame(DetailAST ast) {
final int astType = ast.getType();
return Arrays.binarySearch(FRAME_TOKENS, astType) >= 0;
}
/**
* Check whether the frame or its parent, which is a private method,
* is a static method candidate.
* @param parentFrame the frame to check.
* @return true if the frame or its parent, which is a private method,
* is a static method candidate.
*/
private boolean checkFrame(Frame parentFrame) {
boolean isStaticCandidate = true;
for (Frame frame: parentFrame.children) {
if (frame.isShouldBeChecked) {
isStaticCandidate = checkFrame(frame);
if (!frame.isClassOrEnum) {
isStaticCandidate = isStaticCandidate
&& !frame.hasLiteralThisOrSuper
&& isFrameExpressionsAcceptable(frame)
&& isFrameTypesAcceptable(frame);
if (frame.isPrivateMethod) {
if (isStaticCandidate) {
log(frame.ast, MSG_KEY, frame.frameName);
}
}
else if (!isStaticCandidate) {
break;
}
}
}
}
return isStaticCandidate;
}
/**
* Get the name of the field.
* @param field to get the name from.
* @return name of the field.
*/
private static String getIdentText(DetailAST field) {
return field.findFirstToken(TokenTypes.IDENT).getText();
}
/**
* Whether the ast has static modifier.
* @param ast the ast to check.
* @return true if the ast has static modifier.
*/
private static boolean hasStaticModifier(DetailAST ast) {
return ast.findFirstToken(TokenTypes.MODIFIERS)
.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
}
/**
* Check expressions in the given frame for being acceptable is static methods.
* @param frame the frame to check.
* @return true if the currently checked method
* is still a static method candidate.
*/
private static boolean isFrameExpressionsAcceptable(final Frame frame) {
final Predicate<DetailAST> predicate = new Predicate<DetailAST>() {
@Override
public boolean apply(DetailAST ast) {
return !isExprAcceptable(frame, ast);
}
};
final Optional<DetailAST> result = Iterables.tryFind(frame.expressions, predicate);
return !result.isPresent();
}
/**
* Check types in the given frame for being acceptable in static methods.
* @param frame the frame to check.
* @return true if the currently checked method
* is still a static method candidate.
*/
private static boolean isFrameTypesAcceptable(final Frame frame) {
final Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean apply(String type) {
final Optional<Frame> typeFrame = findFrameByName(frame, type);
return typeFrame.isPresent() && !typeFrame.get().isShouldBeChecked
|| findTypeVariable(frame, type);
}
};
final Optional<String> result = Iterables.tryFind(frame.types, predicate);
return !result.isPresent();
}
/**
* Check whether the expression only contains fields and method calls accepted
* in static methods (which can be checked).
* @param frame the frame where the expression is located.
* @param expr the expression to check.
* @return true if the currently checked method
* is still a static method candidate.
*/
private static boolean isExprAcceptable(Frame frame, DetailAST expr) {
boolean isStaticCandidate = true;
if (expr.branchContains(TokenTypes.IDENT)) {
DetailAST childAst = expr.getFirstChild();
while (childAst != null
&& isStaticCandidate) {
if (childAst.getType() == TokenTypes.METHOD_CALL) {
isStaticCandidate = isStaticMethod(frame, childAst)
&& isExprAcceptable(frame, childAst);
}
else if (childAst.getType() == TokenTypes.IDENT
&& isIdentShouldBeChecked(expr)) {
isStaticCandidate = isStaticFieldOrLocalVariable(frame, childAst);
}
else if (childAst.getType() == TokenTypes.LITERAL_NEW) {
final Optional<Frame> typeFrame = findFrameByName(
frame, childAst.getFirstChild().getText());
isStaticCandidate = isTypeFrameShouldBeChecked(typeFrame)
&& isExprAcceptable(frame, childAst);
}
else {
isStaticCandidate = isExprAcceptable(frame, childAst);
}
childAst = childAst.getNextSibling();
}
}
return isStaticCandidate;
}
/**
* Find a frame with the specified name among the current frame and its parents.
* @param frame the frame to start searching from.
* @param frameName the specified name.
* @return search result.
*/
private static Optional<Frame> findFrameByName(Frame frame, String frameName) {
Optional<Frame> result = Optional.absent();
Optional<Frame> parentFrame = Optional.of(frame.parent);
while (parentFrame.isPresent() && !result.isPresent()) {
for (Frame child: parentFrame.get().children) {
if (child.isClassOrEnum
&& frameName.equals(child.frameName)) {
result = Optional.of(child);
break;
}
}
parentFrame = Optional.fromNullable(parentFrame.get().parent);
}
return result;
}
/**
* Find a type variable with the specified name.
* @param frame the frame to start searching from.
* @param type the name of the type variable to find.
* @return true if a type variable with the specified name is found.
*/
private static boolean findTypeVariable(Frame frame, String type) {
boolean result = false;
Optional<Frame> searchFrame = Optional.of(frame);
while (!result && searchFrame.isPresent()) {
result = searchFrame.get().typeVariables.contains(type);
searchFrame = Optional.fromNullable(searchFrame.get().parent);
}
return result;
}
/**
* Check whether a {@code static} method is called.
* @param frame the frame where the method call is located.
* @param methodCallAst METHOD_CALL ast.
* @return true if a {@code static} method is called.
*/
private static boolean isStaticMethod(Frame frame, DetailAST methodCallAst) {
boolean result = false;
final DetailAST firstChild = methodCallAst.getFirstChild();
if (firstChild.getType() == TokenTypes.DOT) {
final DetailAST objCalledOn = getTheLeftmostIdent(methodCallAst);
if (objCalledOn.getType() == TokenTypes.IDENT) {
final Optional<DetailAST> field = findField(frame, objCalledOn);
if (field.isPresent()) {
result = isAcceptableField(field.get());
}
else if (findFrameByName(frame, objCalledOn.getText()).isPresent()) {
result = true;
}
}
else {
result = true;
}
}
else {
result = findStaticMethod(frame, methodCallAst, firstChild.getText());
}
return result;
}
/**
* Determine whether the method call should be checked.
* @param parentAst parent ast of the ident.
* @return true, if LITERAL_THIS is used or the usage is too complex to check.
*/
private static boolean isIdentShouldBeChecked(DetailAST parentAst) {
final int parentAstType = parentAst.getType();
return parentAstType != TokenTypes.LITERAL_NEW
&& parentAstType != TokenTypes.TYPE
&& parentAstType != TokenTypes.METHOD_DEF;
}
/**
* Check whether a {@code static} field or a local variable is used.
* @param frame the frame where the field is located.
* @param identAst the identifier ast of the checked field.
* @return true if the field is {@code static} or local.
*/
private static boolean isStaticFieldOrLocalVariable(Frame frame, DetailAST identAst) {
final boolean result;
final int parentType = identAst.getParent().getType();
if (parentType == TokenTypes.DOT) {
if (identAst.getNextSibling() == null) {
result = true;
}
else {
final Optional<DetailAST> field = findField(frame, identAst);
if (field.isPresent()) {
result = isAcceptableField(field.get());
}
else {
result = findFrameByName(frame, identAst.getText()).isPresent();
}
}
}
else if (parentType == TokenTypes.METHOD_CALL) {
result = true;
}
else {
final Optional<DetailAST> field = findField(frame, identAst);
result = field.isPresent() && isAcceptableField(field.get());
}
return result;
}
/**
* Whether the type frame should be checked.
* @param typeFrame the frame of the type to check.
* @return true if the type frame should be checked.
*/
private static boolean isTypeFrameShouldBeChecked(final Optional<Frame> typeFrame) {
return !typeFrame.isPresent()
|| typeFrame.get().isShouldBeChecked;
}
/**
* Get the leftmost ident of the method call.
* @param mCall METHOD_CALL to get ident from.
* @return the leftmost's ident DetailAST.
*/
private static DetailAST getTheLeftmostIdent(DetailAST mCall) {
DetailAST result = mCall.getFirstChild();
while (result.getChildCount() != 0
&& result.getType() != TokenTypes.METHOD_CALL) {
result = result.getFirstChild();
}
return result;
}
/**
* Find a static field definition or local variable.
* @param startFrame the frame to start searching from.
* @param identAst the IDENT ast to check.
* @return search result.
*/
private static Optional<DetailAST> findField(Frame startFrame, DetailAST identAst) {
Optional<DetailAST> result = Optional.absent();
Optional<Frame> frame = Optional.of(startFrame);
final String fieldName = identAst.getText();
while (frame.isPresent() && !result.isPresent()) {
final Optional<DetailAST> field = frame.get().findFieldInFrame(fieldName);
if (field.isPresent()) {
if (!isLocalVariable(field.get())
|| checkFieldLocation(field.get(), identAst)) {
result = field;
}
}
else {
result = frame.get().findEnumConstInFrame(fieldName);
}
frame = Optional.fromNullable(frame.get().parent);
}
return result;
}
/**
* Check whether the field is acceptable is a {@code static} method.
* @param field the checked field.
* @return true if the checked field is acceptable is a {@code static} method.
*/
private static boolean isAcceptableField(DetailAST field) {
boolean result = false;
if (isLocalVariable(field)
|| field.getType() == TokenTypes.ENUM_CONSTANT_DEF
|| hasStaticModifier(field)) {
result = true;
}
return result;
}
/**
* Find a {@code static} method definition of the specified method call
* and ensure that there are no non-{@code static} methods with the same name and
* number of parameters in the current frame or its parents.
* @param startFrame the frame to start searching from.
* @param methodCall METHOD_CALL ast.
* @param checkedMethodName the name of the called method.
* @return true if a {@code static} method definition of the specified method call
* is found and no non-{@code static} with the same name and number of parameters found.
*/
private static boolean findStaticMethod(Frame startFrame, DetailAST methodCall,
String checkedMethodName) {
final int argsNumber = methodCall.findFirstToken(TokenTypes.ELIST).getChildCount();
Optional<Frame> frame = Optional.of(startFrame);
// if we do not find neither static nor non-static method, then we cannot claim
// that the checked method can be static
boolean hasNonStaticMethod = false;
boolean hasStaticMethod = false;
while (!hasNonStaticMethod
&& frame.isPresent()) {
for (DetailAST method: frame.get().methods) {
final DetailAST parametersAst = method.findFirstToken(TokenTypes.PARAMETERS);
if (checkedMethodName.equals(getIdentText(method))
&& (parametersAst.getChildCount() == argsNumber
|| parametersAst.branchContains(TokenTypes.ELLIPSIS))) {
final DetailAST modifiersAst = method.findFirstToken(TokenTypes.MODIFIERS);
if (modifiersAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
// if a non-static method is found, then the checked method
// cannot be static
hasNonStaticMethod = true;
break;
}
else {
// if a static method is found, we keep searching for a similar
// non-static one to the end of the frame and if a non-static
// method is not found, then the checked method is still
// a static method candidate
hasStaticMethod = true;
}
}
}
frame = Optional.fromNullable(frame.get().parent);
}
return hasStaticMethod
&& !hasNonStaticMethod;
}
/**
* Check whether the field is a local variable.
* @param ast VARIABLE_DEF ast.
* @return true if the field is a local variable.
*/
private static boolean isLocalVariable(DetailAST ast) {
final int parentType = ast.getParent().getParent().getType();
return parentType != TokenTypes.CLASS_DEF
&& parentType != TokenTypes.ENUM_DEF;
}
/**
* Check whether the field is declared before its usage in case of methods.
* @param field field to check.
* @param objCalledOn object equals method called on.
* @return true if the field is declared before the method call.
*/
private static boolean checkFieldLocation(DetailAST field, DetailAST objCalledOn) {
boolean result = false;
if (field.getLineNo() < objCalledOn.getLineNo()
|| field.getLineNo() == objCalledOn.getLineNo()
&& field.getColumnNo() < objCalledOn.getColumnNo()) {
result = true;
}
return result;
}
/**
* Contains information about the frame.
*/
private static class Frame {
/** Name of the class, enum or method. */
private String frameName;
/** Parent frame. */
private final Frame parent;
/** List of frame's children. */
private final List<Frame> children = new LinkedList<>();
/** List of fields. */
private final List<DetailAST> fields = new LinkedList<>();
/** List of methods. */
private final List<DetailAST> methods = new LinkedList<>();
/** List of typeVariables. */
private final List<String> typeVariables = new LinkedList<>();
/** List of method calls. */
private final List<DetailAST> expressions = new ArrayList<>();
/** List of types. */
private final Set<String> types = Sets.newHashSet();
/** Set of enumConstants. */
private final Set<DetailAST> enumConstants = Sets.newHashSet();
/** Whether the frame is CLASS_DEF or ENUM_DEF. */
private boolean isClassOrEnum;
/** Whether the frame is {@code private} METHOD_DEF. */
private boolean isPrivateMethod;
/**
* Whether the frame should be checked.
* It is used in order not to check non-private methods, static methods
* and frames, where static methods cannot be defined: local and inner classes,
* constructors, anonymous classes, enum constant definitions, initializers.
*/
private boolean isShouldBeChecked = true;
/** Whether the frame has LITERAL_THIS or LITERAL_SUPER. */
private boolean hasLiteralThisOrSuper;
/** AST where the frame is declared. */
private DetailAST ast;
/**
* Creates new frame.
* @param parent parent frame.
*/
/* package */ Frame(Frame parent) {
this.parent = parent;
}
/**
* Add method call to this Frame.
* @param exprAst EXPR ast.
*/
public void addExpr(DetailAST exprAst) {
expressions.add(exprAst);
}
/**
* Add child frame to this frame.
* @param child frame to add.
*/
public void addChild(Frame child) {
children.add(child);
}
/**
* Add field to this Frame.
* @param field the ast of the field.
*/
public void addField(DetailAST field) {
fields.add(field);
}
/**
* Add method definition to this frame.
* @param method METHOD_DEF ast.
*/
public void addMethod(DetailAST method) {
methods.add(method);
}
/**
* Add method call to this frame.
* @param enumConst ENUM_CONST_DEF ast.
*/
public void addEnumConst(DetailAST enumConst) {
enumConstants.add(enumConst);
}
/**
* Add type variable name to this frame.
* @param typeVariable the type variable name.
*/
public void addTypeVariable(String typeVariable) {
typeVariables.add(typeVariable);
}
/**
* Add type to this frame.
* @param type the type name.
*/
public void addType(String type) {
types.add(type);
}
/**
* Determine whether this Frame contains the field.
* @param name the name of the field to check.
* @return search result.
*/
public Optional<DetailAST> findFieldInFrame(final String name) {
final Predicate<DetailAST> predicate = new Predicate<DetailAST>() {
@Override
public boolean apply(DetailAST field) {
return getIdentText(field).equals(name);
}
};
return Iterables.tryFind(fields, predicate);
}
/**
* Determine whether this Frame contains the enum constant.
* @param name the name of the enum constant to check.
* @return search result.
*/
public Optional<DetailAST> findEnumConstInFrame(final String name) {
final Predicate<DetailAST> predicate = new Predicate<DetailAST>() {
@Override
public boolean apply(DetailAST enumConstant) {
return getIdentText(enumConstant).equals(name);
}
};
return Iterables.tryFind(enumConstants, predicate);
}
}
}