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
/******************************************************************************* * 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 *******************************************************************************/
package org.eclipse.jdt.internal.core; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.IClasspathAttribute; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IModuleDescription; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo; import org.eclipse.jdt.internal.core.nd.IReader; import org.eclipse.jdt.internal.core.nd.java.JavaIndex; import org.eclipse.jdt.internal.core.nd.java.NdResourceFile; import org.eclipse.jdt.internal.core.nd.java.NdType; import org.eclipse.jdt.internal.core.nd.java.NdZipEntry; import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject; import org.eclipse.jdt.internal.core.util.Util;
A package fragment root that corresponds to a .jar or .zip.

NOTE: The only visible entries from a .jar or .zip package fragment root are .class files.

NOTE: A jar package fragment root may or may not have an associated resource.

See Also:
/** * A package fragment root that corresponds to a .jar or .zip. * * <p>NOTE: The only visible entries from a .jar or .zip package fragment root * are .class files. * <p>NOTE: A jar package fragment root may or may not have an associated resource. * * @see org.eclipse.jdt.core.IPackageFragmentRoot * @see org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo */
@SuppressWarnings({"rawtypes", "unchecked"}) public class JarPackageFragmentRoot extends PackageFragmentRoot { protected final static ArrayList EMPTY_LIST = new ArrayList();
The path to the jar file (a workspace relative path if the jar is internal, or an OS path if the jar is external)
/** * The path to the jar file * (a workspace relative path if the jar is internal, * or an OS path if the jar is external) */
protected final IPath jarPath; boolean knownToBeModuleLess; private boolean multiVersion;
Reflects the extra attributes of the classpath entry declaring this root. Caution, this field is used in hashCode() & equals() to avoid overzealous sharing. Can be null, if lookup via the corresponding classpath entry failed.
/** * Reflects the extra attributes of the classpath entry declaring this root. * Caution, this field is used in hashCode() & equals() to avoid overzealous sharing. * Can be null, if lookup via the corresponding classpath entry failed. */
final protected IClasspathAttribute[] extraAttributes;
Constructs a package fragment root which is the root of the Java package directory hierarchy based on a JAR file.
/** * Constructs a package fragment root which is the root of the Java package directory hierarchy * based on a JAR file. */
public JarPackageFragmentRoot(IResource resource, IPath externalJarPath, JavaProject project, IClasspathAttribute[] attributes) { super(resource, project); this.jarPath = externalJarPath; if (attributes == null) { // attributes could either be // (1) provided by the caller (particularly when creating from memento), // (2) retrieved from the corresponding classpath entry (if a resolved classpath is available). // These two cases should cover all normal scenarios, else extraAttributes will be null. try { PerProjectInfo perProjectInfo = project.getPerProjectInfo(); synchronized (perProjectInfo) { if (perProjectInfo.resolvedClasspath != null && perProjectInfo.unresolvedEntryStatus == JavaModelStatus.VERIFIED_OK) { IClasspathEntry classpathEntry = project.getClasspathEntryFor(externalJarPath); if (classpathEntry != null) attributes = classpathEntry.getExtraAttributes(); } } } catch (JavaModelException e) { // ignore } } this.extraAttributes = attributes; }
Compute the package fragment children of this package fragment root. These are all of the directory zip entries, and any directories implied by the path of class files contained in the jar of this package fragment root.
/** * Compute the package fragment children of this package fragment root. * These are all of the directory zip entries, and any directories implied * by the path of class files contained in the jar of this package fragment root. */
@Override protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws JavaModelException { final HashtableOfArrayToObject rawPackageInfo = new HashtableOfArrayToObject(); final Map<String, String> overridden = new HashMap<>(); IJavaElement[] children = NO_ELEMENTS; try { // always create the default package rawPackageInfo.put(CharOperation.NO_STRINGS, new ArrayList[] { EMPTY_LIST, EMPTY_LIST }); boolean usedIndex = false; if (JavaIndex.isEnabled()) { JavaIndex index = JavaIndex.getIndex(); try (IReader reader = index.getNd().acquireReadLock()) { IPath resourcePath = JavaIndex.getLocationForElement(this); if (!resourcePath.isEmpty()) { NdResourceFile resourceFile = index.getResourceFile(resourcePath.toString().toCharArray()); if (index.isUpToDate(resourceFile)) { usedIndex = true; long level = resourceFile.getJdkLevel(); String compliance = CompilerOptions.versionFromJdkLevel(level); // Locate all the non-classfile entries for (NdZipEntry next : resourceFile.getZipEntries()) { String filename = next.getFileName().getString(); initRawPackageInfo(rawPackageInfo, filename, filename.endsWith("/"), compliance); //$NON-NLS-1$ } // Locate all the classfile entries for (NdType type : resourceFile.getTypes()) { String path = new String(type.getTypeId().getBinaryName()) + ".class"; //$NON-NLS-1$ initRawPackageInfo(rawPackageInfo, path, false, compliance); } } } } } // If we weren't able to compute the set of children from the index (either the index was disabled or didn't // contain an up-to-date entry for this .jar) then fetch it directly from the .jar if (!usedIndex) { Object file = JavaModel.getTarget(getPath(), true); long classLevel = Util.getJdkLevel(file); String projectCompliance = this.getJavaProject().getOption(JavaCore.COMPILER_COMPLIANCE, true); long projectLevel = CompilerOptions.versionToJdkLevel(projectCompliance); ZipFile jar = null; try { jar = getJar(); String version = "META-INF/versions/"; //$NON-NLS-1$ List<String> versions = new ArrayList<>(); if (projectLevel >= ClassFileConstants.JDK9 && jar.getEntry(version) != null) { int earliestJavaVersion = ClassFileConstants.MAJOR_VERSION_9; long latestJDK = CompilerOptions.releaseToJDKLevel(projectCompliance); int latestJavaVer = (int) (latestJDK >> 16); for(int i = latestJavaVer; i >= earliestJavaVersion; i--) { String s = "" + + (i - 44); //$NON-NLS-1$ String versionPath = version + s; if (jar.getEntry(versionPath) != null) { versions.add(s); } } } String[] supportedVersions = versions.toArray(new String[versions.size()]); if (supportedVersions.length > 0) { this.multiVersion = true; } int length = version.length(); for (Enumeration<? extends ZipEntry> e= jar.entries(); e.hasMoreElements();) { ZipEntry member= e.nextElement(); String name = member.getName(); if (this.multiVersion && name.length() > (length + 2) && name.startsWith(version)) { int end = name.indexOf('/', length); if (end >= name.length()) continue; String versionPath = name.substring(0, end); String ver = name.substring(length, end); if(versions.contains(ver) && org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) { name = name.substring(end + 1); overridden.put(name, versionPath); } } initRawPackageInfo(rawPackageInfo, name, member.isDirectory(), CompilerOptions.versionFromJdkLevel(classLevel)); } } finally { JavaModelManager.getJavaModelManager().closeZipFile(jar); } } // loop through all of referenced packages, creating package fragments if necessary // and cache the entry names in the rawPackageInfo table children = new IJavaElement[rawPackageInfo.size()]; int index = 0; for (int i = 0, length = rawPackageInfo.keyTable.length; i < length; i++) { String[] pkgName = (String[]) rawPackageInfo.keyTable[i]; if (pkgName == null) continue; children[index++] = getPackageFragment(pkgName); } } catch (CoreException e) { if (e.getCause() instanceof ZipException) { // not a ZIP archive, leave the children empty Util.log(IStatus.ERROR, "Invalid ZIP archive: " + toStringWithAncestors()); //$NON-NLS-1$ children = NO_ELEMENTS; } else if (e instanceof JavaModelException) { throw (JavaModelException)e; } else { throw new JavaModelException(e); } } info.setChildren(children); ((JarPackageFragmentRootInfo) info).rawPackageInfo = rawPackageInfo; ((JarPackageFragmentRootInfo) info).overriddenClasses = overridden; return true; } protected IJavaElement[] createChildren(final HashtableOfArrayToObject rawPackageInfo) { IJavaElement[] children; // loop through all of referenced packages, creating package fragments if necessary // and cache the entry names in the rawPackageInfo table children = new IJavaElement[rawPackageInfo.size()]; int index = 0; for (int i = 0, length = rawPackageInfo.keyTable.length; i < length; i++) { String[] pkgName = (String[]) rawPackageInfo.keyTable[i]; if (pkgName == null) continue; children[index++] = getPackageFragment(pkgName); } return children; }
Returns a new element info for this element.
/** * Returns a new element info for this element. */
@Override protected Object createElementInfo() { return new JarPackageFragmentRootInfo(); }
A Jar is always K_BINARY.
/** * A Jar is always K_BINARY. */
@Override protected int determineKind(IResource underlyingResource) { return IPackageFragmentRoot.K_BINARY; }
Returns true if this handle represents the same jar as the given handle. Two jars are equal if they share the same zip file.
See Also:
  • equals.equals
/** * Returns true if this handle represents the same jar * as the given handle. Two jars are equal if they share * the same zip file. * * @see Object#equals */
@Override public boolean equals(Object o) { if (this == o) return true; if (o instanceof JarPackageFragmentRoot) { JarPackageFragmentRoot other= (JarPackageFragmentRoot) o; return this.jarPath.equals(other.jarPath) && Arrays.equals(this.extraAttributes, other.extraAttributes); } return false; } @Override public String getElementName() { return this.jarPath.lastSegment(); }
Returns the underlying ZipFile for this Jar package fragment root.
Throws:
  • CoreException – if an error occurs accessing the jar
/** * Returns the underlying ZipFile for this Jar package fragment root. * * @exception CoreException if an error occurs accessing the jar */
public ZipFile getJar() throws CoreException { return JavaModelManager.getJavaModelManager().getZipFile(getPath()); }
See Also:
  • IPackageFragmentRoot
/** * @see IPackageFragmentRoot */
@Override public int getKind() { return IPackageFragmentRoot.K_BINARY; } @Override int internalKind() throws JavaModelException { return IPackageFragmentRoot.K_BINARY; }
Returns an array of non-java resources contained in the receiver.
/** * Returns an array of non-java resources contained in the receiver. */
@Override public Object[] getNonJavaResources() throws JavaModelException { // We want to show non java resources of the default package at the root (see PR #1G58NB8) Object[] defaultPkgResources = ((JarPackageFragment) getPackageFragment(CharOperation.NO_STRINGS)).storedNonJavaResources(); int length = defaultPkgResources.length; if (length == 0) return defaultPkgResources; Object[] nonJavaResources = new Object[length]; for (int i = 0; i < length; i++) { JarEntryResource nonJavaResource = (JarEntryResource) defaultPkgResources[i]; nonJavaResources[i] = nonJavaResource.clone(this); } return nonJavaResources; } @Override public PackageFragment getPackageFragment(String[] pkgName) { return new JarPackageFragment(this, pkgName); } @Override public PackageFragment getPackageFragment(String[] pkgName, String mod) { return new JarPackageFragment(this, pkgName); // Overridden in JImageModuleFragmentBridge } @Override public String getClassFilePath(String classname) { if (this.multiVersion) { JarPackageFragmentRootInfo elementInfo; try { elementInfo = (JarPackageFragmentRootInfo) getElementInfo(); String versionPath = elementInfo.overriddenClasses.get(classname); return versionPath == null ? classname : versionPath + '/' + classname; } catch (JavaModelException e) { // move on } } return classname; } @Override public IModuleDescription getModuleDescription() { if (this.knownToBeModuleLess) return null; IModuleDescription module = super.getModuleDescription(); if (module == null) this.knownToBeModuleLess = true; return module; } @Override public IPath internalPath() { if (isExternal()) { return this.jarPath; } else { return super.internalPath(); } } @Override public IResource resource(PackageFragmentRoot root) { if (this.resource == null) { // external jar return null; } return super.resource(root); }
See Also:
  • IJavaElement
/** * @see IJavaElement */
@Override public IResource getUnderlyingResource() throws JavaModelException { if (isExternal()) { if (!exists()) throw newNotPresentException(); return null; } else { return super.getUnderlyingResource(); } } @Override public int hashCode() { return this.jarPath.hashCode() + Arrays.hashCode(this.extraAttributes); } protected void initRawPackageInfo(HashtableOfArrayToObject rawPackageInfo, String entryName, boolean isDirectory, String compliance) { int lastSeparator; if (isDirectory) { if (entryName.charAt(entryName.length() - 1) == '/') { lastSeparator = entryName.length() - 1; } else { lastSeparator = entryName.length(); } } else { lastSeparator = entryName.lastIndexOf('/'); } String[] pkgName = Util.splitOn('/', entryName, 0, lastSeparator); String[] existing = null; int length = pkgName.length; int existingLength = length; while (existingLength >= 0) { existing = (String[]) rawPackageInfo.getKey(pkgName, existingLength); if (existing != null) break; existingLength--; } JavaModelManager manager = JavaModelManager.getJavaModelManager(); for (int i = existingLength; i < length; i++) { // sourceLevel must be null because we know nothing about it based on a jar file if (Util.isValidFolderNameForPackage(pkgName[i], null, compliance)) { System.arraycopy(existing, 0, existing = new String[i+1], 0, i); existing[i] = manager.intern(pkgName[i]); rawPackageInfo.put(existing, new ArrayList[] { EMPTY_LIST, EMPTY_LIST }); } else { // non-Java resource folder if (!isDirectory) { ArrayList[] children = (ArrayList[]) rawPackageInfo.get(existing); if (children[1/*NON_JAVA*/] == EMPTY_LIST) children[1/*NON_JAVA*/] = new ArrayList(); children[1/*NON_JAVA*/].add(entryName); } return; } } if (isDirectory) return; // add classfile info amongst children ArrayList[] children = (ArrayList[]) rawPackageInfo.get(pkgName); if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(entryName)) { if (children[0/*JAVA*/] == EMPTY_LIST) children[0/*JAVA*/] = new ArrayList(); String nameWithoutExtension = entryName.substring(lastSeparator + 1, entryName.length() - 6); children[0/*JAVA*/].add(nameWithoutExtension); } else { if (children[1/*NON_JAVA*/] == EMPTY_LIST) children[1/*NON_JAVA*/] = new ArrayList(); children[1/*NON_JAVA*/].add(entryName); } }
See Also:
  • IPackageFragmentRoot
/** * @see IPackageFragmentRoot */
@Override public boolean isArchive() { return true; }
See Also:
  • IPackageFragmentRoot
/** * @see IPackageFragmentRoot */
@Override public boolean isExternal() { return resource() == null; }
Jars and jar entries are all read only
/** * Jars and jar entries are all read only */
@Override public boolean isReadOnly() { return true; }
Returns whether the corresponding resource or associated file exists
/** * Returns whether the corresponding resource or associated file exists */
@Override protected boolean resourceExists(IResource underlyingResource) { if (underlyingResource == null) { return JavaModel.getExternalTarget( getPath()/*don't make the path relative as this is an external archive*/, true/*check existence*/) != null; } else { return super.resourceExists(underlyingResource); } } @Override protected void toStringAncestors(StringBuffer buffer) { if (isExternal()) // don't show project as it is irrelevant for external jar files. // also see https://bugs.eclipse.org/bugs/show_bug.cgi?id=146615 return; super.toStringAncestors(buffer); } public URL getIndexPath() { try { IClasspathEntry entry = ((JavaProject) getParent()).getClasspathEntryFor(getPath()); if (entry != null) return ((ClasspathEntry)entry).getLibraryIndexLocation(); } catch (JavaModelException e) { // ignore exception } return null; } @Override public Manifest getManifest() { ZipFile jar = null; try { jar = getJar(); ZipEntry mfEntry = jar.getEntry(TypeConstants.META_INF_MANIFEST_MF); if (mfEntry != null) return new Manifest(jar.getInputStream(mfEntry)); } catch (CoreException | IOException e) { // must do without manifest } finally { JavaModelManager.getJavaModelManager().closeZipFile(jar); } return null; } // @Override // public boolean isModule() { // try { // return ((PackageFragmentRootInfo) getElementInfo()).isModule(resource(), this); // } catch (JavaModelException e) { // return false; // } // } }