25
25
import java .io .PrintWriter ;
26
26
import java .io .StringWriter ;
27
27
import java .util .ArrayList ;
28
+ import java .util .Arrays ;
28
29
import java .util .Collection ;
29
30
import java .util .Collections ;
30
31
import java .util .List ;
32
+ import java .util .Locale ;
31
33
import java .util .Map ;
32
34
import java .util .Objects ;
33
35
import java .util .Set ;
39
41
import javax .lang .model .SourceVersion ;
40
42
import javax .lang .model .element .Element ;
41
43
import javax .lang .model .element .ElementVisitor ;
44
+ import javax .lang .model .element .ExecutableElement ;
45
+ import javax .lang .model .element .Modifier ;
42
46
import javax .lang .model .element .TypeElement ;
47
+ import javax .lang .model .element .VariableElement ;
43
48
import javax .lang .model .util .Elements ;
44
49
import javax .lang .model .util .SimpleElementVisitor7 ;
50
+ import javax .lang .model .util .Types ;
45
51
import javax .tools .Diagnostic ;
46
52
import javax .tools .FileObject ;
47
53
import javax .tools .StandardLocation ;
48
54
import org .apache .logging .log4j .core .config .plugins .Plugin ;
49
55
import org .apache .logging .log4j .core .config .plugins .PluginAliases ;
56
+ import org .apache .logging .log4j .core .config .plugins .PluginBuilderAttribute ;
50
57
import org .apache .logging .log4j .util .Strings ;
51
58
52
59
/**
@@ -83,6 +90,12 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
83
90
final Set <? extends Element > elements = roundEnv .getElementsAnnotatedWith (Plugin .class );
84
91
collectPlugins (elements );
85
92
processedElements .addAll (elements );
93
+
94
+ // process plugin builder Attributes
95
+ final Set <? extends Element > pluginAttributeBuilderElements =
96
+ roundEnv .getElementsAnnotatedWith (PluginBuilderAttribute .class );
97
+ processBuilderAttributes (pluginAttributeBuilderElements );
98
+ processedElements .addAll (pluginAttributeBuilderElements );
86
99
}
87
100
// Write the cache file
88
101
if (roundEnv .processingOver () && !processedElements .isEmpty ()) {
@@ -107,6 +120,93 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
107
120
return false ;
108
121
}
109
122
123
+ private void processBuilderAttributes (final Iterable <? extends Element > elements ) {
124
+ for (final Element element : elements ) {
125
+ if (element instanceof VariableElement ) {
126
+ processBuilderAttributes ((VariableElement ) element );
127
+ }
128
+ }
129
+ }
130
+
131
+ private void processBuilderAttributes (final VariableElement element ) {
132
+ final String fieldName = element .getSimpleName ().toString (); // Getting the name of the field
133
+ SuppressWarnings suppress = element .getAnnotation (SuppressWarnings .class );
134
+ if (suppress != null && Arrays .asList (suppress .value ()).contains ("log4j.public.setter" )) {
135
+ // Suppress the warning due to annotation
136
+ return ;
137
+ }
138
+ final Element enclosingElement = element .getEnclosingElement ();
139
+ // `element is a field
140
+ if (enclosingElement instanceof TypeElement ) {
141
+ final TypeElement typeElement = (TypeElement ) enclosingElement ;
142
+ // Check the siblings of the field
143
+ for (final Element enclosedElement : typeElement .getEnclosedElements ()) {
144
+ // `enclosedElement is a method or constructor
145
+ if (enclosedElement instanceof ExecutableElement ) {
146
+ final ExecutableElement methodElement = (ExecutableElement ) enclosedElement ;
147
+ final String methodName = methodElement .getSimpleName ().toString ();
148
+
149
+ if ((methodName .toLowerCase (Locale .ROOT ).startsWith ("set" ) // Typical pattern for setters
150
+ || methodName
151
+ .toLowerCase (Locale .ROOT )
152
+ .startsWith ("with" )) // Typical pattern for setters
153
+ && methodElement .getParameters ().size ()
154
+ == 1 // It is a weird pattern to not have public setter
155
+ ) {
156
+
157
+ Types typeUtils = processingEnv .getTypeUtils ();
158
+
159
+ boolean followsNamePattern = methodName
160
+ .toLowerCase (Locale .ROOT )
161
+ .equals (String .format ("set%s" , fieldName .toLowerCase (Locale .ROOT )))
162
+ || methodName
163
+ .toLowerCase (Locale .ROOT )
164
+ .equals (String .format ("with%s" , fieldName .toLowerCase (Locale .ROOT )));
165
+
166
+ if (fieldName .toLowerCase (Locale .ROOT ).startsWith ("is" )) {
167
+ followsNamePattern = methodName
168
+ .toLowerCase (Locale .ROOT )
169
+ .equals (String .format (
170
+ "set%s" ,
171
+ fieldName
172
+ .toLowerCase (Locale .ROOT )
173
+ .substring (2 )))
174
+ || methodName
175
+ .toLowerCase (Locale .ROOT )
176
+ .equals (String .format (
177
+ "with%s" ,
178
+ fieldName
179
+ .toLowerCase (Locale .ROOT )
180
+ .substring (2 )));
181
+ }
182
+
183
+ // Check if method is public
184
+ boolean isPublicMethod = methodElement .getModifiers ().contains (Modifier .PUBLIC );
185
+
186
+ // Check if the return type of the method element is Assignable.
187
+ // Assuming it is a builder class the type of it should be assignable to its parent
188
+ boolean checkForAssignable = typeUtils .isAssignable (
189
+ methodElement .getReturnType (),
190
+ methodElement .getEnclosingElement ().asType ());
191
+
192
+ boolean foundPublicSetter = followsNamePattern && checkForAssignable && isPublicMethod ;
193
+ if (foundPublicSetter ) {
194
+ // Hurray we found a public setter for the field!
195
+ return ;
196
+ }
197
+ }
198
+ }
199
+ }
200
+ // If the setter was not found generate a compiler warning.
201
+ processingEnv
202
+ .getMessager ()
203
+ .printMessage (
204
+ Diagnostic .Kind .WARNING ,
205
+ String .format ("The field `%s` does not have a public setter" , fieldName ),
206
+ element );
207
+ }
208
+ }
209
+
110
210
private void collectPlugins (final Iterable <? extends Element > elements ) {
111
211
final Elements elementUtils = processingEnv .getElementUtils ();
112
212
final ElementVisitor <PluginEntry , Plugin > pluginVisitor = new PluginElementVisitor (elementUtils );
0 commit comments