2009年5月21日 星期四

interface finder


/*
 * @(#)InterfaceFinder.java v0.8, 2009/05/06-2020/2/8
 *
 *   Search the class path for classes which implement a specific interface
 *
 *   Usage: java -cp jar_file;class_folder;. InterfaceFinder interface_name_to_search
 */

import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import java.util.Stack;
import java.util.HashSet;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
  The interface finder is used to find all classes
  implementing a specific interface.
  The location in search includes all the subdirectories
  in the class path. Typical usage is as follows.

    List<Class> InterfaceFinder.getAvailableInterfaces("java.util.List");

 * @since 0.8
 * @version 0.8, 2009/05/06-2020/2/8
 * @author Seke Wei
*/
public class InterfaceFinder
{
  public static final String version="InterfaceFinder.java v0.8 2020/02/08";

  /**
    a file lister which uses the depth first strategy
    to traverse all files under a given directory.
  */
  public static class FileLister implements Enumeration
  {
    boolean hasMore;
    Stack<File> fstack;

    /**
      constructor for file lister.
      @param dir the root directory for search.
    */
    public FileLister(File dir)
    {
      fstack = new Stack<>();
      if(dir.isDirectory())
        fstack.push(dir);
      hasMore = true;
    }

    /**
      check if there is still file available for listing.
      @return true for elements available and false for otherwise.
    */
    public boolean hasMoreElements()
    {
      return hasMore;
    }

    /**
      get the next available file.
      directories are skipped.
      @return the file.
    */
    public File nextElement()
    {
      File next = null;

      while(fstack.empty()==false)
      {
        next = (File) fstack.pop();
        if(next.isFile())
          break;

        for(File f : next.listFiles())
          fstack.push(f);
      }

      if(fstack.empty()) hasMore=false;

      return next;
    }
  }

  /**
    get classes from a jar file which implements an interface.
    @param jarFileName the jar filename.
    @param iface the interface in search.
    @return the set of classes in jar implementing the interface.
  */
  public static List<Class> getClassesFromJARFile(String jarFileName, Class iface)
  {
    final List<Class> classes = new Vector<>();
    JarInputStream jarFile = null;
    String className = "";
    try
    {
      jarFile = new JarInputStream(new FileInputStream(jarFileName));
      JarEntry jarEntry;
      while(true)
      {
        jarEntry = jarFile.getNextJarEntry();
        if(jarEntry == null) break;

        className = jarEntry.toString();

        //System.err.println(className);

        if(className.endsWith(".class") && className.indexOf("$") < 0 )
        {
          //extractClassFromJar(jar, packageName, classes, jarEntry)
          className = className.replace('/','.');
          className = className.substring(0, className.length() - ".class".length());
          Class cl = Class.forName(className);
          if(iface.isAssignableFrom(cl))
          {
            //System.err.println("\t"+className+" can be assigned to "+iface);
            classes.add(cl);
          }
        }
      }
      jarFile.close();
    }
    catch (IOException ioe)
    {
      System.err.println("Unable to get Jar input stream from '"+jarFileName+"'"+ioe);
    }
    catch (ClassNotFoundException cnfe)
    {
      System.err.println("unable to find class named " + className.replace('/', '.') + "' within jar '" + jarFileName + "'"+cnfe);
    }

    return classes;
  }

  /**
    get classes from a root directory which implements an interface.
    @param dir the root directory.
    @param iface the interface in search.
    @return the set of classes in jar implementing the interface.
    @throws IOException when there is a file open error.
  */
  public static List<Class> getClassesFromDirectory(File dir, Class iface)
  {
    List<Class> classes = new Vector<>();

    FileLister lister = new FileLister(dir);
    while(lister.hasMoreElements())
    {
      File f = lister.nextElement();
      String className = f.toString();

      //System.err.println(className);

      if(className.endsWith(".class") && className.indexOf("$") < 0 )
      {
        className = className.replace(File.separatorChar,'.');
        className = className.substring(0, className.length() - ".class".length());
        while(className.charAt(0)=='.') className = className.substring(1);
        //System.err.println("\t"+className);

        try
        {
          Class cl = Class.forName(className);
          if(iface.isAssignableFrom(cl))
          {
            //System.err.println("\t"+className+" can be assigned to "+iface);
            classes.add(cl);
          }
        }
        catch(ClassNotFoundException cnfe)
        {}
      }
    }

    return classes;
  }

  /**
    get classes from the class path which implements an interface.
    @param iface the interface in search.
    @return the set of classes in jar implementing the interface.
  */
  public static List<Class> getAvailableClassesOfInterface(String iface)
  {
    List<Class> result = null;

    try
    {
      Class iface_class = Class.forName(iface);
      result = getAvailableClassesOfInterface(iface_class);
    }
    catch(ClassNotFoundException cnfe) {}

    return result;
  }

  /**
    get classes from the class path which implements an interface.
    @param iface the interface in search.
    @return the set of classes in jar implementing the interface.
  */
  public static List<Class> getAvailableClassesOfInterface(Class iface)
  {
    String cp = System.getProperty("java.class.path");
    //System.out.println("java.class.path = " + cp);

    List<Class> result = new Vector<>();

    // semicolon (Windows) or colon (Unix) by System.getProperty("path.separator")
    String separator = System.getProperty("path.separator");
    for(String p : cp.split(separator))
    {
      File f = new File(p);
      if(f.isDirectory())
        result.addAll(getClassesFromDirectory(f, iface));
      else if(f.isFile() && p.endsWith("jar"))
        result.addAll(getClassesFromJARFile(p, iface));
    }

    HashSet<Class> set = new HashSet<>(result);

    result.clear();

    for(Class c : set)
    {
      //System.err.println(c);
      result.add(c);
    }

    return result;
  }

  /**
     InterfaceFinder.java
 
        Search the class path for classes which implement a specific interface

     Usage: java -cp jar_file;class_folder;. InterfaceFinder interface_name_to_search

     Example:
     > javac InterfaceFinder.java
     > java -cp rt.jar;. InterfaceFinder java.util.List
     class javax.management.relation.RoleUnresolvedList
     class java.util.AbstractList
     class java.util.SubList
     class java.util.concurrent.CopyOnWriteArrayList
     class javax.management.AttributeList
     class java.util.AbstractSequentialList
     class java.util.RandomAccessSubList
     class java.util.ArrayList
     class java.util.Vector
     class java.util.Stack
     class java.util.LinkedList
     class javax.management.relation.RoleList
     interface java.util.List

     Note that since JDK 9 there is no rt.jar for testing
  */
  public static void main(String args[])
  {
    String iface = "java.util.List";
    if(args.length > 0)
      iface = args[0];

    List<Class> classes =
      getAvailableClassesOfInterface(iface);

    for(Class c : classes)
    {
      System.err.println(c);
    }
  }
}