org.eclipse.jdt.internal.core.builder.ClasspathJar.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.core.builder.ClasspathJar.java

Source

/*******************************************************************************
 * 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() //$NON-NLS-1$
                                + ") [ClasspathJar.cleanup()] Closed ZipFile on " + this.zipFilename); //$NON-NLS-1$
                    }
                } 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() //$NON-NLS-1$
                                + ") [ClasspathJar.cleanup()] Closed Annotation ZipFile on " + this.zipFilename); //$NON-NLS-1$
                    }
                } 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() //$NON-NLS-1$
                            + ") [ClasspathJar.cleanup()] ZipFile NOT closed on " + this.zipFilename); //$NON-NLS-1$
                } 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() //$NON-NLS-1$
                            + ") [ClasspathJar.isPackage(String)] Creating ZipFile on " + this.zipFilename); //$NON-NLS-1$
                }
                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;
    }
}