1 /*******************************************************************************
2 * Copyright (c) 2016, 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.builder;
15
16 import java.io.File;
17 import java.io.IOException;
18 import java.nio.file.FileVisitResult;
19 import java.nio.file.Path;
20 import java.nio.file.attribute.BasicFileAttributes;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.function.Predicate;
28 import java.util.zip.ZipFile;
29
30 import org.eclipse.core.runtime.IPath;
31 import org.eclipse.jdt.core.compiler.CharOperation;
32 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
33 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
34 import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
35 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
36 import org.eclipse.jdt.internal.compiler.env.IBinaryType;
37 import org.eclipse.jdt.internal.compiler.env.IModule;
38 import org.eclipse.jdt.internal.compiler.env.IModule.IModuleReference;
39 import org.eclipse.jdt.internal.compiler.env.IMultiModuleEntry;
40 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
41 import org.eclipse.jdt.internal.compiler.util.JRTUtil;
42 import org.eclipse.jdt.internal.compiler.util.SimpleSet;
43 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
44 import org.eclipse.jdt.internal.core.JavaProject;
45
46 public class ClasspathJrt extends ClasspathLocation implements IMultiModuleEntry {
47
48 //private HashMap<String, SimpleSet> packagesInModule = null;
49 protected static HashMap<String, HashMap<String, SimpleSet>> PackageCache = new HashMap<>();
50 protected static HashMap<String, HashMap<String, IModule>> ModulesCache = new HashMap<>();
51 String externalAnnotationPath;
52 protected ZipFile annotationZipFile;
53 String zipFilename; // keep for equals
54 File jrtFile;
55 AccessRuleSet accessRuleSet;
56
57 static final Set<String> NO_LIMIT_MODULES = new HashSet<>();
58
59 /*
60 * Only for use from ClasspathJrtWithReleaseOption
61 */
ClasspathJrt()62 protected ClasspathJrt() {
63 }
ClasspathJrt(String zipFilename, AccessRuleSet accessRuleSet, IPath externalAnnotationPath)64 public ClasspathJrt(String zipFilename, AccessRuleSet accessRuleSet, IPath externalAnnotationPath) {
65 setZipFile(zipFilename);
66 this.accessRuleSet = accessRuleSet;
67 if (externalAnnotationPath != null)
68 this.externalAnnotationPath = externalAnnotationPath.toString();
69 loadModules(this);
70 }
71
setZipFile(String zipFilename)72 void setZipFile(String zipFilename) {
73 this.zipFilename = zipFilename;
74 if(zipFilename != null) {
75 this.jrtFile = new File(zipFilename);
76 }
77 }
78
79 /**
80 * Calculate and cache the package list available in the zipFile.
81 * @param jrt The ClasspathJar to use
82 * @return A SimpleSet with the all the package names in the zipFile.
83 */
findPackagesInModules(final ClasspathJrt jrt)84 static HashMap<String, SimpleSet> findPackagesInModules(final ClasspathJrt jrt) {
85 String zipFileName = jrt.zipFilename;
86 HashMap<String, SimpleSet> cache = PackageCache.get(jrt.getKey());
87 if (cache != null) {
88 return cache;
89 }
90 final HashMap<String, SimpleSet> packagesInModule = new HashMap<>();
91 PackageCache.put(zipFileName, packagesInModule);
92 try {
93 final File imageFile = jrt.jrtFile;
94 org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(imageFile,
95 new org.eclipse.jdt.internal.compiler.util.JRTUtil.JrtFileVisitor<Path>() {
96 SimpleSet packageSet = null;
97 @Override
98 public FileVisitResult visitPackage(Path dir, Path mod, BasicFileAttributes attrs) throws IOException {
99 ClasspathJar.addToPackageSet(this.packageSet, dir.toString(), true);
100 return FileVisitResult.CONTINUE;
101 }
102
103 @Override
104 public FileVisitResult visitFile(Path file, Path mod, BasicFileAttributes attrs) throws IOException {
105 return FileVisitResult.CONTINUE;
106 }
107
108 @Override
109 public FileVisitResult visitModule(Path path, String name) throws IOException {
110 jrt.acceptModule(JRTUtil.getClassfileContent(imageFile, IModule.MODULE_INFO_CLASS, name), name);
111 this.packageSet = new SimpleSet(41);
112 this.packageSet.add(""); //$NON-NLS-1$
113 if (name.endsWith("/")) { //$NON-NLS-1$
114 name = name.substring(0, name.length() - 1);
115 }
116 packagesInModule.put(name, this.packageSet);
117 return FileVisitResult.CONTINUE;
118 }
119 }, JRTUtil.NOTIFY_PACKAGES | JRTUtil.NOTIFY_MODULES);
120 } catch (IOException e) {
121 // TODO: Java 9 Should report better
122 }
123 return packagesInModule;
124 }
125
loadModules(final ClasspathJrt jrt)126 public static void loadModules(final ClasspathJrt jrt) {
127 HashMap<String, IModule> cache = ModulesCache.get(jrt.getKey());
128
129 if (cache == null) {
130 try {
131 final File imageFile = jrt.jrtFile;
132 org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(imageFile,
133 new org.eclipse.jdt.internal.compiler.util.JRTUtil.JrtFileVisitor<Path>() {
134 SimpleSet packageSet = null;
135
136 @Override
137 public FileVisitResult visitPackage(Path dir, Path mod, BasicFileAttributes attrs)
138 throws IOException {
139 ClasspathJar.addToPackageSet(this.packageSet, dir.toString(), true);
140 return FileVisitResult.CONTINUE;
141 }
142
143 @Override
144 public FileVisitResult visitFile(Path file, Path mod, BasicFileAttributes attrs)
145 throws IOException {
146 return FileVisitResult.CONTINUE;
147 }
148
149 @Override
150 public FileVisitResult visitModule(Path path, String name) throws IOException {
151 jrt.acceptModule(JRTUtil.getClassfileContent(imageFile, IModule.MODULE_INFO_CLASS, name), name);
152 return FileVisitResult.SKIP_SUBTREE;
153 }
154 }, JRTUtil.NOTIFY_MODULES);
155 } catch (IOException e) {
156 // TODO: Java 9 Should report better
157 }
158 } else {
159 // for (IModuleDeclaration iModule : cache) {
160 // jimage.env.acceptModule(iModule, jimage);
161 // }
162 }
163 }
getKey()164 protected String getKey() {
165 return this.zipFilename;
166 }
acceptModule(byte[] content, String name)167 void acceptModule(byte[] content, String name) {
168 if (content == null)
169 return;
170 ClassFileReader reader = null;
171 try {
172 reader = new ClassFileReader(content, IModule.MODULE_INFO_CLASS.toCharArray());
173 } catch (ClassFormatException e) {
174 e.printStackTrace();
175 }
176 if (reader != null) {
177 String key = getKey();
178 IModule moduleDecl = reader.getModuleDeclaration();
179 if (moduleDecl != null) {
180 HashMap<String, IModule> cache = ModulesCache.get(key);
181 if (cache == null) {
182 ModulesCache.put(key, cache = new HashMap<String, IModule>());
183 }
184 cache.put(name, moduleDecl);
185 }
186 }
187 }
188 @Override
cleanup()189 public void cleanup() {
190 if (this.annotationZipFile != null) {
191 try {
192 this.annotationZipFile.close();
193 } catch(IOException e) { // ignore it
194 }
195 this.annotationZipFile = null;
196 }
197 }
198
199 @Override
equals(Object o)200 public boolean equals(Object o) {
201 if (this == o) return true;
202 if (!(o instanceof ClasspathJrt)) return false;
203 ClasspathJrt jar = (ClasspathJrt) o;
204 if (this.accessRuleSet != jar.accessRuleSet)
205 if (this.accessRuleSet == null || !this.accessRuleSet.equals(jar.accessRuleSet))
206 return false;
207 return this.zipFilename.endsWith(jar.zipFilename) && areAllModuleOptionsEqual(jar);
208 }
209
210 @Override
findClass(String binaryFileName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly, Predicate<String> moduleNameFilter)211 public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName,
212 boolean asBinaryOnly, Predicate<String> moduleNameFilter) {
213 if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case
214
215 try {
216 String fileNameWithoutExtension = qualifiedBinaryFileName.substring(0, qualifiedBinaryFileName.length() - SuffixConstants.SUFFIX_CLASS.length);
217 IBinaryType reader = ClassFileReader.readFromModule(this.jrtFile, moduleName, qualifiedBinaryFileName, moduleNameFilter);
218 return createAnswer(fileNameWithoutExtension, reader);
219 } catch (ClassFormatException | IOException e) { // treat as if class file is missing
220 }
221 return null;
222 }
createAnswer(String fileNameWithoutExtension, IBinaryType reader)223 protected NameEnvironmentAnswer createAnswer(String fileNameWithoutExtension, IBinaryType reader) {
224 if (reader != null) {
225 if (this.externalAnnotationPath != null) {
226 try {
227 if (this.annotationZipFile == null) {
228 this.annotationZipFile = ExternalAnnotationDecorator.getAnnotationZipFile(this.externalAnnotationPath, null);
229 }
230 reader = ExternalAnnotationDecorator.create(reader, this.externalAnnotationPath, fileNameWithoutExtension, this.annotationZipFile);
231 } catch (IOException e) {
232 // don't let error on annotations fail class reading
233 }
234 }
235 if (this.accessRuleSet == null)
236 return new NameEnvironmentAnswer(reader, null, reader.getModule());
237 return new NameEnvironmentAnswer(reader,
238 this.accessRuleSet.getViolatedRestriction(fileNameWithoutExtension.toCharArray()),
239 reader.getModule());
240 }
241 return null;
242 }
243
244 @Override
getProjectRelativePath()245 public IPath getProjectRelativePath() {
246 return null;
247 }
248
249 @Override
hashCode()250 public int hashCode() {
251 return this.zipFilename == null ? super.hashCode() : this.zipFilename.hashCode();
252 }
253 @Override
getModulesDeclaringPackage(String qualifiedPackageName, String moduleName)254 public char[][] getModulesDeclaringPackage(String qualifiedPackageName, String moduleName) {
255 List<String> moduleNames = JRTUtil.getModulesDeclaringPackage(this.jrtFile, qualifiedPackageName, moduleName);
256 return CharOperation.toCharArrays(moduleNames);
257 }
258 @Override
hasCompilationUnit(String qualifiedPackageName, String moduleName)259 public boolean hasCompilationUnit(String qualifiedPackageName, String moduleName) {
260 return JRTUtil.hasCompilationUnit(this.jrtFile, qualifiedPackageName, moduleName);
261 }
262 @Override
isPackage(String qualifiedPackageName, String moduleName)263 public boolean isPackage(String qualifiedPackageName, String moduleName) {
264 return JRTUtil.getModulesDeclaringPackage(this.jrtFile, qualifiedPackageName, moduleName) != null;
265 }
266
267 @Override
toString()268 public String toString() {
269 String start = "Classpath jrt file " + this.zipFilename; //$NON-NLS-1$
270 return start;
271 }
272
273 @Override
debugPathString()274 public String debugPathString() {
275 return this.zipFilename;
276 }
277 @Override
findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly, Predicate<String> moduleNameFilter)278 public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName,
279 boolean asBinaryOnly, Predicate<String> moduleNameFilter) {
280 String fileName = new String(typeName);
281 return findClass(fileName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, asBinaryOnly, moduleNameFilter);
282 }
283 @Override
hasModule()284 public boolean hasModule() {
285 return true;
286 }
287 @Override
getModule(char[] moduleName)288 public IModule getModule(char[] moduleName) {
289 return getModule(String.valueOf(moduleName));
290 }
getModule(String moduleName)291 public IModule getModule(String moduleName) {
292 HashMap<String, IModule> modules = ModulesCache.get(getKey());
293 if (modules != null) {
294 return modules.get(moduleName);
295 }
296 return null;
297 }
298 @Override
getModuleNames(Collection<String> limitModules)299 public Collection<String> getModuleNames(Collection<String> limitModules) {
300 HashMap<String, SimpleSet> cache = findPackagesInModules(this);
301 if (cache != null)
302 return selectModules(cache.keySet(), limitModules);
303 return Collections.emptyList();
304 }
305
selectModules(Set<String> keySet, Collection<String> limitModules)306 protected Collection<String> selectModules(Set<String> keySet, Collection<String> limitModules) {
307 Collection<String> rootModules;
308 if (limitModules == NO_LIMIT_MODULES) {
309 rootModules = new HashSet<>(keySet);
310 } else if (limitModules != null) {
311 Set<String> result = new HashSet<>(keySet);
312 result.retainAll(limitModules);
313 rootModules = result;
314 } else {
315 rootModules = JavaProject.internalDefaultRootModules(keySet, s -> s, m -> getModule(m));
316 }
317 Set<String> allModules = new HashSet<>(rootModules);
318 for (String mod : rootModules)
319 addRequired(mod, allModules);
320 return allModules;
321 }
322
addRequired(String mod, Set<String> allModules)323 protected void addRequired(String mod, Set<String> allModules) {
324 IModule iMod = getModule(mod);
325 if(iMod == null) {
326 return;
327 }
328 for (IModuleReference requiredRef : iMod.requires()) {
329 String moduleName = String.valueOf(requiredRef.name());
330 IModule reqMod = getModule(moduleName);
331 if (reqMod != null) {
332 if (allModules.add(moduleName))
333 addRequired(moduleName, allModules);
334 }
335 }
336 }
337 @Override
findClass(String typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName)338 public NameEnvironmentAnswer findClass(String typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) {
339 //
340 return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false, null);
341 }
342 /** TEST ONLY */
resetCaches()343 public static void resetCaches() {
344 PackageCache.clear();
345 ModulesCache.clear();
346 }
347 }
348