Browse Source

use META-INF/services storage by default

pull/85/head
Decebal Suiu 9 years ago
parent
commit
6a666aa419
  1. 145
      pf4j/src/main/java/ro/fortsoft/pf4j/AbstractExtensionFinder.java
  2. 195
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java
  3. 138
      pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionsIndexer.java
  4. 124
      pf4j/src/main/java/ro/fortsoft/pf4j/LegacyExtensionFinder.java
  5. 134
      pf4j/src/main/java/ro/fortsoft/pf4j/ServiceProviderExtensionFinder.java
  6. 219
      pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionAnnotationProcessor.java
  7. 73
      pf4j/src/main/java/ro/fortsoft/pf4j/processor/ExtensionStorage.java
  8. 91
      pf4j/src/main/java/ro/fortsoft/pf4j/processor/LegacyExtensionStorage.java
  9. 96
      pf4j/src/main/java/ro/fortsoft/pf4j/processor/ServiceProviderExtensionStorage.java
  10. 2
      pf4j/src/main/resources/META-INF/services/javax.annotation.processing.Processor

145
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<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;
}
}

195
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java

@ -15,208 +15,57 @@
*/ */
package ro.fortsoft.pf4j; 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.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
* The default implementation for ExtensionFinder. * The default implementation for ExtensionFinder.
* All extensions declared in a plugin are indexed in a file "META-INF/extensions.idx". * It's a compound ExtensionFinder.
* This class lookup extensions in all extensions index files "META-INF/extensions.idx".
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class DefaultExtensionFinder implements ExtensionFinder, PluginStateListener { public class DefaultExtensionFinder implements ExtensionFinder, PluginStateListener {
private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFinder.class); protected List<ExtensionFinder> finders = new ArrayList<>();
protected PluginManager pluginManager;
protected volatile Map<String, Set<String>> entries; // cache by pluginId
public DefaultExtensionFinder(PluginManager pluginManager) { public DefaultExtensionFinder(PluginManager pluginManager) {
this.pluginManager = pluginManager; addDefaults(pluginManager);
} }
@Override @Override
public <T> List<ExtensionWrapper<T>> find(Class<T> type) { public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
log.debug("Checking extension point '{}'", type.getName()); List<ExtensionWrapper<T>> extensions = new ArrayList<>();
if (!isExtensionPoint(type)) { for (ExtensionFinder finder : finders) {
log.warn("'{}' is not an extension point", type.getName()); extensions.addAll(finder.find(type));
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 return extensions;
Collections.sort(result);
return result;
}
@Override
public Set<String> findClassNames(String pluginId) {
return getEntries().get(pluginId);
} }
@Override @Override
public void pluginStateChanged(PluginStateEvent event) { public Set<String> findClassNames(String pluginId) {
// TODO optimize (do only for some transitions) Set<String> classNames = new HashSet<>();
// clear cache for (ExtensionFinder finder : finders) {
entries = null; classNames.addAll(finder.findClassNames(pluginId));
}
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);
} }
return result; return classNames;
} }
private Map<String, Set<String>> readPluginsIndexFiles() { @Override
log.debug("Reading extensions index files from plugins"); public void pluginStateChanged(PluginStateEvent event) {
for (ExtensionFinder finder : finders) {
Map<String, Set<String>> result = new LinkedHashMap<>(); if (finder instanceof PluginStateListener) {
((PluginStateListener) finder).pluginStateChanged(event);
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);
} }
} }
return result;
}
private boolean isExtensionPoint(Class<?> type) {
return ExtensionPoint.class.isAssignableFrom(type);
} }
private Map<String, Set<String>> getEntries() { protected void addDefaults(PluginManager pluginManager) {
if (entries == null) { finders.add(new ServiceProviderExtensionFinder(pluginManager));
entries = readIndexFiles(); finders.add(new LegacyExtensionFinder(pluginManager));
}
return entries;
} }
} }

138
pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionsIndexer.java

@ -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();
}
}

124
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<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;
}
}

134
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<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;
}
}

219
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<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();
}
}

73
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<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);
}
}

91
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<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());
}
}
}

96
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<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());
}
}
}
}

2
pf4j/src/main/resources/META-INF/services/javax.annotation.processing.Processor

@ -1 +1 @@
ro.fortsoft.pf4j.ExtensionsIndexer ro.fortsoft.pf4j.processor.ExtensionAnnotationProcessor

Loading…
Cancel
Save