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;
@SuppressWarnings({"rawtypes", "unchecked"})
public class JarPackageFragmentRoot extends PackageFragmentRoot {
protected final static ArrayList EMPTY_LIST = new ArrayList();
protected final IPath jarPath;
boolean knownToBeModuleLess;
private boolean multiVersion;
final protected IClasspathAttribute[] ;
public JarPackageFragmentRoot(IResource resource, IPath externalJarPath, JavaProject project, IClasspathAttribute[] attributes) {
super(resource, project);
this.jarPath = externalJarPath;
if (attributes == 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) {
}
}
this.extraAttributes = attributes;
}
@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 {
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);
for (NdZipEntry next : resourceFile.getZipEntries()) {
String filename = next.getFileName().getString();
initRawPackageInfo(rawPackageInfo, filename, filename.endsWith("/"), compliance);
}
for (NdType type : resourceFile.getTypes()) {
String path = new String(type.getTypeId().getBinaryName()) + ".class";
initRawPackageInfo(rawPackageInfo, path, false, compliance);
}
}
}
}
}
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/";
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);
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);
}
}
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) {
Util.log(IStatus.ERROR, "Invalid ZIP archive: " + toStringWithAncestors());
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;
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;
}
@Override
protected Object createElementInfo() {
return new JarPackageFragmentRootInfo();
}
@Override
protected int determineKind(IResource underlyingResource) {
return IPackageFragmentRoot.K_BINARY;
}
@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();
}
public ZipFile getJar() throws CoreException {
return JavaModelManager.getJavaModelManager().getZipFile(getPath());
}
@Override
public int getKind() {
return IPackageFragmentRoot.K_BINARY;
}
@Override
int internalKind() throws JavaModelException {
return IPackageFragmentRoot.K_BINARY;
}
@Override
public Object[] getNonJavaResources() throws JavaModelException {
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);
}
@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) {
}
}
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) {
return null;
}
return super.resource(root);
}
@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++) {
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 {
if (!isDirectory) {
ArrayList[] children = (ArrayList[]) rawPackageInfo.get(existing);
if (children[1] == EMPTY_LIST) children[1] = new ArrayList();
children[1].add(entryName);
}
return;
}
}
if (isDirectory)
return;
ArrayList[] children = (ArrayList[]) rawPackageInfo.get(pkgName);
if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(entryName)) {
if (children[0] == EMPTY_LIST) children[0] = new ArrayList();
String nameWithoutExtension = entryName.substring(lastSeparator + 1, entryName.length() - 6);
children[0].add(nameWithoutExtension);
} else {
if (children[1] == EMPTY_LIST) children[1] = new ArrayList();
children[1].add(entryName);
}
}
@Override
public boolean isArchive() {
return true;
}
@Override
public boolean isExternal() {
return resource() == null;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
protected boolean resourceExists(IResource underlyingResource) {
if (underlyingResource == null) {
return
JavaModel.getExternalTarget(
getPath(),
true) != null;
} else {
return super.resourceExists(underlyingResource);
}
}
@Override
protected void toStringAncestors(StringBuffer buffer) {
if (isExternal())
return;
super.toStringAncestors(buffer);
}
public URL getIndexPath() {
try {
IClasspathEntry entry = ((JavaProject) getParent()).getClasspathEntryFor(getPath());
if (entry != null) return ((ClasspathEntry)entry).getLibraryIndexLocation();
} catch (JavaModelException e) {
}
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) {
} finally {
JavaModelManager.getJavaModelManager().closeZipFile(jar);
}
return null;
}
}