001: // Copyright (c) Corporation for National Research Initiatives
002: // Copyright 2000 Samuele Pedroni
003:
004: package org.python.core;
005:
006: import java.io.BufferedInputStream;
007: import java.io.BufferedOutputStream;
008: import java.io.DataInputStream;
009: import java.io.DataOutputStream;
010: import java.io.EOFException;
011: import java.io.File;
012: import java.io.FileInputStream;
013: import java.io.FileOutputStream;
014: import java.io.IOException;
015: import java.io.InputStream;
016: import java.lang.reflect.Modifier;
017: import java.net.URL;
018: import java.net.URLConnection;
019: import java.security.AccessControlException;
020: import java.util.Enumeration;
021: import java.util.Hashtable;
022: import java.util.Vector;
023: import java.util.zip.ZipEntry;
024: import java.util.zip.ZipInputStream;
025:
026: /**
027: * Abstract package manager that gathers info about statically known classes
028: * from a set of jars. This info can be eventually cached. Off-the-shelf this
029: * class offers a local file-system based cache impl.
030: */
031: public abstract class CachedJarsPackageManager extends PackageManager {
032:
033: /**
034: * Message log method - hook. This default impl does nothing.
035: *
036: * @param msg message text
037: */
038: protected void message(String msg) {
039: }
040:
041: /**
042: * Warning log method - hook. This default impl does nothing.
043: *
044: * @param warn warning text
045: */
046: protected void warning(String warn) {
047: }
048:
049: /**
050: * Comment log method - hook. This default impl does nothing.
051: *
052: * @param msg message text
053: */
054: protected void comment(String msg) {
055: }
056:
057: /**
058: * Debug log method - hook. This default impl does nothing.
059: *
060: * @param msg message text
061: */
062: protected void debug(String msg) {
063: }
064:
065: /**
066: * Filter class/pkg by name helper method - hook. The default impl. is used
067: * by {@link #addJarToPackages} in order to filter out classes whose name
068: * contains '$' (e.g. inner classes,...). Should be used or overriden by
069: * derived classes too. Also to be used in {@link #doDir}.
070: *
071: * @param name class/pkg name
072: * @param pkg if true, name refers to a pkg
073: * @return true if name must be filtered out
074: */
075: protected boolean filterByName(String name, boolean pkg) {
076: return name.indexOf('$') != -1;
077: }
078:
079: /**
080: * Filter class by access perms helper method - hook. The default impl. is
081: * used by {@link #addJarToPackages} in order to filter out non-public
082: * classes. Should be used or overriden by derived classes too. Also to be
083: * used in {@link #doDir}. Access perms can be read with
084: * {@link #checkAccess}.
085: *
086: * @param name class name
087: * @param acc class access permissions as int
088: * @return true if name must be filtered out
089: */
090: protected boolean filterByAccess(String name, int acc) {
091: return (acc & Modifier.PUBLIC) != Modifier.PUBLIC;
092: }
093:
094: private boolean indexModified;
095:
096: private Hashtable jarfiles;
097:
098: private static String vectorToString(Vector vec) {
099: int n = vec.size();
100: StringBuffer ret = new StringBuffer();
101: for (int i = 0; i < n; i++) {
102: ret.append((String) vec.elementAt(i));
103: if (i < n - 1) {
104: ret.append(",");
105: }
106: }
107: return ret.toString();
108: }
109:
110: // Add a single class from zipFile to zipPackages
111: // Only add valid, public classes
112: private void addZipEntry(Hashtable zipPackages, ZipEntry entry,
113: ZipInputStream zip) throws IOException {
114: String name = entry.getName();
115: // System.err.println("entry: "+name);
116: if (!name.endsWith(".class")) {
117: return;
118: }
119:
120: char sep = '/';
121: int breakPoint = name.lastIndexOf(sep);
122: if (breakPoint == -1) {
123: breakPoint = name.lastIndexOf('\\');
124: sep = '\\';
125: }
126:
127: String packageName;
128: if (breakPoint == -1) {
129: packageName = "";
130: } else {
131: packageName = name.substring(0, breakPoint).replace(sep,
132: '.');
133: }
134:
135: String className = name.substring(breakPoint + 1,
136: name.length() - 6);
137:
138: if (filterByName(className, false)) {
139: return;
140: }
141:
142: Vector[] vec = (Vector[]) zipPackages.get(packageName);
143: if (vec == null) {
144: vec = new Vector[] { new Vector(), new Vector() };
145: zipPackages.put(packageName, vec);
146: }
147: int access = checkAccess(zip);
148: if ((access != -1) && !filterByAccess(name, access)) {
149: vec[0].addElement(className);
150: } else {
151: vec[1].addElement(className);
152: }
153: }
154:
155: // Extract all of the packages in a single jarfile
156: private Hashtable getZipPackages(InputStream jarin)
157: throws IOException {
158: Hashtable zipPackages = new Hashtable();
159:
160: ZipInputStream zip = new ZipInputStream(jarin);
161:
162: ZipEntry entry;
163: while ((entry = zip.getNextEntry()) != null) {
164: addZipEntry(zipPackages, entry, zip);
165: zip.closeEntry();
166: }
167:
168: // Turn each vector into a comma-separated String
169: for (Enumeration e = zipPackages.keys(); e.hasMoreElements();) {
170: Object key = e.nextElement();
171: Vector[] vec = (Vector[]) zipPackages.get(key);
172: String classes = vectorToString(vec[0]);
173: if (vec[1].size() > 0) {
174: classes += '@' + vectorToString(vec[1]);
175: }
176: zipPackages.put(key, classes);
177: }
178:
179: return zipPackages;
180: }
181:
182: /**
183: * Gathers classes info from jar specified by jarurl URL. Eventually just
184: * using previously cached info. Eventually updated info is not cached.
185: * Persistent cache storage access goes through inOpenCacheFile() and
186: * outCreateCacheFile().
187: */
188: public void addJarToPackages(java.net.URL jarurl) {
189: addJarToPackages(jarurl, null, false);
190: }
191:
192: /**
193: * Gathers classes info from jar specified by jarurl URL. Eventually just
194: * using previously cached info. Eventually updated info is (re-)cached if
195: * param cache is true. Persistent cache storage access goes through
196: * inOpenCacheFile() and outCreateCacheFile().
197: */
198: public void addJarToPackages(URL jarurl, boolean cache) {
199: addJarToPackages(jarurl, null, cache);
200: }
201:
202: /**
203: * Gathers classes info from jar specified by File jarfile. Eventually just
204: * using previously cached info. Eventually updated info is not cached.
205: * Persistent cache storage access goes through inOpenCacheFile() and
206: * outCreateCacheFile().
207: */
208: public void addJarToPackages(File jarfile) {
209: addJarToPackages(null, jarfile, false);
210: }
211:
212: /**
213: * Gathers classes info from jar specified by File jarfile. Eventually just
214: * using previously cached info. Eventually updated info is (re-)cached if
215: * param cache is true. Persistent cache storage access goes through
216: * inOpenCacheFile() and outCreateCacheFile().
217: */
218: public void addJarToPackages(File jarfile, boolean cache) {
219: addJarToPackages(null, jarfile, cache);
220: }
221:
222: private void addJarToPackages(URL jarurl, File jarfile,
223: boolean cache) {
224: try {
225: boolean caching = this .jarfiles != null;
226:
227: URLConnection jarconn = null;
228: boolean localfile = true;
229:
230: if (jarfile == null) {
231: jarconn = jarurl.openConnection();
232: // This is necessary because 'file:' url-connections
233: // return always 0 through getLastModified (bug?).
234: // And in order to handle localfiles (from urls too)
235: // uniformly.
236: if (jarconn.getURL().getProtocol().equals("file")) {
237: // ??pending: need to use java2 URLDecoder.decode?
238: String jarfilename = jarurl.getFile();
239: jarfilename = jarfilename.replace('/',
240: File.separatorChar);
241: jarfile = new File(jarfilename);
242: } else {
243: localfile = false;
244: }
245: }
246:
247: if (localfile && !jarfile.exists()) {
248: return;
249: }
250:
251: Hashtable zipPackages = null;
252:
253: long mtime = 0;
254: String jarcanon = null;
255: JarXEntry entry = null;
256: boolean brandNew = false;
257:
258: if (caching) {
259:
260: if (localfile) {
261: mtime = jarfile.lastModified();
262: jarcanon = jarfile.getCanonicalPath();
263: } else {
264: mtime = jarconn.getLastModified();
265: jarcanon = jarurl.toString();
266: }
267:
268: entry = (JarXEntry) this .jarfiles.get(jarcanon);
269:
270: if ((entry == null || !(new File(entry.cachefile)
271: .exists()))
272: && cache) {
273: message("processing new jar, '" + jarcanon + "'");
274:
275: String jarname;
276: if (localfile) {
277: jarname = jarfile.getName();
278: } else {
279: jarname = jarurl.getFile();
280: int slash = jarname.lastIndexOf('/');
281: if (slash != -1)
282: jarname = jarname.substring(slash + 1);
283: }
284: jarname = jarname
285: .substring(0, jarname.length() - 4);
286:
287: entry = new JarXEntry(jarname);
288: this .jarfiles.put(jarcanon, entry);
289:
290: brandNew = true;
291: }
292:
293: if (mtime != 0 && entry != null && entry.mtime == mtime) {
294: zipPackages = readCacheFile(entry, jarcanon);
295: }
296:
297: }
298:
299: if (zipPackages == null) {
300: caching = caching && cache;
301:
302: if (caching) {
303: this .indexModified = true;
304: if (entry.mtime != 0) {
305: message("processing modified jar, '" + jarcanon
306: + "'");
307: }
308: entry.mtime = mtime;
309: }
310:
311: InputStream jarin;
312: if (jarconn == null) {
313: jarin = new BufferedInputStream(
314: new FileInputStream(jarfile));
315: } else {
316: jarin = jarconn.getInputStream();
317: }
318:
319: zipPackages = getZipPackages(jarin);
320:
321: if (caching) {
322: writeCacheFile(entry, jarcanon, zipPackages,
323: brandNew);
324: }
325: }
326:
327: addPackages(zipPackages, jarcanon);
328: } catch (IOException ioe) {
329: // silently skip any bad directories
330: warning("skipping bad jar, '"
331: + (jarfile != null ? jarfile.toString() : jarurl
332: .toString()) + "'");
333: }
334:
335: }
336:
337: private void addPackages(Hashtable zipPackages, String jarfile) {
338: for (Enumeration e = zipPackages.keys(); e.hasMoreElements();) {
339: String pkg = (String) e.nextElement();
340: String classes = (String) zipPackages.get(pkg);
341:
342: int idx = classes.indexOf('@');
343: if (idx >= 0 && Options.respectJavaAccessibility) {
344: classes = classes.substring(0, idx);
345: }
346:
347: makeJavaPackage(pkg, classes, jarfile);
348: }
349: }
350:
351: // Read in cache file storing package info for a single .jar
352: // Return null and delete this cachefile if it is invalid
353: private Hashtable readCacheFile(JarXEntry entry, String jarcanon) {
354: String cachefile = entry.cachefile;
355: long mtime = entry.mtime;
356:
357: debug("reading cache, '" + jarcanon + "'");
358:
359: try {
360: DataInputStream istream = inOpenCacheFile(cachefile);
361: String old_jarcanon = istream.readUTF();
362: long old_mtime = istream.readLong();
363: if ((!old_jarcanon.equals(jarcanon))
364: || (old_mtime != mtime)) {
365: comment("invalid cache file: " + cachefile + ", "
366: + jarcanon + ":" + old_jarcanon + ", " + mtime
367: + ":" + old_mtime);
368: deleteCacheFile(cachefile);
369: return null;
370: }
371: Hashtable packs = new Hashtable();
372: try {
373: while (true) {
374: String packageName = istream.readUTF();
375: String classes = istream.readUTF();
376: packs.put(packageName, classes);
377: }
378: } catch (EOFException eof) {
379: ;
380: }
381: istream.close();
382:
383: return packs;
384: } catch (IOException ioe) {
385: // if (cachefile.exists()) cachefile.delete();
386: return null;
387: }
388: }
389:
390: // Write a cache file storing package info for a single .jar
391: private void writeCacheFile(JarXEntry entry, String jarcanon,
392: Hashtable zipPackages, boolean brandNew) {
393: try {
394: DataOutputStream ostream = outCreateCacheFile(entry,
395: brandNew);
396: ostream.writeUTF(jarcanon);
397: ostream.writeLong(entry.mtime);
398: comment("rewriting cachefile for '" + jarcanon + "'");
399:
400: for (Enumeration e = zipPackages.keys(); e
401: .hasMoreElements();) {
402: String packageName = (String) e.nextElement();
403: String classes = (String) zipPackages.get(packageName);
404: ostream.writeUTF(packageName);
405: ostream.writeUTF(classes);
406: }
407: ostream.close();
408: } catch (IOException ioe) {
409: warning("can't write cache file for '" + jarcanon + "'");
410: }
411: }
412:
413: /**
414: * Initializes cache. Eventually reads back cache index. Index persistent
415: * storage is accessed through inOpenIndex().
416: */
417: protected void initCache() {
418: this .indexModified = false;
419: this .jarfiles = new Hashtable();
420:
421: try {
422: DataInputStream istream = inOpenIndex();
423: if (istream == null) {
424: return;
425: }
426:
427: try {
428: while (true) {
429: String jarcanon = istream.readUTF();
430: String cachefile = istream.readUTF();
431: long mtime = istream.readLong();
432: this .jarfiles.put(jarcanon, new JarXEntry(
433: cachefile, mtime));
434: }
435: } catch (EOFException eof) {
436: ;
437: }
438: istream.close();
439: } catch (IOException ioe) {
440: warning("invalid index file");
441: }
442:
443: }
444:
445: /**
446: * Write back cache index. Index persistent storage is accessed through
447: * outOpenIndex().
448: */
449: public void saveCache() {
450: if (this .jarfiles == null || !this .indexModified) {
451: return;
452: }
453:
454: this .indexModified = false;
455:
456: comment("writing modified index file");
457:
458: try {
459: DataOutputStream ostream = outOpenIndex();
460: for (Enumeration e = this .jarfiles.keys(); e
461: .hasMoreElements();) {
462: String jarcanon = (String) e.nextElement();
463: JarXEntry entry = (JarXEntry) this .jarfiles
464: .get(jarcanon);
465: ostream.writeUTF(jarcanon);
466: ostream.writeUTF(entry.cachefile);
467: ostream.writeLong(entry.mtime);
468: }
469: ostream.close();
470: } catch (IOException ioe) {
471: warning("can't write index file");
472: }
473: }
474:
475: // hooks for changing cache storage
476:
477: /**
478: * To pass a cachefile id by ref. And for internal use. See
479: * outCreateCacheFile
480: */
481: public static class JarXEntry extends Object {
482: /** cachefile id */
483: public String cachefile;
484:
485: public long mtime;
486:
487: public JarXEntry(String cachefile) {
488: this .cachefile = cachefile;
489: }
490:
491: public JarXEntry(String cachefile, long mtime) {
492: this .cachefile = cachefile;
493: this .mtime = mtime;
494: }
495:
496: }
497:
498: /**
499: * Open cache index for reading from persistent storage - hook. Must Return
500: * null if this is absent. This default impl is part of the off-the-shelf
501: * local file-system cache impl. Can be overriden.
502: */
503: protected DataInputStream inOpenIndex() throws IOException {
504: File indexFile = new File(this .cachedir, "packages.idx");
505:
506: if (!indexFile.exists()) {
507: return null;
508: }
509:
510: DataInputStream istream = new DataInputStream(
511: new BufferedInputStream(new FileInputStream(indexFile)));
512:
513: return istream;
514: }
515:
516: /**
517: * Open cache index for writing back to persistent storage - hook. This
518: * default impl is part of the off-the-shelf local file-system cache impl.
519: * Can be overriden.
520: */
521: protected DataOutputStream outOpenIndex() throws IOException {
522: File indexFile = new File(this .cachedir, "packages.idx");
523:
524: return new DataOutputStream(new BufferedOutputStream(
525: new FileOutputStream(indexFile)));
526: }
527:
528: /**
529: * Open cache file for reading from persistent storage - hook. This default
530: * impl is part of the off-the-shelf local file-system cache impl. Can be
531: * overriden.
532: */
533: protected DataInputStream inOpenCacheFile(String cachefile)
534: throws IOException {
535: return new DataInputStream(new BufferedInputStream(
536: new FileInputStream(cachefile)));
537: }
538:
539: /**
540: * Delete (invalidated) cache file from persistent storage - hook. This
541: * default impl is part of the off-the-shelf local file-system cache impl.
542: * Can be overriden.
543: */
544: protected void deleteCacheFile(String cachefile) {
545: new File(cachefile).delete();
546: }
547:
548: /**
549: * Create/open cache file for rewriting back to persistent storage - hook.
550: * If create is false, cache file is supposed to exist and must be opened
551: * for rewriting, entry.cachefile is a valid cachefile id. If create is
552: * true, cache file must be created. entry.cachefile is a flat jarname to be
553: * used to produce a valid cachefile id (to be put back in entry.cachefile
554: * on exit). This default impl is part of the off-the-shelf local
555: * file-system cache impl. Can be overriden.
556: */
557: protected DataOutputStream outCreateCacheFile(JarXEntry entry,
558: boolean create) throws IOException {
559: File cachefile = null;
560:
561: if (create) {
562: int index = 1;
563: String suffix = "";
564: String jarname = entry.cachefile;
565: while (true) {
566: cachefile = new File(this .cachedir, jarname + suffix
567: + ".pkc");
568: // System.err.println("try cachefile: "+cachefile);
569: if (!cachefile.exists()) {
570: break;
571: }
572: suffix = "$" + index;
573: index += 1;
574: }
575: entry.cachefile = cachefile.getCanonicalPath();
576: } else
577: cachefile = new File(entry.cachefile);
578:
579: return new DataOutputStream(new BufferedOutputStream(
580: new FileOutputStream(cachefile)));
581: }
582:
583: // for default cache (local fs based) impl
584:
585: private File cachedir;
586:
587: /**
588: * Initialize off-the-shelf (default) local file-system cache impl. Must be
589: * called before {@link #initCache}. cachedir is the cache repository
590: * directory, this is eventually created. Returns true if dir works.
591: */
592: protected boolean useCacheDir(File aCachedir1) {
593: if (aCachedir1 == null) {
594: return false;
595: }
596: try {
597: if (!aCachedir1.isDirectory()
598: && aCachedir1.mkdirs() == false) {
599: warning("can't create package cache dir, '"
600: + aCachedir1 + "'");
601: return false;
602: }
603: } catch (AccessControlException ace) {
604: warning("The java security manager isn't allowing access to the package cache dir, '"
605: + aCachedir1 + "'");
606: return false;
607: }
608:
609: this .cachedir = aCachedir1;
610:
611: return true;
612: }
613:
614: }
|