1 /*******************************************************************************
2  * Copyright (c) 2015, 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.nio.file.FileVisitResult;
18 import java.nio.file.Path;
19 import java.nio.file.attribute.BasicFileAttributes;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 
23 import org.eclipse.core.resources.IResource;
24 import org.eclipse.core.runtime.IPath;
25 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.jdt.core.IClasspathAttribute;
27 import org.eclipse.jdt.core.IClasspathEntry;
28 import org.eclipse.jdt.core.IJavaModelStatusConstants;
29 import org.eclipse.jdt.core.IModuleDescription;
30 import org.eclipse.jdt.core.JavaModelException;
31 import org.eclipse.jdt.core.compiler.CharOperation;
32 import org.eclipse.jdt.internal.compiler.env.IModule;
33 import org.eclipse.jdt.internal.compiler.env.IModulePathEntry;
34 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
35 import org.eclipse.jdt.internal.compiler.util.JRTUtil;
36 import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject;
37 import org.eclipse.jdt.internal.core.util.Util;
38 
39 /**
40  * A package fragment root that corresponds to a module in a JRT file system.
41  *
42  * @see org.eclipse.jdt.core.IPackageFragmentRoot
43  * @see org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo
44  */
45 public class JrtPackageFragmentRoot extends JarPackageFragmentRoot implements IModulePathEntry {
46 
47 	String moduleName;
48 
49 	public static final ThreadLocal<Boolean> workingOnOldClasspath = new ThreadLocal<>();
50 
51 	/**
52 	 * Constructs a package fragment root which represents a module
53 	 * contained in a JRT.
54 	 */
JrtPackageFragmentRoot(IPath jrtPath, String moduleName, JavaProject project, IClasspathAttribute[] extraAttributes)55 	protected JrtPackageFragmentRoot(IPath jrtPath, String moduleName, JavaProject project, IClasspathAttribute[] extraAttributes) {
56 		super(null, jrtPath, project, extraAttributes);
57 		this.moduleName = moduleName;
58 	}
59 
60 	@Override
computeChildren(OpenableElementInfo info, IResource underlyingResource)61 	protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws JavaModelException {
62 		final HashtableOfArrayToObject rawPackageInfo = new HashtableOfArrayToObject();
63 		final String compliance = CompilerOptions.VERSION_1_8; // TODO: Java 9 Revisit
64 
65 		// always create the default package
66 		rawPackageInfo.put(CharOperation.NO_STRINGS, new ArrayList[] { EMPTY_LIST, EMPTY_LIST });
67 
68 		try {
69 			org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(this.jarPath.toFile(),
70 					new org.eclipse.jdt.internal.compiler.util.JRTUtil.JrtFileVisitor<Path>() {
71 				@Override
72 				public FileVisitResult visitPackage(Path dir, Path mod, BasicFileAttributes attrs) throws IOException {
73 					initRawPackageInfo(rawPackageInfo, dir.toString(), true, compliance);
74 					return FileVisitResult.CONTINUE;
75 				}
76 
77 				@Override
78 				public FileVisitResult visitFile(Path path, Path mod, BasicFileAttributes attrs) throws IOException {
79 					initRawPackageInfo(rawPackageInfo, path.toString(), false, compliance);
80 					return FileVisitResult.CONTINUE;
81 				}
82 
83 				@Override
84 				public FileVisitResult visitModule(Path path, String name) throws IOException {
85 					if (!JrtPackageFragmentRoot.this.moduleName.equals(name)) {
86 						return FileVisitResult.SKIP_SUBTREE;
87 					}
88 					return FileVisitResult.CONTINUE;
89 				}
90 			}, JRTUtil.NOTIFY_ALL);
91 		} catch (IOException e) {
92 			Util.log(IStatus.ERROR, "Error reading modules" + toStringWithAncestors()); //$NON-NLS-1$
93 		}
94 
95 		info.setChildren(createChildren(rawPackageInfo));
96 		((JarPackageFragmentRootInfo) info).rawPackageInfo = rawPackageInfo;
97 		return true;
98 	}
99 	@Override
createSourceMapper(IPath sourcePath, IPath rootPath)100 	SourceMapper createSourceMapper(IPath sourcePath, IPath rootPath) throws JavaModelException {
101 		IClasspathEntry entry = ((JavaProject) getParent()).getClasspathEntryFor(getPath());
102 		String encoding = (entry== null) ? null : ((ClasspathEntry) entry).getSourceAttachmentEncoding();
103 		IModule mod = getModule();
104 		String modName = mod == null ? null : new String(mod.name());
105 		SourceMapper mapper = new SourceMapper(
106 			sourcePath,
107 			rootPath == null ? modName : rootPath.toOSString(),
108 			getJavaProject().getOptions(true),// cannot use workspace options if external jar is 1.5 jar and workspace options are 1.4 options
109 			encoding);
110 		return mapper;
111 	}
112 	@Override
equals(Object o)113 	public boolean equals(Object o) {
114 		if (this == o)
115 			return true;
116 		if (o instanceof JrtPackageFragmentRoot) {
117 			JrtPackageFragmentRoot other= (JrtPackageFragmentRoot) o;
118 			return this.moduleName.equals(other.moduleName) &&
119 					this.jarPath.equals(other.jarPath) &&
120 					Arrays.equals(this.extraAttributes, other.extraAttributes);
121 		}
122 		return false;
123 	}
124 	@Override
getElementName()125 	public String getElementName() {
126 		return this.moduleName;
127 	}
128 	@Override
getPackageFragment(String[] pkgName)129 	public PackageFragment getPackageFragment(String[] pkgName) {
130 		// NOTE: Do we need a different kind of package fragment?
131 		return new JarPackageFragment(this, pkgName);
132 	}
133 	@Override
hashCode()134 	public int hashCode() {
135 		return this.jarPath.hashCode() + this.moduleName.hashCode() + Arrays.hashCode(this.extraAttributes);
136 	}
137 	@Override
toStringInfo(int tab, StringBuffer buffer, Object info, boolean showResolvedInfo)138 	protected void toStringInfo(int tab, StringBuffer buffer, Object info, boolean showResolvedInfo) {
139 		buffer.append(tabString(tab));
140 		buffer.append("<module:").append(this.moduleName).append(">"); //$NON-NLS-1$ //$NON-NLS-2$
141 		if (info == null) {
142 			buffer.append(" (not open)"); //$NON-NLS-1$
143 		}
144 	}
145 
146 	@Override
getModule()147 	public IModule getModule() {
148 		IModuleDescription desc = getModuleDescription();
149 		if (desc != null) {
150 			try {
151 				return (IModule)((JavaElement) desc).getElementInfo();
152 			} catch (JavaModelException e) {
153 				Util.log(e);
154 			}
155 		}
156 		return null;
157 	}
158 
159 	@Override
isComplianceJava9OrHigher()160 	protected boolean isComplianceJava9OrHigher() {
161 		return true;
162 	}
163 
164 	@Override
getModulesDeclaringPackage(String qualifiedPackageName, String requestedModuleName)165 	public char[][] getModulesDeclaringPackage(String qualifiedPackageName, String requestedModuleName) {
166 		if (requestedModuleName != null && !requestedModuleName.equals(this.moduleName))
167 			return null;
168 		if (getPackageFragment(qualifiedPackageName).exists()) {
169 			return new char[][] { requestedModuleName.toCharArray() };
170 		}
171 		return null;
172 	}
173 	@Override
ignoreErrorStatus(IStatus status)174 	protected boolean ignoreErrorStatus(IStatus status) {
175 		if (status.getCode() == IJavaModelStatusConstants.ELEMENT_NOT_ON_CLASSPATH
176 				&& workingOnOldClasspath.get() == Boolean.TRUE)
177 			return true;
178 		return false;
179 	}
180 }
181