blob: b2bfac7488b7fbe624b04eba9a12b179239186be [file] [log] [blame]
/*******************************************************************************
* 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;
}
}