From 6a666aa4196b650022935c6bd7b19245b5a5f733 Mon Sep 17 00:00:00 2001 From: Decebal Suiu Date: Mon, 4 Jan 2016 09:42:17 +0200 Subject: [PATCH] use META-INF/services storage by default --- .../pf4j/AbstractExtensionFinder.java | 145 ++++++++++++ .../fortsoft/pf4j/DefaultExtensionFinder.java | 195 ++-------------- .../ro/fortsoft/pf4j/ExtensionsIndexer.java | 138 ----------- .../fortsoft/pf4j/LegacyExtensionFinder.java | 124 ++++++++++ .../pf4j/ServiceProviderExtensionFinder.java | 134 +++++++++++ .../ExtensionAnnotationProcessor.java | 219 ++++++++++++++++++ .../pf4j/processor/ExtensionStorage.java | 73 ++++++ .../processor/LegacyExtensionStorage.java | 91 ++++++++ .../ServiceProviderExtensionStorage.java | 96 ++++++++ .../javax.annotation.processing.Processor | 2 +- 10 files changed, 905 insertions(+), 312 deletions(-) create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/AbstractExtensionFinder.java delete mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionsIndexer.java create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/LegacyExtensionFinder.java create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/ServiceProviderExtensionFinder.java create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionAnnotationProcessor.java create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionStorage.java create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/processor/LegacyExtensionStorage.java create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/processor/ServiceProviderExtensionStorage.java 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 index 0000000..12f6526 --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/AbstractExtensionFinder.java @@ -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> entries; // cache by pluginId + + public AbstractExtensionFinder(PluginManager pluginManager) { + this.pluginManager = pluginManager; + } + + public abstract Map> readPluginsStorages(); + + public abstract Map> readClasspathStorages(); + + @Override + public List> find(Class 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> entries = getEntries(); + + List> result = new ArrayList<>(); + for (Map.Entry> 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 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> readStorages() { + Map> result = new LinkedHashMap<>(); + + result.putAll(readClasspathStorages()); + result.putAll(readPluginsStorages()); + + return result; + } + + private boolean isExtensionPoint(Class type) { + return ExtensionPoint.class.isAssignableFrom(type); + } + + private Map> getEntries() { + if (entries == null) { + entries = readStorages(); + } + + return entries; + } + +} diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java index 9ff571a..cf8c87b 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java @@ -15,208 +15,57 @@ */ 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> entries; // cache by pluginId + protected List finders = new ArrayList<>(); public DefaultExtensionFinder(PluginManager pluginManager) { - this.pluginManager = pluginManager; - } + addDefaults(pluginManager); + } @Override - public List> find(Class 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> entries = getEntries(); - - List> result = new ArrayList<>(); - for (Map.Entry> 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 List> find(Class type) { + List> extensions = new ArrayList<>(); + for (ExtensionFinder finder : finders) { + extensions.addAll(finder.find(type)); } - // sort by "ordinal" property - Collections.sort(result); - - return result; - } - - @Override - public Set 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> readIndexFiles() { - Map> result = new LinkedHashMap<>(); - - result.putAll(readClasspathIndexFiles()); - result.putAll(readPluginsIndexFiles()); - - return result; - } - - private Map> readClasspathIndexFiles() { - log.debug("Reading extensions index files from classpath"); - - Map> result = new LinkedHashMap<>(); - - Set bucket = new HashSet<>(); - try { - Enumeration 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 findClassNames(String pluginId) { + Set classNames = new HashSet<>(); + for (ExtensionFinder finder : finders) { + classNames.addAll(finder.findClassNames(pluginId)); } - return result; + return classNames; } - private Map> readPluginsIndexFiles() { - log.debug("Reading extensions index files from plugins"); - - Map> result = new LinkedHashMap<>(); - - List plugins = pluginManager.getPlugins(); - for (PluginWrapper plugin : plugins) { - String pluginId = plugin.getDescriptor().getPluginId(); - log.debug("Reading extensions index file for plugin '{}'", pluginId); - Set 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> 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 index d06dbba..0000000 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionsIndexer.java +++ /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 extensions = new ArrayList<>(); - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latest(); - } - - @Override - public Set getSupportedAnnotationTypes() { - Set annotationTypes = new HashSet<>(); - annotationTypes.add(Extension.class.getName()); - - return annotationTypes; - } - - @Override - public boolean process(Set 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 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 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 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 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 index 0000000..4937cda --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/LegacyExtensionFinder.java @@ -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> readClasspathStorages() { + log.debug("Reading extensions storages from classpath"); + + Map> result = new LinkedHashMap<>(); + + Set bucket = new HashSet<>(); + try { + Enumeration 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> readPluginsStorages() { + log.debug("Reading extensions storages from plugins"); + + Map> result = new LinkedHashMap<>(); + + List plugins = pluginManager.getPlugins(); + for (PluginWrapper plugin : plugins) { + String pluginId = plugin.getDescriptor().getPluginId(); + log.debug("Reading extensions storage for plugin '{}'", pluginId); + Set 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 index 0000000..e6501eb --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/ServiceProviderExtensionFinder.java @@ -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> readClasspathStorages() { + log.debug("Reading extensions storages from classpath"); + + Map> result = new LinkedHashMap<>(); + + Set 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> readPluginsStorages() { + log.debug("Reading extensions storages from plugins"); + + Map> result = new LinkedHashMap<>(); + + List plugins = pluginManager.getPlugins(); + for (PluginWrapper plugin : plugins) { + String pluginId = plugin.getDescriptor().getPluginId(); + log.debug("Reading extensions storages for plugin '{}'", pluginId); + Set 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 index 0000000..d0ffd6a --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionAnnotationProcessor.java @@ -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> extensions = new HashMap<>(); // the key is the extension point + private Map> 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 getSupportedAnnotationTypes() { + Set annotationTypes = new HashSet<>(); + annotationTypes.add(Extension.class.getName()); + + return annotationTypes; + } + + @Override + public boolean process(Set 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 extensionPointElements = findExtensionPoints(extensionElement); + if (extensionPointElements.isEmpty()) { + // TODO throw error ? + continue; + } + + String extension = getBinaryName(extensionElement); + for (TypeElement extensionPointElement : extensionPointElements) { + String extensionPoint = getBinaryName(extensionPointElement); + Set 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> 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> getExtensions() { + return extensions; + } + + public Map> getOldExtensions() { + return oldExtensions; + } + + private List findExtensionPoints(TypeElement extensionElement) { + List 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 index 0000000..9aeec14 --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionStorage.java @@ -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> read(); + + public abstract void write(Map> 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 index 0000000..84f9720 --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/processor/LegacyExtensionStorage.java @@ -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 entries) throws IOException { + BufferedReader bufferedReader = new BufferedReader(reader); + + String line; + while ((line = bufferedReader.readLine()) != null) { + entries.add(line); + } + + bufferedReader.close(); + } + @Override + public Map> read() { + Map> extensions = new HashMap<>(); + + try { + FileObject file = getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE); + // TODO try to calculate the extension point + Set 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> 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> 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 index 0000000..0cb1264 --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/processor/ServiceProviderExtensionStorage.java @@ -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 entries) throws IOException { + BufferedReader bufferedReader = new BufferedReader(reader); + + String line; + while ((line = bufferedReader.readLine()) != null) { + entries.add(line); + } + + bufferedReader.close(); + } + + @Override + public Map> read() { + Map> extensions = new HashMap<>(); + + for (String extensionPoint : processor.getExtensions().keySet()) { + try { + FileObject file = getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE + + "/" + extensionPoint); + Set 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> extensions) { + for (Map.Entry> 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()); + } + } + } + +} diff --git a/pf4j/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/pf4j/src/main/resources/META-INF/services/javax.annotation.processing.Processor index a944d7c..c616e6e 100644 --- a/pf4j/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/pf4j/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1 @@ -ro.fortsoft.pf4j.ExtensionsIndexer +ro.fortsoft.pf4j.processor.ExtensionAnnotationProcessor