Sunday, May 9, 2010

Java, Know the Subclasses of a class.

While I was working on one of my project I came across with a need to know all the subclasses of particular class in a classpath.

How does one go about and try to find all subclasses of a given class (or all implementors of a given interface) in Java? As of now, I have a method to do this. The method is:

1.Get a list of all class names that exist on the class path
2.Load each class and test to see if it is a subclass or implementor of the desired class or interface

In Eclipse, there is a nice feature called the Type Hierarchy that manages to show this quite efficiently. How does one go about and do it programmatically?

I know Scanning for classes is not easy with pure Java, And the Spring framework offers a class called ClassPathScanningCandidateComponentProvider that can do what you need.

But here is a way of achieving it with in stand alone applications.

May not be an efficient way of coding, used Vectors in some places, which is not required, we can safely replace them with ArrayList, Sesrializing might not be the appropriate solution, but logic works well.. here the class which I implemented named with SubClassFinder.java.

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Will scan and list all the subclasses of a given class or Interface,
 * Its even search for classes in a jar file too.. 
 * <li>Get a list of all class names that exist on the class path</li>
 *  <li>
 * Load each class and test to see if</li>
 * While scanning all the classes it serializes the results, 
 * 
 * 
 * @author rajendra
 */
public class SubClassFinder {
 private Class<?> classToSearch = null;
 private Map<URL, String> classpathLocations = new HashMap<URL, String>();
 private Map<Class<?>, URL> results = new HashMap<Class<?>, URL>();
 private List<Throwable> errors = new ArrayList<Throwable>();

 public SubClassFinder() {
  refreshLocations();
 }

 /**
  * Rescan the classpath, cacheing all possible file locations.
  */
 public final void refreshLocations() {
  synchronized (classpathLocations) {
   classpathLocations = getClasspathLocations();
  }
 }

 /**
  * @param fqcn
  *            Name of superclass/interface on which to search
  */
 public final Vector<Class<?>> findSubclasses(String fqcn) {
  synchronized (classpathLocations) {
   synchronized (results) {
    try {
     classToSearch = null;
     errors = new ArrayList<Throwable>();
     results = new TreeMap<Class<?>, URL>(CLASS_COMPARATOR);
     // filter malformed FQCN
     if (fqcn.startsWith(".") || fqcn.endsWith(".")) {
      return new Vector<Class<?>>();
     }

     // Determine search class from fqcn
     try {
      classToSearch = Class.forName(fqcn);
     } catch (ClassNotFoundException ex) {
      errors.add(ex);
      return new Vector<Class<?>>();
     }

     return findSubclasses(classToSearch, classpathLocations);
    } finally {
    }
   }
  }
 }

 public final List<Throwable> getErrors() {
  return new ArrayList<Throwable>(errors);
 }

 /**
  * The result of the last search is cached in this object, along with the
  * URL that corresponds to each class returned. This method may be called to
  * query the cache for the location at which the given class was found.
  * <code>null</code> will be returned if the given class was not found
  * during the last search, or if the result cache has been cleared.
  */
 public final URL getLocationOf(Class<?> cls) {
  if (results != null)
   return results.get(cls);
  else
   return null;
 }

 /**
  * Determine every URL location defined by the current classpath, and it's
  * associated package name.
  */
 public final Map<URL, String> getClasspathLocations() {
  Map<URL, String> map = new TreeMap<URL, String>(URL_COMPARATOR);
  File file = null;

  String pathSep = System.getProperty("path.separator");
  String classpath = System.getProperty("java.class.path");
  // System.out.println ("classpath=" + classpath);

  StringTokenizer st = new StringTokenizer(classpath, pathSep);
  while (st.hasMoreTokens()) {
   String path = st.nextToken();
   file = new File(path);
   include(null, file, map);
  }
  return map;
 }

 private final static FileFilter DIRECTORIES_ONLY = new FileFilter() {
  public boolean accept(File f) {
   if (f.exists() && f.isDirectory())
    return true;
   else
    return false;
  }
 };

 private final static Comparator<URL> URL_COMPARATOR = new Comparator<URL>() {
  public int compare(URL u1, URL u2) {
   return String.valueOf(u1).compareTo(String.valueOf(u2));
  }
 };

 private final static Comparator<Class<?>> CLASS_COMPARATOR = new Comparator<Class<?>>() {
  public int compare(Class<?> c1, Class<?> c2) {
   return String.valueOf(c1).compareTo(String.valueOf(c2));
  }
 };

 private final void include(String name, File file, Map<URL, String> map) {
  if (!file.exists())
   return;
  if (!file.isDirectory()) {
   // could be a JAR file
   includeJar(file, map);
   return;
  }

  if (name == null)
   name = "";
  else
   name += ".";

  // add subpackages
  File[] dirs = file.listFiles(DIRECTORIES_ONLY);
  for (int i = 0; i < dirs.length; i++) {
   try {
    // add the present package
    map.put(new URL("file://" + dirs[i].getCanonicalPath()), name
      + dirs[i].getName());
   } catch (IOException ioe) {
    return;
   }

   include(name + dirs[i].getName(), dirs[i], map);
  }
 }

 private void includeJar(File file, Map<URL, String> map) {
  if (file.isDirectory())
   return;

  URL jarURL = null;
  JarFile jar = null;
  try {
   jarURL = new URL("file:/" + file.getCanonicalPath());
   jarURL = new URL("jar:" + jarURL.toExternalForm() + "!/");
   JarURLConnection conn = (JarURLConnection) jarURL.openConnection();
   jar = conn.getJarFile();
  } catch (Exception e) {
   // not a JAR or disk I/O error
   // either way, just skip
   return;
  }

  if (jar == null || jarURL == null)
   return;

  // include the jar's "default" package (i.e. jar's root)
  map.put(jarURL, "");

  Enumeration<JarEntry> e = jar.entries();
  while (e.hasMoreElements()) {
   JarEntry entry = e.nextElement();

   if (entry.isDirectory()) {
    if (entry.getName().toUpperCase().equals("META-INF/"))
     continue;

    try {
     map.put(new URL(jarURL.toExternalForm() + entry.getName()),
       packageNameFor(entry));
    } catch (MalformedURLException murl) {
     // whacky entry?
     continue;
    }
   }
  }
 }

 private static String packageNameFor(JarEntry entry) {
  if (entry == null)
   return "";
  String s = entry.getName();
  if (s == null)
   return "";
  if (s.length() == 0)
   return s;
  if (s.startsWith("/"))
   s = s.substring(1, s.length());
  if (s.endsWith("/"))
   s = s.substring(0, s.length() - 1);
  return s.replace('/', '.');
 }

 private final Vector<Class<?>> findSubclasses(Class<?> superClass,
   Map<URL, String> locations) {
  Vector<Class<?>> v = new Vector<Class<?>>();

  Vector<Class<?>> w = null; // new Vector<Class<?>> ();

  Iterator<URL> it = locations.keySet().iterator();
  while (it.hasNext()) {
   URL url = it.next();
   // System.out.println (url + "-->" + locations.get (url));

   w = findSubclasses(url, locations.get(url), superClass);
   if (w != null && (w.size() > 0))
    v.addAll(w);
  }

  return v;
 }

 private final Vector<Class<?>> findSubclasses(URL location,
   String packageName, Class<?> superClass) {
  // System.out.println ("looking in package:" + packageName);
  // System.out.println ("looking for  class:" + superClass);

  synchronized (results) {

   // hash guarantees unique names...
   Map<Class<?>, URL> thisResult = new TreeMap<Class<?>, URL>(
     CLASS_COMPARATOR);
   Vector<Class<?>> v = new Vector<Class<?>>(); // ...but return a
   // vector

   // TODO: double-check for null search class
   String fqcn = classToSearch.getName();

   List<URL> knownLocations = new ArrayList<URL>();
   knownLocations.add(location);
   // TODO: add getResourceLocations() to this list

   // iterate matching package locations...
   for (int loc = 0; loc < knownLocations.size(); loc++) {
    URL url = knownLocations.get(loc);

    // Get a File object for the package
    File directory = new File(url.getFile());

    // System.out.println ("\tlooking in " + directory);

    if (directory.exists()) {
     // Get the list of the files contained in the package
     String[] files = directory.list();
     for (int i = 0; i < files.length; i++) {
      // we are only interested in .class files
      if (files[i].endsWith(".class")) {
       // removes the .class extension
       String classname = files[i].substring(0, files[i]
         .length() - 6);

       // System.out.println ("\t\tchecking file " +
       // classname);

       try {
        Class<?> c = Class.forName(packageName + "."
          + classname);
        if (superClass.isAssignableFrom(c)
          && !fqcn.equals(packageName + "."
            + classname)) {
         thisResult.put(c, url);
        }
       } catch (ClassNotFoundException cnfex) {
        errors.add(cnfex);
        // System.err.println(cnfex);
       } catch (Exception ex) {
        errors.add(ex);
        // System.err.println (ex);
       }
      }
     }
    } else {
     try {
      // It does not work with the filesystem: we must
      // be in the case of a package contained in a jar file.
      JarURLConnection conn = (JarURLConnection) url
        .openConnection();
      // String starts = conn.getEntryName();
      JarFile jarFile = conn.getJarFile();

      // System.out.println ("starts=" + starts);
      // System.out.println ("JarFile=" + jarFile);

      Enumeration<JarEntry> e = jarFile.entries();
      while (e.hasMoreElements()) {
       JarEntry entry = e.nextElement();
       String entryname = entry.getName();

       // System.out.println ("\tconsidering entry: " +
       // entryname);

       if (!entry.isDirectory()
         && entryname.endsWith(".class")) {
        String classname = entryname.substring(0,
          entryname.length() - 6);
        if (classname.startsWith("/"))
         classname = classname.substring(1);
        classname = classname.replace('/', '.');

        // System.out.println ("\t\ttesting classname: "
        // + classname);

        try {
         // TODO: verify this block
         Class<?> c = Class.forName(classname);

         if (superClass.isAssignableFrom(c)
           && !fqcn.equals(classname)) {
          thisResult.put(c, url);
         }
        } catch (ClassNotFoundException cnfex) {
         // that's strange since we're scanning
         // the same classpath the classloader's
         // using... oh, well
         errors.add(cnfex);
        } catch (NoClassDefFoundError ncdfe) {
         // dependency problem... class is
         // unusable anyway, so just ignore it
         errors.add(ncdfe);
        } catch (UnsatisfiedLinkError ule) {
         // another dependency problem... class is
         // unusable anyway, so just ignore it
         errors.add(ule);
        } catch (Exception exception) {
         // unexpected problem
         // System.err.println (ex);
         errors.add(exception);
        } catch (Error error) {
         // lots of things could go wrong
         // that we'll just ignore since
         // they're so rare...
         errors.add(error);
        }
       }
      }
     } catch (IOException ioex) {
      // System.err.println(ioex);
      errors.add(ioex);
     }
    }
   } // while

   // System.out.println ("results = " + thisResult);

   results.putAll(thisResult);

   Iterator<Class<?>> it = thisResult.keySet().iterator();
   while (it.hasNext()) {
    v.add(it.next());
   }
   return v;

  } // synch results
 }

 public static void main(String[] args) {
  args = new String[] { "java.lang.Object" };

  SubClassFinder finder = null;
  Vector<Class<?>> v = null;
  List<Throwable> errors = null;

  if (args.length == 1) {
   finder = new SubClassFinder();
   v = finder.findSubclasses(args[0]);
   errors = finder.getErrors();
  } else {
   System.out
     .println("Usage: java SubClassFinder <fully.qualified.superclass.name>");
   return;
  }

  System.out.println("RESULTS:");
  if (v != null && v.size() > 0) {
   for (Class<?> cls : v) {
    System.out.println(cls
      + " in "
      + ((finder != null) ? String.valueOf(finder
        .getLocationOf(cls)) : "?"));
   }
  } else {
   System.out.println("No subclasses of " + args[0] + " found.");
  }
 }
}

Monday, May 3, 2010

Undersating Java Dynamic Proxy

UNDERSTANDING DYNAMIC PROXIES

Dynamic proxy is a class that is created at runtime implementing a set of given interfaces that you need. ‘Created at runtime’ means that you will not be actually writing and compiling any class that implements the interfaces that you need. Instead you will be creating a class dynamically at runtime using the java.lang.reflect.Proxy class. It is called proxy because it is based on the Proxy Design Pattern.

Dynamic proxy invocation is a well established pattern in Java. Especially, Containers that provide middle-ware services use this pattern extensively to intercept the calls and embed their services.
Like Hibernate, spring and many more technologies will use third party libraries to proxy our instances.

Class you should get to Know:
1) java.lang.reflect.InvocationHandler – When you create proxy objects you would want come code to be executed when any method is called on the Proxy Objects, you write that code to be executed in your implementation of this InvocationHandler Interface.
The Invocation Handler interface has only one method:-
Object invoke(Object proxy, Method method, Object[] args)
Here you can see that There are three arguments passed to this method, lets see what they mean:-
a) The proxy Object – This is nothing but the object on which a method was called which caused this method on the invocation handler to the invoked. You might think ‘why this argument is needed?’ Well, you can use one invocation handler to create more that one proxy which implements different interfaces in which case you would want to know the object on which the method was called.
b) The Method - The method in the proxy Object which was called. Its trivial why this is needed.
c) args Object array - The arguments that were passed to the method that was invoked.
2) java.lang.reflect.Proxy – User to create Proxy Objects which will be implementing all the interfaces that you desire. To create a new Proxy object, you use the static method
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler)
Lets see what the arguments mean:-
a) Loader :- it is the class loader to be used to hold the Dynamic Class that was created. The dynamic class that is created will be extending the Proxy class and implementing the interfaces given by you and the proxy object that is returned will be an instance of this class.
b) Interfaces:- is the list of interfaces that this proxy object should implement
c) Handler:- is the handler which should be invoked when any method is invoked on the proxy object.
 
Example
To learn to use dynamic proxies lets develop a small application where we shall be using dynamic proxy to create a listener that implementsMouseListener and MouseMotionListener and then use this listener instance to print all the mouse events that occur on a JFrame.
 
First let’s create a class called TestHandler that implements InvocationHandler.
public class TestHandler implements InvocationHandler
{
             public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
        {
            System.out.println("Invocation handler invoke method "
+ method.getName() + " with args " + args.toString());
                         return null;
        }
}

In the invoke Method, we are just printing the method name and the arguments
 
 
Now lets write our main function.
 
public static void main(String[] args)
{
    JFrame frame = new Testframe();
    Object proxy = Proxy.newProxyInstance(frame.getClass().getClassLoader(),
    new Class[] { MouseListener.class, MouseMotionListener.class },new TestHandler());
    frame.addMouseListener((MouseListener) proxy);
    frame.addMouseMotionListener((MouseMotionListener) proxy);
    frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
    frame.setSize(400, 400);
    frame.setVisible(true);
}
Lets now inspect the snippet of code that create a proxy instance
Object proxy = Proxy.newProxyInstance(frame.getClass().getClassLoader(),
new Class[] { MouseListener.class, MouseMotionListener.class },new TestHandler());
 
We are creating a new proxy instance for a dynamic class that implements MouseListener and MouseMotionListener . All the method calls in this object will be delegated to the instance of TestHandler that is passed.
 
Program Output:
Invocation handler invoke method mouseEntered with args [Ljava.lang.Object;@1ffb8dc
Invocation handler invoke method mouseMoved with args [Ljava.lang.Object;@1ffbd68
Invocation handler invoke method mouseMoved with args [Ljava.lang.Object;@ec16a4
Invocation handler invoke method mouseMoved with args [Ljava.lang.Object;@1cd8669
Invocation handler invoke method mouseMoved with args [Ljava.lang.Object;@119cca4
Invocation handler invoke method mouseMoved with args [Ljava.lang.Object;@ca2dce

I will try to update this post with more examples later, this is just a quick wrap up