| /******************************************************************************* |
| * Copyright (c) 2016, 2022 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.IOException; |
| import java.nio.file.FileSystem; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| 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.CtSym; |
| 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 { |
| |
| static String MODULE_INFO = "module-info.sig"; //$NON-NLS-1$ |
| |
| final String release; |
| String releaseCode; |
| /** |
| * Null for releases without ct.sym file or for releases matching current one |
| */ |
| private FileSystem fs; |
| protected Path releasePath; |
| protected Path modulePath; |
| private String modPathString; |
| CtSym ctSym; |
| |
| |
| |
| 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$ |
| } |
| setZipFile(zipFilename); |
| this.accessRuleSet = accessRuleSet; |
| if (externalAnnotationPath != null) { |
| this.externalAnnotationPath = externalAnnotationPath.toString(); |
| } |
| this.release = getReleaseOptionFromCompliance(release); |
| try { |
| this.ctSym = JRTUtil.getCtSym(Paths.get(this.zipFilename).getParent().getParent()); |
| } catch (IOException e) { |
| throw new CoreException(new Status(IStatus.ERROR, ClasspathJrtWithReleaseOption.class, |
| "Failed to init ct.sym for " + this.zipFilename, e)); //$NON-NLS-1$ |
| } |
| initialize(); |
| loadModules(); |
| } |
| /* |
| * 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) throws CoreException { |
| 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 { |
| if (comp.indexOf('.') == -1) { |
| return comp; |
| } |
| throw new CoreException(new Status(IStatus.ERROR, ClasspathJrtWithReleaseOption.class, |
| "Invalid value for --release argument:" + comp)); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * 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, see {@link CtSym} javadoc. |
| * |
| * @see CtSym |
| */ |
| protected void initialize() throws CoreException { |
| this.releaseCode = CtSym.getReleaseCode(this.release); |
| this.fs = this.ctSym.getFs(); |
| this.releasePath = this.ctSym.getRoot(); |
| Path modPath = this.fs.getPath(this.releaseCode + (this.ctSym.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(this.releasePath.resolve(this.releaseCode))) { |
| 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.releaseCode, "system-modules"))) { //$NON-NLS-1$ |
| this.fs = null; // Fallback to default version, all classes are on jrt fs, not here. |
| } |
| } |
| |
| Map<String, SimpleSet> findPackagesInModules() { |
| // 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 (this.fs == null || !this.ctSym.isJRE12Plus()) { |
| return ClasspathJrt.findPackagesInModules(this); |
| } |
| if (this.modPathString == null) { |
| return Map.of(); |
| } |
| Map<String, SimpleSet> cache = PackageCache.computeIfAbsent(this.modPathString, key -> { |
| final Map<String, SimpleSet> packagesInModule = new HashMap<>(); |
| try { |
| JRTUtil.walkModuleImage(this.jrtFile, this.release, new JrtPackageVisitor(packagesInModule), JRTUtil.NOTIFY_PACKAGES | JRTUtil.NOTIFY_MODULES); |
| } catch (IOException e) { |
| Util.log(e, "Failed to init packages for " + this.modPathString); //$NON-NLS-1$ |
| } |
| return packagesInModule.isEmpty() ? null : Collections.unmodifiableMap(packagesInModule); |
| }); |
| return cache; |
| } |
| |
| public void loadModules() { |
| if (this.fs == null || !this.ctSym.isJRE12Plus()) { |
| ClasspathJrt.loadModules(this); |
| return; |
| } |
| if (this.modPathString == null) { |
| return; |
| } |
| ModulesCache.computeIfAbsent(this.modPathString, key -> { |
| List<Path> releaseRoots = this.ctSym.releaseRoots(this.releaseCode); |
| Map<String, IModule> newCache = new HashMap<>(); |
| for (Path root : releaseRoots) { |
| try { |
| Files.walkFileTree(root, Collections.emptySet(), 2, new JRTUtil.AbstractFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(Path f, BasicFileAttributes attrs) throws IOException { |
| if (attrs.isDirectory() || f.getNameCount() < 3) { |
| return FileVisitResult.CONTINUE; |
| } |
| if (f.getFileName().toString().equals(MODULE_INFO)) { |
| byte[] content = ClasspathJrtWithReleaseOption.this.ctSym.getFileBytes(f); |
| if (content == null) { |
| return FileVisitResult.CONTINUE; |
| } |
| ClasspathJrtWithReleaseOption.this.acceptModule(content, f.getParent().getFileName().toString(), newCache); |
| } |
| return FileVisitResult.SKIP_SIBLINGS; |
| } |
| }); |
| } catch (IOException e) { |
| Util.log(e, "Failed to init modules cache for " + key); //$NON-NLS-1$ |
| } |
| } |
| return newCache.isEmpty() ? null : Collections.unmodifiableMap(newCache); |
| }); |
| } |
| |
| |
| @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 |
| } |
| List<Path> releaseRoots = this.ctSym.releaseRoots(this.releaseCode); |
| try { |
| IBinaryType reader = null; |
| byte[] content = null; |
| String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, |
| qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length); |
| if (!releaseRoots.isEmpty()) { |
| qualifiedBinaryFileName = qualifiedBinaryFileName.replace(".class", ".sig"); //$NON-NLS-1$ //$NON-NLS-2$ |
| Path fullPath = this.ctSym.getFullPath(this.releaseCode, qualifiedBinaryFileName, moduleName); |
| // If file is known, read it from ct.sym |
| if (fullPath != null) { |
| content = this.ctSym.getFileBytes(fullPath); |
| if (content != null) { |
| reader = new ClassFileReader(content, qualifiedBinaryFileName.toCharArray()); |
| if (moduleName != null) { |
| ((ClassFileReader) reader).moduleName = moduleName.toCharArray(); |
| } else { |
| if (this.ctSym.isJRE12Plus()) { |
| moduleName = this.ctSym.getModuleInJre12plus(this.releaseCode, qualifiedBinaryFileName); |
| if (moduleName != null) { |
| ((ClassFileReader) reader).moduleName = moduleName.toCharArray(); |
| } |
| } |
| } |
| } |
| } |
| } else { |
| // Read the file in a "classic" way from the JDK itself |
| reader = ClassFileReader.readFromModule(this.jrtFile, moduleName, qualifiedBinaryFileName, |
| moduleNameFilter); |
| } |
| if (reader != null) |
| return createAnswer(fileNameWithoutExtension, reader, reader.getModule()); |
| } catch (ClassFormatException | IOException e) { |
| // treat as if class file is missing |
| } |
| return null; |
| } |
| |
| @Override |
| public Collection<String> getModuleNames(Collection<String> limitModules) { |
| Map<String, SimpleSet> cache = findPackagesInModules(); |
| if (cache != null) |
| return selectModules(cache.keySet(), limitModules); |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| public void cleanup() { |
| try { |
| super.cleanup(); |
| } finally { |
| // The same file system is also used in JRTUtil, so don't close it here. |
| this.fs = null; |
| this.ctSym = null; |
| } |
| } |
| |
| @Override |
| public boolean hasModule() { |
| return this.fs == null ? super.hasModule() : this.modPathString != null; |
| } |
| |
| @Override |
| protected String getKey() { |
| return this.fs == null ? super.getKey() : 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; |
| } |
| |
| } |