1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.di.tool;
20
21 import javax.annotation.processing.AbstractProcessor;
22 import javax.annotation.processing.RoundEnvironment;
23 import javax.annotation.processing.SupportedAnnotationTypes;
24 import javax.annotation.processing.SupportedSourceVersion;
25 import javax.lang.model.SourceVersion;
26 import javax.lang.model.element.Element;
27 import javax.lang.model.element.PackageElement;
28 import javax.lang.model.element.TypeElement;
29 import javax.tools.Diagnostic;
30 import javax.tools.FileObject;
31 import javax.tools.StandardLocation;
32
33 import java.io.BufferedReader;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.io.PrintWriter;
37 import java.io.StringWriter;
38 import java.io.Writer;
39 import java.util.HashSet;
40 import java.util.Set;
41 import java.util.TreeSet;
42
43 import org.apache.maven.api.di.Named;
44
45 @SupportedAnnotationTypes("org.apache.maven.api.di.Named")
46 @SupportedSourceVersion(SourceVersion.RELEASE_17)
47 public class DiIndexProcessor extends AbstractProcessor {
48
49 private final Set<String> processedClasses = new HashSet<>();
50
51 @Override
52 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
53 logMessage(
54 Diagnostic.Kind.NOTE, "Processing " + roundEnv.getRootElements().size() + " classes");
55
56 for (Element element : roundEnv.getElementsAnnotatedWith(Named.class)) {
57 if (element instanceof TypeElement typeElement) {
58 String className = getFullClassName(typeElement);
59 processedClasses.add(className);
60 }
61 }
62
63 if (roundEnv.processingOver()) {
64 try {
65 updateFileIfChanged();
66 } catch (Exception e) {
67 logError("Error updating file", e);
68 }
69 }
70
71 return true;
72 }
73
74 private String getFullClassName(TypeElement typeElement) {
75 StringBuilder className = new StringBuilder(typeElement.getSimpleName());
76 Element enclosingElement = typeElement.getEnclosingElement();
77
78 while (enclosingElement instanceof TypeElement) {
79 className.insert(0, "$").insert(0, ((TypeElement) enclosingElement).getSimpleName());
80 enclosingElement = enclosingElement.getEnclosingElement();
81 }
82
83 if (enclosingElement instanceof PackageElement) {
84 className.insert(0, ".").insert(0, ((PackageElement) enclosingElement).getQualifiedName());
85 }
86
87 return className.toString();
88 }
89
90 private void updateFileIfChanged() throws IOException {
91 String path = "META-INF/maven/org.apache.maven.api.di.Inject";
92 Set<String> existingClasses = new TreeSet<>();
93 String existingContent = "";
94
95
96 try {
97 FileObject inputFile = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", path);
98 try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputFile.openInputStream()))) {
99 String line;
100 StringBuilder contentBuilder = new StringBuilder();
101 while ((line = reader.readLine()) != null) {
102 if (!line.trim().startsWith("#")) {
103 existingClasses.add(line.trim());
104 }
105 contentBuilder.append(line).append("\n");
106 }
107 existingContent = contentBuilder.toString();
108 }
109 } catch (IOException e) {
110 logMessage(Diagnostic.Kind.NOTE, "Unable to read existing file. Proceeding with empty content.");
111 }
112
113 Set<String> allClasses = new TreeSet<>(existingClasses);
114 allClasses.addAll(processedClasses);
115
116 StringBuilder newContentBuilder = new StringBuilder();
117 for (String className : allClasses) {
118 newContentBuilder.append(className).append("\n");
119 }
120 String newContent = newContentBuilder.toString();
121
122 if (!newContent.equals(existingContent)) {
123 logMessage(Diagnostic.Kind.NOTE, "Content has changed. Updating file.");
124 try {
125 FileObject outputFile =
126 processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", path);
127 try (Writer writer = outputFile.openWriter()) {
128 writer.write(newContent);
129 }
130 } catch (IOException e) {
131 logError("Failed to write to file", e);
132 throw e;
133 }
134 } else {
135 logMessage(Diagnostic.Kind.NOTE, "Content unchanged. Skipping file update.");
136 }
137 }
138
139 private void logMessage(Diagnostic.Kind kind, String message) {
140 processingEnv.getMessager().printMessage(kind, message);
141 }
142
143 private void logError(String message, Exception e) {
144 StringWriter sw = new StringWriter();
145 PrintWriter pw = new PrintWriter(sw);
146 e.printStackTrace(pw);
147 String stackTrace = sw.toString();
148
149 String fullMessage = message + "\n" + "Exception: "
150 + e.getClass().getName() + "\n" + "Message: "
151 + e.getMessage() + "\n" + "Stack trace:\n"
152 + stackTrace;
153
154 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, fullMessage);
155 }
156 }