blob: ca01706a477cbc4133e066a5b279750aabeb27fa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 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
* Tal Lev-Ami - added package cache for zip files
* Stephan Herrmann - Contribution for
* Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
*******************************************************************************/
package org.eclipse.jdt.internal.core.builder;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.function.Predicate;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IFile;
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.classfmt.ExternalAnnotationDecorator;
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.lookup.BinaryTypeBinding.ExternalAnnotationStatus;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
import org.eclipse.jdt.internal.compiler.util.SimpleSet;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.util.Util;
@SuppressWarnings("rawtypes")
public class ClasspathJar extends ClasspathLocation {
final boolean isOnModulePath;
static class PackageCacheEntry {
long lastModified;
long fileSize;
SimpleSet packageSet;
PackageCacheEntry(long lastModified, long fileSize, SimpleSet packageSet) {
this.lastModified = lastModified;
this.fileSize = fileSize;
this.packageSet = packageSet;
}
}
protected static SimpleLookupTable PackageCache = new SimpleLookupTable();
protected static SimpleLookupTable ModuleCache = new SimpleLookupTable();
protected static void addToPackageSet(SimpleSet packageSet, String fileName, boolean endsWithSep) {
int last = endsWithSep ? fileName.length() : fileName.lastIndexOf('/');
while (last > 0) {
// extract the package name
String packageName = fileName.substring(0, last);
if (packageSet.addIfNotIncluded(packageName) == null)
return; // already existed
last = packageName.lastIndexOf('/');
}
}
/**
* Calculate and cache the package list available in the zipFile.
* @return A SimpleSet with the all the package names in the zipFile.
*/
protected SimpleSet findPackageSet() {
String zipFileName = this.zipFilename;
PackageCacheEntry cacheEntry = (PackageCacheEntry) PackageCache.get(zipFileName);
long timestamp = this.lastModified();
long fileSize = new File(zipFileName).length();
if (cacheEntry != null && cacheEntry.lastModified == timestamp && cacheEntry.fileSize == fileSize) {
return cacheEntry.packageSet;
}
final SimpleSet packageSet = new SimpleSet(41);
packageSet.add(""); //$NON-NLS-1$
readJarContent(packageSet);
PackageCache.put(zipFileName, new PackageCacheEntry(timestamp, fileSize, packageSet));
return packageSet;
}
protected String readJarContent(final SimpleSet packageSet) {
String modInfo = null;
for (Enumeration e = this.zipFile.entries(); e.hasMoreElements(); ) {
String fileName = ((ZipEntry) e.nextElement()).getName();
if (fileName.startsWith("META-INF/")) //$NON-NLS-1$
continue;
if (modInfo == null) {
int folderEnd = fileName.lastIndexOf('/');
folderEnd += 1;
String className = fileName.substring(folderEnd, fileName.length());
if (className.equalsIgnoreCase(IModule.MODULE_INFO_CLASS)) {
modInfo = fileName;
}
}
addToPackageSet(packageSet, fileName, false);
}
return modInfo;
}
IModule initializeModule() {
IModule mod = null;
ZipFile file = null;
try {
file = new ZipFile(this.zipFilename);
String releasePath = "META-INF/versions/" + this.compliance + '/' + IModule.MODULE_INFO_CLASS; //$NON-NLS-1$
ClassFileReader classfile = null;
try {
classfile = ClassFileReader.read(file, releasePath);
} catch (Exception e) {
e.printStackTrace();
// move on to the default
}
if (classfile == null) {
classfile = ClassFileReader.read(file, IModule.MODULE_INFO_CLASS); // FIXME: use jar cache
}
if (classfile != null) {
mod = classfile.getModuleDeclaration();
}
} catch (ClassFormatException | IOException e) {
// do nothing
} finally {
try {
if (file != null)
file.close();
} catch (IOException e) {
// do nothing
}
}
return mod;
}
String zipFilename; // keep for equals
IFile resource;
ZipFile zipFile;
ZipFile annotationZipFile;
long lastModified;
boolean closeZipFileAtEnd;
private SimpleSet knownPackageNames;
AccessRuleSet accessRuleSet;
String externalAnnotationPath;
// Meant for ClasspathMultiReleaseJar, not used in here
String compliance;
ClasspathJar(IFile resource, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath) {
this.resource = resource;
try {
java.net.URI location = resource.getLocationURI();
if (location == null) {
this.zipFilename = ""; //$NON-NLS-1$
} else {
File localFile = Util.toLocalFile(location, null);
this.zipFilename = localFile.getPath();
}
} catch (CoreException e) {
// ignore
this.zipFilename = ""; //$NON-NLS-1$
}
this.zipFile = null;
this.knownPackageNames = null;
this.accessRuleSet = accessRuleSet;
if (externalAnnotationPath != null)
this.externalAnnotationPath = externalAnnotationPath.toString();
this.isOnModulePath = isOnModulePath;
}
ClasspathJar(String zipFilename, long lastModified, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath) {
this.zipFilename = zipFilename;
this.lastModified = lastModified;
this.zipFile = null;
this.knownPackageNames = null;
this.accessRuleSet = accessRuleSet;
if (externalAnnotationPath != null)
this.externalAnnotationPath = externalAnnotationPath.toString();
this.isOnModulePath = isOnModulePath;
}
public ClasspathJar(ZipFile zipFile, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath) {
this(zipFile.getName(), accessRuleSet, externalAnnotationPath, isOnModulePath);
this.zipFile = zipFile;
this.closeZipFileAtEnd = true;
}
public ClasspathJar(String fileName, AccessRuleSet accessRuleSet, IPath externalAnnotationPath, boolean isOnModulePath) {
this(fileName, 0, accessRuleSet, externalAnnotationPath, isOnModulePath);
if (externalAnnotationPath != null)
this.externalAnnotationPath = externalAnnotationPath.toString();
}
@Override
public void cleanup() {
if (this.closeZipFileAtEnd) {
if (this.zipFile != null) {
try {
this.zipFile.close();
if (org.eclipse.jdt.internal.core.JavaModelManager.ZIP_ACCESS_VERBOSE) {
System.out.println("(" + Thread.currentThread() + ") [ClasspathJar.cleanup()] Closed ZipFile on " + this.zipFilename); //$NON-NLS-1$ //$NON-NLS-2$
}
} catch(IOException e) { // ignore it
JavaCore.getPlugin().getLog().log(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, "Error closing " + this.zipFile.getName(), e)); //$NON-NLS-1$
}
this.zipFile = null;
}
if (this.annotationZipFile != null) {
try {
this.annotationZipFile.close();
if (org.eclipse.jdt.internal.core.JavaModelManager.ZIP_ACCESS_VERBOSE) {
System.out.println("(" + Thread.currentThread() + ") [ClasspathJar.cleanup()] Closed Annotation ZipFile on " + this.zipFilename); //$NON-NLS-1$ //$NON-NLS-2$
}
} catch(IOException e) { // ignore it
JavaCore.getPlugin().getLog().log(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, "Error closing " + this.annotationZipFile.getName(), e)); //$NON-NLS-1$
}
this.annotationZipFile = null;
}
} else {
if (this.zipFile != null && org.eclipse.jdt.internal.core.JavaModelManager.ZIP_ACCESS_VERBOSE) {
try {
this.zipFile.size();
System.out.println("(" + Thread.currentThread() + ") [ClasspathJar.cleanup()] ZipFile NOT closed on " + this.zipFilename); //$NON-NLS-1$ //$NON-NLS-2$
} catch (IllegalStateException e) {
// OK: the file was already closed
}
}
}
this.module = null; // TODO(SHMOD): is this safe?
this.knownPackageNames = null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ClasspathJar)) return false;
ClasspathJar jar = (ClasspathJar) o;
if (this.accessRuleSet != jar.accessRuleSet)
if (this.accessRuleSet == null || !this.accessRuleSet.equals(jar.accessRuleSet))
return false;
if (!Util.equalOrNull(this.compliance, jar.compliance)) {
return false;
}
return this.zipFilename.equals(jar.zipFilename)
&& lastModified() == jar.lastModified()
&& this.isOnModulePath == jar.isOnModulePath
&& areAllModuleOptionsEqual(jar);
}
@Override
public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly, Predicate<String> moduleNameFilter) {
if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case
try {
IBinaryType reader = ClassFileReader.read(this.zipFile, qualifiedBinaryFileName);
if (reader != null) {
char[] modName = this.module == null ? null : this.module.name();
if (reader instanceof ClassFileReader) {
ClassFileReader classReader = (ClassFileReader) reader;
if (classReader.moduleName == null)
classReader.moduleName = modName;
else
modName = classReader.moduleName;
}
String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length);
if (this.externalAnnotationPath != null) {
try {
if (this.annotationZipFile == null) {
this.annotationZipFile = ExternalAnnotationDecorator
.getAnnotationZipFile(this.externalAnnotationPath, null);
}
reader = ExternalAnnotationDecorator.create(reader, this.externalAnnotationPath,
fileNameWithoutExtension, this.annotationZipFile);
} catch (IOException e) {
// don't let error on annotations fail class reading
}
if (reader.getExternalAnnotationStatus() == ExternalAnnotationStatus.NOT_EEA_CONFIGURED) {
// ensure a reader that answers NO_EEA_FILE
reader = new ExternalAnnotationDecorator(reader, null);
}
}
if (this.accessRuleSet == null)
return new NameEnvironmentAnswer(reader, null, modName);
return new NameEnvironmentAnswer(reader,
this.accessRuleSet.getViolatedRestriction(fileNameWithoutExtension.toCharArray()),
modName);
}
} catch (IOException | ClassFormatException e) { // treat as if class file is missing
}
return null;
}
@Override
public IPath getProjectRelativePath() {
if (this.resource == null) return null;
return this.resource.getProjectRelativePath();
}
@Override
public int hashCode() {
return this.zipFilename == null ? super.hashCode() : this.zipFilename.hashCode();
}
@Override
public boolean isPackage(String qualifiedPackageName, String moduleName) {
if (moduleName != null) {
if (this.module == null || !moduleName.equals(String.valueOf(this.module.name())))
return false;
}
if (this.knownPackageNames == null)
scanContent();
return this.knownPackageNames.includes(qualifiedPackageName);
}
@Override
public boolean hasCompilationUnit(String pkgName, String moduleName) {
if (scanContent()) {
for (Enumeration<? extends ZipEntry> e = this.zipFile.entries(); e.hasMoreElements(); ) {
String fileName = e.nextElement().getName();
if (fileName.startsWith(pkgName)
&& fileName.toLowerCase().endsWith(SuffixConstants.SUFFIX_STRING_class)
&& fileName.indexOf('/', pkgName.length()+1) == -1)
return true;
}
}
return false;
}
/** Scan the contained packages and try to locate the module descriptor. */
private boolean scanContent() {
try {
if (this.zipFile == null) {
if (org.eclipse.jdt.internal.core.JavaModelManager.ZIP_ACCESS_VERBOSE) {
System.out.println("(" + Thread.currentThread() + ") [ClasspathJar.isPackage(String)] Creating ZipFile on " + this.zipFilename); //$NON-NLS-1$ //$NON-NLS-2$
}
this.zipFile = new ZipFile(this.zipFilename);
this.closeZipFileAtEnd = true;
this.knownPackageNames = findPackageSet();
} else {
this.knownPackageNames = findPackageSet();
}
return true;
} catch(Exception e) {
this.knownPackageNames = new SimpleSet(); // assume for this build the zipFile is empty
return false;
}
}
public long lastModified() {
if (this.lastModified == 0)
this.lastModified = new File(this.zipFilename).lastModified();
return this.lastModified;
}
@Override
public String toString() {
String start = "Classpath jar file " + this.zipFilename; //$NON-NLS-1$
if (this.accessRuleSet == null)
return start;
return start + " with " + this.accessRuleSet; //$NON-NLS-1$
}
@Override
public String debugPathString() {
long time = lastModified();
if (time == 0)
return this.zipFilename;
return this.zipFilename + '(' + (new Date(time)) + " : " + time + ')'; //$NON-NLS-1$
}
@Override
public IModule getModule() {
if (this.knownPackageNames == null)
scanContent();
return this.module;
}
@Override
public NameEnvironmentAnswer findClass(String typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) {
//
return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false, null);
}
public Manifest getManifest() {
if (!scanContent()) // ensure zipFile is initialized
return null;
ZipEntry entry = this.zipFile.getEntry(TypeConstants.META_INF_MANIFEST_MF);
try {
if (entry != null)
return new Manifest(this.zipFile.getInputStream(entry));
} catch (IOException e) {
// cannot use manifest
}
return null;
}
@Override
public char[][] listPackages() {
if (!scanContent()) // ensure zipFile is initialized
return null;
char[][] result = new char[this.knownPackageNames.elementSize][];
int count = 0;
for (int i=0; i<this.knownPackageNames.values.length; i++) {
String string = (String) this.knownPackageNames.values[i];
if (string != null &&!string.isEmpty()) {
result[count++] = string.replace('/', '.').toCharArray();
}
}
if (count < result.length)
return Arrays.copyOf(result, count);
return result;
}
}