| package org.checkerframework.framework.stub; |
| |
| import com.sun.source.tree.CompilationUnitTree; |
| import io.github.classgraph.ClassGraph; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.JarURLConnection; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.StringJoiner; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.lang.model.element.AnnotationMirror; |
| 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.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.tools.Diagnostic.Kind; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.checker.signature.qual.CanonicalNameOrEmpty; |
| import org.checkerframework.common.basetype.BaseTypeChecker; |
| import org.checkerframework.framework.qual.StubFiles; |
| import org.checkerframework.framework.source.SourceChecker; |
| import org.checkerframework.framework.stub.AnnotationFileParser.AnnotationFileAnnotations; |
| import org.checkerframework.framework.stub.AnnotationFileUtil.AnnotationFileType; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.Pair; |
| import org.checkerframework.javacutil.SystemUtil; |
| import org.checkerframework.javacutil.TypesUtils; |
| |
| /** |
| * Holds information about types parsed from annotation files (stub files or ajava files). When |
| * using an ajava file, only holds information on public elements as with stub files. |
| */ |
| public class AnnotationFileElementTypes { |
| /** Annotations from annotation files (but not from annotated JDK files). */ |
| private final AnnotationFileAnnotations annotationFileAnnos; |
| |
| /** |
| * Whether or not a file is currently being parsed. (If one is being parsed, don't try to parse |
| * another.) |
| */ |
| private boolean parsing; |
| |
| /** AnnotatedTypeFactory. */ |
| private final AnnotatedTypeFactory factory; |
| |
| /** |
| * Mapping from fully-qualified class name to corresponding JDK stub file from the file system. By |
| * contrast, {@link #jdkStubFilesJar} contains JDK stub files from checker.jar. |
| */ |
| private final Map<String, Path> jdkStubFiles = new HashMap<>(); |
| |
| /** |
| * Mapping from fully-qualified class name to corresponding JDK stub files from checker.jar. By |
| * contrast, {@link #jdkStubFiles} contains JDK stub files from the file system. |
| */ |
| private final Map<String, String> jdkStubFilesJar = new HashMap<>(); |
| |
| /** Which version number of the annotated JDK should be used? */ |
| private final String annotatedJdkVersion; |
| |
| /** Should the JDK be parsed? */ |
| private final boolean shouldParseJdk; |
| |
| /** Parse all JDK files at startup rather than as needed. */ |
| private final boolean parseAllJdkFiles; |
| |
| /** |
| * Creates an empty annotation source. |
| * |
| * @param factory AnnotatedTypeFactory |
| */ |
| public AnnotationFileElementTypes(AnnotatedTypeFactory factory) { |
| this.factory = factory; |
| this.annotationFileAnnos = new AnnotationFileAnnotations(); |
| this.parsing = false; |
| String release = SystemUtil.getReleaseValue(factory.getProcessingEnv()); |
| this.annotatedJdkVersion = |
| release != null ? release : String.valueOf(SystemUtil.getJreVersion()); |
| |
| this.shouldParseJdk = !factory.getChecker().hasOption("ignorejdkastub"); |
| this.parseAllJdkFiles = factory.getChecker().hasOption("parseAllJdk"); |
| } |
| |
| /** |
| * Returns true if files are currently being parsed; otherwise, false. |
| * |
| * @return true if files are currently being parsed; otherwise, false |
| */ |
| public boolean isParsing() { |
| return parsing; |
| } |
| |
| /** |
| * Parses the stub files in the following order: |
| * |
| * <ol> |
| * <li>jdk.astub in the same directory as the checker, if it exists and ignorejdkastub option is |
| * not supplied |
| * <li>If parsing annotated JDK as stub files, all package-info.java files under the jdk/ |
| * directory |
| * <li>Stub files listed in @StubFiles annotation on the checker; must be in same directory as |
| * the checker |
| * <li>Stub files returned by {@link BaseTypeChecker#getExtraStubFiles} (treated like those |
| * listed in @StubFiles annotation) |
| * <li>Stub files provided via {@code -Astubs} compiler option |
| * </ol> |
| * |
| * <p>If a type is annotated with a qualifier from the same hierarchy in more than one stub file, |
| * the qualifier in the last stub file is applied. |
| * |
| * <p>If using JDK 11, then the JDK stub files are only parsed if a type or declaration annotation |
| * is requested from a class in that file. |
| */ |
| public void parseStubFiles() { |
| parsing = true; |
| BaseTypeChecker checker = factory.getChecker(); |
| ProcessingEnvironment processingEnv = factory.getProcessingEnv(); |
| // 1. jdk.astub |
| // Only look in .jar files, and parse it right away. |
| if (!checker.hasOption("ignorejdkastub")) { |
| InputStream jdkStubIn = checker.getClass().getResourceAsStream("jdk.astub"); |
| if (jdkStubIn != null) { |
| AnnotationFileParser.parseStubFile( |
| checker.getClass().getResource("jdk.astub").toString(), |
| jdkStubIn, |
| factory, |
| processingEnv, |
| annotationFileAnnos, |
| AnnotationFileType.BUILTIN_STUB); |
| } |
| String jdkVersionStub = "jdk" + annotatedJdkVersion + ".astub"; |
| InputStream jdkVersionStubIn = checker.getClass().getResourceAsStream(jdkVersionStub); |
| if (jdkVersionStubIn != null) { |
| AnnotationFileParser.parseStubFile( |
| checker.getClass().getResource(jdkVersionStub).toString(), |
| jdkVersionStubIn, |
| factory, |
| processingEnv, |
| annotationFileAnnos, |
| AnnotationFileType.BUILTIN_STUB); |
| } |
| |
| // 2. Annotated JDK |
| // This preps but does not parse the JDK files (except package-info.java files). |
| // The JDK source code files will be parsed later, on demand. |
| prepJdkStubs(); |
| // prepping the JDK parses all package-info.java files, which sets the `parsing` field to |
| // false, so re-set it to true. |
| parsing = true; |
| } |
| |
| // 3. Stub files listed in @StubFiles annotation on the checker |
| StubFiles stubFilesAnnotation = checker.getClass().getAnnotation(StubFiles.class); |
| if (stubFilesAnnotation != null) { |
| parseAnnotationFiles( |
| Arrays.asList(stubFilesAnnotation.value()), AnnotationFileType.BUILTIN_STUB); |
| } |
| |
| // 4. Stub files returned by the `getExtraStubFiles()` method |
| parseAnnotationFiles(checker.getExtraStubFiles(), AnnotationFileType.BUILTIN_STUB); |
| |
| // 5. Stub files provided via -Astubs command-line option |
| String stubsOption = checker.getOption("stubs"); |
| if (stubsOption != null) { |
| parseAnnotationFiles( |
| Arrays.asList(stubsOption.split(File.pathSeparator)), |
| AnnotationFileType.COMMAND_LINE_STUB); |
| } |
| |
| parsing = false; |
| } |
| |
| /** Parses the ajava files passed through the -Aajava command-line option. */ |
| public void parseAjavaFiles() { |
| parsing = true; |
| // TODO: Error if this is called more than once? |
| SourceChecker checker = factory.getChecker(); |
| List<String> ajavaFiles = new ArrayList<>(); |
| String ajavaOption = checker.getOption("ajava"); |
| if (ajavaOption != null) { |
| Collections.addAll(ajavaFiles, ajavaOption.split(File.pathSeparator)); |
| } |
| |
| parseAnnotationFiles(ajavaFiles, AnnotationFileType.AJAVA); |
| parsing = false; |
| } |
| |
| /** |
| * Parses the ajava file at {@code ajavaPath} assuming {@code root} represents the compilation |
| * unit of that file. Uses {@code root} to get information from javac on specific elements of |
| * {@code ajavaPath}, enabling storage of more detailed annotation information than with just the |
| * ajava file. |
| * |
| * @param ajavaPath path to an ajava file |
| * @param root javac tree for the compilation unit stored in {@code ajavaFile} |
| */ |
| public void parseAjavaFileWithTree(String ajavaPath, CompilationUnitTree root) { |
| parsing = true; |
| SourceChecker checker = factory.getChecker(); |
| ProcessingEnvironment processingEnv = factory.getProcessingEnv(); |
| try { |
| InputStream in = new FileInputStream(ajavaPath); |
| AnnotationFileParser.parseAjavaFile( |
| ajavaPath, in, root, factory, processingEnv, annotationFileAnnos); |
| } catch (IOException e) { |
| checker.message(Kind.NOTE, "Could not read ajava file: " + ajavaPath); |
| } |
| |
| parsing = false; |
| } |
| |
| /** |
| * Parses the files in {@code annotationFiles} of the given file type. This includes files listed |
| * directly in {@code annotationFiles} and for each listed directory, also includes all files |
| * located in that directory (recursively). |
| * |
| * @param annotationFiles list of files and directories to parse |
| * @param fileType the file type of files to parse |
| */ |
| private void parseAnnotationFiles(List<String> annotationFiles, AnnotationFileType fileType) { |
| SourceChecker checker = factory.getChecker(); |
| ProcessingEnvironment processingEnv = factory.getProcessingEnv(); |
| for (String path : annotationFiles) { |
| // Special case when running in jtreg. |
| String base = System.getProperty("test.src"); |
| String fullPath = (base == null) ? path : base + "/" + path; |
| |
| List<AnnotationFileResource> allFiles = |
| AnnotationFileUtil.allAnnotationFiles(fullPath, fileType); |
| if (allFiles != null) { |
| for (AnnotationFileResource resource : allFiles) { |
| InputStream annotationFileStream; |
| try { |
| annotationFileStream = resource.getInputStream(); |
| } catch (IOException e) { |
| checker.message( |
| Kind.NOTE, "Could not read annotation resource: " + resource.getDescription()); |
| continue; |
| } |
| // We use parseStubFile here even for ajava files because at this stage ajava |
| // files are parsed as stub files. The extra annotation data in an ajava file is |
| // parsed when type-checking the ajava file's corresponding Java file. |
| AnnotationFileParser.parseStubFile( |
| resource.getDescription(), |
| annotationFileStream, |
| factory, |
| processingEnv, |
| annotationFileAnnos, |
| fileType == AnnotationFileType.AJAVA ? AnnotationFileType.AJAVA_AS_STUB : fileType); |
| } |
| } else { |
| // We didn't find the files. |
| // If the file has a prefix of "checker.jar/" then look for the file in the top |
| // level directory of the jar that contains the checker. |
| if (path.startsWith("checker.jar/")) { |
| path = path.substring("checker.jar/".length()); |
| } |
| InputStream in = checker.getClass().getResourceAsStream(path); |
| if (in != null) { |
| AnnotationFileParser.parseStubFile( |
| path, in, factory, processingEnv, annotationFileAnnos, fileType); |
| } else { |
| // Didn't find the file. Issue a warning. |
| |
| // When using a compound checker, the target file may be found by the |
| // current checker's parent checkers. Also check this to avoid a false |
| // warning. Currently, only the original checker will try to parse the target |
| // file, the parent checkers are only used to reduce false warnings. |
| SourceChecker currentChecker = checker; |
| boolean findByParentCheckers = false; |
| while (currentChecker != null) { |
| URL topLevelResource = currentChecker.getClass().getResource("/" + path); |
| if (topLevelResource != null) { |
| currentChecker.message( |
| Kind.WARNING, |
| path |
| + " should be in the same directory as " |
| + currentChecker.getClass().getSimpleName() |
| + ".class, but is at the top level of a jar file: " |
| + topLevelResource); |
| findByParentCheckers = true; |
| break; |
| } else { |
| currentChecker = currentChecker.getParentChecker(); |
| } |
| } |
| // If there exists one parent checker that can find this file, don't report a warning. |
| if (!findByParentCheckers) { |
| File parentPath = new File(path).getParentFile(); |
| String parentPathDescription = |
| (parentPath == null |
| ? "current directory" |
| : "directory " + parentPath.getAbsolutePath()); |
| String msg = |
| checker.getClass().getSimpleName() |
| + " did not find annotation file or directory " |
| + path |
| + " on classpath or within " |
| + parentPathDescription |
| + (fullPath.equals(path) ? "" : (" or at " + fullPath)); |
| StringJoiner sj = new StringJoiner(System.lineSeparator() + " "); |
| sj.add(msg); |
| sj.add("Classpath:"); |
| for (URI uri : new ClassGraph().getClasspathURIs()) { |
| sj.add(uri.toString()); |
| } |
| checker.message(Kind.WARNING, sj.toString()); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the annotated type for {@code e} containing only annotations explicitly written in an |
| * annotation file or {@code null} if {@code e} does not appear in an annotation file. |
| * |
| * @param e an Element whose type is returned |
| * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written in |
| * the annotation file and in the element. {@code null} is returned if {@code element} does |
| * not appear in an annotation file. |
| */ |
| public AnnotatedTypeMirror getAnnotatedTypeMirror(Element e) { |
| if (parsing) { |
| return null; |
| } |
| parseEnclosingClass(e); |
| AnnotatedTypeMirror type = annotationFileAnnos.atypes.get(e); |
| return type == null ? null : type.deepCopy(); |
| } |
| |
| /** |
| * Returns the set of declaration annotations for {@code e} containing only annotations explicitly |
| * written in an annotation file or the empty set if {@code e} does not appear in an annotation |
| * file. |
| * |
| * @param elt element for which annotations are returned |
| * @return an AnnotatedTypeMirror for {@code e} containing only annotations explicitly written in |
| * the annotation file and in the element. {@code null} is returned if {@code element} does |
| * not appear in an annotation file. |
| */ |
| public Set<AnnotationMirror> getDeclAnnotation(Element elt) { |
| if (parsing) { |
| return Collections.emptySet(); |
| } |
| |
| parseEnclosingClass(elt); |
| String eltName = ElementUtils.getQualifiedName(elt); |
| if (annotationFileAnnos.declAnnos.containsKey(eltName)) { |
| return annotationFileAnnos.declAnnos.get(eltName); |
| } |
| return Collections.emptySet(); |
| } |
| |
| /** |
| * Returns the method type of the most specific fake override for the given element, when used as |
| * a member of the given type. |
| * |
| * @param elt element for which annotations are returned |
| * @param receiverType the type of the class that contains member (or a subtype of it) |
| * @return the most specific AnnotatedTypeMirror for {@code elt} that is a fake override, or null |
| * if there are no fake overrides |
| */ |
| public @Nullable AnnotatedTypeMirror getFakeOverride( |
| Element elt, AnnotatedTypeMirror receiverType) { |
| if (parsing) { |
| throw new BugInCF("parsing while calling getFakeOverride"); |
| } |
| |
| if (elt.getKind() != ElementKind.METHOD) { |
| return null; |
| } |
| |
| ExecutableElement method = (ExecutableElement) elt; |
| |
| TypeMirror methodReceiverType = method.getReceiverType(); |
| if (methodReceiverType != null && methodReceiverType.getKind() == TypeKind.NONE) { |
| return null; |
| } |
| |
| // This is a list of pairs of (where defined, method type) for fake overrides. The second |
| // element of each pair is currently always an AnnotatedExecutableType. |
| List<Pair<TypeMirror, AnnotatedTypeMirror>> candidates = |
| annotationFileAnnos.fakeOverrides.get(elt); |
| |
| if (candidates == null || candidates.isEmpty()) { |
| return null; |
| } |
| |
| TypeMirror receiverTypeMirror = receiverType.getUnderlyingType(); |
| |
| // A list of fake receiver types. |
| List<TypeMirror> applicableClasses = new ArrayList<>(); |
| List<TypeMirror> applicableInterfaces = new ArrayList<>(); |
| for (Pair<TypeMirror, AnnotatedTypeMirror> candidatePair : candidates) { |
| TypeMirror fakeLocation = candidatePair.first; |
| AnnotatedExecutableType candidate = (AnnotatedExecutableType) candidatePair.second; |
| if (factory.types.isSameType(receiverTypeMirror, fakeLocation)) { |
| return candidate; |
| } else if (factory.types.isSubtype(receiverTypeMirror, fakeLocation)) { |
| TypeElement fakeElement = TypesUtils.getTypeElement(fakeLocation); |
| switch (fakeElement.getKind()) { |
| case CLASS: |
| case ENUM: |
| applicableClasses.add(fakeLocation); |
| break; |
| case INTERFACE: |
| case ANNOTATION_TYPE: |
| applicableInterfaces.add(fakeLocation); |
| break; |
| default: |
| throw new BugInCF( |
| "What type? %s %s %s", fakeElement.getKind(), fakeElement.getClass(), fakeElement); |
| } |
| } |
| } |
| |
| if (applicableClasses.isEmpty() && applicableInterfaces.isEmpty()) { |
| return null; |
| } |
| TypeMirror fakeReceiverType = |
| TypesUtils.mostSpecific( |
| !applicableClasses.isEmpty() ? applicableClasses : applicableInterfaces, |
| factory.getProcessingEnv()); |
| if (fakeReceiverType == null) { |
| StringJoiner message = new StringJoiner(System.lineSeparator()); |
| message.add( |
| String.format( |
| "No most specific fake override found for %s with receiver %s." |
| + " These fake overrides are applicable:", |
| elt, receiverTypeMirror)); |
| for (TypeMirror candidate : applicableClasses) { |
| message.add(" class candidate: " + candidate); |
| } |
| for (TypeMirror candidate : applicableInterfaces) { |
| message.add(" interface candidate: " + candidate); |
| } |
| throw new BugInCF(message.toString()); |
| } |
| |
| for (Pair<TypeMirror, AnnotatedTypeMirror> candidatePair : candidates) { |
| TypeMirror candidateReceiverType = candidatePair.first; |
| if (factory.types.isSameType(fakeReceiverType, candidateReceiverType)) { |
| return (AnnotatedExecutableType) candidatePair.second; |
| } |
| } |
| |
| throw new BugInCF( |
| "No match for %s in %s %s %s", |
| fakeReceiverType, candidates, applicableClasses, applicableInterfaces); |
| } |
| |
| /// |
| /// End of public methods, private helper methods follow |
| /// |
| |
| /** |
| * Parses the outermost enclosing class of {@code e} if there exists an annotation file for it and |
| * it has not already been parsed. |
| * |
| * @param e element whose outermost enclosing class will be parsed |
| */ |
| private void parseEnclosingClass(Element e) { |
| if (!shouldParseJdk) { |
| return; |
| } |
| String className = getOutermostEnclosingClass(e); |
| if (className == null || className.isEmpty()) { |
| return; |
| } |
| if (jdkStubFiles.containsKey(className)) { |
| parseJdkStubFile(jdkStubFiles.get(className)); |
| jdkStubFiles.remove(className); |
| } else if (jdkStubFilesJar.containsKey(className)) { |
| parseJdkJarEntry(jdkStubFilesJar.get(className)); |
| jdkStubFilesJar.remove(className); |
| } |
| } |
| |
| /** |
| * Returns the fully qualified name of the outermost enclosing class of {@code e} or {@code null} |
| * if no such class exists for {@code e}. |
| * |
| * @param e an element whose outermost enclosing class to return |
| * @return the canonical name of the outermost enclosing class of {@code e} or {@code null} if no |
| * class encloses {@code e} |
| */ |
| private @CanonicalNameOrEmpty String getOutermostEnclosingClass(Element e) { |
| TypeElement enclosingClass = ElementUtils.enclosingTypeElement(e); |
| if (enclosingClass == null) { |
| return null; |
| } |
| while (true) { |
| Element element = enclosingClass.getEnclosingElement(); |
| if (element == null || element.getKind() == ElementKind.PACKAGE) { |
| break; |
| } |
| TypeElement t = ElementUtils.enclosingTypeElement(element); |
| if (t == null) { |
| break; |
| } |
| enclosingClass = t; |
| } |
| @SuppressWarnings("signature:assignment" // https://tinyurl.com/cfissue/658: |
| // Name.toString should be @PolySignature |
| ) |
| @CanonicalNameOrEmpty String result = enclosingClass.getQualifiedName().toString(); |
| return result; |
| } |
| |
| /** |
| * Parses the stub file in {@code path}. |
| * |
| * @param path path to file to parse |
| */ |
| private void parseJdkStubFile(Path path) { |
| parsing = true; |
| try (FileInputStream jdkStub = new FileInputStream(path.toFile())) { |
| AnnotationFileParser.parseJdkFileAsStub( |
| path.toFile().getName(), |
| jdkStub, |
| factory, |
| factory.getProcessingEnv(), |
| annotationFileAnnos); |
| } catch (IOException e) { |
| throw new BugInCF("cannot open the jdk stub file " + path, e); |
| } finally { |
| parsing = false; |
| } |
| } |
| |
| /** |
| * Parses the stub file in the given jar entry. |
| * |
| * @param jarEntryName name of the jar entry to parse |
| */ |
| private void parseJdkJarEntry(String jarEntryName) { |
| JarURLConnection connection = getJarURLConnectionToJdk(); |
| parsing = true; |
| try (JarFile jarFile = connection.getJarFile()) { |
| InputStream jdkStub; |
| try { |
| jdkStub = jarFile.getInputStream(jarFile.getJarEntry(jarEntryName)); |
| } catch (IOException e) { |
| throw new BugInCF("cannot open the jdk stub file " + jarEntryName, e); |
| } |
| AnnotationFileParser.parseJdkFileAsStub( |
| jarEntryName, jdkStub, factory, factory.getProcessingEnv(), annotationFileAnnos); |
| } catch (IOException e) { |
| throw new BugInCF("cannot open the Jar file " + connection.getEntryName(), e); |
| } catch (BugInCF e) { |
| throw new BugInCF("Exception while parsing " + jarEntryName + ": " + e.getMessage(), e); |
| } finally { |
| parsing = false; |
| } |
| } |
| |
| /** |
| * Returns a JarURLConnection to "/jdk*". |
| * |
| * @return a JarURLConnection to "/jdk*" |
| */ |
| private JarURLConnection getJarURLConnectionToJdk() { |
| URL resourceURL = factory.getClass().getResource("/annotated-jdk"); |
| JarURLConnection connection; |
| try { |
| connection = (JarURLConnection) resourceURL.openConnection(); |
| |
| // disable caching / connection sharing of the low level URLConnection to the Jarfile |
| connection.setDefaultUseCaches(false); |
| connection.setUseCaches(false); |
| |
| connection.connect(); |
| } catch (IOException e) { |
| throw new BugInCF("cannot open a connection to the Jar file " + resourceURL.getFile(), e); |
| } |
| return connection; |
| } |
| |
| /** |
| * Walk through the jdk directory and create a mapping, {@link #jdkStubFiles}, from file name to |
| * the class contained with in it. Also, parses all package-info.java files. |
| */ |
| private void prepJdkStubs() { |
| if (!shouldParseJdk) { |
| return; |
| } |
| URL resourceURL = factory.getClass().getResource("/annotated-jdk"); |
| if (resourceURL == null) { |
| if (factory.getChecker().hasOption("permitMissingJdk") |
| // temporary, for backward compatibility |
| || factory.getChecker().hasOption("nocheckjdk")) { |
| return; |
| } |
| throw new BugInCF("JDK not found"); |
| } else if (resourceURL.getProtocol().contentEquals("jar")) { |
| prepJdkFromJar(resourceURL); |
| } else if (resourceURL.getProtocol().contentEquals("file")) { |
| prepJdkFromFile(resourceURL); |
| } else { |
| if (factory.getChecker().hasOption("permitMissingJdk") |
| // temporary, for backward compatibility |
| || factory.getChecker().hasOption("nocheckjdk")) { |
| return; |
| } |
| throw new BugInCF("JDK not found"); |
| } |
| } |
| |
| /** |
| * Walk through the JDK directory and create a mapping, {@link #jdkStubFiles}, from file name to |
| * the class contained with in it. Also, parses all package-info.java files. |
| * |
| * @param resourceURL the URL pointing to the JDK directory |
| */ |
| private void prepJdkFromFile(URL resourceURL) { |
| Path root; |
| try { |
| root = Paths.get(resourceURL.toURI()); |
| } catch (URISyntaxException e) { |
| throw new BugInCF("Can parse URL: " + resourceURL.toString(), e); |
| } |
| |
| try (Stream<Path> walk = Files.walk(root)) { |
| List<Path> paths = |
| walk.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java")) |
| .collect(Collectors.toList()); |
| for (Path path : paths) { |
| if (path.getFileName().toString().equals("package-info.java")) { |
| parseJdkStubFile(path); |
| continue; |
| } |
| if (path.getFileName().toString().equals("module-info.java")) { |
| // JavaParser can't parse module-info files, so skip them. |
| continue; |
| } |
| if (parseAllJdkFiles) { |
| parseJdkStubFile(path); |
| continue; |
| } |
| Path relativePath = root.relativize(path); |
| // 4: /src/<module>/share/classes |
| Path savepath = relativePath.subpath(4, relativePath.getNameCount()); |
| String s = savepath.toString().replace(".java", "").replace(File.separatorChar, '.'); |
| jdkStubFiles.put(s, path); |
| } |
| } catch (IOException e) { |
| throw new BugInCF("prepJdkFromFile(" + resourceURL + ")", e); |
| } |
| } |
| |
| /** |
| * Walk through the JDK directory and create a mapping, {@link #jdkStubFilesJar}, from file name |
| * to the class contained with in it. Also, parses all package-info.java files. |
| * |
| * @param resourceURL the URL pointing to the JDK directory |
| */ |
| private void prepJdkFromJar(URL resourceURL) { |
| JarURLConnection connection = getJarURLConnectionToJdk(); |
| |
| try (JarFile jarFile = connection.getJarFile()) { |
| for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) { |
| JarEntry jarEntry = e.nextElement(); |
| // filter out directories and non-class files |
| if (!jarEntry.isDirectory() |
| && jarEntry.getName().endsWith(".java") |
| && jarEntry.getName().startsWith("annotated-jdk") |
| // JavaParser can't parse module-info files, so skip them. |
| && !jarEntry.getName().contains("module-info")) { |
| String jarEntryName = jarEntry.getName(); |
| if (parseAllJdkFiles) { |
| parseJdkJarEntry(jarEntryName); |
| continue; |
| } |
| int index = jarEntry.getName().indexOf("/share/classes/"); |
| String shortName = |
| jarEntryName |
| .substring(index + "/share/classes/".length()) |
| .replace(".java", "") |
| .replace('/', '.'); |
| jdkStubFilesJar.put(shortName, jarEntryName); |
| if (jarEntryName.endsWith("package-info.java")) { |
| parseJdkJarEntry(jarEntryName); |
| } |
| } |
| } |
| } catch (IOException e) { |
| throw new BugInCF("cannot open the Jar file " + resourceURL.getFile(), e); |
| } |
| } |
| } |