001: /*****************************************************************************
002: * *
003: * This file is part of the BeanShell Java Scripting distribution. *
004: * Documentation and updates may be found at http://www.beanshell.org/ *
005: * *
006: * Sun Public License Notice: *
007: * *
008: * The contents of this file are subject to the Sun Public License Version *
009: * 1.0 (the "License"); you may not use this file except in compliance with *
010: * the License. A copy of the License is available at http://www.sun.com *
011: * *
012: * The Original Code is BeanShell. The Initial Developer of the Original *
013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
014: * (C) 2000. All Rights Reserved. *
015: * *
016: * GNU Public License Notice: *
017: * *
018: * Alternatively, the contents of this file may be used under the terms of *
019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
020: * provisions of LGPL are applicable instead of those above. If you wish to *
021: * allow use of your version of this file only under the terms of the LGPL *
022: * and not to allow others to use your version of this file under the SPL, *
023: * indicate your decision by deleting the provisions above and replace *
024: * them with the notice and other provisions required by the LGPL. If you *
025: * do not delete the provisions above, a recipient may use your version of *
026: * this file under either the SPL or the LGPL. *
027: * *
028: * Patrick Niemeyer (pat@pat.net) *
029: * Author of Learning Java, O'Reilly & Associates *
030: * http://www.pat.net/~pat/ *
031: * *
032: *****************************************************************************/package org.gjt.sp.jedit.bsh.classpath;
033:
034: import java.util.*;
035: import java.util.zip.*;
036: import java.io.*;
037: import java.net.*;
038: import java.io.File;
039:
040: import org.gjt.sp.jedit.bsh.StringUtil;
041: import org.gjt.sp.jedit.bsh.ClassPathException;
042: import java.lang.ref.WeakReference;
043: import org.gjt.sp.jedit.bsh.NameSource;
044:
045: /**
046: A BshClassPath encapsulates knowledge about a class path of URLs.
047: It can maps all classes the path which may include:
048: jar/zip files and base dirs
049:
050: A BshClassPath may composite other BshClassPaths as components of its
051: path and will reflect changes in those components through its methods
052: and listener interface.
053:
054: Classpath traversal is done lazily when a call is made to
055: getClassesForPackage() or getClassSource()
056: or can be done explicitily through insureInitialized().
057: Feedback on mapping progress is provided through the MappingFeedback
058: interface.
059:
060: Design notes:
061: Several times here we traverse ourselves and our component paths to
062: produce a composite view of some thing relating to the path. This would
063: be an opportunity for a visitor pattern.
064: */
065: public class BshClassPath implements ClassPathListener, NameSource {
066: String name;
067:
068: /** The URL path components */
069: private List path;
070: /** Ordered list of components BshClassPaths */
071: private List compPaths;
072:
073: /** Set of classes in a package mapped by package name */
074: private Map packageMap;
075: /** Map of source (URL or File dir) of every clas */
076: private Map classSource;
077: /** The packageMap and classSource maps have been built. */
078: private boolean mapsInitialized;
079:
080: private UnqualifiedNameTable unqNameTable;
081:
082: /**
083: This used to be configurable, but now we always include them.
084: */
085: private boolean nameCompletionIncludesUnqNames = true;
086:
087: Vector listeners = new Vector();
088:
089: // constructors
090:
091: public BshClassPath(String name) {
092: this .name = name;
093: reset();
094: }
095:
096: public BshClassPath(String name, URL[] urls) {
097: this (name);
098: add(urls);
099: }
100:
101: // end constructors
102:
103: // mutators
104:
105: public void setPath(URL[] urls) {
106: reset();
107: add(urls);
108: }
109:
110: /**
111: Add the specified BshClassPath as a component of our path.
112: Changes in the bcp will be reflected through us.
113: */
114: public void addComponent(BshClassPath bcp) {
115: if (compPaths == null)
116: compPaths = new ArrayList();
117: compPaths.add(bcp);
118: bcp.addListener(this );
119: }
120:
121: public void add(URL[] urls) {
122: path.addAll(Arrays.asList(urls));
123: if (mapsInitialized)
124: map(urls);
125: }
126:
127: public void add(URL url) throws IOException {
128: path.add(url);
129: if (mapsInitialized)
130: map(url);
131: }
132:
133: /**
134: Get the path components including any component paths.
135: */
136: public URL[] getPathComponents() {
137: return (URL[]) getFullPath().toArray(new URL[0]);
138: }
139:
140: /**
141: Return the set of class names in the specified package
142: including all component paths.
143: */
144: synchronized public Set getClassesForPackage(String pack) {
145: insureInitialized();
146: Set set = new HashSet();
147: Collection c = (Collection) packageMap.get(pack);
148: if (c != null)
149: set.addAll(c);
150:
151: if (compPaths != null)
152: for (int i = 0; i < compPaths.size(); i++) {
153: c = ((BshClassPath) compPaths.get(i))
154: .getClassesForPackage(pack);
155: if (c != null)
156: set.addAll(c);
157: }
158: return set;
159: }
160:
161: /**
162: Return the source of the specified class which may lie in component
163: path.
164: */
165: synchronized public ClassSource getClassSource(String className) {
166: // Before triggering classpath mapping (initialization) check for
167: // explicitly set class sources (e.g. generated classes). These would
168: // take priority over any found in the classpath anyway.
169: ClassSource cs = (ClassSource) classSource.get(className);
170: if (cs != null)
171: return cs;
172:
173: insureInitialized(); // trigger possible mapping
174:
175: cs = (ClassSource) classSource.get(className);
176: if (cs == null && compPaths != null)
177: for (int i = 0; i < compPaths.size() && cs == null; i++)
178: cs = ((BshClassPath) compPaths.get(i))
179: .getClassSource(className);
180: return cs;
181: }
182:
183: /**
184: Explicitly set a class source. This is used for generated classes, but
185: could potentially be used to allow a user to override which version of
186: a class from the classpath is located.
187: */
188: synchronized public void setClassSource(String className,
189: ClassSource cs) {
190: classSource.put(className, cs);
191: }
192:
193: /**
194: If the claspath map is not initialized, do it now.
195: If component maps are not do them as well...
196:
197: Random note:
198: Should this be "insure" or "ensure". I know I've seen "ensure" used
199: in the JDK source. Here's what Webster has to say:
200:
201: Main Entry:ensure Pronunciation:in-'shur
202: Function:transitive verb Inflected
203: Form(s):ensured; ensuring : to make sure,
204: certain, or safe : GUARANTEE synonyms ENSURE,
205: INSURE, ASSURE, SECURE mean to make a thing or
206: person sure. ENSURE, INSURE, and ASSURE are
207: interchangeable in many contexts where they
208: indicate the making certain or inevitable of an
209: outcome, but INSURE sometimes stresses the
210: taking of necessary measures beforehand, and
211: ASSURE distinctively implies the removal of
212: doubt and suspense from a person's mind. SECURE
213: implies action taken to guard against attack or
214: loss.
215: */
216: public void insureInitialized() {
217: insureInitialized(true);
218: }
219:
220: /**
221: @param topPath indicates that this is the top level classpath
222: component and it should send the startClassMapping message
223: */
224: protected synchronized void insureInitialized(boolean topPath) {
225: // If we are the top path and haven't been initialized before
226: // inform the listeners we are going to do expensive map
227: if (topPath && !mapsInitialized)
228: startClassMapping();
229:
230: // initialize components
231: if (compPaths != null)
232: for (int i = 0; i < compPaths.size(); i++)
233: ((BshClassPath) compPaths.get(i))
234: .insureInitialized(false);
235:
236: // initialize ourself
237: if (!mapsInitialized)
238: map((URL[]) path.toArray(new URL[0]));
239:
240: if (topPath && !mapsInitialized)
241: endClassMapping();
242:
243: mapsInitialized = true;
244: }
245:
246: /**
247: Get the full path including component paths.
248: (component paths listed first, in order)
249: Duplicate path components are removed.
250: */
251: protected List getFullPath() {
252: List list = new ArrayList();
253: if (compPaths != null) {
254: for (int i = 0; i < compPaths.size(); i++) {
255: List l = ((BshClassPath) compPaths.get(i))
256: .getFullPath();
257: // take care to remove dups
258: // wish we had an ordered set collection
259: Iterator it = l.iterator();
260: while (it.hasNext()) {
261: Object o = it.next();
262: if (!list.contains(o))
263: list.add(o);
264: }
265: }
266: }
267: list.addAll(path);
268: return list;
269: }
270:
271: /**
272: Support for super import "*";
273: Get the full name associated with the unqualified name in this
274: classpath. Returns either the String name or an AmbiguousName object
275: encapsulating the various names.
276: */
277: public String getClassNameByUnqName(String name)
278: throws ClassPathException {
279: insureInitialized();
280: UnqualifiedNameTable unqNameTable = getUnqualifiedNameTable();
281:
282: Object obj = unqNameTable.get(name);
283: if (obj instanceof AmbiguousName)
284: throw new ClassPathException("Ambigous class names: "
285: + ((AmbiguousName) obj).get());
286:
287: return (String) obj;
288: }
289:
290: /*
291: Note: we could probably do away with the unqualified name table
292: in favor of a second name source
293: */
294: private UnqualifiedNameTable getUnqualifiedNameTable() {
295: if (unqNameTable == null)
296: unqNameTable = buildUnqualifiedNameTable();
297: return unqNameTable;
298: }
299:
300: private UnqualifiedNameTable buildUnqualifiedNameTable() {
301: UnqualifiedNameTable unqNameTable = new UnqualifiedNameTable();
302:
303: // add component names
304: if (compPaths != null)
305: for (int i = 0; i < compPaths.size(); i++) {
306: Set s = ((BshClassPath) compPaths.get(i)).classSource
307: .keySet();
308: Iterator it = s.iterator();
309: while (it.hasNext())
310: unqNameTable.add((String) it.next());
311: }
312:
313: // add ours
314: Iterator it = classSource.keySet().iterator();
315: while (it.hasNext())
316: unqNameTable.add((String) it.next());
317:
318: return unqNameTable;
319: }
320:
321: public String[] getAllNames() {
322: insureInitialized();
323:
324: List names = new ArrayList();
325: Iterator it = getPackagesSet().iterator();
326: while (it.hasNext()) {
327: String pack = (String) it.next();
328: names
329: .addAll(removeInnerClassNames(getClassesForPackage(pack)));
330: }
331:
332: if (nameCompletionIncludesUnqNames)
333: names.addAll(getUnqualifiedNameTable().keySet());
334:
335: return (String[]) names.toArray(new String[0]);
336: }
337:
338: /**
339: call map(url) for each url in the array
340: */
341: synchronized void map(URL[] urls) {
342: for (int i = 0; i < urls.length; i++)
343: try {
344: map(urls[i]);
345: } catch (IOException e) {
346: String s = "Error constructing classpath: " + urls[i]
347: + ": " + e;
348: errorWhileMapping(s);
349: }
350: }
351:
352: synchronized void map(URL url) throws IOException {
353: String name = url.getFile();
354: File f = new File(name);
355:
356: if (f.isDirectory()) {
357: classMapping("Directory " + f.toString());
358: map(traverseDirForClasses(f), new DirClassSource(f));
359: } else if (isArchiveFileName(name)) {
360: classMapping("Archive: " + url);
361: map(searchJarForClasses(url), new JarClassSource(url));
362: }
363: /*
364: else if ( isClassFileName( name ) )
365: map( looseClass( name ), url );
366: */
367: else {
368: String s = "Not a classpath component: " + name;
369: errorWhileMapping(s);
370: }
371: }
372:
373: private void map(String[] classes, Object source) {
374: for (int i = 0; i < classes.length; i++) {
375: //System.out.println( classes[i] +": "+ source );
376: mapClass(classes[i], source);
377: }
378: }
379:
380: private void mapClass(String className, Object source) {
381: // add to package map
382: String[] sa = splitClassname(className);
383: String pack = sa[0];
384: String clas = sa[1];
385: Set set = (Set) packageMap.get(pack);
386: if (set == null) {
387: set = new HashSet();
388: packageMap.put(pack, set);
389: }
390: set.add(className);
391:
392: // Add to classSource map
393: Object obj = classSource.get(className);
394: // don't replace previously set (found earlier in classpath or
395: // explicitly set via setClassSource() )
396: if (obj == null)
397: classSource.put(className, source);
398: }
399:
400: /**
401: Clear everything and reset the path to empty.
402: */
403: synchronized private void reset() {
404: path = new ArrayList();
405: compPaths = null;
406: clearCachedStructures();
407: }
408:
409: /**
410: Clear anything cached. All will be reconstructed as necessary.
411: */
412: synchronized private void clearCachedStructures() {
413: mapsInitialized = false;
414: packageMap = new HashMap();
415: classSource = new HashMap();
416: unqNameTable = null;
417: nameSpaceChanged();
418: }
419:
420: public void classPathChanged() {
421: clearCachedStructures();
422: notifyListeners();
423: }
424:
425: /*
426: public void setNameCompletionIncludeUnqNames( boolean b ) {
427: if ( nameCompletionIncludesUnqNames != b ) {
428: nameCompletionIncludesUnqNames = b;
429: nameSpaceChanged();
430: }
431: }
432: */
433:
434: // Begin Static stuff
435: static String[] traverseDirForClasses(File dir) throws IOException {
436: List list = traverseDirForClassesAux(dir, dir);
437: return (String[]) list.toArray(new String[0]);
438: }
439:
440: static List traverseDirForClassesAux(File topDir, File dir)
441: throws IOException {
442: List list = new ArrayList();
443: String top = topDir.getAbsolutePath();
444:
445: File[] children = dir.listFiles();
446: for (int i = 0; i < children.length; i++) {
447: File child = children[i];
448: if (child.isDirectory())
449: list.addAll(traverseDirForClassesAux(topDir, child));
450: else {
451: String name = child.getAbsolutePath();
452: if (isClassFileName(name)) {
453: /*
454: Remove absolute (topdir) portion of path and leave
455: package-class part
456: */
457: if (name.startsWith(top))
458: name = name.substring(top.length() + 1);
459: else
460: throw new IOException("problem parsing paths");
461:
462: name = canonicalizeClassName(name);
463: list.add(name);
464: }
465: }
466: }
467:
468: return list;
469: }
470:
471: /**
472: Get the class file entries from the Jar
473: */
474: static String[] searchJarForClasses(URL jar) throws IOException {
475: Vector v = new Vector();
476: InputStream in = jar.openStream();
477: ZipInputStream zin = new ZipInputStream(in);
478:
479: ZipEntry ze;
480: while ((ze = zin.getNextEntry()) != null) {
481: String name = ze.getName();
482: if (isClassFileName(name))
483: v.addElement(canonicalizeClassName(name));
484: }
485: zin.close();
486:
487: String[] sa = new String[v.size()];
488: v.copyInto(sa);
489: return sa;
490: }
491:
492: public static boolean isClassFileName(String name) {
493: return (name.toLowerCase().endsWith(".class"));
494: //&& (name.indexOf('$')==-1) );
495: }
496:
497: public static boolean isArchiveFileName(String name) {
498: name = name.toLowerCase();
499: return (name.endsWith(".jar") || name.endsWith(".zip"));
500: }
501:
502: /**
503: Create a proper class name from a messy thing.
504: Turn / or \ into ., remove leading class and trailing .class
505:
506: Note: this makes lots of strings... could be faster.
507: */
508: public static String canonicalizeClassName(String name) {
509: String classname = name.replace('/', '.');
510: classname = classname.replace('\\', '.');
511: if (classname.startsWith("class "))
512: classname = classname.substring(6);
513: if (classname.endsWith(".class"))
514: classname = classname.substring(0, classname.length() - 6);
515: return classname;
516: }
517:
518: /**
519: Split class name into package and name
520: */
521: public static String[] splitClassname(String classname) {
522: classname = canonicalizeClassName(classname);
523:
524: int i = classname.lastIndexOf(".");
525: String classn, packn;
526: if (i == -1) {
527: // top level class
528: classn = classname;
529: packn = "<unpackaged>";
530: } else {
531: packn = classname.substring(0, i);
532: classn = classname.substring(i + 1);
533: }
534: return new String[] { packn, classn };
535: }
536:
537: /**
538: Return a new collection without any inner class names
539: */
540: public static Collection removeInnerClassNames(Collection col) {
541: List list = new ArrayList();
542: list.addAll(col);
543: Iterator it = list.iterator();
544: while (it.hasNext()) {
545: String name = (String) it.next();
546: if (name.indexOf("$") != -1)
547: it.remove();
548: }
549: return list;
550: }
551:
552: /**
553: The user classpath from system property
554: java.class.path
555: */
556:
557: static URL[] userClassPathComp;
558:
559: public static URL[] getUserClassPathComponents()
560: throws ClassPathException {
561: if (userClassPathComp != null)
562: return userClassPathComp;
563:
564: String cp = System.getProperty("java.class.path");
565: String[] paths = StringUtil.split(cp, File.pathSeparator);
566:
567: URL[] urls = new URL[paths.length];
568: try {
569: for (int i = 0; i < paths.length; i++)
570: // We take care to get the canonical path first.
571: // Java deals with relative paths for it's bootstrap loader
572: // but JARClassLoader doesn't.
573: urls[i] = new File(new File(paths[i])
574: .getCanonicalPath()).toURL();
575: } catch (IOException e) {
576: throw new ClassPathException("can't parse class path: " + e);
577: }
578:
579: userClassPathComp = urls;
580: return urls;
581: }
582:
583: /**
584: Get a list of all of the known packages
585: */
586: public Set getPackagesSet() {
587: insureInitialized();
588: Set set = new HashSet();
589: set.addAll(packageMap.keySet());
590:
591: if (compPaths != null)
592: for (int i = 0; i < compPaths.size(); i++)
593: set.addAll(((BshClassPath) compPaths.get(i)).packageMap
594: .keySet());
595: return set;
596: }
597:
598: public void addListener(ClassPathListener l) {
599: listeners.addElement(new WeakReference(l));
600: }
601:
602: public void removeListener(ClassPathListener l) {
603: listeners.removeElement(l);
604: }
605:
606: /**
607: */
608: void notifyListeners() {
609: for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
610: WeakReference wr = (WeakReference) e.nextElement();
611: ClassPathListener l = (ClassPathListener) wr.get();
612: if (l == null) // garbage collected
613: listeners.removeElement(wr);
614: else
615: l.classPathChanged();
616: }
617: }
618:
619: static BshClassPath userClassPath;
620:
621: /**
622: A BshClassPath initialized to the user path
623: from java.class.path
624: */
625: public static BshClassPath getUserClassPath()
626: throws ClassPathException {
627: if (userClassPath == null)
628: userClassPath = new BshClassPath("User Class Path",
629: getUserClassPathComponents());
630: return userClassPath;
631: }
632:
633: static BshClassPath bootClassPath;
634:
635: /**
636: Get the boot path including the lib/rt.jar if possible.
637: */
638: public static BshClassPath getBootClassPath()
639: throws ClassPathException {
640: if (bootClassPath == null) {
641: try {
642: //String rtjar = System.getProperty("java.home")+"/lib/rt.jar";
643: String rtjar = getRTJarPath();
644: URL url = new File(rtjar).toURL();
645: bootClassPath = new BshClassPath("Boot Class Path",
646: new URL[] { url });
647: } catch (MalformedURLException e) {
648: throw new ClassPathException(" can't find boot jar: "
649: + e);
650: }
651: }
652: return bootClassPath;
653: }
654:
655: private static String getRTJarPath() {
656: String urlString = Class.class.getResource(
657: "/java/lang/String.class").toExternalForm();
658:
659: if (!urlString.startsWith("jar:file:"))
660: return null;
661:
662: int i = urlString.indexOf("!");
663: if (i == -1)
664: return null;
665:
666: return urlString.substring("jar:file:".length(), i);
667: }
668:
669: public abstract static class ClassSource {
670: Object source;
671:
672: abstract byte[] getCode(String className);
673: }
674:
675: public static class JarClassSource extends ClassSource {
676: JarClassSource(URL url) {
677: source = url;
678: }
679:
680: public URL getURL() {
681: return (URL) source;
682: }
683:
684: /*
685: Note: we should implement this for consistency, however our
686: BshClassLoader can natively load from a JAR because it is a
687: URLClassLoader... so it may be better to allow it to do it.
688: */
689: public byte[] getCode(String className) {
690: throw new Error("Unimplemented");
691: }
692:
693: public String toString() {
694: return "Jar: " + source;
695: }
696: }
697:
698: public static class DirClassSource extends ClassSource {
699: DirClassSource(File dir) {
700: source = dir;
701: }
702:
703: public File getDir() {
704: return (File) source;
705: }
706:
707: public String toString() {
708: return "Dir: " + source;
709: }
710:
711: public byte[] getCode(String className) {
712: return readBytesFromFile(getDir(), className);
713: }
714:
715: public static byte[] readBytesFromFile(File base,
716: String className) {
717: String n = className.replace('.', File.separatorChar)
718: + ".class";
719: File file = new File(base, n);
720:
721: if (file == null || !file.exists())
722: return null;
723:
724: byte[] bytes;
725: try {
726: FileInputStream fis = new FileInputStream(file);
727: DataInputStream dis = new DataInputStream(fis);
728:
729: bytes = new byte[(int) file.length()];
730:
731: dis.readFully(bytes);
732: dis.close();
733: } catch (IOException ie) {
734: throw new RuntimeException("Couldn't load file: "
735: + file);
736: }
737:
738: return bytes;
739: }
740:
741: }
742:
743: public static class GeneratedClassSource extends ClassSource {
744: GeneratedClassSource(byte[] bytecode) {
745: source = bytecode;
746: }
747:
748: public byte[] getCode(String className) {
749: return (byte[]) source;
750: }
751: }
752:
753: public static void main(String[] args) throws Exception {
754: URL[] urls = new URL[args.length];
755: for (int i = 0; i < args.length; i++)
756: urls[i] = new File(args[i]).toURL();
757: BshClassPath bcp = new BshClassPath("Test", urls);
758: }
759:
760: public String toString() {
761: return "BshClassPath " + name + "(" + super .toString()
762: + ") path= " + path + "\n" + "compPaths = {"
763: + compPaths + " }";
764: }
765:
766: /*
767: Note: we could probably do away with the unqualified name table
768: in favor of a second name source
769: */
770: static class UnqualifiedNameTable extends HashMap {
771: void add(String fullname) {
772: String name = splitClassname(fullname)[1];
773: Object have = super .get(name);
774:
775: if (have == null)
776: super .put(name, fullname);
777: else if (have instanceof AmbiguousName)
778: ((AmbiguousName) have).add(fullname);
779: else // String
780: {
781: AmbiguousName an = new AmbiguousName();
782: an.add((String) have);
783: an.add(fullname);
784: super .put(name, an);
785: }
786: }
787: }
788:
789: public static class AmbiguousName {
790: List list = new ArrayList();
791:
792: public void add(String name) {
793: list.add(name);
794: }
795:
796: public List get() {
797: //return (String[])list.toArray(new String[0]);
798: return list;
799: }
800: }
801:
802: /**
803: Fire the NameSourceListeners
804: */
805: void nameSpaceChanged() {
806: if (nameSourceListeners == null)
807: return;
808:
809: for (int i = 0; i < nameSourceListeners.size(); i++)
810: ((NameSource.Listener) (nameSourceListeners.get(i)))
811: .nameSourceChanged(this );
812: }
813:
814: List nameSourceListeners;
815:
816: /**
817: Implements NameSource
818: Add a listener who is notified upon changes to names in this space.
819: */
820: public void addNameSourceListener(NameSource.Listener listener) {
821: if (nameSourceListeners == null)
822: nameSourceListeners = new ArrayList();
823: nameSourceListeners.add(listener);
824: }
825:
826: /** only allow one for now */
827: static MappingFeedback mappingFeedbackListener;
828:
829: /**
830: */
831: public static void addMappingFeedback(MappingFeedback mf) {
832: if (mappingFeedbackListener != null)
833: throw new RuntimeException(
834: "Unimplemented: already a listener");
835: mappingFeedbackListener = mf;
836: }
837:
838: void startClassMapping() {
839: if (mappingFeedbackListener != null)
840: mappingFeedbackListener.startClassMapping();
841: else
842: System.err.println("Start ClassPath Mapping");
843: }
844:
845: void classMapping(String msg) {
846: if (mappingFeedbackListener != null) {
847: mappingFeedbackListener.classMapping(msg);
848: } else
849: System.err.println("Mapping: " + msg);
850: }
851:
852: void errorWhileMapping(String s) {
853: if (mappingFeedbackListener != null)
854: mappingFeedbackListener.errorWhileMapping(s);
855: else
856: System.err.println(s);
857: }
858:
859: void endClassMapping() {
860: if (mappingFeedbackListener != null)
861: mappingFeedbackListener.endClassMapping();
862: else
863: System.err.println("End ClassPath Mapping");
864: }
865:
866: public static interface MappingFeedback {
867: public void startClassMapping();
868:
869: /**
870: Provide feedback on the progress of mapping the classpath
871: @param msg is a message about the path component being mapped
872: @perc is an integer in the range 0-100 indicating percentage done
873: public void classMapping( String msg, int perc );
874: */
875:
876: /**
877: Provide feedback on the progress of mapping the classpath
878: */
879: public void classMapping(String msg);
880:
881: public void errorWhileMapping(String msg);
882:
883: public void endClassMapping();
884: }
885:
886: }
|