1 /*******************************************************************************
2  * Copyright (c) 2000, 2019 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.internal.core;
15 
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.net.URL;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Enumeration;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.jar.Manifest;
26 import java.util.zip.ZipEntry;
27 import java.util.zip.ZipException;
28 import java.util.zip.ZipFile;
29 
30 import org.eclipse.core.resources.IResource;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IPath;
33 import org.eclipse.core.runtime.IStatus;
34 import org.eclipse.jdt.core.IClasspathAttribute;
35 import org.eclipse.jdt.core.IClasspathEntry;
36 import org.eclipse.jdt.core.IJavaElement;
37 import org.eclipse.jdt.core.IModuleDescription;
38 import org.eclipse.jdt.core.IPackageFragmentRoot;
39 import org.eclipse.jdt.core.JavaCore;
40 import org.eclipse.jdt.core.JavaModelException;
41 import org.eclipse.jdt.core.compiler.CharOperation;
42 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
43 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
44 import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
45 import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo;
46 import org.eclipse.jdt.internal.core.nd.IReader;
47 import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
48 import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
49 import org.eclipse.jdt.internal.core.nd.java.NdType;
50 import org.eclipse.jdt.internal.core.nd.java.NdZipEntry;
51 import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject;
52 import org.eclipse.jdt.internal.core.util.Util;
53 
54 /**
55  * A package fragment root that corresponds to a .jar or .zip.
56  *
57  * <p>NOTE: The only visible entries from a .jar or .zip package fragment root
58  * are .class files.
59  * <p>NOTE: A jar package fragment root may or may not have an associated resource.
60  *
61  * @see org.eclipse.jdt.core.IPackageFragmentRoot
62  * @see org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo
63  */
64 @SuppressWarnings({"rawtypes", "unchecked"})
65 public class JarPackageFragmentRoot extends PackageFragmentRoot {
66 
67 	protected final static ArrayList EMPTY_LIST = new ArrayList();
68 
69 	/**
70 	 * The path to the jar file
71 	 * (a workspace relative path if the jar is internal,
72 	 * or an OS path if the jar is external)
73 	 */
74 	protected final IPath jarPath;
75 
76 	boolean knownToBeModuleLess;
77 
78 	private boolean multiVersion;
79 
80 	/**
81 	 * Reflects the extra attributes of the classpath entry declaring this root.
82 	 * Caution, this field is used in hashCode() & equals() to avoid overzealous sharing.
83 	 * Can be null, if lookup via the corresponding classpath entry failed.
84 	 */
85 	final protected IClasspathAttribute[] extraAttributes;
86 
87 	/**
88 	 * Constructs a package fragment root which is the root of the Java package directory hierarchy
89 	 * based on a JAR file.
90 	 */
JarPackageFragmentRoot(IResource resource, IPath externalJarPath, JavaProject project, IClasspathAttribute[] attributes)91 	public JarPackageFragmentRoot(IResource resource, IPath externalJarPath, JavaProject project, IClasspathAttribute[] attributes) {
92 		super(resource, project);
93 		this.jarPath = externalJarPath;
94 		if (attributes == null) {
95 			// attributes could either be
96 			// (1) provided by the caller (particularly when creating from memento),
97 			// (2) retrieved from the corresponding classpath entry (if a resolved classpath is available).
98 			// These two cases should cover all normal scenarios, else extraAttributes will be null.
99 			try {
100 				PerProjectInfo perProjectInfo = project.getPerProjectInfo();
101 				synchronized (perProjectInfo) {
102 					if (perProjectInfo.resolvedClasspath != null && perProjectInfo.unresolvedEntryStatus == JavaModelStatus.VERIFIED_OK) {
103 						IClasspathEntry classpathEntry = project.getClasspathEntryFor(externalJarPath);
104 						if (classpathEntry != null)
105 							attributes = classpathEntry.getExtraAttributes();
106 					}
107 				}
108 			} catch (JavaModelException e) {
109 				// ignore
110 			}
111 		}
112 		this.extraAttributes = attributes;
113 	}
114 
115 	/**
116 	 * Compute the package fragment children of this package fragment root.
117 	 * These are all of the directory zip entries, and any directories implied
118 	 * by the path of class files contained in the jar of this package fragment root.
119 	 */
120 	@Override
computeChildren(OpenableElementInfo info, IResource underlyingResource)121 	protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws JavaModelException {
122 		final HashtableOfArrayToObject rawPackageInfo = new HashtableOfArrayToObject();
123 		final Map<String, String> overridden = new HashMap<>();
124 		IJavaElement[] children = NO_ELEMENTS;
125 		try {
126 			// always create the default package
127 			rawPackageInfo.put(CharOperation.NO_STRINGS, new ArrayList[] { EMPTY_LIST, EMPTY_LIST });
128 
129 			boolean usedIndex = false;
130 			if (JavaIndex.isEnabled()) {
131 				JavaIndex index = JavaIndex.getIndex();
132 				try (IReader reader = index.getNd().acquireReadLock()) {
133 					IPath resourcePath = JavaIndex.getLocationForElement(this);
134 					if (!resourcePath.isEmpty()) {
135 						NdResourceFile resourceFile = index.getResourceFile(resourcePath.toString().toCharArray());
136 						if (index.isUpToDate(resourceFile)) {
137 							usedIndex = true;
138 							long level = resourceFile.getJdkLevel();
139 							String compliance = CompilerOptions.versionFromJdkLevel(level);
140 							// Locate all the non-classfile entries
141 							for (NdZipEntry next : resourceFile.getZipEntries()) {
142 								String filename = next.getFileName().getString();
143 								initRawPackageInfo(rawPackageInfo, filename, filename.endsWith("/"), compliance); //$NON-NLS-1$
144 							}
145 
146 							// Locate all the classfile entries
147 							for (NdType type : resourceFile.getTypes()) {
148 								String path = new String(type.getTypeId().getBinaryName()) + ".class"; //$NON-NLS-1$
149 								initRawPackageInfo(rawPackageInfo, path, false, compliance);
150 							}
151 						}
152 					}
153 				}
154 			}
155 
156 			// If we weren't able to compute the set of children from the index (either the index was disabled or didn't
157 			// contain an up-to-date entry for this .jar) then fetch it directly from the .jar
158 			if (!usedIndex) {
159 				Object file = JavaModel.getTarget(getPath(), true);
160 				long classLevel = Util.getJdkLevel(file);
161 				String projectCompliance = this.getJavaProject().getOption(JavaCore.COMPILER_COMPLIANCE, true);
162 				long projectLevel = CompilerOptions.versionToJdkLevel(projectCompliance);
163 				ZipFile jar = null;
164 				try {
165 					jar = getJar();
166 					String version = "META-INF/versions/";  //$NON-NLS-1$
167 					List<String> versions = new ArrayList<>();
168 					if (projectLevel >= ClassFileConstants.JDK9 && jar.getEntry(version) != null) {
169 						int earliestJavaVersion = ClassFileConstants.MAJOR_VERSION_9;
170 						long latestJDK = CompilerOptions.releaseToJDKLevel(projectCompliance);
171 						int latestJavaVer = (int) (latestJDK >> 16);
172 
173 						for(int i = latestJavaVer; i >= earliestJavaVersion; i--) {
174 							String s = "" + + (i - 44); //$NON-NLS-1$
175 							String versionPath = version + s;
176 							if (jar.getEntry(versionPath) != null) {
177 								versions.add(s);
178 							}
179 						}
180 					}
181 
182 					String[] supportedVersions = versions.toArray(new String[versions.size()]);
183 					if (supportedVersions.length > 0) {
184 						this.multiVersion = true;
185 					}
186 					int length = version.length();
187 					for (Enumeration<? extends ZipEntry> e= jar.entries(); e.hasMoreElements();) {
188 						ZipEntry member= e.nextElement();
189 						String name = member.getName();
190 						if (this.multiVersion && name.length() > (length + 2) && name.startsWith(version)) {
191 							int end = name.indexOf('/', length);
192 							if (end >= name.length()) continue;
193 							String versionPath = name.substring(0, end);
194 							String ver = name.substring(length, end);
195 							if(versions.contains(ver) && org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) {
196 								name = name.substring(end + 1);
197 								overridden.put(name, versionPath);
198 							}
199 						}
200 						initRawPackageInfo(rawPackageInfo, name, member.isDirectory(), CompilerOptions.versionFromJdkLevel(classLevel));
201 					}
202 				}  finally {
203 					JavaModelManager.getJavaModelManager().closeZipFile(jar);
204 				}
205 			}
206 			// loop through all of referenced packages, creating package fragments if necessary
207 			// and cache the entry names in the rawPackageInfo table
208 			children = new IJavaElement[rawPackageInfo.size()];
209 			int index = 0;
210 			for (int i = 0, length = rawPackageInfo.keyTable.length; i < length; i++) {
211 				String[] pkgName = (String[]) rawPackageInfo.keyTable[i];
212 				if (pkgName == null) continue;
213 				children[index++] = getPackageFragment(pkgName);
214 			}
215 		} catch (CoreException e) {
216 			if (e.getCause() instanceof ZipException) {
217 				// not a ZIP archive, leave the children empty
218 				Util.log(IStatus.ERROR, "Invalid ZIP archive: " + toStringWithAncestors()); //$NON-NLS-1$
219 				children = NO_ELEMENTS;
220 			} else if (e instanceof JavaModelException) {
221 				throw (JavaModelException)e;
222 			} else {
223 				throw new JavaModelException(e);
224 			}
225 		}
226 		info.setChildren(children);
227 		((JarPackageFragmentRootInfo) info).rawPackageInfo = rawPackageInfo;
228 		((JarPackageFragmentRootInfo) info).overriddenClasses = overridden;
229 		return true;
230 	}
createChildren(final HashtableOfArrayToObject rawPackageInfo)231 	protected IJavaElement[] createChildren(final HashtableOfArrayToObject rawPackageInfo) {
232 		IJavaElement[] children;
233 		// loop through all of referenced packages, creating package fragments if necessary
234 		// and cache the entry names in the rawPackageInfo table
235 		children = new IJavaElement[rawPackageInfo.size()];
236 		int index = 0;
237 		for (int i = 0, length = rawPackageInfo.keyTable.length; i < length; i++) {
238 			String[] pkgName = (String[]) rawPackageInfo.keyTable[i];
239 			if (pkgName == null) continue;
240 			children[index++] = getPackageFragment(pkgName);
241 		}
242 		return children;
243 	}
244 	/**
245 	 * Returns a new element info for this element.
246 	 */
247 	@Override
createElementInfo()248 	protected Object createElementInfo() {
249 		return new JarPackageFragmentRootInfo();
250 	}
251 	/**
252 	 * A Jar is always K_BINARY.
253 	 */
254 	@Override
determineKind(IResource underlyingResource)255 	protected int determineKind(IResource underlyingResource) {
256 		return IPackageFragmentRoot.K_BINARY;
257 	}
258 	/**
259 	 * Returns true if this handle represents the same jar
260 	 * as the given handle. Two jars are equal if they share
261 	 * the same zip file.
262 	 *
263 	 * @see Object#equals
264 	 */
265 	@Override
equals(Object o)266 	public boolean equals(Object o) {
267 		if (this == o)
268 			return true;
269 		if (o instanceof JarPackageFragmentRoot) {
270 			JarPackageFragmentRoot other= (JarPackageFragmentRoot) o;
271 			return this.jarPath.equals(other.jarPath)
272 					&& Arrays.equals(this.extraAttributes, other.extraAttributes);
273 		}
274 		return false;
275 	}
276 	@Override
getElementName()277 	public String getElementName() {
278 		return this.jarPath.lastSegment();
279 	}
280 	/**
281 	 * Returns the underlying ZipFile for this Jar package fragment root.
282 	 *
283 	 * @exception CoreException if an error occurs accessing the jar
284 	 */
getJar()285 	public ZipFile getJar() throws CoreException {
286 		return JavaModelManager.getJavaModelManager().getZipFile(getPath());
287 	}
288 	/**
289 	 * @see IPackageFragmentRoot
290 	 */
291 	@Override
getKind()292 	public int getKind() {
293 		return IPackageFragmentRoot.K_BINARY;
294 	}
295 	@Override
internalKind()296 	int internalKind() throws JavaModelException {
297 		return IPackageFragmentRoot.K_BINARY;
298 	}
299 	/**
300 	 * Returns an array of non-java resources contained in the receiver.
301 	 */
302 	@Override
getNonJavaResources()303 	public Object[] getNonJavaResources() throws JavaModelException {
304 		// We want to show non java resources of the default package at the root (see PR #1G58NB8)
305 		Object[] defaultPkgResources =  ((JarPackageFragment) getPackageFragment(CharOperation.NO_STRINGS)).storedNonJavaResources();
306 		int length = defaultPkgResources.length;
307 		if (length == 0)
308 			return defaultPkgResources;
309 		Object[] nonJavaResources = new Object[length];
310 		for (int i = 0; i < length; i++) {
311 			JarEntryResource nonJavaResource = (JarEntryResource) defaultPkgResources[i];
312 			nonJavaResources[i] = nonJavaResource.clone(this);
313 		}
314 		return nonJavaResources;
315 	}
316 	@Override
getPackageFragment(String[] pkgName)317 	public PackageFragment getPackageFragment(String[] pkgName) {
318 		return new JarPackageFragment(this, pkgName);
319 	}
320 	@Override
getPackageFragment(String[] pkgName, String mod)321 	public PackageFragment getPackageFragment(String[] pkgName, String mod) {
322 		return new JarPackageFragment(this, pkgName); // Overridden in JImageModuleFragmentBridge
323 	}
324 
325 	@Override
getClassFilePath(String classname)326 	public String getClassFilePath(String classname) {
327 		if (this.multiVersion) {
328 			JarPackageFragmentRootInfo elementInfo;
329 			try {
330 				elementInfo = (JarPackageFragmentRootInfo) getElementInfo();
331 				String versionPath = elementInfo.overriddenClasses.get(classname);
332 				return versionPath == null ? classname : versionPath + '/' + classname;
333 			} catch (JavaModelException e) {
334 				// move on
335 			}
336 		}
337 		return classname;
338 	}
339 	@Override
getModuleDescription()340 	public IModuleDescription getModuleDescription() {
341 		if (this.knownToBeModuleLess)
342 			return null;
343 		IModuleDescription module = super.getModuleDescription();
344 		if (module == null)
345 			this.knownToBeModuleLess = true;
346 		return module;
347 	}
348 
349 	@Override
internalPath()350 	public IPath internalPath() {
351 		if (isExternal()) {
352 			return this.jarPath;
353 		} else {
354 			return super.internalPath();
355 		}
356 	}
357 	@Override
resource(PackageFragmentRoot root)358 	public IResource resource(PackageFragmentRoot root) {
359 		if (this.resource == null) {
360 			// external jar
361 			return null;
362 		}
363 		return super.resource(root);
364 	}
365 
366 
367 	/**
368 	 * @see IJavaElement
369 	 */
370 	@Override
getUnderlyingResource()371 	public IResource getUnderlyingResource() throws JavaModelException {
372 		if (isExternal()) {
373 			if (!exists()) throw newNotPresentException();
374 			return null;
375 		} else {
376 			return super.getUnderlyingResource();
377 		}
378 	}
379 	@Override
hashCode()380 	public int hashCode() {
381 		return this.jarPath.hashCode() + Arrays.hashCode(this.extraAttributes);
382 	}
initRawPackageInfo(HashtableOfArrayToObject rawPackageInfo, String entryName, boolean isDirectory, String compliance)383 	protected void initRawPackageInfo(HashtableOfArrayToObject rawPackageInfo, String entryName, boolean isDirectory, String compliance) {
384 		int lastSeparator;
385 		if (isDirectory) {
386 			if (entryName.charAt(entryName.length() - 1) == '/') {
387 				lastSeparator = entryName.length() - 1;
388 			} else {
389 				lastSeparator = entryName.length();
390 			}
391 		} else {
392 			lastSeparator = entryName.lastIndexOf('/');
393 		}
394 		String[] pkgName = Util.splitOn('/', entryName, 0, lastSeparator);
395 		String[] existing = null;
396 		int length = pkgName.length;
397 		int existingLength = length;
398 		while (existingLength >= 0) {
399 			existing = (String[]) rawPackageInfo.getKey(pkgName, existingLength);
400 			if (existing != null) break;
401 			existingLength--;
402 		}
403 		JavaModelManager manager = JavaModelManager.getJavaModelManager();
404 		for (int i = existingLength; i < length; i++) {
405 			// sourceLevel must be null because we know nothing about it based on a jar file
406 			if (Util.isValidFolderNameForPackage(pkgName[i], null, compliance)) {
407 				System.arraycopy(existing, 0, existing = new String[i+1], 0, i);
408 				existing[i] = manager.intern(pkgName[i]);
409 				rawPackageInfo.put(existing, new ArrayList[] { EMPTY_LIST, EMPTY_LIST });
410 			} else {
411 				// non-Java resource folder
412 				if (!isDirectory) {
413 					ArrayList[] children = (ArrayList[]) rawPackageInfo.get(existing);
414 					if (children[1/*NON_JAVA*/] == EMPTY_LIST) children[1/*NON_JAVA*/] = new ArrayList();
415 					children[1/*NON_JAVA*/].add(entryName);
416 				}
417 				return;
418 			}
419 		}
420 		if (isDirectory)
421 			return;
422 
423 		// add classfile info amongst children
424 		ArrayList[] children = (ArrayList[]) rawPackageInfo.get(pkgName);
425 		if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(entryName)) {
426 			if (children[0/*JAVA*/] == EMPTY_LIST) children[0/*JAVA*/] = new ArrayList();
427 			String nameWithoutExtension = entryName.substring(lastSeparator + 1, entryName.length() - 6);
428 			children[0/*JAVA*/].add(nameWithoutExtension);
429 		} else {
430 			if (children[1/*NON_JAVA*/] == EMPTY_LIST) children[1/*NON_JAVA*/] = new ArrayList();
431 			children[1/*NON_JAVA*/].add(entryName);
432 		}
433 
434 	}
435 	/**
436 	 * @see IPackageFragmentRoot
437 	 */
438 	@Override
isArchive()439 	public boolean isArchive() {
440 		return true;
441 	}
442 	/**
443 	 * @see IPackageFragmentRoot
444 	 */
445 	@Override
isExternal()446 	public boolean isExternal() {
447 		return resource() == null;
448 	}
449 	/**
450 	 * Jars and jar entries are all read only
451 	 */
452 	@Override
isReadOnly()453 	public boolean isReadOnly() {
454 		return true;
455 	}
456 	/**
457 	 * Returns whether the corresponding resource or associated file exists
458 	 */
459 	@Override
resourceExists(IResource underlyingResource)460 	protected boolean resourceExists(IResource underlyingResource) {
461 		if (underlyingResource == null) {
462 			return
463 				JavaModel.getExternalTarget(
464 					getPath()/*don't make the path relative as this is an external archive*/,
465 					true/*check existence*/) != null;
466 		} else {
467 			return super.resourceExists(underlyingResource);
468 		}
469 	}
470 
471 	@Override
toStringAncestors(StringBuffer buffer)472 	protected void toStringAncestors(StringBuffer buffer) {
473 		if (isExternal())
474 			// don't show project as it is irrelevant for external jar files.
475 			// also see https://bugs.eclipse.org/bugs/show_bug.cgi?id=146615
476 			return;
477 		super.toStringAncestors(buffer);
478 	}
479 
getIndexPath()480 	public URL getIndexPath() {
481 		try {
482 			IClasspathEntry entry = ((JavaProject) getParent()).getClasspathEntryFor(getPath());
483 			if (entry != null) return ((ClasspathEntry)entry).getLibraryIndexLocation();
484 		} catch (JavaModelException e) {
485 			// ignore exception
486 		}
487 		return null;
488 	}
489 
490 	@Override
getManifest()491 	public Manifest getManifest() {
492 		ZipFile jar = null;
493 		try {
494 			jar = getJar();
495 			ZipEntry mfEntry = jar.getEntry(TypeConstants.META_INF_MANIFEST_MF);
496 			if (mfEntry != null) {
497 				try (InputStream is = jar.getInputStream(mfEntry)) {
498 					return new Manifest(is);
499 				}
500 			}
501 		} catch (CoreException | IOException e) {
502 			// must do without manifest
503 		} finally {
504 			JavaModelManager.getJavaModelManager().closeZipFile(jar);
505 		}
506 		return null;
507 	}
508 
509 //	@Override
510 //	public boolean isModule() {
511 //	 	try {
512 //	 		return ((PackageFragmentRootInfo) getElementInfo()).isModule(resource(), this);
513 //	 	} catch (JavaModelException e) {
514 //	 		return false;
515 //	 	}
516 //	}
517 }
518