Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (497)
Games in Android Showcase (114)
games submitted by our members
Games in WIP (563)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  find all implementors of an interface?  (Read 3735 times)
0 Members and 1 Guest are viewing this topic.
Offline duncanIdaho

Junior Member




invert mouse


« Posted 2004-01-22 20:04:16 »

Sometimes I wish I was still a Smalltalk programmer ...  :-/

Is it possible to find all the implementors of an interface? That is, all sublclasses - not instances.  After a thorough run through of the Reflection API and the classes Class, ClassLoader, and Package I'm inclined to say this isn't possible.

Can I inject my own classloader in somewhere?  The desired functionality is this:  You add your class to the classpath, and I know automatically at runtime.

Thanks!

[size=1]edit - added that the goal is subclasses, not instances[/size]
Offline swpalmer

JGO Coder




Where's the Kaboom?


« Reply #1 - Posted 2004-01-22 21:43:29 »

It is only possible in the sense that you will need to scan every class and test if it implements the interface..  This is one way of dealing with plugins for instance.  But the search is limited to a specific folder or package.  Maybe that is good enough for you.

Offline duncanIdaho

Junior Member




invert mouse


« Reply #2 - Posted 2004-01-22 22:00:30 »

That would be good enough for now, but I don't see from where I can get a list of classes.  ClassLoader and Package don't offer lists of classes they know about, and Class only goes up the hierarchy.  

I'm all ears if you know of a way to get a list of classes or can point out what I've missed.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline cfmdobbie

Senior Member


Medals: 1


Who, me?


« Reply #3 - Posted 2004-01-22 22:03:16 »

If this is a one-off task, JavaDoc will tell you the heirarchy of your classes.  If you want to do this at runtime, sorry!

Hellomynameis Charlie Dobbie.
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #4 - Posted 2004-01-22 22:16:42 »

Quote
Sometimes I wish I was still a Smalltalk programmer ...  :-/


Really? Tongue

Quote

Is it possible to find all the implementors of an interface? That is, all sublclasses - not instances.  After a thorough run through of the Reflection API and the classes Class, ClassLoader, and Package I'm inclined to say this isn't possible.


Replace the main system classloader. There's several things that could be considered to be the "main system" classloader, but it's fairly obvious which the important one is when you look at the ClassLoader class API docs.

There are several articles on the net (I recall at least one on the JDC) that explain fun tricks to do with classloaders, and interposing your own classloader(s). The API docs (just had a quick look) explain how you can do it using a system property, but there are other ways too (just make sure you load your classloader before you reference any classes, IIRC).

malloc will be first against the wall when the revolution comes...
Offline Jeff

JGO Coder




Got any cats?


« Reply #5 - Posted 2004-01-23 07:05:30 »

There are static analysis tools that will do this for a codebase.  At ru-time yeah you need to implement your own sub-class of ClassLoader (I suggest sub of URLClassLoader actually)  that wil let you peek at whats been loaded/cached.

Realize though that any analysis only gives you the answer for oen state. Since classes are loadable dynamically, and under certai ncondistions ar even unloadable (collectable really), there is no one answer that is gauranteed correct for the entire run of the program.

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #6 - Posted 2004-01-23 08:36:58 »

Quote

Since classes are loadable dynamically, and under certai ncondistions ar even unloadable (collectable really), there is no one answer that is gauranteed correct for the entire run of the program.


...but the classloader approach will give you an answer that is correct everytime you need it, AND contains the sum total "history" to date, too.

malloc will be first against the wall when the revolution comes...
Offline duncanIdaho

Junior Member




invert mouse


« Reply #7 - Posted 2004-01-23 13:50:34 »

I see now that we've put the cart before the horse.  I found some code to change the system Classloader, but then I realized that for the ClassLoader approach to work my codebase would have to already know the name of the subclass in order for it to be loaded.

Now I'm thinking that I should analyze java.class.path and run through all the directories and jars, force eveything to load, and use instanceof.  Can't wait to see how slow this is!  

Quote
Really?  Tongue

1  
OrderedCollection subs := MyClass allSubclasses.

Only sometimes Wink
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #8 - Posted 2004-01-23 15:22:38 »

Quote
I see now that we've put the cart before the horse.  I found some code to change the system Classloader, but then I realized that for the ClassLoader approach to work my codebase would have to already know the name of the subclass in order for it to be loaded.


You've lost me now; I have no idea why you would need to already know the name of each of the subclasses?. Perhaps you should explain what it is you're trying to achieve, rather than just ask about one low-level feature you're not sure about how/why to use (...in java; i.e. what you're familiar with from other environments seems to be sufficiently dissimilar to "the java way" for this particular situation as to be non helpful...).

malloc will be first against the wall when the revolution comes...
Offline duncanIdaho

Junior Member




invert mouse


« Reply #9 - Posted 2004-01-23 16:08:41 »

My goal is stated above.
Quote
The desired functionality is this:  You add your class to the classpath, and I know automatically at runtime.

You asked:
Quote
I have no idea why you would need to already know the name of each of the subclasses?

Though earlier, you said:
Quote
(just make sure you load your classloader before you reference any classes, IIRC).


If I don't know the name of your class, I can't reference it in my code.  If I reference your class, I already know what type it is.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline swpalmer

JGO Coder




Where's the Kaboom?


« Reply #10 - Posted 2004-01-23 16:11:24 »

You want to scan the jars and directories on the classpath (or a subset of it) and then peek at the files ending with .class using your classloader.
In otherwords if they aren't loaded yet - load them.  Hopefully they don't have to stay loaded.. I haven't actually done this before.

Offline Jeff

JGO Coder




Got any cats?


« Reply #11 - Posted 2004-01-23 18:46:58 »

It sounds like you are trying to do what the Plugin code in JInput does, which is find all the installed implementors oif a given interface.

JInput does it the easy way.  It uses a naming convention (*Plugin.class) to identify likely candidates, loads and instances them, and then uses reflection to make sure they really are plug-ins (implementors of the Plugin interface,)

This code is all available as part of JUtils.

If you dont want to use a naming convention and instance though you are going to have to dig into the VM spec and figure out how to read the inheritance data out of a raw .class file.  

Nasty.


Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #12 - Posted 2004-01-23 19:13:15 »

Quote
My goal is stated above.
You asked:
Though earlier, you said:

If I don't know the name of your class, I can't reference it in my code.  If I reference your class, I already know what type it is.


Sorry, I wrongly assumed the classes would get referenced by someone else's code. You can reference one class belonging to someone else and end up loading many classes, none of which you have any idea existed (this is all done automatically).

If your use-case is "people give me class files; I load them without any further involvement of the author(s)", then I now understand OK. As Jeff has pointed out, the standard approach is to have a directory where the classes will be found and/or use a filename-prefix (the file-naming scheme is not mandatory to make this succeed).

Unfortunately, the design (flawed, IMHO, although most people disagree Smiley) of java classloaders means that ".isInstance(...)" will *always* return false for a class loaded by a different classloader. So, when you're trying to check which are instances and which aren't, the object you use for comparison must be created with the same classloader as is loading these external classes; this sometimes bites people who wonder why none of the imported classes seem to be accepted by their classloader...

malloc will be first against the wall when the revolution comes...
Offline duncanIdaho

Junior Member




invert mouse


« Reply #13 - Posted 2004-01-23 19:39:09 »

A naming convention sounds like a great compromise!  Then I could scan all directories and jar files, but only load and check instanceof with the ideal subset.  First, I think I'll check out JUtils though ... Smiley

Thanks everyone!
Offline duncanIdaho

Junior Member




invert mouse


« Reply #14 - Posted 2004-01-23 20:01:11 »

blahblahblahh:  Good point.  One example of being bitten by a ClassLoader is trying to use XmlEncoder on an externally loaded class.

One way around that can be found here: http://forum.java.sun.com/thread.jsp?thread=344817&forum=39&message=1423943
Offline Jeff

JGO Coder




Got any cats?


« Reply #15 - Posted 2004-01-24 01:16:37 »

Quote


Unfortunately, the design (flawed, IMHO, although most people disagree Smiley) of java classloaders means that ".isInstance(...)" will *always* return false for a class loaded by a different classloader.


This "flaw" is vital to the operation of many more sophisticated Java systems such as RMI and Jini.  Without name-space separation it becomes virtually impossible to do either code-mobility or code-container infra-structures.

The key is to understand that a ClassLoader does more then load classes.  It is the public face of a class namespace and is well-isolated from other such namespaces.  Class "foo" loaded nby classloader 1 IS a different class then class "foo" loaded by classloader 2 even if they are loaded from the same classfile.  

This allows such things as static variables to operate as expected in the face of multiple apps running in the same VM and based on the same source.





Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline Jeff

JGO Coder




Got any cats?


« Reply #16 - Posted 2004-01-24 01:20:56 »

Quote
So, when you're trying to check which are instances and which aren't, the object you use for comparison must be created with the same classloader as is loading these external classes;


Actually this isn't exactly true.  Look at JUtils.  The trick here is that class loaders chain and they defer to their parents first, so if a parent has loaded a class that is the class you will get.  The simplest way to handle this is to have the Interface you are checking for either in the bootclasspath or the classpath.  These loaders by definition MUST be at the beginning of a loader chain.


Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #17 - Posted 2004-01-24 11:32:30 »

Quote

This "flaw" is vital to the operation of many more sophisticated Java systems such as RMI and Jini.  Without name-space separation it becomes virtually impossible to do either code-mobility or code-container infra-structures.


Doesn't mean it has to be mandatory; it could be optional (theoretically - I'm not claiming it would have been easy to integrate with the other requirements of the original spec).

Given how many java programmers don't use RMI nor Jini (although probably many more should use the latter, and many fewer should use the former), but do use isInstance(), I think it's fair enough to say this part of the design could have been done better. I'm not claiming the current behaviour isn't valuable, just that IMHO it would be more intuitive NOT to be the default.

Quote

The key is to understand that a ClassLoader does more then load classes.


I agree entirely, and this is a large part of why I consider the current design "flawed". Since it does "much more than load classes" it should NOT be named "ClassLoader".

IMHO, a better API design would start from the concept of splitting the namespace-management, the class-loading, and the class-management (i.e. for stuff like type comparisons) into two, or possibly three, separate classes / sets of classes. I suspect that "hierarchy" namespace-management and "class" namespace management are two separate things, hence the need for three rather than two classes. The former deals with sandboxing, and separating differently-sourced class hierarchies; the latter is sort-of aspect-oriented, and cuts across sandboxes. Using both in conjunction, abstracted into separate classes, could give you all the benefits of both.

This could lead to a worse design, of course; as a systems architect, I don't feel that's a reason not to try Smiley. I live in hope that the ClassLoader's etc will get a "refresh" in some future release of java...

Quote

Actually this isn't exactly true.


Yes, sorry, I wasn't as precise as I could have been Sad. The important point, of course, is the gotcha, and there's more than one way to skin the cat Smiley.

malloc will be first against the wall when the revolution comes...
Offline Jeff

JGO Coder




Got any cats?


« Reply #18 - Posted 2004-01-25 02:09:03 »

I'll grant you that ClassLoaders and all they do isn't immediately obvious or intuitive at first. "ClassSpace" might have been a better name.

But I can't think of another way to accomplish what they accomplish is cleanly and uncomplicatedly as they do.

And the things they make possible IMO are well worth the learning curve.

**shrug**

Got a question about Java and game programming?  Just new to the Java Game Development Community?  Try my FAQ.  Its likely you'll learn something!

http://wiki.java.net/bin/view/Games/JeffFAQ
Offline princec

JGO Kernel


Medals: 378
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #19 - Posted 2004-01-25 15:51:58 »

'srite, if you need to use classloaders you're probably doing something weird and difficult anyway.

Cas Smiley

Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #20 - Posted 2004-01-25 19:59:44 »

Quote

And the things they make possible IMO are well worth the learning curve.


Definitely agree here Smiley

Quote

But I can't think of another way to accomplish what they accomplish is cleanly and uncomplicatedly as they do.


Good API design is very hard, yes. Since it's my specialism, I'm more demanding than most  Roll Eyes. There are many bits of java that are excellent, but I get frustrated when the quality of API's (and/or documentation - e.g Swing has both some really really good docs, e.g. a 4 page class-comment IIRC, and some really terrible ones, e.g. some classes with no comments at all!) has very large variance.

malloc will be first against the wall when the revolution comes...
Offline duncanIdaho

Junior Member




invert mouse


« Reply #21 - Posted 2004-04-06 21:07:53 »

Not exactly a timely post, but here is code to find all subclasses of a class (or all implementors of an interface) on your classpath.  

To use it, add these classes to your classpath and from somewhere in your program call:  
Class[] interestingClasses = Subclasses.of( MyInterface.class );
// or add a regex
Class[] interestingClasses = Subclasses.of( MyClass.class, ".*Suffix$ );
Once you have the array of classes you can instantiate them with relfection, but I beleive that only works if your class has an empty constructor.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
import java.util.ArrayList;
import java.util.Iterator;

/**
 * Created with Eclipse
 * User: duncanIdaho for java-gaming.org
 *
 * copyright 2004
 */


/**
 * A class to find all subclasses of a given <code>Class</code> or interface.
 *
 */

public class Subclasses {
     
      /**
       * Find all subclasses of the given <code>Class</code> or interface by
       * loading all classes on the class path.
       *
       * @param targetType the superclass of all returned classes.
       * @return an array of all subclasses of <code>targetType</code>
       */

      public static Class[] of( Class targetType ) {
            return of( targetType, ".*" );
           
      }
     
      /**
       * Find all subclasses of the given <code>Class</code> or interface by
       * loading only those classes with names that match the given regular
       * expression.
       *
       * @param targetType the superclass of all returned classes.
       * @param regex a regular expression that will match with every subclass
       * @return an array of all subclasses of <code>targetType</code>
       */

      public static Class[] of( Class targetType, String regex ) {
            ArrayList matches = new ArrayList();
            ClassPath cp = new ClassPath();
            Iterator i = cp.classNameIterator();
            while ( i.hasNext() ) {
                  String className = (String)i.next();
                  if ( className.matches( regex ) &&
                              !className.equals( targetType.getName() ) ) {
                        Class clazz = null;
                        try {
                              clazz = Class.forName( className );
                             
                        } catch (ClassNotFoundException cnfx ) {
                              continue;
                             
                        } finally {
                              if ( clazz != null && targetType.isAssignableFrom( clazz ) ) {
                                    matches.add( clazz );
                                   
                              }
                             
                        }
                       
                  }
                 
            }
            return (Class[])matches.toArray( new Class[0] );
           
      }
     
}

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
202  
203  
204  
205  
206  
207  
208  
209  
210  
211  
212  
213  
214  
215  
216  
217  
218  
219  
220  
221  
222  
223  
224  
225  
226  
227  
228  
229  
230  
231  
232  
233  
234  
235  
236  
237  
238  
239  
240  
241  
242  
243  
244  
245  
246  
247  
248  
249  
250  
251  
252  
253  
254  
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;


/**
 * Created with Eclipse
 * User: duncanIdaho for java-gaming.org
 *
 * copyright 2004
 */



/**
 * ClassPath find and records the fully qualified name of every Class
 * on the classpath via the system property "java.class.path".
 *
 */

public class ClassPath {
     
      public static final int SILENT = 0;
      public static final int QUIET = 1;
      public static final int VERBOSE = 2;
     
      public static final String JAR_EXT = ".jar";
      public static final String ZIP_EXT = ".zip";
      public static final String CLASS_EXT = ".class";

      private ArrayList classNames;
     
      private int outputLevel;
     
      /**
       * create a new ClassPath instance and find all classes on the classpath
       *
       */

      public ClassPath() {
            this( SILENT );
           
      }
     
     
      public ClassPath(int type) {
            super();
            outputLevel = type;
            findAllClassNames();
     
      }
     
      /**
       * Answers an <code>Iterator</code> over fully qualified <code>Class</code>
       * names found on the classpath.
       * @return an <code>Iterator</code> over the elements in this list
       */

      public Iterator classNameIterator() {
            return classNames.iterator();
           
      }
     
      /**
       * Answers an <code>ArrayList</code> of all <code>Class</code> names found
       * on the classpath.
       * @return an <code>ArrayList</code> of class names.
       */

      public ArrayList getClassNames() {
            return classNames;
           
      }

      /**
       * Initialize the member variable <code>classNames</code> and
       * look for classes.
       *
       */

      private void findAllClassNames() {
            String path = null;
            classNames = new ArrayList();
            try {
                  path = System.getProperty( "java.class.path" );
            } catch ( Exception x ) {
                  x.printStackTrace();
                 
            }
            if ( outputLevel != SILENT )
                  System.out.println( "scanning classpath: " + path );
            StringTokenizer toke = new StringTokenizer( path, File.pathSeparator );
            while ( toke.hasMoreTokens()) {
                  String pathElement = toke.nextToken();
                  File elementFile = new File( pathElement );
                  String elementName = elementFile.getAbsolutePath();
                  if ( elementName.endsWith( JAR_EXT ) ) {
                        addJarContents( elementFile );
                       
                  } else if ( elementName.endsWith( ZIP_EXT ) ) {
                        addZipContents( elementFile );
                       
                  } else if ( elementName.endsWith( CLASS_EXT ) ) {
                        addClass( elementFile );
                       
                  } else {
                        addDirectoryContents( elementFile );
                       
                  }

            }
            if ( outputLevel != SILENT )
                  System.out.println( "found " + classNames.size() + " classes." );
           
            if ( outputLevel == VERBOSE ) {
                  Iterator i = classNames.iterator();
                  while ( i.hasNext() ) {
                        String name = (String)i.next();
                        System.out.println( name );
                       
                  }
                 
            }
           
      }
     
      /**
       * Adds a file explicitly mentioned on the classpath to the list
       * of classes.
       * @param classFile a class file listed on the classpath itself.
       */

      private void addClass( File classFile ) {
            classNames.add( getClassNameFrom( classFile.getName() ) );
           
      }
     
      /**
       * Adds all class names found in the jar.
       * @param jarFile a jar file explicitly listed on the classpath.
       */

      private void addJarContents( File jarFile ) {
            JarFile jar = null;
            try {
                  jar = new JarFile( jarFile );

            } catch ( IOException iox ) {
                  // boom!
           }
            if ( jar != null ) {
                  Enumeration e = jar.entries();
                  while (e.hasMoreElements()) {
                        JarEntry entry = (JarEntry)e.nextElement();
                        if ( !entry.isDirectory() && entry.getName().endsWith( CLASS_EXT ) ) {
                              String className = getClassNameFrom( entry.getName() );
                              classNames.add( className );

                        }
                  }      
            }
      }
     
      /**
       * Adds all class names found in the zip mentioned
       * @param zipFile
       */

      private void addZipContents( File zipFile ) {
            ZipFile zip = null;
            try {
                  zip = new JarFile( zipFile );
                 
            } catch ( IOException iox ) {
                 
            }
            if ( zip != null ) {
                  Enumeration e = zip.entries();
                  while (e.hasMoreElements()) {
                        ZipEntry entry = (ZipEntry)e.nextElement();
                        if ( !entry.isDirectory() && entry.getName().endsWith( CLASS_EXT ) ) {
                              String className = getClassNameFrom( entry.getName() );
                              classNames.add( className );
                             
                        }
                  }      
            }      
      }

      /**
       * This method takes a top level classpath dir i.e. 'classes' or bin
       * @param dir
       */

      private void addDirectoryContents( File dir ) {
            // drill through contained dirs ... this is expected to be the
           // 'classes' or 'bin' dir
           File files[] = dir.listFiles();
            for( int i = 0; i < files.length ; ++i ) {
                  File f = files[i];
                  if ( f.isDirectory() ) {
                        addDirectoryContents( "", f );
                       
                  } else {
                        if ( f.getName().endsWith( CLASS_EXT ) )
                              addClass( f );
                                   
                  }
                 
            }
           
      }
     
      /**
       * This method does the real directory recursion, passing along the
       * the corresponding package-path to this directory.
       *
       * @param pathTo the preceding path to this directory
       * @param dir a directory to search for class files
       */

      private void addDirectoryContents( String pathTo, File dir ) {
            String pathToHere = pathTo + dir.getName() + File.separator;
            File files[] = dir.listFiles();
            for( int i = 0; i < files.length ; ++i ) {
                  File f = files[i];
                  if ( f.isDirectory() ) {
                        addDirectoryContents( pathToHere, f );
                       
                  } else {
                        if ( f.getName().endsWith( CLASS_EXT ) ) {
                              String absFilePath = pathToHere + f.getName();
                              classNames.add( getClassNameFrom( absFilePath ) );
                             
                        }
                                   
                  }
                 
            }
           
      }
     
      /**
       * replace ANY slashes with dots and remove the .class at the
       * end of the file name.
       * @param entryName a file name relative to the classpath.  A class
       * of package org found in directory bin would be passed into this
       * method as "org/MyClass.class"
       * @return a fully qualified Class name.
       */

      private String getClassNameFrom( String entryName ) {
            String foo = new String(entryName).replace( '/', '.' );
            foo = foo.replace( '\\', '.' );
            return foo.substring( 0, foo.lastIndexOf( '.' ) );
           
      }

}
Offline ap_kelly

Junior Member




Java rocks!


« Reply #22 - Posted 2004-04-07 00:24:32 »

I've written a plugin loader for a PVR software application I'm writing. Here is the code, it'll scan through all jars in a given directory and find me classes inside those jars which implement my plugin interface.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
package com.swizel.javamediacenter.plugins;

import java.io.File;
import java.io.IOException;
import java.util.Vector;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.jar.JarFile;
import java.net.URLClassLoader;
import java.net.URL;
import java.net.MalformedURLException;

import com.swizel.javamediacenter.io.*;

public class PluginLoader {

  private static final PluginLoader thisInstance = new PluginLoader();
  private static URLClassLoader pluginClassLoader;
  private static Vector pluginJarFiles;
  private static Object[] pluginClasses;

  private PluginLoader() {
    // Scan the plugins directory for jar files.
   File pluginsDir  = new File("plugins");
    File[] jarFiles  = pluginsDir.listFiles(new JarFileFilter());

    // Loop through jar files and add then to a plugin class loader
   URL[]  lvJarURLs = new URL[jarFiles.length];
    pluginJarFiles = new Vector();
    for (int i=0; i<jarFiles.length; i++) {
      try {
        lvJarURLs[i] = jarFiles[i].toURL();
        pluginJarFiles.addElement(new JarFile(jarFiles[i]));
      } catch (MalformedURLException murle) {
      } catch (IOException ioe) {
      }
    }
    pluginClassLoader = new URLClassLoader(lvJarURLs);
  }

  public static PluginLoader getInstance() {
    return thisInstance;
  }

  public static URLClassLoader getClassLoader() {
    return pluginClassLoader;
  }

  public static Object[] getPlugins() {
    if (pluginClasses == null) {
      Vector lvPluginClasses = new Vector();

      // Load each jar file and scan through classes looking for plugins
     for (int i=0; i<pluginJarFiles.size(); i++) {
        JarFile lvJar = (JarFile) pluginJarFiles.elementAt(i);

        Class lvPlugin = findPluginInJarFile(lvJar);
        if (lvPlugin != null) {
          try {
            lvPluginClasses.addElement(lvPlugin.newInstance());
          } catch (Exception e) {
            System.out.println("Can't create plugin : " + lvPlugin.getName());
          }
        }
      }

      pluginClasses = lvPluginClasses.toArray();
    }

    return pluginClasses;
  }

  private static Class findPluginInJarFile(JarFile pJarFile) {
    // This method assumes that there is only 1 plugin per jar file.
   Enumeration lvEntries = pJarFile.entries();

    while (lvEntries.hasMoreElements()) {
      ZipEntry lvFile = (ZipEntry) lvEntries.nextElement();

      // Should I exclude classes here that contain a $ in their name since they're inner classes?
     if ((!lvFile.isDirectory()) && (lvFile.getName().trim().toLowerCase().endsWith(".class"))) {
        String lvName = lvFile.getName().replace('/', '.').substring(0, lvFile.getName().length() - 6);

        try {
          Class lvClass = pluginClassLoader.loadClass(lvName);
          Class[] lvInterfaces = lvClass.getInterfaces();
          for (int j=0; j<lvInterfaces.length; j++) {
            // Remember each class that implements one of our interfaces.
           if (PluginInterface.class.isAssignableFrom(lvInterfaces[j])) {
              return lvClass;
            }
          }
        } catch (Exception e) {
          System.out.println(lvName + " : " + e);
        }
      }
    }

    return null;
  }


}


This code is limited to finding only 1 class per jar file that implements my interface, which is ok for my app that I'm writing, you may want to change this.

Andy.

Offline Mark Thornton

Senior Member





« Reply #23 - Posted 2004-04-07 10:55:42 »

The file scanning code above is flawed in that it won't work correctly where jar files have a class-path entry in the manifest. This invisibly adds additional jar files to the class path.

There are two main uses for this type of functionality
a) in an IDE (for refactoring or other developer search aids). In this case you will already know where all the relevant source and class files are located and can search them pretty much as described in the other posts.

b) You want to find all available implementations for some service. In this case you (the service implementors) explicitly list the implementing classes in a text resource. The text resources are located using ClassLoader.getResources() as this will allow each jar file or directory tree on the class path to have an instance of the resource. Merge all the results you thus obtain. I think there is even a semi-standard implementation of this.
Offline duncanIdaho

Junior Member




invert mouse


« Reply #24 - Posted 2004-04-07 19:00:59 »

Good point.

This version of ClassPath fixes that issue ( use with SubClasses above ) by scanning the manifest and digging through any Class-Path entries.

note:  javac assumes Class-Path entries are relative to the path of the jar they are in, so I do as well!!  I have tested this with a main.jar, a second.jar referenced in main's manifest, and a third.jar referenced in second.jar's manifest.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
202  
203  
204  
205  
206  
207  
208  
209  
210  
211  
212  
213  
214  
215  
216  
217  
218  
219  
220  
221  
222  
223  
224  
225  
226  
227  
228  
229  
230  
231  
232  
233  
234  
235  
236  
237  
238  
239  
240  
241  
242  
243  
244  
245  
246  
247  
248  
249  
250  
251  
252  
253  
254  
255  
256  
257  
258  
259  
260  
261  
262  
263  
264  
265  
266  
267  
268  
269  
270  
271  
272  
273  
274  
275  
276  
277  
278  
279  
280  
281  
282  
283  
284  
285  
286  
287  
288  
289  
290  
291  
292  
293  
294  
295  
296  
297  
298  
299  
300  
301  
302  
303  
304  
305  
306  
307  
308  
309  
310  
311  
312  
313  
314  
315  
316  
317  
318  
319  
320  
321  
322  
323  
324  
325  
326  
327  
328  
329  
330  
331  
332  
333  
334  
335  
336  
337  
338  
339  
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;


/**
 * Created with Eclipse
 * User: duncanIdaho for java-gaming.org
 *
 */


/**
 * ClassPath find and records the fully qualified name of every Class
 * on the classpath via the system property "java.class.path".
 *
 */

public class ClassPath {
     
      public static final int SILENT = 0;
      public static final int QUIET = 1;
      public static final int VERBOSE = 2;
     
      public static final String JAR_EXT = ".jar";
      public static final String ZIP_EXT = ".zip";
      public static final String CLASS_EXT = ".class";

      private ArrayList classNames;
     
      private int outputLevel;
     
      /**
       * create a new ClassPath instance and find all classes on the classpath
       *
       */

      public ClassPath() {
            this( SILENT );
           
      }
     
     
      public ClassPath(int type) {
            super();
            outputLevel = type;
            findAllClassNames();
     
      }
     
      /**
       * Answers an <code>Iterator</code> over fully qualified <code>Class</code>
       * names found on the classpath.
       * @return an <code>Iterator</code> over the elements in this list
       */

      public Iterator classNameIterator() {
            return classNames.iterator();
           
      }
     
      /**
       * Answers an <code>ArrayList</code> of all <code>Class</code> names found
       * on the classpath.
       * @return an <code>ArrayList</code> of class names.
       */

      public ArrayList getClassNames() {
            return classNames;
           
      }

      /**
       * Initialize the member variable <code>classNames</code> and
       * look for classes.
       *
       */

      private void findAllClassNames() {
            String path = null;
            classNames = new ArrayList();
            try {
                  path = System.getProperty( "java.class.path" );
            } catch ( Exception x ) {
                  x.printStackTrace();
                 
            }
            if ( outputLevel != SILENT )
                  System.out.println( "scanning classpath: " + path );
            StringTokenizer toke = new StringTokenizer( path, File.pathSeparator );
           
            analyzeClasspathTokens( toke );

            if ( outputLevel != SILENT )
                  System.out.println( "found " + classNames.size() + " classes." );
           
            if ( outputLevel == VERBOSE ) {
                  Iterator i = classNames.iterator();
                  while ( i.hasNext() ) {
                        String name = (String)i.next();
                        System.out.println( name );
                       
                  }
                 
            }
           
      }
     
      /**
       * Adds a file explicitly mentioned on the classpath to the list
       * of classes.
       * @param classFile a class file listed on the classpath itself.
       */

      private void addClass( File classFile ) {
            classNames.add( getClassNameFrom( classFile.getName() ) );
           
      }
     
      /**
       * Adds all class names found in the jar.
       * @param jarFile a jar file explicitly listed on the classpath.
       */

      private void addJarContents( File jarFile ) {
            JarFile jar = null;
            try {
                  jar = new JarFile( jarFile );

            } catch ( IOException iox ) {
                  // boom!
           }
            if ( jar != null ) {
                  Manifest man = null;
                  try {
                        man = jar.getManifest();
                  } catch( IOException iox ) {
                        System.err.println("error obtaining manifest from: " + jar.getName());
                       
                  } finally {
                        if ( man != null ) {
                              this.scanClasspath( man, jar, jarFile );
                             
                        }
                       
                  }
                  Enumeration e = jar.entries();
                  while (e.hasMoreElements()) {
                        JarEntry entry = (JarEntry)e.nextElement();
                        if ( !entry.isDirectory() && entry.getName().endsWith( CLASS_EXT ) ) {
                              String className = getClassNameFrom( entry.getName() );
                              classNames.add( className );

                        }
                 
                  }      

            }
           
      }
     
      /**
       * Adds all class names found in the zip mentioned
       * @param zipFile
       */

      private void addZipContents( File zipFile ) {
            ZipFile zip = null;
            try {
                  zip = new JarFile( zipFile );
                 
            } catch ( IOException iox ) {
                 
            }
            if ( zip != null ) {
                  Enumeration e = zip.entries();
                  while (e.hasMoreElements()) {
                        ZipEntry entry = (ZipEntry)e.nextElement();
                        if ( !entry.isDirectory() && entry.getName().endsWith( CLASS_EXT ) ) {
                              String className = getClassNameFrom( entry.getName() );
                              classNames.add( className );
                             
                        }
                  }      
            }      
      }

      /**
       * This method takes a top level classpath dir i.e. 'classes' or bin
       * @param dir
       */

      private void addDirectoryContents( File dir ) {
            // drill through contained dirs ... this is expected to be the
           // 'classes' or 'bin' dir
           File files[] = dir.listFiles();
            for( int i = 0; i < files.length ; ++i ) {
                  File f = files[i];
                  if ( f.isDirectory() ) {
                        addDirectoryContents( "", f );
                       
                  } else {
                        if ( f.getName().endsWith( CLASS_EXT ) )
                              addClass( f );
                                   
                  }
                 
            }
           
      }
     
      /**
       * This method does the real directory recursion, passing along the
       * the corresponding package-path to this directory.
       *
       * @param pathTo the preceding path to this directory
       * @param dir a directory to search for class files
       */

      private void addDirectoryContents( String pathTo, File dir ) {
            String pathToHere = pathTo + dir.getName() + File.separator;
            File files[] = dir.listFiles();
            for( int i = 0; i < files.length ; ++i ) {
                  File f = files[i];
                  if ( f.isDirectory() ) {
                        addDirectoryContents( pathToHere, f );
                       
                  } else {
                        if ( f.getName().endsWith( CLASS_EXT ) ) {
                              String absFilePath = pathToHere + f.getName();
                              classNames.add( getClassNameFrom( absFilePath ) );
                             
                        }
                                   
                  }
                 
            }
           
      }
     
      /**
       * While the StringTokenizer has classpath elements, attempt to
       * add any contained classes to the list.
       *
       * @param toke class path elements
       */

      private void analyzeClasspathTokens( StringTokenizer toke ) {
            while ( toke.hasMoreTokens()) {
                  String pathElement = toke.nextToken();
                  analyzeClasspathElement( pathElement );

            }
           
      }
     
      /**
       * Make a file out of the String, determine which kind of
       * interesting classpath file it might be, and add it to the list.
       *
       * @param pathElement
       */

      private void analyzeClasspathElement( String pathElement ) {
            File elementFile = new File( pathElement );
            String elementName = elementFile.getAbsolutePath();
            if ( elementName.endsWith( JAR_EXT ) ) {
                  addJarContents( elementFile );
                 
            } else if ( elementName.endsWith( ZIP_EXT ) ) {
                  addZipContents( elementFile );
                 
            } else if ( elementName.endsWith( CLASS_EXT ) ) {
                  addClass( elementFile );
                 
            } else {
                  addDirectoryContents( elementFile );
                 
            }
           
      }
     
      /**
       * replace ANY slashes with dots and remove the .class at the
       * end of the file name.
       * @param entryName a file name relative to the classpath.  A class
       * of package org found in directory bin would be passed into this
       * method as "org/MyClass.class"
       * @return a fully qualified Class name.
       */

      private String getClassNameFrom( String entryName ) {
            String foo = new String(entryName).replace( '/', '.' );
            foo = foo.replace( '\\', '.' );
            return foo.substring( 0, foo.lastIndexOf( '.' ) );
           
      }
     
      /**
       * Use the manifest associated with the jar to determine if there are
       * any Class-Path elements names in the jar that should also be scanned.
       *
       * @param man the manifest of the given jar
       * @param jar the jar associated with the given manifest.
       */

      private void scanClasspath( Manifest man, JarFile jar, File jarFile ) {
            Map map = man.getEntries();
            if ( map != null ) {
                  Attributes atts = man.getMainAttributes();
                  if ( atts != null ) {
                        Set keys = atts.keySet();
                        Iterator i = keys.iterator();
                        while ( i.hasNext() ) {
                              Object key = (Object)i.next();
                              String value = (String)atts.get( key );
                              if ( outputLevel == VERBOSE )
                                    System.out.println( jar.getName() + "  " + key + ": " + value );
                              if ( key.toString().equals( "Class-Path" )) {
                                    if ( outputLevel != SILENT )
                                          System.out.println( "scanning " + jar.getName() +"'s manifest classpath: " + value);
                                    StringTokenizer toke = new StringTokenizer( value );
                                    while (toke.hasMoreTokens() ) {
                                          String element = toke.nextToken();
                                          if ( jarFile.getParent() == null )
                                                analyzeClasspathElement( element );
                                          else {
                                                analyzeClasspathElement( jarFile.getParent() + File.separator +  element );
                                          }
                                         
                                    }
                                   
                              }
                             
                        }
                       
                  }      
                 
            }
           
      }

}
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #25 - Posted 2004-06-18 08:55:39 »

All of a sudden, I need similar functionality myself Smiley.

Jeff's JUtils plugin system won't work for me because it only searches in one place.
(also Huh I'm worried it might not work with webstart (I saw several uses of File-based access that weren't shielded by backup plans using non-File mechanisms...)?)

Duncan's ClassPath class is what I just spent 15 minutes searching Sun's JDK for - and I am disappointed that:
 a. it doesn't exist (why the heck not?!??!)
 b. ...and the whole of the Package class in java.lang.reflect. That thing is an abomination! Not only is it hardcoded to assume you only ever have ONE manifest for a single package (not only is it possible to have arbitrarily many, but in fact is common) but it doesn't have the most obvious of all methods for a Package class - getClassNames() Sad. (In fact, it seems it's just part of the last-minute hacking Sun did to make J2EE a little less painful - Package is not *really* "Package", it's really a class called "ManifestFilePackageEntry" that someone was too lazy to type the full name for Tongue)

But rather than matching subclasses, I'm after instances. So, I was wondering if perhaps I could take Duncan's ClassPath and make an alternative to his SubClasses class which was a "front-end" to a range of searches...so it would include his SubClasses lookup, but also an instances lookup, etc.

And then make it all available as a tiny self-contained JAR, with no reserved rights other than authorship details...

Duncan?

malloc will be first against the wall when the revolution comes...
Offline duncanIdaho

Junior Member




invert mouse


« Reply #26 - Posted 2004-06-28 21:46:12 »

Quote
And then make it all available as a tiny self-contained JAR, with no reserved rights other than authorship details...

Duncan?


I was given permission to post the code with the intent to give back to the community, so I think that sounds great.

Offline mhale

Senior Newbie




Take pity, I'm just a poor blob!


« Reply #27 - Posted 2004-09-15 19:01:08 »

I believe if you use the URLClassLoader with a JAR URL, then manifest attributes such as Class-Path are automatically respected. At least that is what seems to happen with my plugin loader.
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

BurntPizza (25 views)
2014-09-19 03:14:18

Dwinin (39 views)
2014-09-12 09:08:26

Norakomi (68 views)
2014-09-10 13:57:51

TehJavaDev (93 views)
2014-09-10 06:39:09

Tekkerue (47 views)
2014-09-09 02:24:56

mitcheeb (68 views)
2014-09-08 06:06:29

BurntPizza (51 views)
2014-09-07 01:13:42

Longarmx (38 views)
2014-09-07 01:12:14

Longarmx (44 views)
2014-09-07 01:11:22

Longarmx (40 views)
2014-09-07 01:10:19
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!