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

org.checkerframework.framework.util.JavaParserUtil 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.42.0-eisop4
Show newest version
package org.checkerframework.framework.util;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseProblemException;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ParserConfiguration.LanguageLevel;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.StubUnit;
import com.github.javaparser.ast.body.AnnotationDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.body.RecordDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
import com.github.javaparser.ast.expr.BinaryExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

import org.checkerframework.javacutil.BugInCF;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Optional;

import javax.annotation.processing.ProcessingEnvironment;

/**
 * Utility methods for working with JavaParser. It is a replacement for StaticJavaParser that does
 * not leak memory, and it provides some other methods.
 */
public class JavaParserUtil {

    /**
     * The Language Level to use when parsing if a specific level isn't applied. This should be the
     * highest version of Java that the Checker Framework can process.
     */
    // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as of version
    // 3.25.1 (2023-02-28).  See
    // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html .
    public static final LanguageLevel DEFAULT_LANGUAGE_LEVEL = LanguageLevel.JAVA_17;

    ///
    /// Replacements for StaticJavaParser
    ///

    /**
     * Parses the Java code contained in the {@code InputStream} and returns a {@code
     * CompilationUnit} that represents it.
     *
     * 

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because * it creates a new instance of JavaParser each time it is invoked. Re-using {@code * StaticJavaParser} causes memory problems because it retains too much memory. * * @param inputStream the Java source code * @return CompilationUnit representing the Java source code * @throws ParseProblemException if the source code has parser errors */ public static CompilationUnit parseCompilationUnit(InputStream inputStream) { ParserConfiguration parserConfiguration = new ParserConfiguration(); parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); JavaParser javaParser = new JavaParser(parserConfiguration); ParseResult parseResult = javaParser.parse(inputStream); if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { return parseResult.getResult().get(); } else { throw new ParseProblemException(parseResult.getProblems()); } } /** * Parses the Java code contained in the {@code File} and returns a {@code CompilationUnit} that * represents it. * *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because * it creates a new instance of JavaParser each time it is invoked. Re-using {@code * StaticJavaParser} causes memory problems because it retains too much memory. * * @param file the Java source code * @return CompilationUnit representing the Java source code * @throws ParseProblemException if the source code has parser errors * @throws FileNotFoundException if the file was not found */ public static CompilationUnit parseCompilationUnit(File file) throws FileNotFoundException { ParserConfiguration configuration = new ParserConfiguration(); configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); JavaParser javaParser = new JavaParser(configuration); ParseResult parseResult = javaParser.parse(file); if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { return parseResult.getResult().get(); } else { throw new ParseProblemException(parseResult.getProblems()); } } /** * Parses the Java code contained in the {@code String} and returns a {@code CompilationUnit} * that represents it. * *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because * it creates a new instance of JavaParser each time it is invoked. Re-using {@code * StaticJavaParser} causes memory problems because it retains too much memory. * * @param javaSource the Java source code * @return CompilationUnit representing the Java source code * @throws ParseProblemException if the source code has parser errors */ public static CompilationUnit parseCompilationUnit(String javaSource) { ParserConfiguration parserConfiguration = new ParserConfiguration(); parserConfiguration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); JavaParser javaParser = new JavaParser(parserConfiguration); ParseResult parseResult = javaParser.parse(javaSource); if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { return parseResult.getResult().get(); } else { throw new ParseProblemException(parseResult.getProblems()); } } /** * Parses the stub file contained in the {@code InputStream} and returns a {@code StubUnit} that * represents it. * *

This is like {@code StaticJavaParser.parse}, but it does not lead to memory leaks because * it creates a new instance of JavaParser each time it is invoked. Re-using {@code * StaticJavaParser} causes memory problems because it retains too much memory. * * @param inputStream the stub file * @return StubUnit representing the stub file * @throws ParseProblemException if the source code has parser errors */ public static StubUnit parseStubUnit(InputStream inputStream) { // The ParserConfiguration accumulates data each time parse is called, so create a new one // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a // JavaParser has to be created each time. ParserConfiguration configuration = new ParserConfiguration(); configuration.setLanguageLevel(DEFAULT_LANGUAGE_LEVEL); // Store the tokens so that errors have line and column numbers. // configuration.setStoreTokens(false); configuration.setLexicalPreservationEnabled(false); configuration.setAttributeComments(false); configuration.setDetectOriginalLineSeparator(false); JavaParser javaParser = new JavaParser(configuration); ParseResult parseResult = javaParser.parseStubUnit(inputStream); if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { return parseResult.getResult().get(); } else { throw new ParseProblemException(parseResult.getProblems()); } } /** * Parses the {@code expression} and returns an {@code Expression} that represents it. * *

This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory * leaks because it creates a new instance of JavaParser each time it is invoked. Re-using * {@code StaticJavaParser} causes memory problems because it retains too much memory. * * @param expression the expression string * @return the parsed expression * @throws ParseProblemException if the expression has parser errors */ public static Expression parseExpression(String expression) { return parseExpression(expression, DEFAULT_LANGUAGE_LEVEL); } /** * Parses the {@code expression} and returns an {@code Expression} that represents it. * *

This is like {@code StaticJavaParser.parseExpression}, but it does not lead to memory * leaks because it creates a new instance of JavaParser each time it is invoked. Re-using * {@code StaticJavaParser} causes memory problems because it retains too much memory. * * @param expression the expression string * @param languageLevel the language level to use when parsing the Java source * @return the parsed expression * @throws ParseProblemException if the expression has parser errors */ public static Expression parseExpression(String expression, LanguageLevel languageLevel) { // The ParserConfiguration accumulates data each time parse is called, so create a new one // each time. There's no method to set the ParserConfiguration used by a JavaParser, so a // JavaParser has to be created each time. ParserConfiguration configuration = new ParserConfiguration(); configuration.setLanguageLevel(languageLevel); configuration.setStoreTokens(false); configuration.setLexicalPreservationEnabled(false); configuration.setAttributeComments(false); configuration.setDetectOriginalLineSeparator(false); JavaParser javaParser = new JavaParser(configuration); ParseResult parseResult = javaParser.parseExpression(expression); if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) { return parseResult.getResult().get(); } else { throw new ParseProblemException(parseResult.getProblems()); } } /// /// Other methods /// /** * Given the compilation unit node for a source file, returns the top level type definition with * the given name. * * @param root compilation unit to search * @param name name of a top level type declaration in {@code root} * @return a top level type declaration in {@code root} named {@code name} */ public static TypeDeclaration getTypeDeclarationByName(CompilationUnit root, String name) { Optional classDecl = root.getClassByName(name); if (classDecl.isPresent()) { return classDecl.get(); } Optional interfaceDecl = root.getInterfaceByName(name); if (interfaceDecl.isPresent()) { return interfaceDecl.get(); } Optional enumDecl = root.getEnumByName(name); if (enumDecl.isPresent()) { return enumDecl.get(); } Optional annoDecl = root.getAnnotationDeclarationByName(name); if (annoDecl.isPresent()) { return annoDecl.get(); } Optional recordDecl = getRecordByName(root, name); if (recordDecl.isPresent()) { return recordDecl.get(); } Optional storage = root.getStorage(); if (storage.isPresent()) { throw new BugInCF("Type " + name + " not found in " + storage.get().getPath()); } else { throw new BugInCF("Type " + name + " not found in " + root); } } /** * JavaParser's {@link CompilationUnit} class has methods like this for every other kind of * class-like structure (e.g., classes, enums, annotation declarations, etc.), but not for * records. This implementation is based on the implementation of {@link * CompilationUnit#getClassByName(String)}, and has the same interface as the other, similar * JavaParser methods (except that it is static and takes the CompilationUnit as a parameter, * rather than being an instance method on the CompilationUnit). * * @param cu the CompilationUnit to search * @param recordName the name of the record * @return the record declaration in the compilation unit with the given name, or an empty * Optional if no such record declaration exists */ private static Optional getRecordByName( CompilationUnit cu, String recordName) { return cu.getTypes().stream() .filter( (type) -> { return type.getNameAsString().equals(recordName) && type instanceof RecordDeclaration; }) .findFirst() .map( (t) -> { return (RecordDeclaration) t; }); } /** * Returns the fully qualified name of a type appearing in a given compilation unit. * * @param type a type declaration * @param compilationUnit the compilation unit containing {@code type} * @return the fully qualified name of {@code type} if {@code compilationUnit} contains a * package declaration, or just the name of {@code type} otherwise */ public static String getFullyQualifiedName( TypeDeclaration type, CompilationUnit compilationUnit) { if (compilationUnit.getPackageDeclaration().isPresent()) { return compilationUnit.getPackageDeclaration().get().getNameAsString() + "." + type.getNameAsString(); } else { return type.getNameAsString(); } } /** * Side-effects {@code node} by removing all annotations from anywhere inside its subtree. * * @param node a JavaParser Node */ public static void clearAnnotations(Node node) { node.accept(new ClearAnnotationsVisitor(), null); } /** A visitor that clears all annotations from a JavaParser AST. */ private static class ClearAnnotationsVisitor extends VoidVisitorWithDefaultAction { @Override public void defaultAction(Node node) { for (Node child : new ArrayList<>(node.getChildNodes())) { if (child instanceof AnnotationExpr) { node.remove(child); } } } @Override public void visit(ArrayInitializerExpr node, Void p) { // Do not remove annotations that are array elements. } } /** * Side-effects node by combining any added String literals in node's subtree into their * concatenation. For example, the expression {@code "a" + "b"} becomes {@code "ab"}. This * occurs even if, when reading from left to right, the two string literals are not added * directly. For example, the expression {@code 1 + "a" + "b"} parses as {@code (1 + "a") + * "b"}}, but it is transformed into {@code 1 + "ab"}. * *

This is the same transformation performed by javac automatically. Javac seems to ignore * string literals surrounded in parentheses, so this method does as well. * * @param node a JavaParser Node */ public static void concatenateAddedStringLiterals(Node node) { node.accept(new StringLiteralConcatenateVisitor(), null); } /** Visitor that combines added String literals, see {@link #concatenateAddedStringLiterals}. */ public static class StringLiteralConcatenateVisitor extends VoidVisitorAdapter { @Override public void visit(BinaryExpr node, Void p) { super.visit(node, p); if (node.getOperator() == BinaryExpr.Operator.PLUS && node.getRight().isStringLiteralExpr()) { String right = node.getRight().asStringLiteralExpr().getValue(); if (node.getLeft().isStringLiteralExpr()) { String left = node.getLeft().asStringLiteralExpr().getValue(); node.replace(new StringLiteralExpr(left + right)); } else if (node.getLeft().isBinaryExpr()) { BinaryExpr leftExpr = node.getLeft().asBinaryExpr(); if (leftExpr.getOperator() == BinaryExpr.Operator.PLUS && leftExpr.getRight().isStringLiteralExpr()) { String left = leftExpr.getRight().asStringLiteralExpr().getValue(); node.replace( new BinaryExpr( leftExpr.getLeft(), new StringLiteralExpr(left + right), BinaryExpr.Operator.PLUS)); } } } } } /** * Initialized by {@link #getCurrentSourceVersion(ProcessingEnvironment)}. Use that method to * access. */ private static LanguageLevel currentSourceVersion = null; /** * Returns the {@link com.github.javaparser.ParserConfiguration.LanguageLevel} corresponding to * the current source version. * * @param env processing environment used to get source version * @return the current source version */ public static ParserConfiguration.LanguageLevel getCurrentSourceVersion( ProcessingEnvironment env) { if (currentSourceVersion == null) { // Use String comparison so we can compile on older JDKs which // don't have all the latest SourceVersion constants: switch (env.getSourceVersion().name()) { case "RELEASE_8": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_8; break; case "RELEASE_9": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_9; break; case "RELEASE_10": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_10; break; case "RELEASE_11": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_11; break; case "RELEASE_12": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_12; break; case "RELEASE_13": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_13; break; case "RELEASE_14": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_14; break; case "RELEASE_15": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_15; break; case "RELEASE_16": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_16; break; case "RELEASE_17": currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_17; break; // JavaParser's ParserConfiguration.LanguageLevel has no constant for JDK 18, as // of version 3.25.1 (2023-02-28). See // https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/latest/com/github/javaparser/ParserConfiguration.LanguageLevel.html . // case "RELEASE_18": // currentSourceVersion = ParserConfiguration.LanguageLevel.JAVA_18; // break; default: currentSourceVersion = DEFAULT_LANGUAGE_LEVEL; } } return currentSourceVersion; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy