mirror of https://github.com/pf4j/pf4j.git
Decebal Suiu
9 years ago
10 changed files with 905 additions and 312 deletions
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue