--- /dev/null
+/*
+ * 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;
+ }
+
+}
*/
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));
}
}
+++ /dev/null
-/*
- * 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();
- }
-
-}
-
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
+
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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());
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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());
+ }
+ }
+ }
+
+}
-ro.fortsoft.pf4j.ExtensionsIndexer
+ro.fortsoft.pf4j.processor.ExtensionAnnotationProcessor