aboutsummaryrefslogtreecommitdiff
path: root/src/spiProcessor
diff options
context:
space:
mode:
Diffstat (limited to 'src/spiProcessor')
-rw-r--r--src/spiProcessor/lombok/spi/Provides.java33
-rw-r--r--src/spiProcessor/lombok/spi/SpiProcessor.java254
-rw-r--r--src/spiProcessor/lombok/spi/SpiProcessorCollector.java107
-rw-r--r--src/spiProcessor/lombok/spi/SpiProcessorPersistence.java148
-rw-r--r--src/spiProcessor/lombok/spi/SpiProcessorService.java83
5 files changed, 625 insertions, 0 deletions
diff --git a/src/spiProcessor/lombok/spi/Provides.java b/src/spiProcessor/lombok/spi/Provides.java
new file mode 100644
index 00000000..de930819
--- /dev/null
+++ b/src/spiProcessor/lombok/spi/Provides.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.spi;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Provides {
+ Class<?>[] value() default {};
+}
diff --git a/src/spiProcessor/lombok/spi/SpiProcessor.java b/src/spiProcessor/lombok/spi/SpiProcessor.java
new file mode 100644
index 00000000..4ad27bbe
--- /dev/null
+++ b/src/spiProcessor/lombok/spi/SpiProcessor.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2021 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.spi;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+
+import javax.tools.Diagnostic.Kind;
+
+@SupportedAnnotationTypes("*")
+public class SpiProcessor extends AbstractProcessor {
+ private SpiProcessorCollector data;
+ private SpiProcessorPersistence persistence;
+
+ static String getRootPathOfServiceFiles() {
+ return "META-INF/services/";
+ }
+
+ @Override public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ static String toErrorMsg(Exception e, String pathName) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Exception applying SPI processor on ").append(pathName).append(": ").append(toStringLong(e));
+ return sb.toString();
+ }
+
+ private static String toStringLong(Throwable t) {
+ if (t == null) return "NULL";
+ StringBuilder out = new StringBuilder();
+
+ String msg = t.getMessage();
+ out.append(t.getClass().getName());
+ if (msg != null) out.append(": ").append(msg);
+ String indent = " ";
+
+ StackTraceElement[] elems = t.getStackTrace();
+ if (elems != null) for (int i = 0; i < elems.length; i++) {
+ out.append("\n").append(indent).append(elems[i]);
+ }
+ Throwable cause = t.getCause();
+ while (cause != null) {
+ indent = indent + " ";
+ out.append("\n").append(indent).append("Caused by: ").append(cause.getClass().getName());
+ msg = cause.getMessage();
+ if (msg != null) out.append(": ").append(msg);
+ elems = cause.getStackTrace();
+ indent = indent + " ";
+ if (elems != null) for (int i = 0; i < elems.length; i++) {
+ out.append("\n").append(indent).append(elems[i]);
+ }
+ cause = cause.getCause();
+ }
+ return out.toString();
+ }
+
+ @Override public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ persistence = new SpiProcessorPersistence("SpiProcessor", processingEnv.getFiler(), processingEnv.getMessager());
+ data = new SpiProcessorCollector(processingEnv);
+ for (String serviceName : persistence.tryFind()) data.getService(serviceName);
+ data.stripProvidersWithoutSourceFile();
+ }
+
+ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ removeStaleData(roundEnv);
+ handleAnnotations(roundEnv);
+ if (roundEnv.processingOver()) writeData();
+
+ return false;
+ }
+
+ private void writeData() {
+ for (SpiProcessorService service : data.services()) {
+ try {
+ persistence.write(service.getName(), service.toProvidersListFormat());
+ }
+ catch (IOException e) {
+ processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage());
+ }
+ }
+ }
+
+ private void removeStaleData(RoundEnvironment roundEnv) {
+ for (Element e : roundEnv.getRootElements()) {
+ if (e instanceof TypeElement) {
+ TypeElement currentClass = (TypeElement) e;
+ data.removeProvider(createProperQualifiedName(currentClass));
+ }
+ }
+ }
+
+ private void handleAnnotations(RoundEnvironment roundEnv) {
+ Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Provides.class);
+ for (Element e : elements) handleProvidingElement(e);
+ }
+
+ private void handleProvidingElement(Element element) {
+ if (element.getKind() != ElementKind.CLASS) {
+ report(element, "is not a class definition");
+ return;
+ }
+
+ TypeElement elem = (TypeElement) element;
+
+ Element enclosing = elem.getEnclosingElement();
+ if (enclosing != null && enclosing.getKind() == ElementKind.CLASS && !elem.getModifiers().contains(Modifier.STATIC)) {
+ report(elem, "is a non-static inner class");
+ return;
+ }
+
+ boolean hasConstructors = false;
+ boolean hasNoArgsConstructor = false;
+ for (Element child : elem.getEnclosedElements()) {
+ if (child.getKind() != ElementKind.CONSTRUCTOR) continue;
+ ExecutableElement ee = (ExecutableElement) child;
+ hasConstructors = true;
+ if (ee.getParameters().isEmpty()) {
+ hasNoArgsConstructor = true;
+ break;
+ }
+ }
+
+ if (hasConstructors && !hasNoArgsConstructor) {
+ report(elem, "has no no-args constructor");
+ return;
+ }
+
+ List<TypeMirror> spiTypes = new ArrayList<TypeMirror>();
+
+ for (AnnotationMirror annMirror : element.getAnnotationMirrors()) {
+ if (!getQualifiedTypeName(annMirror).contentEquals(Provides.class.getName())) continue;
+ for (Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annMirror.getElementValues().entrySet()) {
+ if (!entry.getKey().getSimpleName().contentEquals("value")) continue;
+ Object list = entry.getValue().getValue();
+ if (list instanceof TypeMirror) spiTypes.add((TypeMirror) list);
+ else if (list instanceof List<?>) {
+ for (Object tm : (List<?>) list) {
+ if (tm instanceof AnnotationValue) tm = ((AnnotationValue) tm).getValue();
+ if (tm instanceof TypeMirror) {
+ TypeMirror mirror = (TypeMirror) tm;
+ mirror = processingEnv.getTypeUtils().erasure(mirror);
+ spiTypes.add(mirror);
+ }
+ }
+ }
+ }
+ }
+
+ TypeMirror superclass = elem.getSuperclass();
+ if (spiTypes.isEmpty()) {
+ List<TypeMirror> qualifying = new ArrayList<TypeMirror>();
+ qualifying.addAll(elem.getInterfaces());
+ if (superclass != null && !toElement(superclass).getQualifiedName().contentEquals("java.lang.Object")) qualifying.add(superclass);
+
+ if (qualifying.isEmpty()) {
+ report(elem, "is marked @Provides but implements/extends nothing");
+ return;
+ }
+ if (qualifying.size() > 1) {
+ report(elem, "is marked @Provides but implements/extends multiple types; explicitly specify which interface(s) this provides for");
+ return;
+ }
+ spiTypes.add(qualifying.get(0));
+ } else {
+ Deque<TypeMirror> parentage = new ArrayDeque<TypeMirror>();
+ parentage.addAll(elem.getInterfaces());
+ parentage.add(superclass);
+ List<TypeMirror> needed = new ArrayList<TypeMirror>();
+ needed.addAll(spiTypes);
+ while (!parentage.isEmpty() && !spiTypes.isEmpty()) {
+ TypeMirror parent = parentage.pollFirst();
+ if (parent == null) continue;
+ needed.remove(parent);
+ parentage.addAll(processingEnv.getTypeUtils().directSupertypes(parent));
+ }
+ if (!needed.isEmpty()) {
+ report(elem, "is marked as providing " + needed + " but does not implement it");
+ return;
+ }
+ }
+
+ for (TypeMirror spiType : spiTypes) {
+ String spiTypeName = createProperQualifiedName(toElement(spiType));
+ String createProperQualifiedName = createProperQualifiedName(elem);
+ data.getService(spiTypeName).addProvider(createProperQualifiedName);
+ }
+ }
+
+ private Name getQualifiedTypeName(AnnotationMirror mirror) {
+ Element elem = mirror.getAnnotationType().asElement();
+ if (!(elem instanceof TypeElement)) return null;
+ return ((TypeElement) elem).getQualifiedName();
+ }
+
+ private TypeElement toElement(TypeMirror typeMirror) {
+ if (typeMirror instanceof DeclaredType) {
+ Element asElement = ((DeclaredType) typeMirror).asElement();
+ if (asElement instanceof TypeElement) return (TypeElement) asElement;
+ }
+ return null;
+ }
+
+ private void report(Element elem, String message) {
+ /* In eclipse, messages just seem to get ignored, so we throw instead. */
+ if (Boolean.TRUE) throw new RuntimeException(elem.getSimpleName() + " " + message);
+ processingEnv.getMessager().printMessage(Kind.ERROR, elem.getSimpleName() + " " + message, elem);
+ }
+
+ private String createProperQualifiedName(TypeElement provider) {
+ return processingEnv.getElementUtils().getBinaryName(provider).toString();
+ }
+}
diff --git a/src/spiProcessor/lombok/spi/SpiProcessorCollector.java b/src/spiProcessor/lombok/spi/SpiProcessorCollector.java
new file mode 100644
index 00000000..52e557f6
--- /dev/null
+++ b/src/spiProcessor/lombok/spi/SpiProcessorCollector.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2021 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.spi;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.util.Elements;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+class SpiProcessorCollector {
+ private final Map<String, SpiProcessorService> services = new HashMap<String, SpiProcessorService>();
+ private final List<String> removed = new ArrayList<String>();
+
+ private final Elements elements;
+ private final Messager logger;
+ private final Filer filer;
+
+ SpiProcessorCollector(ProcessingEnvironment processingEnv) {
+ this.elements = processingEnv.getElementUtils();
+ this.logger = processingEnv.getMessager();
+ this.filer = processingEnv.getFiler();
+ }
+
+ SpiProcessorCollector(Elements elements, Messager logger, Filer filer) {
+ if (elements == null) throw new NullPointerException("elements");
+ if (logger == null) throw new NullPointerException("logger");
+ if (filer == null) throw new NullPointerException("filer");
+ this.elements = elements;
+ this.logger = logger;
+ this.filer = filer;
+ }
+
+ SpiProcessorService getService(String serviceName) {
+ if (serviceName == null) throw new NullPointerException("serviceName");
+ if (!services.containsKey(serviceName)) {
+ SpiProcessorService newService = new SpiProcessorService(serviceName);
+ CharSequence initialData = readInitialData(serviceName);
+ if (initialData != null) {
+ newService.addAllFromProvidersNameList(initialData.toString());
+ for (String provider : removed) newService.removeProvider(provider);
+ }
+ services.put(serviceName, newService);
+ }
+ return services.get(serviceName);
+ }
+
+ Collection<SpiProcessorService> services() {
+ return Collections.unmodifiableMap(services).values();
+ }
+
+ void removeProvider(String provider) {
+ if (provider == null) throw new NullPointerException("provider");
+ removed.add(provider);
+ for (SpiProcessorService service : services.values()) service.removeProvider(provider);
+ }
+
+ @Override public String toString() {
+ return services.values().toString();
+ }
+
+ private CharSequence readInitialData(String serviceName) {
+ String pathName = SpiProcessor.getRootPathOfServiceFiles() + serviceName;
+ FileObject resource;
+
+ try {
+ resource = filer.getResource(StandardLocation.CLASS_OUTPUT, "", pathName);
+ } catch (Exception e) {
+ logger.printMessage(Kind.ERROR, SpiProcessor.toErrorMsg(e, pathName));
+ return null;
+ }
+
+ return SpiProcessorPersistence.readFilerResource(resource, logger, pathName);
+ }
+
+ void stripProvidersWithoutSourceFile() {
+ for (SpiProcessorService s : services.values()) s.stripProvidersWithoutSourceFile(elements);
+ }
+}
diff --git a/src/spiProcessor/lombok/spi/SpiProcessorPersistence.java b/src/spiProcessor/lombok/spi/SpiProcessorPersistence.java
new file mode 100644
index 00000000..c9bd745f
--- /dev/null
+++ b/src/spiProcessor/lombok/spi/SpiProcessorPersistence.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2021 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.spi;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+class SpiProcessorPersistence {
+ private final String name;
+ private final String path;
+ final Filer filer;
+ private final Messager logger;
+
+ SpiProcessorPersistence(String name, Filer filer, Messager logger) {
+ this.name = name;
+ this.logger = logger;
+ this.path = SpiProcessor.getRootPathOfServiceFiles();
+ this.filer = filer;
+ }
+
+ static CharSequence readFilerResource(FileObject resource, Messager logger, String pathName) {
+ try {
+ // Eclipse can't handle getCharContent, so we must use a reader...
+ return tryWithReader(resource);
+ } catch (FileNotFoundException e) {
+ return null;
+ } catch (IOException e) {
+ if (
+ e.getClass().getName().equals("org.eclipse.core.internal.resources.ResourceException") &&
+ e.getMessage() != null &&
+ e.getMessage().endsWith("does not exist.")) {
+
+ return null;
+ }
+
+ logger.printMessage(Kind.ERROR, SpiProcessor.toErrorMsg(e, pathName));
+ return null;
+ } catch (Exception other) {
+ // otherwise, probably javac: Some versions don't support the `openReader` method.
+ try {
+ return resource.getCharContent(true);
+ } catch (FileNotFoundException e) {
+ return null;
+ } catch (IOException e) {
+ logger.printMessage(Kind.ERROR, SpiProcessor.toErrorMsg(e, pathName));
+ return null;
+ }
+ }
+ }
+
+ private static CharSequence tryWithReader(FileObject resource) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ Reader raw = resource.openReader(true);
+ try {
+ BufferedReader in = new BufferedReader(raw);
+ for (String line = in.readLine(); line != null; line = in.readLine()) sb.append(line).append('\n');
+ return sb;
+ } finally {
+ if (raw != null) raw.close();
+ }
+ }
+
+ Collection<String> tryFind() {
+ File dir = determineOutputLocation();
+ if (dir == null || !dir.isDirectory()) return Collections.emptyList();
+ List<String> out = new ArrayList<String>();
+ for (File p : dir.listFiles()) {
+ if (!p.isFile()) continue;
+ out.add(p.getName());
+ }
+ return out;
+ }
+
+ private File determineOutputLocation() {
+ FileObject resource;
+ try {
+ resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "META-INF", "locator.tmp");
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ // Could happen
+ return null;
+ } catch (IOException e) {
+ e.printStackTrace();
+ logger.printMessage(Kind.NOTE, "IOException while determining output location: " + e.getMessage());
+ return null;
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ // Happens when the path is invalid. For instance absolute or relative to a path
+ // not part of the class output folder.
+ //
+ // Due to a bug in javac for Linux, this also occurs when no output path is specified
+ // for javac using the -d parameter.
+ // See http://forums.sun.com/thread.jspa?threadID=5240999&tstart=45
+ // and http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6647996
+
+ return null;
+ }
+
+ URI uri = resource.toUri();
+ return new File(new File(uri).getParentFile(), "services");
+ }
+
+ void write(String serviceName, String value) throws IOException {
+ FileObject output = filer.createResource(StandardLocation.CLASS_OUTPUT, "", path + serviceName);
+ Writer writer = output.openWriter();
+ writer.write("# Generated by " + name + "\n");
+ writer.write("# " + new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US).format(new Date()) + "\n");
+ writer.write(value);
+ writer.close();
+ }
+}
diff --git a/src/spiProcessor/lombok/spi/SpiProcessorService.java b/src/spiProcessor/lombok/spi/SpiProcessorService.java
new file mode 100644
index 00000000..be8d5a21
--- /dev/null
+++ b/src/spiProcessor/lombok/spi/SpiProcessorService.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.spi;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import javax.lang.model.util.Elements;
+
+final class SpiProcessorService {
+ private final String name;
+ private final Set<String> providers = new TreeSet<String>();
+
+ SpiProcessorService(String name) {
+ if (name == null) throw new NullPointerException("name");
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ void addProvider(String className) {
+ if (className == null) throw new NullPointerException("className");
+ providers.add(className);
+ }
+
+ boolean removeProvider(String provider) {
+ return providers.remove(provider);
+ }
+
+ String toProvidersListFormat() {
+ return providers.stream().collect(Collectors.joining("\n"));
+ }
+
+ void addAllFromProvidersNameList(String in) {
+ for (String line : in.split("\\n")) {
+ String[] content = line.split("#", 2);
+ if (content.length == 0) continue;
+ String trimmed = content[0].trim();
+ String[] elems = trimmed.split("\\s+", 2);
+ if (elems.length == 0 || elems[0].isEmpty()) continue;
+ String cn = elems[0];
+ addProvider(cn);
+ }
+ }
+
+ void stripProvidersWithoutSourceFile(Elements elements) {
+ Iterator<String> it = providers.iterator();
+ while (it.hasNext()) {
+ if (!sourceExists(elements, it.next())) it.remove();
+ }
+ }
+
+ private boolean sourceExists(Elements elements, String typeName) {
+ return elements.getTypeElement(typeName) != null;
+ }
+
+ @Override public String toString() {
+ return name + " = " + providers;
+ }
+}