All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jdt.internal.core.JarPackageFragmentRoot Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2000, 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;

import java.io.IOException;
import java.io.InputStream;
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.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.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 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) */ 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. */ final protected IClasspathAttribute[] extraAttributes; /** * 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. */ @Override protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws JavaModelException { final HashtableOfArrayToObject rawPackageInfo = new HashtableOfArrayToObject(); final Map overridden = new HashMap<>(); IJavaElement[] children = NO_ELEMENTS; try { // always create the default package rawPackageInfo.put(CharOperation.NO_STRINGS, new ArrayList[] { EMPTY_LIST, EMPTY_LIST }); Object file = JavaModel.getTarget(this, 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 versions = new ArrayList<>(); if (projectLevel >= ClassFileConstants.JDK9 && jar.getEntry(version) != null) { int earliestJavaVersion = ClassFileConstants.MAJOR_VERSION_9; long latestJDK = CompilerOptions.versionToJdkLevel(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 e= jar.entries(); e.hasMoreElements();) { ZipEntry member= e.nextElement(); String name = member.getName(); if (name.contains("..")) { //$NON-NLS-1$ throw new IllegalArgumentException("Bad zip entry: "+name+" in "+jar.getName()); //$NON-NLS-1$ //$NON-NLS-2$ } 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 zipex) { // not a ZIP archive, leave the children empty Util.log(zipex, "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. */ @Override protected JarPackageFragmentRootInfo createElementInfo() { return new JarPackageFragmentRootInfo(); } /** * 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 Object#equals */ @Override public boolean equals(Object o) { if (this == o) return true; if (o instanceof JarPackageFragmentRoot other) { return this.jarPath.equals(other.jarPath) && Arrays.equals(this.extraAttributes, other.extraAttributes); } return false; } @Override protected int calculateHashCode() { return this.jarPath.hashCode() + Arrays.hashCode(this.extraAttributes); } @Override public String getElementName() { return this.jarPath.lastSegment(); } /** * 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 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. */ @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 IJavaElement */ @Override public IResource getUnderlyingResource() throws JavaModelException { if (isExternal()) { if (!exists()) throw newNotPresentException(); return null; } else { return super.getUnderlyingResource(); } } 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 IPackageFragmentRoot */ @Override public boolean isArchive() { return true; } /** * @see IPackageFragmentRoot */ @Override public boolean isExternal() { return resource() == null; } /** * Jars and jar entries are all read only */ @Override public boolean isReadOnly() { return true; } /** * 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(StringBuilder 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) { try (InputStream is = jar.getInputStream(mfEntry)) { return new Manifest(is); } } } 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; // } // } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy