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