Skip to content

Commit aaeaf12

Browse files
committed
Prepare PluginProcessor for introduction of GraalVmProcessor
This refactors and updates PluginProcessor to function more similarly to GraalVmProcessor. This re-uses the recently added PluginIndex class for tracking processed plugins before dumping this data in the final annotation processing round.
1 parent d4829f9 commit aaeaf12

File tree

2 files changed

+134
-154
lines changed

2 files changed

+134
-154
lines changed

log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java

Lines changed: 120 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -25,37 +25,35 @@
2525
import java.io.OutputStreamWriter;
2626
import java.io.PrintWriter;
2727
import java.io.StringWriter;
28-
import java.util.ArrayList;
29-
import java.util.Collection;
30-
import java.util.Collections;
28+
import java.util.Arrays;
29+
import java.util.HashSet;
3130
import java.util.List;
3231
import java.util.Locale;
33-
import java.util.Map;
3432
import java.util.Objects;
3533
import java.util.Optional;
3634
import java.util.Set;
3735
import javax.annotation.processing.AbstractProcessor;
3836
import javax.annotation.processing.Messager;
37+
import javax.annotation.processing.ProcessingEnvironment;
3938
import javax.annotation.processing.Processor;
4039
import javax.annotation.processing.RoundEnvironment;
4140
import javax.annotation.processing.SupportedAnnotationTypes;
4241
import javax.lang.model.SourceVersion;
4342
import javax.lang.model.element.AnnotationValue;
44-
import javax.lang.model.element.Element;
45-
import javax.lang.model.element.Name;
4643
import javax.lang.model.element.TypeElement;
47-
import javax.lang.model.util.SimpleElementVisitor8;
44+
import javax.lang.model.util.ElementFilter;
45+
import javax.lang.model.util.Elements;
4846
import javax.tools.Diagnostic.Kind;
4947
import javax.tools.FileObject;
5048
import javax.tools.JavaFileObject;
5149
import javax.tools.StandardLocation;
5250
import org.apache.logging.log4j.LoggingException;
5351
import org.apache.logging.log4j.plugins.Configurable;
5452
import org.apache.logging.log4j.plugins.Namespace;
55-
import org.apache.logging.log4j.plugins.Node;
5653
import org.apache.logging.log4j.plugins.Plugin;
5754
import org.apache.logging.log4j.plugins.PluginAliases;
5855
import org.apache.logging.log4j.plugins.model.PluginEntry;
56+
import org.apache.logging.log4j.plugins.model.PluginIndex;
5957
import org.apache.logging.log4j.util.Strings;
6058
import org.jspecify.annotations.NullMarked;
6159

@@ -74,7 +72,7 @@
7472
* </p>
7573
*/
7674
@NullMarked
77-
@SupportedAnnotationTypes({"org.apache.logging.log4j.plugins.*", "org.apache.logging.log4j.core.config.plugins.*"})
75+
@SupportedAnnotationTypes("org.apache.logging.log4j.plugins.Plugin")
7876
@ServiceProvider(value = Processor.class, resolution = Resolution.OPTIONAL)
7977
public class PluginProcessor extends AbstractProcessor {
8078

@@ -98,7 +96,9 @@ public class PluginProcessor extends AbstractProcessor {
9896
"META-INF/services/org.apache.logging.log4j.plugins.model.PluginService";
9997

10098
private boolean enableBndAnnotations;
101-
private String packageName = "";
99+
private CharSequence packageName = "";
100+
private final PluginIndex pluginIndex = new PluginIndex();
101+
private final Set<TypeElement> processedElements = new HashSet<>();
102102

103103
public PluginProcessor() {}
104104

@@ -112,89 +112,124 @@ public SourceVersion getSupportedSourceVersion() {
112112
return SourceVersion.latest();
113113
}
114114

115+
@Override
116+
public synchronized void init(ProcessingEnvironment processingEnv) {
117+
super.init(processingEnv);
118+
handleOptions();
119+
}
120+
115121
@Override
116122
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
117-
handleOptions(processingEnv.getOptions());
118-
final Messager messager = processingEnv.getMessager();
119-
messager.printMessage(Kind.NOTE, "Processing Log4j annotations");
120-
try {
121-
final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class);
122-
if (elements.isEmpty()) {
123-
messager.printMessage(Kind.NOTE, "No elements to process");
124-
return true;
123+
// Process the elements for this round
124+
if (!annotations.isEmpty()) {
125+
processPluginAnnotatedClasses(ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Plugin.class)));
126+
}
127+
// Write the generated code
128+
if (roundEnv.processingOver() && !pluginIndex.isEmpty()) {
129+
try {
130+
final Messager messager = processingEnv.getMessager();
131+
messager.printMessage(Kind.NOTE, "Writing Log4j plugin metadata using base package " + packageName);
132+
writeClassFile();
133+
writeServiceFile();
134+
messager.printMessage(Kind.NOTE, "Log4j annotations processed");
135+
} catch (final Exception e) {
136+
handleUnexpectedError(e);
125137
}
126-
messager.printMessage(Kind.NOTE, "Retrieved " + elements.size() + " Plugin elements");
127-
final List<PluginEntry> list = new ArrayList<>();
128-
packageName = collectPlugins(packageName, elements, list);
129-
messager.printMessage(Kind.NOTE, "Writing plugin metadata using base package " + packageName);
130-
Collections.sort(list);
131-
writeClassFile(packageName, list);
132-
writeServiceFile(packageName);
133-
messager.printMessage(Kind.NOTE, "Annotations processed");
134-
} catch (final Exception ex) {
135-
var writer = new StringWriter();
136-
ex.printStackTrace(new PrintWriter(writer));
137-
error(writer.toString());
138138
}
139+
// Do not claim the annotations to allow other annotation processors to run
139140
return false;
140141
}
141142

142-
private void error(final CharSequence message) {
143-
processingEnv.getMessager().printMessage(Kind.ERROR, message);
143+
private void processPluginAnnotatedClasses(Set<TypeElement> pluginClasses) {
144+
final boolean calculatePackageName = packageName.isEmpty();
145+
final Elements elements = processingEnv.getElementUtils();
146+
final Messager messager = processingEnv.getMessager();
147+
for (var pluginClass : pluginClasses) {
148+
final String name = getPluginName(pluginClass);
149+
final String namespace = getNamespace(pluginClass);
150+
final String className = elements.getBinaryName(pluginClass).toString();
151+
var builder =
152+
PluginEntry.builder().setName(name).setNamespace(namespace).setClassName(className);
153+
processConfigurableAnnotation(pluginClass, builder);
154+
var entry = builder.get();
155+
messager.printMessage(Kind.NOTE, "Parsed Log4j plugin " + entry, pluginClass);
156+
if (!pluginIndex.add(entry)) {
157+
messager.printMessage(Kind.WARNING, "Duplicate Log4j plugin parsed " + entry, pluginClass);
158+
}
159+
pluginIndex.addAll(createPluginAliases(pluginClass, builder));
160+
if (calculatePackageName) {
161+
packageName = calculatePackageName(elements, pluginClass, packageName);
162+
}
163+
processedElements.add(pluginClass);
164+
}
144165
}
145166

146-
private String collectPlugins(
147-
String packageName, final Iterable<? extends Element> elements, final List<PluginEntry> list) {
148-
final boolean calculatePackage = packageName.isEmpty();
149-
final var pluginVisitor = new PluginElementVisitor();
150-
final var pluginAliasesVisitor = new PluginAliasesElementVisitor();
151-
for (final Element element : elements) {
152-
// The elements must be annotated with `Plugin`
153-
Plugin plugin = element.getAnnotation(Plugin.class);
154-
final var entry = element.accept(pluginVisitor, plugin);
155-
list.add(entry);
156-
if (calculatePackage) {
157-
packageName = calculatePackage(element, packageName);
158-
}
159-
list.addAll(element.accept(pluginAliasesVisitor, plugin));
167+
private static void processConfigurableAnnotation(TypeElement pluginClass, PluginEntry.Builder builder) {
168+
var configurable = pluginClass.getAnnotation(Configurable.class);
169+
if (configurable != null) {
170+
var elementType = configurable.elementType();
171+
builder.setElementType(elementType.isEmpty() ? builder.getName() : elementType)
172+
.setDeferChildren(configurable.deferChildren())
173+
.setPrintable(configurable.printObject());
160174
}
161-
return packageName;
162175
}
163176

164-
private String calculatePackage(Element element, String packageName) {
165-
final Name name = processingEnv.getElementUtils().getPackageOf(element).getQualifiedName();
166-
if (name.isEmpty()) {
167-
return "";
177+
private static List<PluginEntry> createPluginAliases(TypeElement pluginClass, PluginEntry.Builder builder) {
178+
return Optional.ofNullable(pluginClass.getAnnotation(PluginAliases.class)).map(PluginAliases::value).stream()
179+
.flatMap(Arrays::stream)
180+
.map(alias -> alias.toLowerCase(Locale.ROOT))
181+
.map(key -> builder.setKey(key).get())
182+
.toList();
183+
}
184+
185+
private void handleUnexpectedError(final Exception e) {
186+
var writer = new StringWriter();
187+
e.printStackTrace(new PrintWriter(writer));
188+
processingEnv
189+
.getMessager()
190+
.printMessage(Kind.ERROR, "Unexpected error processing Log4j annotations: " + writer);
191+
}
192+
193+
private static CharSequence calculatePackageName(
194+
Elements elements, TypeElement typeElement, CharSequence packageName) {
195+
var qualifiedName = elements.getPackageOf(typeElement).getQualifiedName();
196+
if (qualifiedName.isEmpty()) {
197+
return packageName;
168198
}
169-
final String pkgName = name.toString();
170199
if (packageName.isEmpty()) {
171-
return pkgName;
200+
return qualifiedName;
172201
}
173-
if (pkgName.length() == packageName.length()) {
202+
int packageLength = packageName.length();
203+
int qualifiedLength = qualifiedName.length();
204+
if (packageLength == qualifiedLength) {
174205
return packageName;
175206
}
176-
if (pkgName.length() < packageName.length() && packageName.startsWith(pkgName)) {
177-
return pkgName;
207+
if (qualifiedLength < packageLength
208+
&& qualifiedName.contentEquals(packageName.subSequence(0, qualifiedLength))) {
209+
return qualifiedName;
178210
}
179-
180-
return commonPrefix(pkgName, packageName);
211+
return commonPrefix(qualifiedName, packageName);
181212
}
182213

183-
private void writeServiceFile(final String pkgName) throws IOException {
214+
private void writeServiceFile() throws IOException {
184215
final FileObject fileObject = processingEnv
185216
.getFiler()
186-
.createResource(StandardLocation.CLASS_OUTPUT, Strings.EMPTY, SERVICE_FILE_NAME);
217+
.createResource(
218+
StandardLocation.CLASS_OUTPUT,
219+
Strings.EMPTY,
220+
SERVICE_FILE_NAME,
221+
processedElements.toArray(TypeElement[]::new));
187222
try (final PrintWriter writer =
188223
new PrintWriter(new BufferedWriter(new OutputStreamWriter(fileObject.openOutputStream(), UTF_8)))) {
189224
writer.println("# Generated by " + PluginProcessor.class.getName());
190-
writer.println(createFqcn(pkgName));
225+
writer.println(createFqcn(packageName));
191226
}
192227
}
193228

194-
private void writeClassFile(final String pkg, final List<PluginEntry> list) {
195-
final String fqcn = createFqcn(pkg);
229+
private void writeClassFile() {
230+
final String fqcn = createFqcn(packageName);
196231
try (final PrintWriter writer = createSourceFile(fqcn)) {
197-
writer.println("package " + pkg + ".plugins;");
232+
writer.println("package " + packageName + ".plugins;");
198233
writer.println("");
199234
if (enableBndAnnotations) {
200235
writer.println("import aQute.bnd.annotation.Resolution;");
@@ -209,9 +244,9 @@ private void writeClassFile(final String pkg, final List<PluginEntry> list) {
209244
writer.println("public class Log4jPlugins extends PluginService {");
210245
writer.println("");
211246
writer.println(" private static final PluginEntry[] ENTRIES = new PluginEntry[] {");
212-
final int max = list.size() - 1;
213-
for (int i = 0; i < list.size(); ++i) {
214-
final PluginEntry entry = list.get(i);
247+
final int max = pluginIndex.size() - 1;
248+
int current = 0;
249+
for (final PluginEntry entry : pluginIndex) {
215250
writer.println(" PluginEntry.builder()");
216251
writer.println(String.format(" .setKey(\"%s\")", entry.key()));
217252
writer.println(String.format(" .setClassName(\"%s\")", entry.className()));
@@ -227,7 +262,8 @@ private void writeClassFile(final String pkg, final List<PluginEntry> list) {
227262
if (entry.deferChildren()) {
228263
writer.println(" .setDeferChildren(true)");
229264
}
230-
writer.println(" .get()" + (i < max ? "," : Strings.EMPTY));
265+
writer.println(" .get()" + (current < max ? "," : Strings.EMPTY));
266+
current++;
231267
}
232268
writer.println(" };");
233269
writer.println(" @Override");
@@ -238,17 +274,25 @@ private void writeClassFile(final String pkg, final List<PluginEntry> list) {
238274

239275
private PrintWriter createSourceFile(final String fqcn) {
240276
try {
241-
final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(fqcn);
277+
final JavaFileObject sourceFile =
278+
processingEnv.getFiler().createSourceFile(fqcn, processedElements.toArray(TypeElement[]::new));
242279
return new PrintWriter(sourceFile.openWriter());
243280
} catch (IOException e) {
244281
throw new LoggingException("Unable to create Plugin Service Class " + fqcn, e);
245282
}
246283
}
247284

248-
private String createFqcn(String packageName) {
285+
private String createFqcn(CharSequence packageName) {
249286
return packageName + ".plugins.Log4jPlugins";
250287
}
251288

289+
private static String getPluginName(TypeElement pluginClass) {
290+
return Optional.ofNullable(pluginClass.getAnnotation(Plugin.class))
291+
.map(Plugin::value)
292+
.filter(s -> !s.isEmpty())
293+
.orElseGet(() -> pluginClass.getSimpleName().toString());
294+
}
295+
252296
private static String getNamespace(final TypeElement e) {
253297
return Optional.ofNullable(e.getAnnotation(Namespace.class))
254298
.map(Namespace::value)
@@ -267,60 +311,27 @@ private static String getNamespace(final TypeElement e) {
267311
.orElse(Plugin.EMPTY));
268312
}
269313

270-
private static PluginEntry configureNamespace(final TypeElement e, final PluginEntry.Builder builder) {
271-
final Configurable configurable = e.getAnnotation(Configurable.class);
272-
if (configurable != null) {
273-
builder.setNamespace(Node.CORE_NAMESPACE)
274-
.setElementType(
275-
configurable.elementType().isEmpty() ? builder.getName() : configurable.elementType())
276-
.setDeferChildren(configurable.deferChildren())
277-
.setPrintable(configurable.printObject());
278-
} else {
279-
builder.setNamespace(getNamespace(e));
280-
}
281-
return builder.get();
282-
}
283-
284-
/**
285-
* ElementVisitor to scan the Plugin annotation.
286-
*/
287-
private final class PluginElementVisitor extends SimpleElementVisitor8<PluginEntry, Plugin> {
288-
@Override
289-
public PluginEntry visitType(final TypeElement e, final Plugin plugin) {
290-
Objects.requireNonNull(plugin, "Plugin annotation is null.");
291-
String name = plugin.value();
292-
if (name.isEmpty()) {
293-
name = e.getSimpleName().toString();
294-
}
295-
final PluginEntry.Builder builder = PluginEntry.builder()
296-
.setKey(name.toLowerCase(Locale.ROOT))
297-
.setName(name)
298-
.setClassName(
299-
processingEnv.getElementUtils().getBinaryName(e).toString());
300-
return configureNamespace(e, builder);
301-
}
302-
}
303-
304-
private String commonPrefix(final String str1, final String str2) {
314+
private static CharSequence commonPrefix(final CharSequence str1, final CharSequence str2) {
305315
final int minLength = Math.min(str1.length(), str2.length());
306316
for (int i = 0; i < minLength; i++) {
307317
if (str1.charAt(i) != str2.charAt(i)) {
308318
if (i > 1 && str1.charAt(i - 1) == '.') {
309-
return str1.substring(0, i - 1);
319+
return str1.subSequence(0, i - 1);
310320
} else {
311-
return str1.substring(0, i);
321+
return str1.subSequence(0, i);
312322
}
313323
}
314324
}
315-
return str1.substring(0, minLength);
325+
return str1.subSequence(0, minLength);
316326
}
317327

318328
private boolean isServiceConsumerClassPresent() {
319329
// Looks for the presence of the annotation on the classpath, not the annotation processor path.
320330
return processingEnv.getElementUtils().getTypeElement("aQute.bnd.annotation.spi.ServiceConsumer") != null;
321331
}
322332

323-
private void handleOptions(Map<String, String> options) {
333+
private void handleOptions() {
334+
var options = processingEnv.getOptions();
324335
packageName = options.getOrDefault(PLUGIN_PACKAGE, "");
325336
String enableBndAnnotationsOption = options.get(ENABLE_BND_ANNOTATIONS);
326337
if (enableBndAnnotationsOption != null) {
@@ -329,38 +340,4 @@ private void handleOptions(Map<String, String> options) {
329340
this.enableBndAnnotations = isServiceConsumerClassPresent();
330341
}
331342
}
332-
333-
/**
334-
* ElementVisitor to scan the PluginAliases annotation.
335-
*/
336-
private final class PluginAliasesElementVisitor extends SimpleElementVisitor8<Collection<PluginEntry>, Plugin> {
337-
338-
private PluginAliasesElementVisitor() {
339-
super(List.of());
340-
}
341-
342-
@Override
343-
public Collection<PluginEntry> visitType(final TypeElement e, final Plugin plugin) {
344-
final PluginAliases aliases = e.getAnnotation(PluginAliases.class);
345-
if (aliases == null) {
346-
return DEFAULT_VALUE;
347-
}
348-
String name = plugin.value();
349-
if (name.isEmpty()) {
350-
name = e.getSimpleName().toString();
351-
}
352-
final PluginEntry.Builder builder = PluginEntry.builder()
353-
.setName(name)
354-
.setClassName(
355-
processingEnv.getElementUtils().getBinaryName(e).toString());
356-
configureNamespace(e, builder);
357-
final Collection<PluginEntry> entries = new ArrayList<>(aliases.value().length);
358-
for (final String alias : aliases.value()) {
359-
final PluginEntry entry =
360-
builder.setKey(alias.toLowerCase(Locale.ROOT)).get();
361-
entries.add(entry);
362-
}
363-
return entries;
364-
}
365-
}
366343
}

0 commit comments

Comments
 (0)