]> source.dussan.org Git - pf4j.git/commitdiff
use META-INF/services storage by default
authorDecebal Suiu <decebal.suiu@gmail.com>
Mon, 4 Jan 2016 07:42:17 +0000 (09:42 +0200)
committerDecebal Suiu <decebal.suiu@gmail.com>
Mon, 4 Jan 2016 07:42:17 +0000 (09:42 +0200)
pf4j/src/main/java/ro/fortsoft/pf4j/AbstractExtensionFinder.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java
pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionsIndexer.java [deleted file]
pf4j/src/main/java/ro/fortsoft/pf4j/LegacyExtensionFinder.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/ServiceProviderExtensionFinder.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionAnnotationProcessor.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionStorage.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/processor/LegacyExtensionStorage.java [new file with mode: 0644]
pf4j/src/main/java/ro/fortsoft/pf4j/processor/ServiceProviderExtensionStorage.java [new file with mode: 0644]
pf4j/src/main/resources/META-INF/services/javax.annotation.processing.Processor

diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractExtensionFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractExtensionFinder.java
new file mode 100644 (file)
index 0000000..12f6526
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2015 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ro.fortsoft.pf4j;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Decebal Suiu
+ */
+public abstract class AbstractExtensionFinder implements ExtensionFinder, PluginStateListener {
+
+    protected static final Logger log = LoggerFactory.getLogger(AbstractExtensionFinder.class);
+
+    protected PluginManager pluginManager;
+    protected volatile Map<String, Set<String>> entries; // cache by pluginId
+
+    public AbstractExtensionFinder(PluginManager pluginManager) {
+        this.pluginManager = pluginManager;
+    }
+
+    public abstract Map<String, Set<String>> readPluginsStorages();
+
+    public abstract Map<String, Set<String>> readClasspathStorages();
+
+    @Override
+       public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
+        log.debug("Checking extension point '{}'", type.getName());
+        if (!isExtensionPoint(type)) {
+            log.warn("'{}' is not an extension point", type.getName());
+
+            return Collections.emptyList(); // or return null ?!
+        }
+
+               log.debug("Finding extensions for extension point '{}'", type.getName());
+        Map<String, Set<String>> entries = getEntries();
+
+        List<ExtensionWrapper<T>> result = new ArrayList<>();
+        for (Map.Entry<String, Set<String>> entry : entries.entrySet()) {
+            String pluginId = entry.getKey();
+
+            if (pluginId != null) {
+                PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
+                if (PluginState.STARTED != pluginWrapper.getPluginState()) {
+                    continue;
+                }
+            }
+
+            for (String className : entry.getValue()) {
+                try {
+                    ClassLoader classLoader;
+                    if (pluginId != null) {
+                        classLoader = pluginManager.getPluginClassLoader(pluginId);
+                    } else {
+                        classLoader = getClass().getClassLoader();
+                    }
+                    log.debug("Loading class '{}' using class loader '{}'", className, classLoader);
+                    Class<?> extensionClass = classLoader.loadClass(className);
+
+                    log.debug("Checking extension type '{}'", className);
+                    if (type.isAssignableFrom(extensionClass) && extensionClass.isAnnotationPresent(Extension.class)) {
+                        Extension extension = extensionClass.getAnnotation(Extension.class);
+                        ExtensionDescriptor descriptor = new ExtensionDescriptor();
+                        descriptor.setOrdinal(extension.ordinal());
+                        descriptor.setExtensionClass(extensionClass);
+
+                        ExtensionWrapper extensionWrapper = new ExtensionWrapper<>(descriptor);
+                        extensionWrapper.setExtensionFactory(pluginManager.getExtensionFactory());
+                        result.add(extensionWrapper);
+                        log.debug("Added extension '{}' with ordinal {}", className, extension.ordinal());
+                    } else {
+                        log.debug("'{}' is not an extension for extension point '{}'", className, type.getName());
+                    }
+                } catch (ClassNotFoundException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+        }
+
+        if (entries.isEmpty()) {
+               log.debug("No extensions found for extension point '{}'", type.getName());
+        } else {
+               log.debug("Found {} extensions for extension point '{}'", result.size(), type.getName());
+        }
+
+        // sort by "ordinal" property
+        Collections.sort(result);
+
+               return result;
+       }
+
+    @Override
+    public Set<String> findClassNames(String pluginId) {
+        return getEntries().get(pluginId);
+    }
+
+    @Override
+       public void pluginStateChanged(PluginStateEvent event) {
+        // TODO optimize (do only for some transitions)
+        // clear cache
+        entries = null;
+    }
+
+    private Map<String, Set<String>> readStorages() {
+        Map<String, Set<String>> result = new LinkedHashMap<>();
+
+        result.putAll(readClasspathStorages());
+        result.putAll(readPluginsStorages());
+
+        return result;
+    }
+
+    private boolean isExtensionPoint(Class<?> type) {
+        return ExtensionPoint.class.isAssignableFrom(type);
+    }
+
+    private Map<String, Set<String>> getEntries() {
+        if (entries == null) {
+            entries = readStorages();
+        }
+
+        return entries;
+    }
+
+}
index 9ff571a8183031ca86201f44789efb90ac0605e4..cf8c87bedd49439558edde4a8ab36bec7631b40c 100644 (file)
  */
 package ro.fortsoft.pf4j;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
  * The default implementation for ExtensionFinder.
- * All extensions declared in a plugin are indexed in a file "META-INF/extensions.idx".
- * This class lookup extensions in all extensions index files "META-INF/extensions.idx".
+ * It's a compound ExtensionFinder.
  *
  * @author Decebal Suiu
  */
 public class DefaultExtensionFinder implements ExtensionFinder, PluginStateListener {
 
-       private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFinder.class);
-
-    protected PluginManager pluginManager;
-    protected volatile Map<String, Set<String>> entries; // cache by pluginId
+    protected List<ExtensionFinder> finders = new ArrayList<>();
 
        public DefaultExtensionFinder(PluginManager pluginManager) {
-        this.pluginManager = pluginManager;
-       }
+        addDefaults(pluginManager);
+    }
 
     @Override
-       public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
-        log.debug("Checking extension point '{}'", type.getName());
-        if (!isExtensionPoint(type)) {
-            log.warn("'{}' is not an extension point", type.getName());
-
-            return Collections.emptyList(); // or return null ?!
-        }
-
-               log.debug("Finding extensions for extension point '{}'", type.getName());
-        Map<String, Set<String>> entries = getEntries();
-
-        List<ExtensionWrapper<T>> result = new ArrayList<>();
-        for (Map.Entry<String, Set<String>> entry : entries.entrySet()) {
-            String pluginId = entry.getKey();
-
-            if (pluginId != null) {
-                PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
-                if (PluginState.STARTED != pluginWrapper.getPluginState()) {
-                    continue;
-                }
-            }
-
-            for (String className : entry.getValue()) {
-                try {
-                    ClassLoader classLoader;
-                    if (pluginId != null) {
-                        classLoader = pluginManager.getPluginClassLoader(pluginId);
-                    } else {
-                        classLoader = getClass().getClassLoader();
-                    }
-                    log.debug("Loading class '{}' using class loader '{}'", className, classLoader);
-                    Class<?> extensionClass = classLoader.loadClass(className);
-
-                    log.debug("Checking extension type '{}'", className);
-                    if (type.isAssignableFrom(extensionClass) && extensionClass.isAnnotationPresent(Extension.class)) {
-                        Extension extension = extensionClass.getAnnotation(Extension.class);
-                        ExtensionDescriptor descriptor = new ExtensionDescriptor();
-                        descriptor.setOrdinal(extension.ordinal());
-                        descriptor.setExtensionClass(extensionClass);
-
-                        ExtensionWrapper extensionWrapper = new ExtensionWrapper<>(descriptor);
-                        extensionWrapper.setExtensionFactory(pluginManager.getExtensionFactory());
-                        result.add(extensionWrapper);
-                        log.debug("Added extension '{}' with ordinal {}", className, extension.ordinal());
-                    } else {
-                        log.debug("'{}' is not an extension for extension point '{}'", className, type.getName());
-                    }
-                } catch (ClassNotFoundException e) {
-                    log.error(e.getMessage(), e);
-                }
-            }
-        }
-
-        if (entries.isEmpty()) {
-               log.debug("No extensions found for extension point '{}'", type.getName());
-        } else {
-               log.debug("Found {} extensions for extension point '{}'", result.size(), type.getName());
+    public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
+        List<ExtensionWrapper<T>> extensions = new ArrayList<>();
+        for (ExtensionFinder finder : finders) {
+            extensions.addAll(finder.find(type));
         }
 
-        // sort by "ordinal" property
-        Collections.sort(result);
-
-               return result;
-       }
-
-    @Override
-    public Set<String> findClassNames(String pluginId) {
-        return getEntries().get(pluginId);
+        return extensions;
     }
 
     @Override
-       public void pluginStateChanged(PluginStateEvent event) {
-        // TODO optimize (do only for some transitions)
-        // clear cache
-        entries = null;
-    }
-
-    protected Map<String, Set<String>> readIndexFiles() {
-        Map<String, Set<String>> result = new LinkedHashMap<>();
-
-        result.putAll(readClasspathIndexFiles());
-        result.putAll(readPluginsIndexFiles());
-
-        return result;
-    }
-
-    private Map<String, Set<String>> readClasspathIndexFiles() {
-        log.debug("Reading extensions index files from classpath");
-
-        Map<String, Set<String>> result = new LinkedHashMap<>();
-
-        Set<String> bucket = new HashSet<>();
-        try {
-            Enumeration<URL> urls = getClass().getClassLoader().getResources(ExtensionsIndexer.EXTENSIONS_RESOURCE);
-            while (urls.hasMoreElements()) {
-                URL url = urls.nextElement();
-                log.debug("Read '{}'", url.getFile());
-                Reader reader = new InputStreamReader(url.openStream(), "UTF-8");
-                ExtensionsIndexer.readIndex(reader, bucket);
-            }
-
-            if (bucket.isEmpty()) {
-                log.debug("No extensions found");
-            } else {
-                log.debug("Found possible {} extensions:", bucket.size());
-                for (String entry : bucket) {
-                    log.debug("   " + entry);
-                }
-            }
-
-            result.put(null, bucket);
-        } catch (IOException e) {
-            log.error(e.getMessage(), e);
+    public Set<String> findClassNames(String pluginId) {
+        Set<String> classNames = new HashSet<>();
+        for (ExtensionFinder finder : finders) {
+            classNames.addAll(finder.findClassNames(pluginId));
         }
 
-        return result;
+        return classNames;
     }
 
-    private Map<String, Set<String>> readPluginsIndexFiles() {
-        log.debug("Reading extensions index files from plugins");
-
-        Map<String, Set<String>> result = new LinkedHashMap<>();
-
-        List<PluginWrapper> plugins = pluginManager.getPlugins();
-        for (PluginWrapper plugin : plugins) {
-            String pluginId = plugin.getDescriptor().getPluginId();
-            log.debug("Reading extensions index file for plugin '{}'", pluginId);
-            Set<String> bucket = new HashSet<>();
-
-            try {
-                URL url = plugin.getPluginClassLoader().getResource(ExtensionsIndexer.EXTENSIONS_RESOURCE);
-                if (url != null) {
-                    log.debug("Read '{}'", url.getFile());
-                    Reader reader = new InputStreamReader(url.openStream(), "UTF-8");
-                    ExtensionsIndexer.readIndex(reader, bucket);
-                } else {
-                    log.debug("Cannot find '{}'", ExtensionsIndexer.EXTENSIONS_RESOURCE);
-                }
-
-                if (bucket.isEmpty()) {
-                    log.debug("No extensions found");
-                } else {
-                    log.debug("Found possible {} extensions:", bucket.size());
-                    for (String entry : bucket) {
-                        log.debug("   " + entry);
-                    }
-                }
-
-                result.put(pluginId, bucket);
-            } catch (IOException e) {
-                log.error(e.getMessage(), e);
+    @Override
+    public void pluginStateChanged(PluginStateEvent event) {
+        for (ExtensionFinder finder : finders) {
+            if (finder instanceof PluginStateListener) {
+                ((PluginStateListener) finder).pluginStateChanged(event);
             }
         }
-
-        return result;
-    }
-
-    private boolean isExtensionPoint(Class<?> type) {
-        return ExtensionPoint.class.isAssignableFrom(type);
     }
 
-    private Map<String, Set<String>> getEntries() {
-        if (entries == null) {
-            entries = readIndexFiles();
-        }
-
-        return entries;
+    protected void addDefaults(PluginManager pluginManager) {
+        finders.add(new ServiceProviderExtensionFinder(pluginManager));
+        finders.add(new LegacyExtensionFinder(pluginManager));
     }
 
 }
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionsIndexer.java b/pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionsIndexer.java
deleted file mode 100644 (file)
index d06dbba..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2013 Decebal Suiu
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ro.fortsoft.pf4j;
-
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.Reader;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.RoundEnvironment;
-import javax.lang.model.SourceVersion;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
-import javax.tools.Diagnostic.Kind;
-import javax.tools.FileObject;
-import javax.tools.StandardLocation;
-
-/**
- * @author Decebal Suiu
- */
-public class ExtensionsIndexer extends AbstractProcessor {
-
-       public static final String EXTENSIONS_RESOURCE = "META-INF/extensions.idx";
-
-       private List<TypeElement> extensions = new ArrayList<>();
-
-       @Override
-       public SourceVersion getSupportedSourceVersion() {
-               return SourceVersion.latest();
-       }
-
-       @Override
-       public Set<String> getSupportedAnnotationTypes() {
-               Set<String> annotationTypes = new HashSet<>();
-        annotationTypes.add(Extension.class.getName());
-
-        return annotationTypes;
-       }
-
-       @Override
-       public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
-               if (roundEnv.processingOver()) {
-            return false;
-        }
-
-               for (Element element : roundEnv.getElementsAnnotatedWith(Extension.class)) {
-                       if (!(element instanceof TypeElement)) {
-                               continue;
-                       }
-
-                       TypeElement typeElement = (TypeElement) element;
-                       String message = "Extension found in " + processingEnv.getElementUtils().getBinaryName(typeElement).toString();
-                       processingEnv.getMessager().printMessage(Kind.NOTE, message);
-                       extensions.add(typeElement);
-        }
-
-               /*
-               if (!roundEnv.processingOver()) {
-                       return false;
-               }
-               */
-
-               write();
-
-               return false;
-//             return true; // no further processing of this annotation type
-       }
-
-       private void write() {
-               Set<String> entries = new HashSet<>();
-               for (TypeElement typeElement : extensions) {
-                       entries.add(processingEnv.getElementUtils().getBinaryName(typeElement).toString());
-               }
-
-               read(entries); // read old entries
-               write(entries); // write entries
-       }
-
-       private void write(Set<String> entries) {
-               try {
-                       FileObject file = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE);
-                       Writer writer = file.openWriter();
-                       for (String entry : entries) {
-                               writer.write(entry);
-                               writer.write("\n");
-                       }
-                       writer.close();
-               } catch (FileNotFoundException e) {
-                       // it's the first time, create the file
-               } catch (IOException e) {
-                       processingEnv.getMessager().printMessage(Kind.ERROR, e.toString());
-               }
-       }
-
-       private void read(Set<String> entries) {
-               try {
-                       FileObject file = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE);
-                       readIndex(file.openReader(true), entries);
-               } catch (FileNotFoundException e) {
-               } catch (IOException e) {
-                       // thrown by Eclipse JDT when not found
-               } catch (UnsupportedOperationException e) {
-                       // java6 does not support reading old index files
-               }
-       }
-
-       public static void readIndex(Reader reader, Set<String> entries) throws IOException {
-               BufferedReader bufferedReader = new BufferedReader(reader);
-
-               String line;
-               while ((line = bufferedReader.readLine()) != null) {
-                       entries.add(line);
-               }
-
-               reader.close();
-       }
-
-}
-
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/LegacyExtensionFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/LegacyExtensionFinder.java
new file mode 100644 (file)
index 0000000..4937cda
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2013 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ro.fortsoft.pf4j;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ro.fortsoft.pf4j.processor.LegacyExtensionStorage;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * All extensions declared in a plugin are indexed in a file "META-INF/extensions.idx".
+ * This class lookup extensions in all extensions index files "META-INF/extensions.idx".
+ *
+ * @author Decebal Suiu
+ */
+public class LegacyExtensionFinder extends AbstractExtensionFinder {
+
+       private static final Logger log = LoggerFactory.getLogger(LegacyExtensionFinder.class);
+
+       public LegacyExtensionFinder(PluginManager pluginManager) {
+        super(pluginManager);
+       }
+
+    @Override
+    public Map<String, Set<String>> readClasspathStorages() {
+        log.debug("Reading extensions storages from classpath");
+
+        Map<String, Set<String>> result = new LinkedHashMap<>();
+
+        Set<String> bucket = new HashSet<>();
+        try {
+            Enumeration<URL> urls = getClass().getClassLoader().getResources(getExtensionsResource());
+            while (urls.hasMoreElements()) {
+                URL url = urls.nextElement();
+                log.debug("Read '{}'", url.getFile());
+                Reader reader = new InputStreamReader(url.openStream(), "UTF-8");
+                LegacyExtensionStorage.read(reader, bucket);
+            }
+
+            if (bucket.isEmpty()) {
+                log.debug("No extensions found");
+            } else {
+                log.debug("Found possible {} extensions:", bucket.size());
+                for (String entry : bucket) {
+                    log.debug("   " + entry);
+                }
+            }
+
+            result.put(null, bucket);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public Map<String, Set<String>> readPluginsStorages() {
+        log.debug("Reading extensions storages from plugins");
+
+        Map<String, Set<String>> result = new LinkedHashMap<>();
+
+        List<PluginWrapper> plugins = pluginManager.getPlugins();
+        for (PluginWrapper plugin : plugins) {
+            String pluginId = plugin.getDescriptor().getPluginId();
+            log.debug("Reading extensions storage for plugin '{}'", pluginId);
+            Set<String> bucket = new HashSet<>();
+
+            try {
+                URL url = plugin.getPluginClassLoader().getResource(getExtensionsResource());
+                if (url != null) {
+                    log.debug("Read '{}'", url.getFile());
+                    Reader reader = new InputStreamReader(url.openStream(), "UTF-8");
+                    LegacyExtensionStorage.read(reader, bucket);
+                } else {
+                    log.debug("Cannot find '{}'", getExtensionsResource());
+                }
+
+                if (bucket.isEmpty()) {
+                    log.debug("No extensions found");
+                } else {
+                    log.debug("Found possible {} extensions:", bucket.size());
+                    for (String entry : bucket) {
+                        log.debug("   " + entry);
+                    }
+                }
+
+                result.put(pluginId, bucket);
+            } catch (IOException e) {
+                log.error(e.getMessage(), e);
+            }
+        }
+
+        return result;
+    }
+
+    private static String getExtensionsResource() {
+        return LegacyExtensionStorage.EXTENSIONS_RESOURCE;
+    }
+
+}
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/ServiceProviderExtensionFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/ServiceProviderExtensionFinder.java
new file mode 100644 (file)
index 0000000..e6501eb
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2015 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ro.fortsoft.pf4j;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ro.fortsoft.pf4j.processor.ServiceProviderExtensionStorage;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The ServiceLoader base implementation for ExtensionFinder.
+ * This class lookup extensions in all extensions index files "META-INF/services".
+ *
+ * @author Decebal Suiu
+ */
+public class ServiceProviderExtensionFinder extends AbstractExtensionFinder {
+
+    private static final Logger log = LoggerFactory.getLogger(ServiceProviderExtensionFinder.class);
+
+    public ServiceProviderExtensionFinder(PluginManager pluginManager) {
+        super(pluginManager);
+    }
+
+    @Override
+    public Map<String, Set<String>> readClasspathStorages() {
+        log.debug("Reading extensions storages from classpath");
+
+        Map<String, Set<String>> result = new LinkedHashMap<>();
+
+        Set<String> bucket = new HashSet<>();
+        try {
+            URL url = getClass().getClassLoader().getResource(getExtensionsResource());
+            if (url != null) {
+                File[] files = new File(url.toURI()).listFiles();
+                if (files != null) {
+                    for (File file : files) {
+                        log.debug("Read '{}'", file);
+                        Reader reader = new FileReader(file);
+                        ServiceProviderExtensionStorage.read(reader, bucket);
+                    }
+                }
+            }
+
+            if (bucket.isEmpty()) {
+                log.debug("No extensions found");
+            } else {
+                log.debug("Found possible {} extensions:", bucket.size());
+                for (String entry : bucket) {
+                    log.debug("   " + entry);
+                }
+            }
+
+            result.put(null, bucket);
+        } catch (IOException | URISyntaxException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public Map<String, Set<String>> readPluginsStorages() {
+        log.debug("Reading extensions storages from plugins");
+
+        Map<String, Set<String>> result = new LinkedHashMap<>();
+
+        List<PluginWrapper> plugins = pluginManager.getPlugins();
+        for (PluginWrapper plugin : plugins) {
+            String pluginId = plugin.getDescriptor().getPluginId();
+            log.debug("Reading extensions storages for plugin '{}'", pluginId);
+            Set<String> bucket = new HashSet<>();
+
+            try {
+                URL url = plugin.getPluginClassLoader().getResource(getExtensionsResource());
+                if (url != null) {
+                    File[] files = new File(url.toURI()).listFiles();
+                    if (files != null) {
+                        for (File file : files) {
+                            log.debug("Read '{}'", file);
+                            Reader reader = new FileReader(file);
+                            ServiceProviderExtensionStorage.read(reader, bucket);
+                        }
+                    }
+                } else {
+                    log.debug("Cannot find '{}'", getExtensionsResource());
+                }
+
+                if (bucket.isEmpty()) {
+                    log.debug("No extensions found");
+                } else {
+                    log.debug("Found possible {} extensions:", bucket.size());
+                    for (String entry : bucket) {
+                        log.debug("   " + entry);
+                    }
+                }
+
+                result.put(pluginId, bucket);
+            } catch (IOException | URISyntaxException e) {
+                log.error(e.getMessage(), e);
+            }
+        }
+
+        return result;
+    }
+
+    private static String getExtensionsResource() {
+        return ServiceProviderExtensionStorage.EXTENSIONS_RESOURCE;
+    }
+
+}
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionAnnotationProcessor.java b/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionAnnotationProcessor.java
new file mode 100644 (file)
index 0000000..d0ffd6a
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2013 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ro.fortsoft.pf4j.processor;
+
+import ro.fortsoft.pf4j.Extension;
+import ro.fortsoft.pf4j.ExtensionPoint;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.tools.Diagnostic;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * @author Decebal Suiu
+ */
+public class ExtensionAnnotationProcessor extends AbstractProcessor {
+
+    private Map<String, Set<String>> extensions = new HashMap<>(); // the key is the extension point
+    private Map<String, Set<String>> oldExtensions = new HashMap<>(); // the key is the extension point
+
+    private ExtensionStorage storage;
+
+    @Override
+    public synchronized void init(ProcessingEnvironment processingEnv) {
+        super.init(processingEnv);
+
+//        storage = new LegacyExtensionStorage(this);
+        storage = new ServiceProviderExtensionStorage(this);
+    }
+
+    @Override
+       public SourceVersion getSupportedSourceVersion() {
+               return SourceVersion.latest();
+       }
+
+       @Override
+       public Set<String> getSupportedAnnotationTypes() {
+               Set<String> annotationTypes = new HashSet<>();
+        annotationTypes.add(Extension.class.getName());
+
+        return annotationTypes;
+       }
+
+       @Override
+       public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+               if (roundEnv.processingOver()) {
+            return false;
+        }
+
+               for (Element element : roundEnv.getElementsAnnotatedWith(Extension.class)) {
+            // check if @Extension is put on class and not on method or constructor
+            if (!(element instanceof TypeElement)) {
+                               continue;
+                       }
+
+            // check if class extends/implements an extension point
+            if (!isExtension(element.asType())) {
+                continue;
+            }
+
+            TypeElement extensionElement = (TypeElement) element;
+//            Extension annotation = element.getAnnotation(Extension.class);
+            List<TypeElement> extensionPointElements = findExtensionPoints(extensionElement);
+            if (extensionPointElements.isEmpty()) {
+                // TODO throw error ?
+                continue;
+            }
+
+            String extension = getBinaryName(extensionElement);
+            for (TypeElement extensionPointElement : extensionPointElements) {
+                String extensionPoint = getBinaryName(extensionPointElement);
+                Set<String> extensionPoints = extensions.get(extensionPoint);
+                if (extensionPoints == null) {
+                    extensionPoints = new TreeSet<>();
+                    extensions.put(extensionPoint, extensionPoints);
+                }
+                extensionPoints.add(extension);
+            }
+        }
+
+               /*
+               if (!roundEnv.processingOver()) {
+                       return false;
+               }
+               */
+
+        // read old extensions
+        oldExtensions = storage.read();
+        for (Map.Entry<String, Set<String>> entry : oldExtensions.entrySet()) {
+            String extensionPoint = entry.getKey();
+            if (extensions.containsKey(extensionPoint)) {
+                entry.getValue().addAll(entry.getValue());
+            } else {
+                extensions.put(extensionPoint, entry.getValue());
+            }
+        }
+
+        // write extensions
+        storage.write(extensions);
+
+               return false;
+//             return true; // no further processing of this annotation type
+       }
+
+    public ProcessingEnvironment getProcessingEnvironment() {
+        return processingEnv;
+    }
+
+    public void error(String message, Object... args) {
+        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(message, args));
+    }
+
+    public void error(Element element, String message, Object... args) {
+        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(message, args), element);
+    }
+
+    public void note(String message, Object... args) {
+        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, String.format(message, args));
+    }
+
+    public void note(Element element, String message, Object... args) {
+        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, String.format(message, args), element);
+    }
+
+    public String getBinaryName(TypeElement element) {
+        return processingEnv.getElementUtils().getBinaryName(element).toString();
+    }
+
+    public Map<String, Set<String>> getExtensions() {
+        return extensions;
+    }
+
+    public Map<String, Set<String>> getOldExtensions() {
+        return oldExtensions;
+    }
+
+    private List<TypeElement> findExtensionPoints(TypeElement extensionElement) {
+        List<TypeElement> extensionPointElements = new ArrayList<>();
+
+        // search in interfaces
+        for (TypeMirror item : extensionElement.getInterfaces()) {
+            boolean isExtensionPoint = processingEnv.getTypeUtils().isSubtype(item, getExtensionPointType());
+            if (isExtensionPoint) {
+                TypeElement extensionPointElement = (TypeElement) ((DeclaredType) item).asElement();
+                extensionPointElements.add(extensionPointElement);
+            }
+        }
+
+        // search in superclass
+        TypeMirror superclass = extensionElement.getSuperclass();
+        if (superclass.getKind() != TypeKind.NONE) {
+            boolean isExtensionPoint = processingEnv.getTypeUtils().isSubtype(superclass, getExtensionPointType());
+            if (isExtensionPoint) {
+                TypeElement extensionPointElement = (TypeElement) ((DeclaredType) superclass).asElement();
+                extensionPointElements.add(extensionPointElement);
+            }
+        }
+
+        return extensionPointElements;
+    }
+
+    /*
+    private boolean isObject(TypeMirror typeMirror) {
+        if (typeMirror instanceof DeclaredType) {
+            DeclaredType declaredType = (DeclaredType) typeMirror;
+            return ((TypeElement) declaredType.asElement()).getQualifiedName().toString().equals("java.lang.Object");
+        }
+
+        return false;
+    }
+    */
+
+    private boolean isExtension(TypeMirror typeMirror) {
+        return processingEnv.getTypeUtils().isAssignable(typeMirror, getExtensionPointType());
+    }
+
+    /*
+    private boolean isExtensionPoint(TypeMirror typeMirror) {
+        if (typeMirror instanceof DeclaredType) {
+            DeclaredType declaredType = (DeclaredType) typeMirror;
+            return ((TypeElement) declaredType.asElement()).equals(getExtensionPointType());
+        }
+
+        return false;
+    }
+    */
+
+    private TypeMirror getExtensionPointType() {
+        return processingEnv.getElementUtils().getTypeElement(ExtensionPoint.class.getName()).asType();
+    }
+
+}
+
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionStorage.java b/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionStorage.java
new file mode 100644 (file)
index 0000000..9aeec14
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ro.fortsoft.pf4j.processor;
+
+import javax.annotation.processing.Filer;
+import javax.lang.model.element.Element;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Decebal Suiu
+ */
+public abstract class ExtensionStorage {
+
+    protected final ExtensionAnnotationProcessor processor;
+
+    public ExtensionStorage(ExtensionAnnotationProcessor processor) {
+        this.processor = processor;
+    }
+
+    public abstract Map<String, Set<String>> read();
+
+    public abstract void write(Map<String, Set<String>> extensions);
+
+    /**
+     * Helper method.
+     */
+    protected Filer getFiler() {
+        return processor.getProcessingEnvironment().getFiler();
+    }
+
+    /**
+     * Helper method.
+     */
+    protected void error(String message, Object... args) {
+        processor.error(message, args);
+    }
+
+    /**
+     * Helper method.
+     */
+    protected void error(Element element, String message, Object... args) {
+        processor.error(element, message, args);
+    }
+
+    /**
+     * Helper method.
+     */
+    protected void note(String message, Object... args) {
+        processor.note(message, args);
+    }
+
+    /**
+     * Helper method.
+     */
+    protected void note(Element element, String message, Object... args) {
+        processor.note(element, message, args);
+    }
+
+}
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/processor/LegacyExtensionStorage.java b/pf4j/src/main/java/ro/fortsoft/pf4j/processor/LegacyExtensionStorage.java
new file mode 100644 (file)
index 0000000..84f9720
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ro.fortsoft.pf4j.processor;
+
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Decebal Suiu
+ */
+public class LegacyExtensionStorage extends ExtensionStorage {
+
+    public static final String EXTENSIONS_RESOURCE = "META-INF/extensions.idx";
+
+    public LegacyExtensionStorage(ExtensionAnnotationProcessor processor) {
+        super(processor);
+    }
+
+    public static void read(Reader reader, Set<String> entries) throws IOException {
+        BufferedReader bufferedReader = new BufferedReader(reader);
+
+        String line;
+        while ((line = bufferedReader.readLine()) != null) {
+            entries.add(line);
+        }
+
+        bufferedReader.close();
+    }
+    @Override
+    public Map<String, Set<String>> read() {
+        Map<String, Set<String>> extensions = new HashMap<>();
+
+        try {
+            FileObject file = getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE);
+            // TODO try to calculate the extension point
+            Set<String> entries = new HashSet<>();
+            read(file.openReader(true), entries);
+            extensions.put(null, entries);
+        } catch (FileNotFoundException e) {
+            // ignore
+        } catch (IOException e) {
+            error(e.getMessage());
+        }
+
+        return extensions;
+    }
+
+    @Override
+    public void write(Map<String, Set<String>> extensions) {
+        try {
+            FileObject file = getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE);
+            BufferedWriter writer = new BufferedWriter(file.openWriter());
+            writer.write("# Generated by PF4J"); // write header
+            for (Map.Entry<String, Set<String>> entry : extensions.entrySet()) {
+                for (String extension : entry.getValue()) {
+                    writer.write(extension);
+                    writer.newLine();
+                }
+            }
+
+            writer.close();
+        } catch (FileNotFoundException e) {
+            // it's the first time, create the file
+        } catch (IOException e) {
+            error(e.toString());
+        }
+    }
+
+}
diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ServiceProviderExtensionStorage.java b/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ServiceProviderExtensionStorage.java
new file mode 100644 (file)
index 0000000..0cb1264
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2015 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ro.fortsoft.pf4j.processor;
+
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Decebal Suiu
+ */
+public class ServiceProviderExtensionStorage extends ExtensionStorage {
+
+    public static final String EXTENSIONS_RESOURCE = "META-INF/services";
+
+    public ServiceProviderExtensionStorage(ExtensionAnnotationProcessor processor) {
+        super(processor);
+    }
+
+    public static void read(Reader reader, Set<String> entries) throws IOException {
+        BufferedReader bufferedReader = new BufferedReader(reader);
+
+        String line;
+        while ((line = bufferedReader.readLine()) != null) {
+            entries.add(line);
+        }
+
+        bufferedReader.close();
+    }
+
+    @Override
+    public Map<String, Set<String>> read() {
+        Map<String, Set<String>> extensions = new HashMap<>();
+
+        for (String extensionPoint : processor.getExtensions().keySet()) {
+            try {
+                FileObject file = getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE
+                    + "/" + extensionPoint);
+                Set<String> entries = new HashSet<>();
+                read(file.openReader(true), entries);
+                extensions.put(extensionPoint, entries);
+            } catch (FileNotFoundException e) {
+                // doesn't exist, ignore
+            } catch (IOException e) {
+                error(e.getMessage());
+            }
+        }
+
+        return extensions;
+    }
+
+    @Override
+    public void write(Map<String, Set<String>> extensions) {
+        for (Map.Entry<String, Set<String>> entry : extensions.entrySet()) {
+            String extensionPoint = entry.getKey();
+            try {
+                FileObject file = getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE
+                    + "/" + extensionPoint);
+                BufferedWriter writer = new BufferedWriter(file.openWriter());
+                writer.write("# Generated by PF4J"); // write header
+                for (String extension : entry.getValue()) {
+                    if (processor.getOldExtensions().containsKey(extensionPoint))
+                    writer.write(extension);
+                    writer.newLine();
+                }
+                writer.close();
+            } catch (FileNotFoundException e) {
+                // it's the first time, create the file
+            } catch (IOException e) {
+                error(e.toString());
+            }
+        }
+    }
+
+}
index a944d7c4fb0222a54c0d76fa303d7168ca1bb042..c616e6ee011f8b636b2a5d24295e9a6b4ae94152 100644 (file)
@@ -1 +1 @@
-ro.fortsoft.pf4j.ExtensionsIndexer
+ro.fortsoft.pf4j.processor.ExtensionAnnotationProcessor