Previous Page
Next Page

20.9. Plug-in ClassLoaders

Most of the time you can easily ignore ClassLoaders, knowing that as long as your classpath is corrector in this case, the dependency declaration in the plug-in manifest (see Section 2.3.1, The Plug-in manifests, on page 71)class loading will happen automatically, without intervention. But what if you want to load classes that are not known when a plug-in is compiled? Information about code developed by the user in the workspace is accessible via the JDT interfaces such as ICompilationUnit, IType, and IMethod; however, it is not normally on a plug-in's classpath and thus cannot be executed. Normally, this is a good thing, because code under development can throw exceptions, or under rare circumstances, crash Eclipse without any warning.

The Eclipse debugger (see Section 1.10, Introduction to Debugging, on page 58) executes user-developed code in a separate VM to avoid these problems, but it is a heavyweight, involving the overhead of launching a separate VM and communicating with it to obtain results. If you need a quick way to execute user-developed code in the same VM as Eclipse and are willing to accept the risks involved in doing so, then you need to write a ClassLoader.

To illustrate and test the ClassLoader, you first declare a new action in the "Favorites ActionSet" of the Favorites plug-in manifest to appear in the top-level Favorites menu (see Section 6.2.3, Defining a menu item and toolbar button, on page 212).

<action
   class="com.qualityeclipse.favorites.actions.
          ExecuteMethodActionDelegate"
   label="Execute method"
   menubarPath="com.qualityeclipse.favorites.workbenchMenu/content"
   id="com.qualityeclipse.favorites.executeMethod"/>
</actionSet>

The ExecuteMethodActionDelegate obtains the selected Java method, loads the type declaring the method, instantiates a new instance of that type, and prints the result of executing the method to the Console view. For simplicity, the selected method must be public with no arguments.

package com.qualityeclipse.favorites.actions;

import ...

public class ExecuteMethodActionDelegate
   implements IWorkbenchWindowActionDelegate
{
   IStructuredSelection selection;

   public void init(IWorkbenchWindow window) {
   }

   public void selectionChanged(IAction action, ISelection selection)
   {
      this.selection = selection instanceof IStructuredSelection
         ? (IStructuredSelection) selection : null;
   }

   public void run(IAction action) {
      System.out.println(executeMethod());
   }

   public void dispose() {
   }
}

The run() method calls the executeMethod() to perform the actual operation and return a message. This message is then appended to the system console.

private String executeMethod() {
   if (selection == null || selection.isEmpty())
      return "Nothing selected";
   Object element = selection.getFirstElement();
   if (!(element instanceof IMethod))
      return "No Java method selected";
   IMethod method = (IMethod) element;
   try {
       if (!Flags.isPublic(method.getFlags()))
          return "Java method must be public";
   }
   catch (JavaModelException e) {
      FavoritesLog.logError(e);
      return "Failed to get method modifiers";
   }
   if (method.getParameterTypes().length != 0)
       return "Java method must have zero arguments";
   IType type = method.getDeclaringType();
   String typeName = type.getFullyQualifiedName();
   ClassLoader loader =
      new ProjectClassLoader(type.getJavaProject());
   Class c;
   try {
      c = loader.loadClass(typeName);
   }
   catch (ClassNotFoundException e) {
      FavoritesLog.logError(e);
      return "Failed to load: " + typeName;
   }
   Object target;
   try {
      target = c.newInstance();
   }
   catch (Exception e) {
      FavoritesLog.logError(e);
      return "Failed to instantiate: " + typeName;
   }
   Method m;
   try {
      m = c.getMethod(method.getElementName(), new Class[] {});
   }
   catch (Exception e) {
      FavoritesLog.logError(e);
      return "Failed to find method: " + method.getElementName();
   }
   Object result;
   try {
      result = m.invoke(target, new Object[] {});
   }
   catch (Exception e) {
      FavoritesLog.logError(e);
      return "Failed to invoke method: " + method.getElementName();
   }
   return "Return value = " + result;
}

The ExecuteMethodActionDelegate uses ProjectClassLoader to load the selected class into the Favorites plug-in to be executed. This ClassLoader locates the class file using the project's Java build path, reads the class file using standard java.io, and creates the class in memory using the superclass' defineClass() method. It is not complete as it only loads source-based classes; loading classes from a jar file or reference project is left as an exercise for the reader.

package com.qualityeclipse.favorites.util;

import ...

public class ProjectClassLoader extends ClassLoader
{
    private IJavaProject project;

    public ProjectClassLoader(IJavaProject project) {
       if (project == null || !project.exists() || !project.isOpen())
          throw new IllegalArgumentException("Invalid project");
       this.project = project;
    }

    protected Class findClass(String name)
        throws ClassNotFoundException
    {
        byte[] buf = readBytes(name);
        if (buf == null)
           throw new ClassNotFoundException(name);
        return defineClass(name, buf, 0, buf.length);
    }

    private byte[] readBytes(String name) {
       IPath rootLoc = ResourcesPlugin
          .getWorkspace().getRoot().getLocation();
       Path relativePathToClassFile =
          new Path(name.replace(".","/") + ".class");
       IClasspathEntry[] entries;
       IPath outputLocation;
       try {
          entries = project.getResolvedClasspath(true);
          outputLocation =
             rootLoc.append(project.getOutputLocation());
       }
       catch (JavaModelException e) {
          FavoritesLog.logError(e);
          return null;
       }
       for (int i = 0; i < entries.length; i++) {
          IClasspathEntry entry = entries[i];
          switch (entry.getEntryKind()) {

             case IClasspathEntry.CPE_SOURCE :
                IPath path = entry.getOutputLocation();
                if (path != null)
                   path = rootLoc.append(path);
                else
                   path = outputLocation;
                path = path.append(relativePathToClassFile);
                byte[] buf = readBytes(path.toFile());
                if (buf != null)
                   return buf;
                break;
             case IClasspathEntry.CPE_LIBRARY:
             case IClasspathEntry.CPE_PROJECT:
                // Handle other entry types here.
                break;

             default :
                break;
          }
       }
       return null;
   }

   private static byte[] readBytes(File file) {
      if (file == null || !file.exists())
         return null;
      InputStream stream = null;
      try {
         stream =
            new BufferedInputStream(
               new FileInputStream(file));
         int size = 0;
         byte[] buf = new byte[10];
         while (true) {
            int count =
               stream.read(buf, size, buf.length - size);
            if (count < 0)
               break;
            size += count;
            if (size < buf.length)
               break;
            byte[] newBuf = new byte[size + 10];
            System.arraycopy(buf, 0, newBuf, 0, size);
            buf = newBuf;
         }
         byte[] result = new byte[size];
         System.arraycopy(buf, 0, result, 0, size);
         return result;
      }
      catch (Exception e) {
         FavoritesLog.logError(e);
         return null;
      }
      finally {
         try {
            if (stream != null)
               stream.close();
         }
         catch (IOException e) {
            FavoritesLog.logError(e);
            return null;
         }
      }
   }
}


Previous Page
Next Page