Search code examples
osgieclipse-rcpclassloaderequinoxload-time-weaving

Eclipse RCP: How can I add an inner class when weaving


In my RCP app, I'm using the WeavingHook service to modify a third party class at load time. I'm not able to make any changes to the third party code, so my weaving implementation has to work with it as-is.

I have it working except for one issue. For one of the mods I made, I instantiated an inner class. It has to be an inner class because it depends on an inner class defined in one of the superclasses.

Now when the third party code loads the modified class it doesn't have the file for the inner class on its classpath, which is only available in the plug-in where I implement the weaving. If the third party plug-in declared a buddy policy of registered, I could add an Eclipse-RegisterBuddy directive to the manifest, but that's not the case.

Is there some way with weaving to enable the modified class to be able to access an inner anonymous class that was added through a binary weave?

The only solution I can think of is to copy the inner class file to the bin/ directory of the third party code when the binary weave occurs. Seems like a hack, so I'm hoping there's a better way.


Solution

  • Here's a solution that works, copying the inner class's class file to the bin/ directory of the third party plug-in containing the weave target. If anyone knows a less hacky way to do this, please post a separate answer.

    Edit: Made the original solution more general, discovering all inner class files for the class being woven, and copying them to the weave target plug-in if missing there or if the sizes differ.

    public class Activator extends Plugin {
    
        ...
    
       public static final String CLASS_FILE_ROOT = "bin/";
    
    /**
     * Binary weaving of a class file may result in the addition or modification of
     * inner class files in this plug-in which need to be placed on the classpath
     * of the plug-in containing the weave target. This method compares inner class
     * files for a given class in this plug-in with inner class files in the plug-in
     * that contains the weave target. Any inner class files missing from the weave
     * target or with a different size than the corresponding file in this plug-in
     * are copied to the appropriate package directory in the weave target plug-in.
     * 
     * @param bundle the bundle of the class being woven
     * @param className the fully qualified name of the class being woven
     * @return true if no errors were encountered in copying the inner class files
     */
    private boolean checkInnerClassFileSync(Bundle bundle, String className) {
        className = className.replaceAll("\\.", "/");
        String classDir = CLASS_FILE_ROOT + className.substring(
                0, FilenameUtils.indexOfLastSeparator(className));
        for (String innerClass : getInnerClassNames(classDir, FilenameUtils.getName(className))) {
            try {
                URL srcUrl = getBundle().getEntry(classDir + "/" + innerClass);
                File srcFile = new File(FileLocator.resolve(srcUrl).toURI());
                URL destUrl = bundle.getEntry(classDir);
                File destDir = new File(FileLocator.resolve(destUrl).toURI());
                File destFile = FileUtils.getFile(destDir, innerClass);
                if (srcFile.isFile() && (!destFile.exists() || destFile.length() != srcFile.length())) {
                    FileUtils.copyFile(srcFile, destFile);
                }
            } catch (IOException | URISyntaxException e) {
                logger.log(Level.ERROR, "An error occurred while trying to copy an inner class file to the weave target bundle", e);
                return false;
            }
        }
        return true;
    }
    
    /**
     * Get the class files representing inner classes defined in a specified class and
     * found in a specified directory.
     * 
     * @param dir a sub-directory containing a class file for <code>className</code>
     * and possibly one or more class files representing inner classes defined in
     * <code>className</code>
     * @param className a class whose class file is found in <code>dir</code>
     * @return class files names representing every inner class defined in
     * <code>className</code> and found in <code>dir</code>.
     */
    private String[] getInnerClassNames(String dir, String className) {
        List<String> classNames = new ArrayList<String>();
        try {
            File classDir = new File(FileLocator.resolve(getBundle().getEntry(dir)).toURI());
            for (String fName : classDir.list()) {
                Pattern p = Pattern.compile("^" + className + "\\$.+$");
                Matcher m = p.matcher(fName);
                if (m.matches()) {
                    classNames.add(fName);
                }
            }
            return classNames.toArray(new String[0]);
        } catch (URISyntaxException | IOException e) {
            logger.log(Level.ERROR, "An error occured while scanning for inner class files", e);
            return classNames.toArray(new String[0]);
        }
    }    
    
        ....
    
    }