You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ClassPathManager.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. /* *******************************************************************
  2. * Copyright (c) 2002, 2017 Contributors
  3. * All rights reserved.
  4. * This program and the accompanying materials are made available
  5. * under the terms of the Eclipse Public License v1.0
  6. * which accompanies this distribution and is available at
  7. * http://www.eclipse.org/legal/epl-v10.html
  8. *
  9. * Contributors:
  10. * Palo Alto Research Center, Incorporated (PARC).
  11. * ******************************************************************/
  12. package org.aspectj.weaver.bcel;
  13. import java.io.ByteArrayInputStream;
  14. import java.io.File;
  15. import java.io.FileInputStream;
  16. import java.io.FileNotFoundException;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.net.URI;
  20. import java.nio.file.FileSystem;
  21. import java.nio.file.FileSystems;
  22. import java.nio.file.FileVisitResult;
  23. import java.nio.file.Files;
  24. import java.nio.file.Path;
  25. import java.nio.file.SimpleFileVisitor;
  26. import java.nio.file.attribute.BasicFileAttributes;
  27. import java.util.ArrayList;
  28. import java.util.Enumeration;
  29. import java.util.HashMap;
  30. import java.util.Iterator;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.zip.ZipEntry;
  34. import java.util.zip.ZipFile;
  35. import org.aspectj.bridge.IMessageHandler;
  36. import org.aspectj.bridge.MessageUtil;
  37. import org.aspectj.util.LangUtil;
  38. import org.aspectj.util.SoftHashMap;
  39. import org.aspectj.weaver.BCException;
  40. import org.aspectj.weaver.UnresolvedType;
  41. import org.aspectj.weaver.WeaverMessages;
  42. import org.aspectj.weaver.tools.Trace;
  43. import org.aspectj.weaver.tools.TraceFactory;
  44. /**
  45. * @author Andy Clement
  46. * @author Mario Ivankovits
  47. */
  48. public class ClassPathManager {
  49. private static Trace trace = TraceFactory.getTraceFactory().getTrace(ClassPathManager.class);
  50. private static int maxOpenArchives = -1;
  51. private static URI JRT_URI = URI.create("jrt:/"); //$NON-NLS-1$
  52. private static final int MAXOPEN_DEFAULT = 1000;
  53. private List<Entry> entries;
  54. // In order to control how many open files we have, we maintain a list.
  55. // The max number is configured through the property:
  56. // org.aspectj.weaver.openarchives
  57. // and it defaults to 1000
  58. private List<ZipFile> openArchives = new ArrayList<ZipFile>();
  59. static {
  60. String openzipsString = getSystemPropertyWithoutSecurityException("org.aspectj.weaver.openarchives",
  61. Integer.toString(MAXOPEN_DEFAULT));
  62. maxOpenArchives = Integer.parseInt(openzipsString);
  63. if (maxOpenArchives < 20) {
  64. maxOpenArchives = 1000;
  65. }
  66. }
  67. public ClassPathManager(List<String> classpath, IMessageHandler handler) {
  68. if (trace.isTraceEnabled()) {
  69. trace.enter("<init>", this, new Object[] { classpath==null?"null":classpath.toString(), handler });
  70. }
  71. entries = new ArrayList<Entry>();
  72. for (String classpathEntry: classpath) {
  73. addPath(classpathEntry,handler);
  74. }
  75. if (trace.isTraceEnabled()) {
  76. trace.exit("<init>");
  77. }
  78. }
  79. protected ClassPathManager() {
  80. }
  81. public void addPath(String name, IMessageHandler handler) {
  82. File f = new File(name);
  83. String lc = name.toLowerCase();
  84. if (!f.isDirectory()) {
  85. if (!f.isFile()) {
  86. if (!lc.endsWith(".jar") || lc.endsWith(".zip")) {
  87. // heuristic-only: ending with .jar or .zip means probably a
  88. // zip file
  89. MessageUtil.info(handler, WeaverMessages.format(WeaverMessages.ZIPFILE_ENTRY_MISSING, name));
  90. } else {
  91. MessageUtil.info(handler, WeaverMessages.format(WeaverMessages.DIRECTORY_ENTRY_MISSING, name));
  92. }
  93. return;
  94. }
  95. try {
  96. if (lc.endsWith(LangUtil.JRT_FS)) { // Java9
  97. entries.add(new JImageEntry());
  98. } else {
  99. entries.add(new ZipFileEntry(f));
  100. }
  101. } catch (IOException ioe) {
  102. MessageUtil.warn(handler,
  103. WeaverMessages.format(WeaverMessages.ZIPFILE_ENTRY_INVALID, name, ioe.getMessage()));
  104. return;
  105. }
  106. } else {
  107. entries.add(new DirEntry(f));
  108. }
  109. }
  110. public ClassFile find(UnresolvedType type) {
  111. if (trace.isTraceEnabled()) {
  112. trace.enter("find", this, type);
  113. }
  114. String name = type.getName();
  115. for (Iterator<Entry> i = entries.iterator(); i.hasNext();) {
  116. Entry entry = i.next();
  117. try {
  118. ClassFile ret = entry.find(name);
  119. if (trace.isTraceEnabled()) {
  120. trace.event("searching for "+type+" in "+entry.toString());
  121. }
  122. if (ret != null) {
  123. if (trace.isTraceEnabled()) {
  124. trace.exit("find", ret);
  125. }
  126. return ret;
  127. }
  128. } catch (IOException ioe) {
  129. // this is NOT an error: it's valid to have missing classpath entries
  130. if (trace.isTraceEnabled()) {
  131. trace.error("Removing classpath entry for "+entry,ioe);
  132. }
  133. i.remove();
  134. }
  135. }
  136. if (trace.isTraceEnabled()) {
  137. trace.exit("find", null);
  138. }
  139. return null;
  140. }
  141. public String toString() {
  142. StringBuffer buf = new StringBuffer();
  143. boolean start = true;
  144. for (Iterator<Entry> i = entries.iterator(); i.hasNext();) {
  145. if (start) {
  146. start = false;
  147. } else {
  148. buf.append(File.pathSeparator);
  149. }
  150. buf.append(i.next());
  151. }
  152. return buf.toString();
  153. }
  154. public abstract static class ClassFile {
  155. public abstract InputStream getInputStream() throws IOException;
  156. public abstract String getPath();
  157. public abstract void close();
  158. }
  159. abstract static class Entry {
  160. public abstract ClassFile find(String name) throws IOException;
  161. }
  162. static class ByteBasedClassFile extends ClassFile {
  163. private byte[] bytes;
  164. private ByteArrayInputStream bais;
  165. private String path;
  166. public ByteBasedClassFile(byte[] bytes, String path) {
  167. this.bytes = bytes;
  168. this.path = path;
  169. }
  170. @Override
  171. public InputStream getInputStream() throws IOException {
  172. this.bais = new ByteArrayInputStream(bytes);
  173. return this.bais;
  174. }
  175. @Override
  176. public String getPath() {
  177. return this.path;
  178. }
  179. @Override
  180. public void close() {
  181. if (this.bais!=null) {
  182. try {
  183. this.bais.close();
  184. } catch (IOException e) {
  185. }
  186. this.bais = null;
  187. }
  188. }
  189. }
  190. static class FileClassFile extends ClassFile {
  191. private File file;
  192. private FileInputStream fis;
  193. public FileClassFile(File file) {
  194. this.file = file;
  195. }
  196. public InputStream getInputStream() throws IOException {
  197. fis = new FileInputStream(file);
  198. return fis;
  199. }
  200. public void close() {
  201. try {
  202. if (fis != null)
  203. fis.close();
  204. } catch (IOException ioe) {
  205. throw new BCException("Can't close class file : " + file.getName(), ioe);
  206. } finally {
  207. fis = null;
  208. }
  209. }
  210. public String getPath() {
  211. return file.getPath();
  212. }
  213. }
  214. class DirEntry extends Entry {
  215. private String dirPath;
  216. public DirEntry(File dir) {
  217. this.dirPath = dir.getPath();
  218. }
  219. public DirEntry(String dirPath) {
  220. this.dirPath = dirPath;
  221. }
  222. public ClassFile find(String name) {
  223. File f = new File(dirPath + File.separator + name.replace('.', File.separatorChar) + ".class");
  224. if (f.isFile())
  225. return new FileClassFile(f);
  226. else
  227. return null;
  228. }
  229. public String toString() {
  230. return dirPath;
  231. }
  232. }
  233. static class ZipEntryClassFile extends ClassFile {
  234. private ZipEntry entry;
  235. private ZipFileEntry zipFile;
  236. private InputStream is;
  237. public ZipEntryClassFile(ZipFileEntry zipFile, ZipEntry entry) {
  238. this.zipFile = zipFile;
  239. this.entry = entry;
  240. }
  241. public InputStream getInputStream() throws IOException {
  242. is = zipFile.getZipFile().getInputStream(entry);
  243. return is;
  244. }
  245. public void close() {
  246. try {
  247. if (is != null)
  248. is.close();
  249. } catch (IOException e) {
  250. e.printStackTrace();
  251. } finally {
  252. is = null;
  253. }
  254. }
  255. public String getPath() {
  256. return entry.getName();
  257. }
  258. }
  259. /**
  260. * Maintains a shared package cache for java runtime image. This maps packages (for example:
  261. * java/lang) to a starting root position in the filesystem (for example: /modules/java.base/java/lang).
  262. * When searching for a type we work out the package name, use it to find where in the filesystem
  263. * to start looking then run from there. Once found we do cache what we learn to make subsequent
  264. * lookups of that type even faster. Maintaining just a package cache rather than complete type cache
  265. * helps reduce memory usage but still gives reasonably fast lookup performance.
  266. */
  267. static class JImageEntry extends Entry {
  268. private final static FileSystem fs = FileSystems.getFileSystem(JRT_URI);
  269. private final static Map<String, Path> fileCache = new SoftHashMap<String, Path>();
  270. private final static Map<String, Path> packageCache = new HashMap<String, Path>();
  271. private static boolean packageCacheInitialized = false;
  272. public JImageEntry() {
  273. buildPackageMap();
  274. }
  275. class PackageCacheBuilderVisitor extends SimpleFileVisitor<Path> {
  276. @Override
  277. public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
  278. if (file.getNameCount() > 3 && file.toString().endsWith(".class")) {
  279. int fnc = file.getNameCount();
  280. if (fnc > 3) { // There is a package name - e.g. /modules/java.base/java/lang/Object.class
  281. Path packagePath = file.subpath(2, fnc-1); // e.g. java/lang
  282. String packagePathString = packagePath.toString();
  283. packageCache.put(packagePathString, file.subpath(0, fnc-1)); // java/lang -> /modules/java.base/java/lang
  284. }
  285. }
  286. return FileVisitResult.CONTINUE;
  287. }
  288. }
  289. /**
  290. * Create a map from package names to the specific directory of the package members in the filesystem.
  291. */
  292. private synchronized void buildPackageMap() {
  293. if (!packageCacheInitialized) {
  294. packageCacheInitialized = true;
  295. Iterable<java.nio.file.Path> roots = fs.getRootDirectories();
  296. PackageCacheBuilderVisitor visitor = new PackageCacheBuilderVisitor();
  297. try {
  298. for (java.nio.file.Path path : roots) {
  299. Files.walkFileTree(path, visitor);
  300. }
  301. } catch (IOException e) {
  302. throw new RuntimeException(e);
  303. }
  304. }
  305. }
  306. class TypeIdentifier extends SimpleFileVisitor<Path> {
  307. // What are we looking for?
  308. private String name;
  309. // If set, where did we find it?
  310. public Path found;
  311. // Basic metric count of how many files we checked before finding it
  312. public int filesSearchedCount;
  313. public TypeIdentifier(String name) {
  314. this.name = name;
  315. }
  316. @Override
  317. public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
  318. filesSearchedCount++;
  319. if (file.getNameCount() > 2 && file.toString().endsWith(".class")) {
  320. int fnc = file.getNameCount();
  321. Path filePath = file.subpath(2, fnc);
  322. String filePathString = filePath.toString();
  323. if (filePathString.equals(name)) {
  324. fileCache.put(filePathString, file);
  325. found = file;
  326. return FileVisitResult.TERMINATE;
  327. }
  328. }
  329. return FileVisitResult.CONTINUE;
  330. }
  331. }
  332. private Path searchForFileAndCache(final Path startPath, final String name) {
  333. TypeIdentifier locator = new TypeIdentifier(name);
  334. try {
  335. Files.walkFileTree(startPath, locator);
  336. } catch (IOException e) {
  337. throw new RuntimeException(e);
  338. }
  339. return locator.found;
  340. }
  341. public ClassFile find(String name) throws IOException {
  342. String fileName = name.replace('.', '/') + ".class";
  343. Path file = fileCache.get(fileName);
  344. if (file == null) {
  345. // Check the packages map to see if we know about this package
  346. int idx = fileName.lastIndexOf('/');
  347. if (idx == -1) {
  348. // Package not here
  349. return null;
  350. }
  351. Path packageStart = null;
  352. String packageName = null;
  353. if (idx !=-1 ) {
  354. packageName = fileName.substring(0, idx);
  355. packageStart = packageCache.get(packageName);
  356. if (packageStart != null) {
  357. file = searchForFileAndCache(packageStart, fileName);
  358. }
  359. }
  360. }
  361. if (file == null) {
  362. return null;
  363. }
  364. byte[] bs = Files.readAllBytes(file);
  365. ClassFile cf = new ByteBasedClassFile(bs, fileName);
  366. return cf;
  367. }
  368. static Map<String, Path> getPackageCache() {
  369. return packageCache;
  370. }
  371. static Map<String, Path> getFileCache() {
  372. return fileCache;
  373. }
  374. }
  375. class ZipFileEntry extends Entry {
  376. private File file;
  377. private ZipFile zipFile;
  378. public ZipFileEntry(File file) throws IOException {
  379. this.file = file;
  380. }
  381. public ZipFileEntry(ZipFile zipFile) {
  382. this.zipFile = zipFile;
  383. }
  384. public ZipFile getZipFile() {
  385. return zipFile;
  386. }
  387. public ClassFile find(String name) throws IOException {
  388. ensureOpen();
  389. String key = name.replace('.', '/') + ".class";
  390. ZipEntry entry = zipFile.getEntry(key);
  391. if (entry != null)
  392. return new ZipEntryClassFile(this, entry);
  393. else
  394. return null; // This zip will be closed when necessary...
  395. }
  396. public List<ZipEntryClassFile> getAllClassFiles() throws IOException {
  397. ensureOpen();
  398. List<ZipEntryClassFile> ret = new ArrayList<ZipEntryClassFile>();
  399. for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) {
  400. ZipEntry entry = e.nextElement();
  401. String name = entry.getName();
  402. if (hasClassExtension(name))
  403. ret.add(new ZipEntryClassFile(this, entry));
  404. }
  405. // if (ret.isEmpty()) close();
  406. return ret;
  407. }
  408. private void ensureOpen() throws IOException {
  409. if (zipFile != null && openArchives.contains(zipFile)) {
  410. if (isReallyOpen())
  411. return;
  412. }
  413. if (openArchives.size() >= maxOpenArchives) {
  414. closeSomeArchives(openArchives.size() / 10); // Close 10% of
  415. // those open
  416. }
  417. zipFile = new ZipFile(file);
  418. if (!isReallyOpen()) {
  419. throw new FileNotFoundException("Can't open archive: " + file.getName() + " (size() check failed)");
  420. }
  421. openArchives.add(zipFile);
  422. }
  423. private boolean isReallyOpen() {
  424. try {
  425. zipFile.size(); // this will fail if the file has been closed
  426. // for
  427. // some reason;
  428. return true;
  429. } catch (IllegalStateException ex) {
  430. // this means the zip file is closed...
  431. return false;
  432. }
  433. }
  434. public void closeSomeArchives(int n) {
  435. for (int i = n - 1; i >= 0; i--) {
  436. ZipFile zf = (ZipFile) openArchives.get(i);
  437. try {
  438. zf.close();
  439. } catch (IOException e) {
  440. e.printStackTrace();
  441. }
  442. openArchives.remove(i);
  443. }
  444. }
  445. public void close() {
  446. if (zipFile == null)
  447. return;
  448. try {
  449. openArchives.remove(zipFile);
  450. zipFile.close();
  451. } catch (IOException ioe) {
  452. throw new BCException("Can't close archive: " + file.getName(), ioe);
  453. } finally {
  454. zipFile = null;
  455. }
  456. }
  457. public String toString() {
  458. return file.getName();
  459. }
  460. }
  461. /* private */static boolean hasClassExtension(String name) {
  462. return name.toLowerCase().endsWith((".class"));
  463. }
  464. public void closeArchives() {
  465. for (Entry entry : entries) {
  466. if (entry instanceof ZipFileEntry) {
  467. ((ZipFileEntry) entry).close();
  468. }
  469. openArchives.clear();
  470. }
  471. }
  472. // Copes with the security manager
  473. private static String getSystemPropertyWithoutSecurityException(String aPropertyName, String aDefaultValue) {
  474. try {
  475. return System.getProperty(aPropertyName, aDefaultValue);
  476. } catch (SecurityException ex) {
  477. return aDefaultValue;
  478. }
  479. }
  480. // Mainly exposed for testing
  481. public List<Entry> getEntries() {
  482. return entries;
  483. }
  484. }