View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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<>(); // Using TreeSet for natural ordering
93          String existingContent = "";
94  
95          // Try to read existing content
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); // Using TreeSet for natural ordering
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; // Re-throw to ensure the compilation fails
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 }