Reflections library not working when used in an Eclipse plug-in

时光毁灭记忆、已成空白 提交于 2019-11-27 21:36:12
Vlad

I assume you already know how to create bundles (otherwise, check this).

After some debuging and exploration of Reflections API I have realised that the problem is that Reflections simply fails to read OSGi URLs (bundleresource://...) resulting in an exception:

org.reflections.ReflectionsException: could not create Vfs.Dir from url, 
no matching UrlType was found [bundleresource://1009.fwk651584550/]

and this suggestion:

either use fromURL(final URL url, final List<UrlType> urlTypes) 
or use the static setDefaultURLTypes(final List<UrlType> urlTypes) 
or addDefaultURLTypes(UrlType urlType) with your specialized UrlType.

So I believe implementing a UrlType for OSGi (e.g. class BundleUrlType implements UrlType {...}) and registering it like this:

Vfs.addDefaultURLTypes(new BundleUrlType());

should make Reflections API usable from inside a bundle. Reflections dependencies should be added to the Eclipse Plugin project as described here.

This is how my sample MANIFEST.MF looked like after adding needed jars:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: ReflectivePlugin
Bundle-SymbolicName: ReflectivePlugin
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: reflectiveplugin.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: javax.annotation;version="1.0.0",
 org.osgi.framework;version="1.3.0",
 org.osgi.service.log;version="1.3",
 org.osgi.util.tracker;version="1.3.1"
Bundle-ClassPath: .,
 lib/dom4j-1.6.1.jar,
 lib/guava-r08.jar,
 lib/javassist-3.12.1.GA.jar,
 lib/reflections-0.9.5.jar,
 lib/slf4j-api-1.6.1.jar,
 lib/xml-apis-1.0.b2.jar
Export-Package: reflectiveplugin, 
 reflectiveplugin.data

Note: Used Reflections v. 0.9.5

Here's a sample UrlType implementation:

package reflectiveplugin;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;

import org.osgi.framework.Bundle;
import org.reflections.vfs.Vfs;
import org.reflections.vfs.Vfs.Dir;
import org.reflections.vfs.Vfs.File;
import org.reflections.vfs.Vfs.UrlType;

import com.google.common.collect.AbstractIterator;

public class BundleUrlType implements UrlType {

    public static final String BUNDLE_PROTOCOL = "bundleresource";

    private final Bundle bundle;

    public BundleUrlType(Bundle bundle) {
        this.bundle = bundle;
    }

    @Override
    public boolean matches(URL url) {
        return BUNDLE_PROTOCOL.equals(url.getProtocol());
    }

    @Override
    public Dir createDir(URL url) {
        return new BundleDir(bundle, url);
    }

    public class BundleDir implements Dir {

        private String path;
        private final Bundle bundle;

        public BundleDir(Bundle bundle, URL url) {
            this(bundle, url.getPath());
        }

        public BundleDir(Bundle bundle, String p) {
            this.bundle = bundle;
            this.path = p;
            if (path.startsWith(BUNDLE_PROTOCOL + ":")) { 
                path = path.substring((BUNDLE_PROTOCOL + ":").length()); 
            }
        }

        @Override
        public String getPath() {
            return path;
        }

        @Override
        public Iterable<File> getFiles() {
            return new Iterable<Vfs.File>() {
                public Iterator<Vfs.File> iterator() {
                    return new AbstractIterator<Vfs.File>() {
                        final Enumeration<URL> entries = bundle.findEntries(path, "*.class", true);

                        protected Vfs.File computeNext() {
                            return entries.hasMoreElements() ? new BundleFile(BundleDir.this, entries.nextElement()) : endOfData();
                        }
                    };
                }
            };
        }

        @Override
        public void close() { }
    }

    public class BundleFile implements File {

        private final BundleDir dir;
        private final String name;
        private final URL url;

        public BundleFile(BundleDir dir, URL url) {
            this.dir = dir;
            this.url = url;
            String path = url.getFile();
            this.name = path.substring(path.lastIndexOf("/") + 1);
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public String getRelativePath() {
            return getFullPath().substring(dir.getPath().length());
        }

        @Override
        public String getFullPath() {
            return url.getFile();
        }

        @Override
        public InputStream openInputStream() throws IOException {
            return url.openStream();
        }
    }
}

And this is how I create reflections in the Activator class:

private Reflections createReflections(Bundle bundle) {
    Vfs.addDefaultURLTypes(new BundleUrlType(bundle));
    Reflections reflections = new Reflections(new Object[] { "reflectiveplugin.data" });
    return reflections;
}

The last bit is very confusing, but still important: if you run your plugin inside of Eclipse (Run As / OSGi Framework) you have to add also your classes output directory to the Reflections path patterns (i.e. "bin" or "target/classes"). Although, it's not needed for a released plugin (to build a plugin/bundle do "Export"->"Deployable plug-ins and fragments").

Just for the records in case someone else has the same problem. Here a small modification to the answer of Vlad in order to avoid having to add the output directory to the Reflections path patterns. The difference is only in the BundleDir class. It seems to work fine in all my tests:

public class BundleUrlType implements UrlType {

public static final String BUNDLE_PROTOCOL = "bundleresource";

private final Bundle bundle;

public BundleUrlType(Bundle bundle) {
    this.bundle = bundle;
}

@Override
public Dir createDir(URL url) {
    return new BundleDir(bundle, url);
}

@Override
public boolean matches(URL url) {
    return BUNDLE_PROTOCOL.equals(url.getProtocol());
}


public static class BundleDir implements Dir {

    private String path;
    private final Bundle bundle;

    private static String urlPath(Bundle bundle, URL url) {
        try {
            URL resolvedURL = FileLocator.resolve(url);
            String resolvedURLAsfile = resolvedURL.getFile();

            URL bundleRootURL = bundle.getEntry("/");
            URL resolvedBundleRootURL = FileLocator.resolve(bundleRootURL);
            String resolvedBundleRootURLAsfile = resolvedBundleRootURL.getFile();
            return("/"+resolvedURLAsfile.substring(resolvedBundleRootURLAsfile.length()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public BundleDir(Bundle bundle, URL url) {
        //this(bundle, url.getPath());
        this(bundle, urlPath(bundle,url));
    }

    public BundleDir(Bundle bundle, String p) {
        this.bundle = bundle;
        this.path = p;
        if (path.startsWith(BUNDLE_PROTOCOL + ":")) { 
            path = path.substring((BUNDLE_PROTOCOL + ":").length()); 
        }
    }

    @Override
    public String getPath() {
        return path;
    }

    @Override
    public Iterable<File> getFiles() {
        return new Iterable<Vfs.File>() {
            public Iterator<Vfs.File> iterator() {
                return new AbstractIterator<Vfs.File>() {
                    final Enumeration<URL> entries = bundle.findEntries(path, "*.class", true);

                    protected Vfs.File computeNext() {
                        return entries.hasMoreElements() ? new BundleFile(BundleDir.this, entries.nextElement()) : endOfData();
                    }
                };
            }
        };
    }

    @Override
    public void close() { }
}


public static class BundleFile implements File {

    private final BundleDir dir;
    private final String name;
    private final URL url;

    public BundleFile(BundleDir dir, URL url) {
        this.dir = dir;
        this.url = url;
        String path = url.getFile();
        this.name = path.substring(path.lastIndexOf("/") + 1);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getRelativePath() {
        return getFullPath().substring(dir.getPath().length());
    }

    @Override
    public String getFullPath() {
        return url.getFile();
    }

    @Override
    public InputStream openInputStream() throws IOException {
        return url.openStream();
    }
}
}

Eclipse is build on top of OSGi and you are up against OSGi class loading... and that is not an easy battle to win.

Have a look at this article of Neil Bartlett: OSGi Readiness — Loading Classes. Also you can google for "OSGi buddy policy".

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!