All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.javacutil.TreeUtilsAfterJava11 Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.48.2
Show newest version
package org.checkerframework.javacutil;

import com.sun.source.tree.CaseTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.lang.model.SourceVersion;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.ClassGetName;
import org.checkerframework.dataflow.qual.Pure;

/**
 * This class contains util methods for reflective accessing Tree classes and methods that were
 * added after Java 11.
 */
public class TreeUtilsAfterJava11 {

  /** Don't use. */
  private TreeUtilsAfterJava11() {
    throw new AssertionError("Cannot be instantiated.");
  }

  /** The latest source version supported by this compiler. */
  private static final int sourceVersionNumber =
      Integer.parseInt(SourceVersion.latest().toString().substring("RELEASE_".length()));

  /** Utility methods for accessing {@code BindingPatternTree} methods. */
  public static class BindingPatternUtils {

    /** Don't use. */
    private BindingPatternUtils() {
      throw new AssertionError("Cannot be instantiated.");
    }

    /** The {@code BindingPatternTree.getVariable} method for Java 16 and higher; null otherwise. */
    private static @Nullable Method GET_VARIABLE = null;

    /**
     * Returns the binding variable of {@code bindingPatternTree}.
     *
     * @param bindingPatternTree the BindingPatternTree whose binding variable is returned
     * @return the binding variable of {@code bindingPatternTree}
     */
    public static VariableTree getVariable(Tree bindingPatternTree) {
      assertVersionAtLeast(16);
      if (GET_VARIABLE == null) {
        Class bindingPatternClass = classForName("com.sun.source.tree.BindingPatternTree");
        GET_VARIABLE = getMethod(bindingPatternClass, "getVariable");
      }
      return (VariableTree) invokeNonNullResult(GET_VARIABLE, bindingPatternTree);
    }
  }

  /** Utility methods for accessing {@code CaseTree} methods. */
  public static class CaseUtils {

    /** Don't use. */
    private CaseUtils() {
      throw new AssertionError("Cannot be instantiated.");
    }

    /** The {@code CaseTree.getExpressions} method for Java 12 and higher; null otherwise. */
    private static @Nullable Method GET_EXPRESSIONS = null;

    /** The {@code CaseTree.getBody} method for Java 12 and higher; null otherwise. */
    private static @Nullable Method GET_BODY = null;

    /** The {@code CaseTree.getKind()} method for Java 12 and higher; null otherwise. */
    private static @Nullable Method GET_KIND = null;

    /** The {@code CaseTree.getLabels} method for Java 21 and higher; null otherwise. */
    private static @Nullable Method GET_LABELS = null;

    /** The {@code CaseTree.getGuard} method for Java 21 and higher; null otherwise. */
    private static @Nullable Method GET_GUARD = null;

    /**
     * Returns true if this is a case rule (as opposed to a case statement).
     *
     * @param caseTree a case tree
     * @return true if {@code caseTree} is a case rule
     */
    public static boolean isCaseRule(CaseTree caseTree) {
      if (sourceVersionNumber < 12) {
        return false;
      }

      if (GET_KIND == null) {
        GET_KIND = getMethod(CaseTree.class, "getCaseKind");
      }
      Enum kind = (Enum) invokeNonNullResult(GET_KIND, caseTree);
      return kind.name().contentEquals("RULE");
    }

    /**
     * Returns the body of the case statement if it is of the form {@code case  ->
     * }. This method should only be called if {@link CaseTree#getStatements()} returns
     * null.
     *
     * @param caseTree the case expression to get the body from
     * @return the body of the case tree
     */
    public static @Nullable Tree getBody(CaseTree caseTree) {
      assertVersionAtLeast(12);
      if (GET_BODY == null) {
        GET_BODY = getMethod(CaseTree.class, "getBody");
      }
      return (Tree) invoke(GET_BODY, caseTree);
    }

    /**
     * Returns true if this is the default case for a switch statement or expression. (Also, returns
     * true if {@code caseTree} is {@code case null, default:}.)
     *
     * @param caseTree a case tree
     * @return true if {@code caseTree} is the default case for a switch statement or expression
     */
    public static boolean isDefaultCaseTree(CaseTree caseTree) {
      if (sourceVersionNumber >= 21) {
        for (Tree label : getLabels(caseTree, true)) {
          if (TreeUtils.isDefaultCaseLabelTree(label)) {
            return true;
          }
        }
        return false;
      } else {
        return getExpressions(caseTree).isEmpty();
      }
    }

    /**
     * Get the list of labels from a case expression. For {@code default}, this is empty. For {@code
     * case null, default}, the list contains {@code null}. Otherwise, in JDK 11 and earlier, this
     * is a list of a single expression tree. In JDK 12+, the list may have multiple expression
     * trees. In JDK 21+, the list might contain a single pattern tree.
     *
     * @param caseTree the case expression to get the labels from
     * @return the list of case labels in the case
     */
    public static List getLabels(CaseTree caseTree) {
      return getLabels(caseTree, false);
    }

    /**
     * Get the list of labels from a case expression.
     *
     * 

For JDKs before 21, if {@code caseTree} is the default case, then the returned list is * empty. * *

For 21+ JDK, if {@code useDefaultCaseLabelTree} is false, then if {@code caseTree} is the * default case or {@code case null, default}, then the returned list is empty. If {@code * useDefaultCaseLabelTree} is true, then if {@code caseTree} is the default case the returned * contains just a {@code DefaultCaseLabelTree}. If {@code useDefaultCaseLabelTree} is false, * then if {@code caseTree} is {@code case null, default} the returned list is a {@code * DefaultCaseLabelTree} and the expression tree for {@code null}. * *

Otherwise, in JDK 11 and earlier, this is a list of a single expression tree. In JDK 12+, * the list may have multiple expression trees. In JDK 21+, the list might contain a single * pattern tree. * * @param caseTree the case expression to get the labels from * @param useDefaultCaseLabelTree weather the result should contain a {@code * DefaultCaseLabelTree}. * @return the list of case labels in the case */ private static List getLabels( CaseTree caseTree, boolean useDefaultCaseLabelTree) { if (sourceVersionNumber >= 21) { if (GET_LABELS == null) { GET_LABELS = getMethod(CaseTree.class, "getLabels"); } @SuppressWarnings("unchecked") List caseLabelTrees = (List) invokeNonNullResult(GET_LABELS, caseTree); List labels = new ArrayList<>(); for (Tree caseLabel : caseLabelTrees) { if (TreeUtils.isDefaultCaseLabelTree(caseLabel)) { if (useDefaultCaseLabelTree) { labels.add(caseLabel); } } else if (TreeUtils.isConstantCaseLabelTree(caseLabel)) { labels.add(ConstantCaseLabelUtils.getConstantExpression(caseLabel)); } else if (TreeUtils.isPatternCaseLabelTree(caseLabel)) { labels.add(PatternCaseLabelUtils.getPattern(caseLabel)); } } return labels; } return getExpressions(caseTree); } /** * Get the list of expressions from a case expression. For the default case, this is empty. * Otherwise, in JDK 11 and earlier, this is a singleton list. In JDK 12 onwards, there can be * multiple expressions per case. * * @param caseTree the case expression to get the expressions from * @return the list of expressions in the case */ @SuppressWarnings("unchecked") public static List getExpressions(CaseTree caseTree) { if (sourceVersionNumber >= 12) { if (GET_EXPRESSIONS == null) { GET_EXPRESSIONS = getMethod(CaseTree.class, "getExpressions"); } return (List) invokeNonNullResult(GET_EXPRESSIONS, caseTree); } @SuppressWarnings("deprecation") // getExpression is deprecated in Java 21 ExpressionTree expression = caseTree.getExpression(); if (expression == null) { return Collections.emptyList(); } return Collections.singletonList(expression); } /** * Returns the guard, the expression after {@code when}, of {@code caseTree}. Wrapper around * {@code CaseTree#getGuard} that can be called on any version of Java. * * @param caseTree the case tree * @return the guard on the case tree or null if one does not exist */ public static @Nullable ExpressionTree getGuard(CaseTree caseTree) { if (sourceVersionNumber < 21) { return null; } if (GET_GUARD == null) { GET_GUARD = getMethod(CaseTree.class, "getGuard"); } return (ExpressionTree) invoke(GET_GUARD, caseTree); } } /** Utility methods for accessing {@code ConstantCaseLabelTree} methods. */ public static class ConstantCaseLabelUtils { /** Don't use. */ private ConstantCaseLabelUtils() { throw new AssertionError("Cannot be instantiated."); } /** * The {@code ConstantCaseLabelTree.getConstantExpression} method for Java 21 and higher; null * otherwise. */ private static @Nullable Method GET_CONSTANT_EXPRESSION = null; /** * Wrapper around {@code ConstantCaseLabelTree#getConstantExpression}. * * @param constantCaseLabelTree a ConstantCaseLabelTree tree * @return the expression in the {@code constantCaseLabelTree} */ public static ExpressionTree getConstantExpression(Tree constantCaseLabelTree) { assertVersionAtLeast(21); if (GET_CONSTANT_EXPRESSION == null) { Class constantCaseLabelTreeClass = classForName("com.sun.source.tree.ConstantCaseLabelTree"); GET_CONSTANT_EXPRESSION = getMethod(constantCaseLabelTreeClass, "getConstantExpression"); } return (ExpressionTree) invokeNonNullResult(GET_CONSTANT_EXPRESSION, constantCaseLabelTree); } } /** Utility methods for accessing {@code DeconstructionPatternTree} methods. */ public static class DeconstructionPatternUtils { /** Don't use. */ private DeconstructionPatternUtils() { throw new AssertionError("Cannot be instantiated."); } /** * The {@code DeconstructionPatternTree.getDeconstructor} method for Java 21 and higher; null * otherwise. */ private static @Nullable Method GET_DECONSTRUCTOR = null; /** * The {@code DeconstructionPatternTree.getNestedPatterns} method for Java 21 and higher; null * otherwise. */ private static @Nullable Method GET_NESTED_PATTERNS = null; /** * Returns the deconstruction type of {@code tree}. Wrapper around {@code * DeconstructionPatternTree#getDeconstructor}. * * @param tree the DeconstructionPatternTree * @return the deconstructor of {@code DeconstructionPatternTree} */ public static ExpressionTree getDeconstructor(Tree tree) { assertVersionAtLeast(21); if (GET_DECONSTRUCTOR == null) { Class deconstructionPatternClass = classForName("com.sun.source.tree.DeconstructionPatternTree"); GET_DECONSTRUCTOR = getMethod(deconstructionPatternClass, "getDeconstructor"); } return (ExpressionTree) invokeNonNullResult(GET_DECONSTRUCTOR, tree); } /** * Wrapper around {@code DeconstructionPatternTree#getNestedPatterns}. * * @param tree the DeconstructionPatternTree * @return the nested patterns of {@code DeconstructionPatternTree} */ @SuppressWarnings("unchecked") public static List getNestedPatterns(Tree tree) { assertVersionAtLeast(21); if (GET_NESTED_PATTERNS == null) { Class deconstructionPatternClass = classForName("com.sun.source.tree.DeconstructionPatternTree"); GET_NESTED_PATTERNS = getMethod(deconstructionPatternClass, "getNestedPatterns"); } return (List) invokeNonNullResult(GET_NESTED_PATTERNS, tree); } } /** Utility methods for accessing {@code PatternCaseLabelTree} methods. */ public static class PatternCaseLabelUtils { /** Don't use. */ private PatternCaseLabelUtils() { throw new AssertionError("Cannot be instantiated."); } /** The PatternCaseLabelTree.getPattern method for Java 21 and higher; null otherwise. */ private static @Nullable Method GET_PATTERN = null; /** * Wrapper around {@code PatternCaseLabelTree#getPattern}. * * @param patternCaseLabelTree a PatternCaseLabelTree tree * @return the {@code PatternTree} in the {@code patternCaseLabelTree} */ public static Tree getPattern(Tree patternCaseLabelTree) { assertVersionAtLeast(21); if (GET_PATTERN == null) { Class patternCaseLabelClass = classForName("com.sun.source.tree.PatternCaseLabelTree"); GET_PATTERN = getMethod(patternCaseLabelClass, "getPattern"); } return (Tree) invokeNonNullResult(GET_PATTERN, patternCaseLabelTree); } } /** Utility methods for accessing {@code SwitchExpressionTree} methods. */ public static class SwitchExpressionUtils { /** Don't use. */ private SwitchExpressionUtils() { throw new AssertionError("Cannot be instantiated."); } /** * The {@code SwitchExpressionTree.getExpression} method for Java 12 and higher; null otherwise. */ private static @Nullable Method GET_EXPRESSION = null; /** The {@code SwitchExpressionTree.getCases} method for Java 12 and higher; null otherwise. */ private static @Nullable Method GET_CASES = null; /** * Returns the cases of {@code switchExpressionTree}. For example * *

     *   switch ( expression ) {
     *     cases
     *   }
     * 
* * @param switchExpressionTree the switch expression whose cases are returned * @return the cases of {@code switchExpressionTree} */ @SuppressWarnings("unchecked") public static List getCases(Tree switchExpressionTree) { assertVersionAtLeast(12); if (GET_CASES == null) { Class switchExpressionClass = classForName("com.sun.source.tree.SwitchExpressionTree"); GET_CASES = getMethod(switchExpressionClass, "getCases"); } return (List) invokeNonNullResult(GET_CASES, switchExpressionTree); } /** * Returns the selector expression of {@code switchExpressionTree}. For example * *
     *   switch ( expression ) { ... }
     * 
* * @param switchExpressionTree the switch expression whose selector expression is returned * @return the selector expression of {@code switchExpressionTree} */ public static ExpressionTree getExpression(Tree switchExpressionTree) { assertVersionAtLeast(12); if (GET_EXPRESSION == null) { Class switchExpressionClass = classForName("com.sun.source.tree.SwitchExpressionTree"); GET_EXPRESSION = getMethod(switchExpressionClass, "getExpression"); } return (ExpressionTree) invokeNonNullResult(GET_EXPRESSION, switchExpressionTree); } } /** Utility methods for accessing {@code YieldTree} methods. */ public static class YieldUtils { /** Don't use. */ private YieldUtils() { throw new AssertionError("Cannot be instantiated."); } /** The {@code YieldTree.getValue} method for Java 13 and higher; null otherwise. */ private static @Nullable Method GET_VALUE = null; /** * Returns the value (expression) for {@code yieldTree}. * * @param yieldTree the yield tree * @return the value (expression) for {@code yieldTree} */ public static ExpressionTree getValue(Tree yieldTree) { assertVersionAtLeast(13); if (GET_VALUE == null) { Class yieldTreeClass = classForName("com.sun.source.tree.YieldTree"); GET_VALUE = getMethod(yieldTreeClass, "getValue"); } return (ExpressionTree) invokeNonNullResult(GET_VALUE, yieldTree); } } /** Utility methods for accessing {@code InstanceOfTree} methods. */ public static class InstanceOfUtils { /** Don't use. */ private InstanceOfUtils() { throw new AssertionError("Cannot be instantiated."); } /** The {@code InstanceOfTree.getPattern} method for Java 16 and higher; null otherwise. */ private static @Nullable Method GET_PATTERN = null; /** * Returns the pattern of {@code instanceOfTree} tree. Returns null if the instanceof does not * have a pattern, including if the JDK version does not support instance-of patterns. * * @param instanceOfTree the {@link InstanceOfTree} whose pattern is returned * @return the {@code PatternTree} of {@code instanceOfTree} or null if it doesn't exist */ @Pure public static @Nullable Tree getPattern(InstanceOfTree instanceOfTree) { if (sourceVersionNumber < 16) { return null; } if (GET_PATTERN == null) { GET_PATTERN = getMethod(InstanceOfTree.class, "getPattern"); } return (Tree) invoke(GET_PATTERN, instanceOfTree); } } /** * Asserts that the latest source version is at least {@code version}. * * @param version version to check * @throws BugInCF if the latest version is smaller than {@code version} */ private static void assertVersionAtLeast(int version) { if (sourceVersionNumber < version) { throw new BugInCF( "Method call requires at least Java version %s, but the current version is %s", version, sourceVersionNumber); } } /** * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as {@code * BugInCF} exceptions. If the results is {@code null} a {@code BugInCF} is thrown. * * @param method a method * @param receiver the receiver for the method * @return the result of invoking {@code method} on {@code receiver} */ private static Object invokeNonNullResult(Method method, Tree receiver) { Object result = invoke(method, receiver); if (result != null) { return result; } throw new BugInCF( "Expected nonnull result for method invocation: %s for tree: %s", method.getName(), receiver); } /** * Reflectively invokes {@code method} with {@code receiver}; rethrowing any exceptions as {@code * BugInCF} exceptions. * * @param method a method * @param receiver the receiver for the method * @return the result of invoking {@code method} on {@code receiver} */ private static @Nullable Object invoke(Method method, Tree receiver) { try { return method.invoke(receiver); } catch (IllegalAccessException | InvocationTargetException e) { throw new BugInCF( e, "Reflection failed for method: %s for tree: %s", method.getName(), receiver); } } /** * Returns the {@link Method} object for the method with name {@code name} in class {@code clazz}. * Rethrowing any exceptions as {@code BugInCF} exceptions. * * @param clazz a class * @param name a method name * @return the {@link Method} object for the method with name {@code name} in class {@code clazz} */ private static Method getMethod(Class clazz, String name) { try { return clazz.getMethod(name); } catch (NoSuchMethodException e) { throw new BugInCF("Method %s not found in class %s", name, clazz); } } /** * Returns the class named {@code name}. Rethrows any exceptions as {@code BugInCF} exceptions. * * @param name a class name * @return the class named {@code name} */ private static Class classForName(@ClassGetName String name) { try { return Class.forName(name); } catch (ClassNotFoundException e) { throw new BugInCF("Class not found " + name); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy