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

org.checkerframework.framework.stub.AnnotationFileUtil 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.43.0
Show newest version
package org.checkerframework.framework.stub;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.StubUnit;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.PrimitiveType;
import com.github.javaparser.ast.type.VoidType;
import com.github.javaparser.ast.type.WildcardType;
import com.github.javaparser.ast.visitor.SimpleVoidVisitor;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Types;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.plumelib.util.IPair;

/** Utility class for annotation files (stub files and ajava files). */
public class AnnotationFileUtil {
  /**
   * The types of files that can contain annotations. Also indicates the file's source, such as from
   * the JDK, built in, or from the command line.
   *
   * 

Stub files have extension ".astub". Ajava files have extension ".ajava". */ public enum AnnotationFileType { /** Stub file in the annotated JDK. */ JDK_STUB, /** Stub file built into a checker. */ BUILTIN_STUB, /** Stub file provided on command line. */ COMMAND_LINE_STUB, /** Ajava file being parsed as if it is a stub file. */ AJAVA_AS_STUB, /** Ajava file provided on command line. */ AJAVA; /** * Returns true if this represents a stub file. * * @return true if this represents a stub file */ public boolean isStub() { switch (this) { case JDK_STUB: case BUILTIN_STUB: case COMMAND_LINE_STUB: case AJAVA_AS_STUB: return true; case AJAVA: return false; default: throw new BugInCF("unhandled case " + this); } } /** * Returns true if this annotation file is built-in (not provided on the command line). * * @return true if this annotation file is built-in (not provided on the command line) */ public boolean isBuiltIn() { switch (this) { case JDK_STUB: case BUILTIN_STUB: return true; case COMMAND_LINE_STUB: case AJAVA_AS_STUB: case AJAVA: return false; default: throw new BugInCF("unhandled case " + this); } } /** * Returns true if this annotation file was provided on the command line (not built-in). * * @return true if this annotation file was provided on the command line (not built-in) */ public boolean isCommandLine() { switch (this) { case JDK_STUB: case BUILTIN_STUB: return false; case COMMAND_LINE_STUB: case AJAVA_AS_STUB: case AJAVA: return true; default: throw new BugInCF("unhandled case " + this); } } } /** * Finds the type declaration with the given class name in a StubUnit. * * @param className fully qualified name of the type declaration to find * @param indexFile a StubUnit to search * @return the declaration in {@code indexFile} with {@code className} if it exists, null * otherwise. */ /*package-private*/ static @Nullable TypeDeclaration findDeclaration( String className, StubUnit indexFile) { int indexOfDot = className.lastIndexOf('.'); if (indexOfDot == -1) { // classes not within a package needs to be the first in the index file assert !indexFile.getCompilationUnits().isEmpty(); assert indexFile.getCompilationUnits().get(0).getPackageDeclaration() == null; return findDeclaration(className, indexFile.getCompilationUnits().get(0)); } String packageName = className.substring(0, indexOfDot); String simpleName = className.substring(indexOfDot + 1); for (CompilationUnit cu : indexFile.getCompilationUnits()) { if (cu.getPackageDeclaration().isPresent() && cu.getPackageDeclaration().get().getNameAsString().equals(packageName)) { TypeDeclaration type = findDeclaration(simpleName, cu); if (type != null) { return type; } } } // Couldn't find it return null; } /*package-private*/ static TypeDeclaration findDeclaration( TypeElement type, StubUnit indexFile) { return findDeclaration(type.getQualifiedName().toString(), indexFile); } /*package-private*/ static @Nullable FieldDeclaration findDeclaration( VariableElement field, StubUnit indexFile) { TypeDeclaration type = findDeclaration((TypeElement) field.getEnclosingElement(), indexFile); if (type == null) { return null; } for (BodyDeclaration member : type.getMembers()) { if (!(member instanceof FieldDeclaration)) { continue; } FieldDeclaration decl = (FieldDeclaration) member; for (VariableDeclarator var : decl.getVariables()) { if (toString(var).equals(field.getSimpleName().toString())) { return decl; } } } return null; } /*package-private*/ static @Nullable BodyDeclaration findDeclaration( ExecutableElement method, StubUnit indexFile) { TypeDeclaration type = findDeclaration((TypeElement) method.getEnclosingElement(), indexFile); if (type == null) { return null; } String methodRep = toString(method); for (BodyDeclaration member : type.getMembers()) { if (member instanceof MethodDeclaration) { if (toString((MethodDeclaration) member).equals(methodRep)) { return member; } } else if (member instanceof ConstructorDeclaration) { if (toString((ConstructorDeclaration) member).equals(methodRep)) { return member; } } } return null; } /*package-private*/ static @Nullable TypeDeclaration findDeclaration( String simpleName, CompilationUnit cu) { for (TypeDeclaration type : cu.getTypes()) { if (simpleName.equals(type.getNameAsString())) { return type; } } // Couldn't find it return null; } /*package-private*/ static String toString(MethodDeclaration method) { return ElementPrinter.toString(method); } /*package-private*/ static String toString(ConstructorDeclaration constructor) { return ElementPrinter.toString(constructor); } /*package-private*/ static String toString(VariableDeclarator field) { return field.getNameAsString(); } /*package-private*/ static String toString(FieldDeclaration field) { assert field.getVariables().size() == 1; return toString(field.getVariables().get(0)); } /*package-private*/ static String toString(VariableElement element) { assert element.getKind().isField(); return element.getSimpleName().toString(); } /*package-private*/ static @Nullable String toString(Element element) { if (element instanceof ExecutableElement) { return toString((ExecutableElement) element); } else if (element instanceof VariableElement) { return toString((VariableElement) element); } else { return null; } } /** * Split a name (which comes from an import statement) into the part before the last period and * the part after the last period. * * @param imported the name to split * @return a pair of the type name and the field name */ @SuppressWarnings("signature") // string parsing public static IPair<@FullyQualifiedName String, String> partitionQualifiedName(String imported) { @FullyQualifiedName String typeName = imported.substring(0, imported.lastIndexOf(".")); String name = imported.substring(imported.lastIndexOf(".") + 1); IPair typeParts = IPair.of(typeName, name); return typeParts; } private static final class ElementPrinter extends SimpleVoidVisitor { public static String toString(Node n) { ElementPrinter printer = new ElementPrinter(); n.accept(printer, null); return printer.getOutput(); } private final StringBuilder sb = new StringBuilder(); public String getOutput() { return sb.toString(); } @Override public void visit(ConstructorDeclaration n, Void arg) { sb.append(""); sb.append("("); if (n.getParameters() != null) { for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { Parameter p = i.next(); p.accept(this, arg); if (i.hasNext()) { sb.append(","); } } } sb.append(")"); } @Override public void visit(MethodDeclaration n, Void arg) { sb.append(n.getName()); sb.append("("); if (n.getParameters() != null) { for (Iterator i = n.getParameters().iterator(); i.hasNext(); ) { Parameter p = i.next(); p.accept(this, arg); if (i.hasNext()) { sb.append(","); } } } sb.append(")"); } @Override public void visit(Parameter n, Void arg) { n.getType().accept(this, arg); if (n.isVarArgs()) { sb.append("[]"); } } // Types @Override public void visit(ClassOrInterfaceType n, Void arg) { sb.append(n.getName()); } @Override public void visit(PrimitiveType n, Void arg) { switch (n.getType()) { case BOOLEAN: sb.append("boolean"); break; case BYTE: sb.append("byte"); break; case CHAR: sb.append("char"); break; case DOUBLE: sb.append("double"); break; case FLOAT: sb.append("float"); break; case INT: sb.append("int"); break; case LONG: sb.append("long"); break; case SHORT: sb.append("short"); break; default: throw new BugInCF("AnnotationFileUtil: unknown type: " + n.getType()); } } @Override public void visit(com.github.javaparser.ast.type.ArrayType n, Void arg) { n.getComponentType().accept(this, arg); sb.append("[]"); } @Override public void visit(VoidType n, Void arg) { sb.append("void"); } @Override public void visit(WildcardType n, Void arg) { // We don't write type arguments // TODO: Why? throw new BugInCF("AnnotationFileUtil: don't print type args"); } } /** * Return annotation files found at a given file system location (does not look on classpath). * * @param location an annotation file (stub file or ajava file), a jarfile, or a directory. Look * for it as an absolute file and relative to the current directory. * @param fileType file type of files to collect * @return annotation files with the given file type found in the file system (does not look on * classpath). Returns null if the file system location does not exist; the caller may wish to * issue a warning in that case. */ public static @Nullable List allAnnotationFiles( String location, AnnotationFileType fileType) { File file = new File(location); if (file.exists()) { List resources = new ArrayList<>(); addAnnotationFilesToList(file, resources, fileType); return resources; } // The file doesn't exist. Maybe it is relative to the current working directory, so try // that. file = new File(System.getProperty("user.dir"), location); if (file.exists()) { List resources = new ArrayList<>(); addAnnotationFilesToList(file, resources, fileType); return resources; } return null; } /** * Returns true if the given file is an annotation file of the given type. * * @param f the file to check * @param fileType the type of file to check against * @return true if {@code f} is a file with file type matching {@code fileType}, false otherwise */ private static boolean isAnnotationFile(File f, AnnotationFileType fileType) { return f.isFile() && isAnnotationFile(f.getName(), fileType); } /** * Returns true if the given file is an annotation file of the given kind. * * @param path a file * @param fileType the type of file to check against * @return true if {@code path} represents a file with file type matching {@code fileType}, false * otherwise */ private static boolean isAnnotationFile(String path, AnnotationFileType fileType) { return path.endsWith(fileType.isStub() ? ".astub" : ".ajava"); } private static boolean isJar(File f) { return f.isFile() && f.getName().endsWith(".jar"); } /** * Side-effects {@code resources} by adding annotation files of the given file type to it. * * @param location an annotation file (a stub file or ajava file), a jarfile, or a directory. If a * stub file or ajava file, add it to the {@code resources} list. If a jarfile, use all * annotation files (of type {@code fileType}) contained in it. If a directory, recurse on all * files contained in it. * @param resources the list to add the found files to * @param fileType type of annotation files to add */ @SuppressWarnings({ "JdkObsolete", // JarFile.entries() "nullness:argument", // inference failed in Arrays.sort "builder:required.method.not.called" // ownership passed to list of // JarEntryAnnotationFileResource, where `file` appears in every element of the list }) private static void addAnnotationFilesToList( File location, List resources, AnnotationFileType fileType) { if (isAnnotationFile(location, fileType)) { resources.add(new FileAnnotationFileResource(location)); } else if (isJar(location)) { JarFile file; try { file = new JarFile(location); } catch (IOException e) { System.err.println("AnnotationFileUtil: could not process JAR file: " + location); return; } Enumeration entries = file.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (isAnnotationFile(entry.getName(), fileType)) { resources.add(new JarEntryAnnotationFileResource(file, entry)); } } } else if (location.isDirectory()) { File[] directoryContents = location.listFiles(); Arrays.sort(directoryContents, Comparator.comparing(File::getName)); for (File enclosed : directoryContents) { addAnnotationFilesToList(enclosed, resources, fileType); } } } /** * Returns true if the given {@link ExecutableElement} is the canonical constructor of a record * (i.e., the parameter types of the constructor correspond to the parameter types of the record * components, ignoring annotations). * * @param elt the constructor/method to check * @param types the Types instance to use for comparing types * @return true if elt is the canonical constructor of the record containing it */ public static boolean isCanonicalConstructor(ExecutableElement elt, Types types) { if (elt.getKind() != ElementKind.CONSTRUCTOR) { return false; } TypeElement enclosing = (TypeElement) elt.getEnclosingElement(); // Can't use RECORD enum constant as it's not available before JDK 16: if (!enclosing.getKind().name().equals("RECORD")) { return false; } List recordComponents = ElementUtils.getRecordComponents(enclosing); if (recordComponents.size() == elt.getParameters().size()) { for (int i = 0; i < recordComponents.size(); i++) { if (!types.isSameType( recordComponents.get(i).asType(), elt.getParameters().get(i).asType())) { return false; } } return true; } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy