diff options
Diffstat (limited to 'org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java')
-rw-r--r-- | org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java new file mode 100644 index 0000000000..a5d8446c37 --- /dev/null +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java @@ -0,0 +1,387 @@ +/******************************************************************************* + * Copyright (c) 2016, 2019 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.core.builder; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; +import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; +import org.eclipse.jdt.internal.compiler.env.IBinaryType; +import org.eclipse.jdt.internal.compiler.env.IModule; +import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; +import org.eclipse.jdt.internal.compiler.util.JRTUtil; +import org.eclipse.jdt.internal.compiler.util.SimpleSet; +import org.eclipse.jdt.internal.compiler.util.SuffixConstants; +import org.eclipse.jdt.internal.core.util.Util; + +public class ClasspathJrtWithReleaseOption extends ClasspathJrt { + + final String release; + String releaseInHex; + private String[] subReleases; + private java.nio.file.FileSystem fs; + protected Path modulePath; + private String modPathString; + private boolean isJRE12Plus; + + public ClasspathJrtWithReleaseOption(String zipFilename, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, + String release) throws CoreException { + super(); + if (release == null || release.equals("")) { //$NON-NLS-1$ + throw new IllegalArgumentException("--release argument can not be null"); //$NON-NLS-1$ + } + this.zipFilename = zipFilename; + this.accessRuleSet = accessRuleSet; + if (externalAnnotationPath != null) + this.externalAnnotationPath = externalAnnotationPath.toString(); + this.release = getReleaseOptionFromCompliance(release); + initialize(); + loadModules(this); + } + /* + * JDK 11 doesn't contain release 5. Hence + * if the compliance is below 6, we simply return the lowest supported + * release, which is 6. + */ + private String getReleaseOptionFromCompliance(String comp) { + if (JavaCore.compareJavaVersions(comp, JavaCore.VERSION_1_5) <= 0) { + return "6"; //$NON-NLS-1$ + } + int index = comp.indexOf("1."); //$NON-NLS-1$ + if (index != -1) { + return comp.substring(index + 2, comp.length()); + } else { + return comp; + } + } + private boolean isJRE12Plus(Path path) { + try (DirectoryStream<java.nio.file.Path> stream = Files.newDirectoryStream(path)) { + for (final java.nio.file.Path subdir : stream) { + String rel = subdir.getFileName().toString(); + if (Files.exists(this.fs.getPath(rel, "system-modules"))) { //$NON-NLS-1$ + int parseInt = Integer.parseInt(rel, 16); + return (parseInt > 11); + } + } + } catch (IOException e) { + this.fs = null; + } + return false; + } + /* + * Set up the paths where modules and regular classes need to be read. We need to deal with two different kind of + * formats of cy.sym: Post JDK 12: ct.sym -> 9 -> java/ -> lang/* 9-modules -> java.base -> module-info.sig + * + * From JDK 12 onward: ct.sym -> 9 -> java.base -> module-info.sig java/ -> lang/* Notably, 1) in JDK 12 modules + * classes and ordinary classes are located in the same location 2) in JDK 12, ordinary classes are found inside + * their respective modules + * + */ + protected void initialize() throws CoreException { + this.releaseInHex = Integer.toHexString(Integer.parseInt(this.release)).toUpperCase(); + Path lib = Paths.get(this.zipFilename).getParent(); + Path filePath = Paths.get(lib.toString(), "ct.sym"); //$NON-NLS-1$ + URI t = filePath.toUri(); + if (!Files.exists(filePath)) { + return; + } + URI uri = URI.create("jar:file:" + t.getRawPath()); //$NON-NLS-1$ + try { + this.fs = FileSystems.getFileSystem(uri); + } catch (FileSystemNotFoundException fne) { + // Ignore and move on + } + if (this.fs == null) { + HashMap<String, ?> env = new HashMap<>(); + try { + this.fs = FileSystems.newFileSystem(uri, env); + } catch (IOException e) { + return; + } + } + Path releasePath = this.fs.getPath("/"); //$NON-NLS-1$ + this.isJRE12Plus = isJRE12Plus(releasePath); + Path modPath = this.fs.getPath(this.releaseInHex + (this.isJRE12Plus ? "" : "-modules")); //$NON-NLS-1$ //$NON-NLS-2$ + if (Files.exists(modPath)) { + this.modulePath = modPath; + this.modPathString = this.zipFilename + "|"+ modPath.toString(); //$NON-NLS-1$ + } + + if (!Files.exists(releasePath.resolve(this.releaseInHex))) { + Exception e = new IllegalArgumentException("release " + this.release + " is not found in the system"); //$NON-NLS-1$//$NON-NLS-2$ + throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, e.getMessage(), e)); + } + if (Files.exists(this.fs.getPath(this.releaseInHex, "system-modules"))) { //$NON-NLS-1$ + this.fs = null; // Fallback to default version + return; + } + if (this.release != null) { + List<String> sub = new ArrayList<>(); + try (DirectoryStream<java.nio.file.Path> stream = Files.newDirectoryStream(releasePath)) { + for (final java.nio.file.Path subdir : stream) { + String rel = subdir.getFileName().toString(); + if (rel.contains(this.releaseInHex)) { + sub.add(rel); + } else { + continue; + } + } + } catch (IOException e) { + this.fs = null; // Fallback to default version + } + this.subReleases = sub.toArray(new String[sub.size()]); + } + } + + static HashMap<String, SimpleSet> findPackagesInModules(final ClasspathJrtWithReleaseOption jrt) { + // In JDK 11 and before, classes are not listed under their respective modules + // Hence, we simply go to the default module system for package-module mapping + if (jrt.fs == null || !jrt.isJRE12Plus) { + return ClasspathJrt.findPackagesInModules(jrt); + } + String zipFileName = jrt.zipFilename; + HashMap<String, SimpleSet> cache = PackageCache.get(jrt.modPathString); + if (cache != null) { + return cache; + } + final HashMap<String, SimpleSet> packagesInModule = new HashMap<>(); + PackageCache.put(jrt.modPathString, packagesInModule); + try { + final File imageFile = new File(zipFileName); + org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(imageFile, jrt.release, + new org.eclipse.jdt.internal.compiler.util.JRTUtil.JrtFileVisitor<Path>() { + SimpleSet packageSet = null; + + @Override + public FileVisitResult visitPackage(Path dir, Path mod, BasicFileAttributes attrs) + throws IOException { + ClasspathJar.addToPackageSet(this.packageSet, dir.toString(), true); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, Path mod, BasicFileAttributes attrs) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitModule(Path mod) throws IOException { + String name = mod.getName(1).toString(); + this.packageSet = new SimpleSet(41); + this.packageSet.add(""); //$NON-NLS-1$ + packagesInModule.put(name, this.packageSet); + return FileVisitResult.CONTINUE; + } + }, JRTUtil.NOTIFY_PACKAGES | JRTUtil.NOTIFY_MODULES); + } catch (IOException e) { + // return empty handed + } + return packagesInModule; + } + + public static void loadModules(final ClasspathJrtWithReleaseOption jrt) { + if (jrt.fs == null || !jrt.isJRE12Plus) { + ClasspathJrt.loadModules(jrt); + return; + } + if (jrt.modPathString == null) + return; + Set<IModule> cache = ModulesCache.get(jrt.modPathString); + if (cache == null) { + try (DirectoryStream<java.nio.file.Path> stream = Files.newDirectoryStream(jrt.modulePath)) { + for (final java.nio.file.Path subdir : stream) { + + Files.walkFileTree(subdir, Collections.EMPTY_SET, 1, new FileVisitor<java.nio.file.Path>() { + @Override + public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(java.nio.file.Path f, BasicFileAttributes attrs) + throws IOException { + byte[] content = null; + if (Files.exists(f)) { + content = JRTUtil.safeReadBytes(f); + if (content == null) + return FileVisitResult.CONTINUE; + jrt.acceptModule(content); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(java.nio.file.Path f, IOException exc) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(java.nio.file.Path dir, IOException exc) + throws IOException { + return FileVisitResult.CONTINUE; + } + }); + } + } catch (IOException e) { + // Nothing much to do + } + } + } + + + @Override + public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPackageName, String moduleName, + String qualifiedBinaryFileName, boolean asBinaryOnly, Predicate<String> moduleNameFilter) { + + if (this.fs == null) { + return super.findClass(binaryFileName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, + asBinaryOnly, moduleNameFilter); + } + if (!isPackage(qualifiedPackageName, moduleName)) + return null; // most common case + + try { + IBinaryType reader = null; + byte[] content = null; + String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, + qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length); + if (this.subReleases != null && this.subReleases.length > 0) { + qualifiedBinaryFileName = qualifiedBinaryFileName.replace(".class", ".sig"); //$NON-NLS-1$ //$NON-NLS-2$ + outer: for (String rel : this.subReleases) { + Path p = null; + inner: if (this.isJRE12Plus) { + if (moduleName != null) { + p = this.fs.getPath(rel, moduleName, qualifiedBinaryFileName); + } + else { + try (DirectoryStream<java.nio.file.Path> stream = Files + .newDirectoryStream(this.fs.getPath(rel))) { + for (final java.nio.file.Path subdir : stream) { + p = subdir.resolve(qualifiedBinaryFileName); + if (Files.exists(p)) { + if (subdir.getNameCount() == 2 ) { + moduleName = subdir.getName(1).toString(); + } + break inner; + } + } + } + } + } else { + p = this.fs.getPath(rel, qualifiedBinaryFileName); + } + if (Files.exists(p)) { + content = JRTUtil.safeReadBytes(p); + if (content != null) { + reader = new ClassFileReader(content, qualifiedBinaryFileName.toCharArray()); + if (moduleName != null) + ((ClassFileReader) reader).moduleName = moduleName.toCharArray(); + break outer; + } + } + } + } else { + reader = ClassFileReader.readFromModule(new File(this.zipFilename), moduleName, qualifiedBinaryFileName, + moduleNameFilter); + } + return createAnswer(fileNameWithoutExtension, reader); + } catch (ClassFormatException e) { + // treat as if class file is missing + } catch (IOException e) { + // treat as if class file is missing + } + return null; + } + + @Override + public Collection<String> getModuleNames(Collection<String> limitModules) { + HashMap<String, SimpleSet> cache = findPackagesInModules(this); + if (cache != null) + return selectModules(cache.keySet(), limitModules); + return Collections.emptyList(); + } + + @Override + public void cleanup() { + try { + super.reset(); + } finally { + // The same file system is also used in JRTUtil, so don't close it here. + this.fs = null; + } + } + + @Override + public boolean hasModule() { + return this.modPathString != null; + } + + @Override + protected String getKey() { + return this.modPathString; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ClasspathJrtWithReleaseOption)) + return false; + ClasspathJrtWithReleaseOption jar = (ClasspathJrtWithReleaseOption) o; + if (!Util.equalOrNull(this.release, jar.release)) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + int hash = this.zipFilename == null ? super.hashCode() : this.zipFilename.hashCode(); + return Util.combineHashCodes(hash, this.release.hashCode()); + } + + @Override + public String toString() { + String start = "Classpath jrt file " + this.zipFilename + " with --release option " + this.release; //$NON-NLS-1$ //$NON-NLS-2$ + return start; + } + +} |