1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.apt.processor;
21
22
23 import org.apache.commons.lang3.StringUtils;
24 import org.apache.myfaces.tobago.apt.AnnotationUtils;
25 import org.apache.myfaces.tobago.apt.annotation.ConverterTag;
26 import org.apache.myfaces.tobago.apt.annotation.SimpleTag;
27 import org.apache.myfaces.tobago.apt.annotation.Tag;
28 import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
29 import org.apache.myfaces.tobago.apt.annotation.Taglib;
30 import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
31 import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
32 import org.apache.myfaces.tobago.apt.annotation.ValidatorTag;
33 import org.w3c.dom.Document;
34 import org.w3c.dom.Element;
35
36 import javax.annotation.processing.SupportedAnnotationTypes;
37 import javax.annotation.processing.SupportedOptions;
38 import javax.annotation.processing.SupportedSourceVersion;
39 import javax.lang.model.SourceVersion;
40 import javax.lang.model.element.ExecutableElement;
41 import javax.lang.model.element.PackageElement;
42 import javax.lang.model.element.TypeElement;
43 import javax.tools.FileObject;
44 import javax.tools.StandardLocation;
45 import javax.xml.parsers.DocumentBuilder;
46 import javax.xml.parsers.DocumentBuilderFactory;
47 import javax.xml.parsers.ParserConfigurationException;
48 import javax.xml.transform.OutputKeys;
49 import javax.xml.transform.Transformer;
50 import javax.xml.transform.TransformerException;
51 import javax.xml.transform.TransformerFactory;
52 import javax.xml.transform.dom.DOMSource;
53 import javax.xml.transform.stream.StreamResult;
54 import java.io.IOException;
55 import java.io.Writer;
56 import java.util.ArrayList;
57 import java.util.Comparator;
58 import java.util.HashSet;
59 import java.util.List;
60 import java.util.Locale;
61 import java.util.Map;
62 import java.util.Set;
63
64 @SupportedSourceVersion(SourceVersion.RELEASE_8)
65 @SupportedAnnotationTypes({
66 "org.apache.myfaces.tobago.apt.annotation.Tag",
67 "org.apache.myfaces.tobago.apt.annotation.TagAttribute",
68 "org.apache.myfaces.tobago.apt.annotation.Taglib"})
69 @SupportedOptions({
70 CheckstyleConfigGenerator.TARGET_CHECKSTYLE})
71 public class CheckstyleConfigGenerator extends AbstractGenerator {
72
73 static final String TARGET_CHECKSTYLE = "targetCheckstyle";
74
75 private Set<String> tagSet = new HashSet<>();
76
77 private String targetCheckstyle;
78
79 @Override
80 public void configure() {
81 final Map<String, String> options = processingEnv.getOptions();
82 targetCheckstyle = options.get(TARGET_CHECKSTYLE);
83
84 info("Generating the tobago-checkstyle.xml");
85 info("Options:");
86 info(TARGET_CHECKSTYLE + ": " + targetCheckstyle);
87 }
88
89 @Override
90 public void generate() throws ParserConfigurationException, IOException, TransformerException,
91 ClassNotFoundException {
92 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
93 dbf.setValidating(false);
94 final DocumentBuilder parser = dbf.newDocumentBuilder();
95 final Document document = parser.newDocument();
96 final Element module = document.createElement("module");
97 module.setAttribute("name", "Checker");
98
99 for (final PackageElement packageElement : getPackages()) {
100 final Taglib taglibAnnotation = packageElement.getAnnotation(Taglib.class);
101 createCheckstyleConfig(taglibAnnotation, packageElement, module, document);
102 }
103
104 document.appendChild(module);
105
106 writeCheckstyleConfig(document);
107 }
108
109 private Document createCheckstyleConfig(
110 final Taglib taglibAnnotation, final PackageElement packageElement, final Element module, final Document document)
111 throws ParserConfigurationException, ClassNotFoundException {
112 resetDuplicateList();
113
114 addLib(taglibAnnotation, module, document);
115
116 for (final TypeElement typeElement : getTypes()) {
117 if (processingEnv.getElementUtils().getPackageOf(typeElement).equals(packageElement)) {
118 appendTag(typeElement, taglibAnnotation.shortName(), module, document);
119 }
120 }
121 return document;
122 }
123
124 protected void writeCheckstyleConfig(final Document document) throws IOException, TransformerException {
125
126 final String path = "checkstyle-tobago.xml";
127 final String name = (StringUtils.isNotBlank(targetCheckstyle) ? targetCheckstyle + '/' : "") + path;
128 final FileObject resource = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", name);
129 info("Writing to file: " + resource.toUri());
130
131 try (Writer writer = resource.openWriter()) {
132 final TransformerFactory transFactory = TransformerFactory.newInstance();
133 transFactory.setAttribute("indent-number", 2);
134 final Transformer transformer = transFactory.newTransformer();
135 transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Puppy Crawl//DTD Check Configuration 1.2//EN");
136 transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://www.puppycrawl.com/dtds/configuration_1_2.dtd");
137 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
138 transformer.transform(new DOMSource(document), new StreamResult(writer));
139 }
140 }
141
142 protected void appendTag(
143 final TypeElement typeElement, final String taglib, final Element parent, final Document document)
144 throws ClassNotFoundException {
145 final Tag annotationTag = typeElement.getAnnotation(Tag.class);
146 if (annotationTag != null) {
147 checkDuplicates(annotationTag.name());
148
149 final String className;
150 if (typeElement.getAnnotation(SimpleTag.class) != null || typeElement.getAnnotation(ValidatorTag.class) != null
151 || typeElement.getAnnotation(ConverterTag.class) != null) {
152 className = AnnotationUtils.generatedTagName(typeElement);
153 } else if (typeElement.getAnnotation(UIComponentTag.class) != null) {
154 className = "org.apache.myfaces.tobago.internal.taglib." + StringUtils.capitalize(annotationTag.name())
155 + "Tag";
156 } else {
157 throw new TobagoGeneratorException("Not supported: " + typeElement.getQualifiedName());
158 }
159 info("Replacing: " + typeElement.getQualifiedName() + " -> " + className);
160 if (typeElement.getAnnotation(Deprecated.class) != null) {
161 addTag(taglib, parent, annotationTag.name(), document);
162 }
163 addAttributes(typeElement, taglib, parent, annotationTag.name(), document);
164 if (annotationTag.deprecatedName() != null && annotationTag.deprecatedName().length() > 0) {
165 addTag(taglib, parent, annotationTag.deprecatedName(), document);
166 addAttributes(typeElement, taglib, parent, annotationTag.name(), document);
167 }
168 addAttributesForTag(typeElement, taglib, parent, annotationTag.name(), document);
169 }
170 }
171
172 protected void addTag(final String taglib, final Element parent, final String tagName, final Document document) {
173
174 final String format = "<" + taglib + ":" + tagName + "\\b";
175 final String message = "The tag '" + tagName + "' is deprecated.";
176
177 final Element tag = createRegexpModule(format, message, document);
178
179 parent.appendChild(tag);
180 }
181
182 private void checkDuplicates(final String tagName) {
183 if (tagSet.contains(tagName)) {
184 throw new IllegalArgumentException("tag with name " + tagName + " already defined!");
185 } else {
186 tagSet.add(tagName);
187 }
188 }
189
190 private void resetDuplicateList() {
191 tagSet = new HashSet<>();
192 }
193
194 protected void addAttributesForTag(
195 final TypeElement type, final String taglib, final Element parent, final String tagName,
196 final Document document)
197 throws ClassNotFoundException {
198
199 final List<String> attributes = new ArrayList<>();
200 for (final javax.lang.model.element.Element element : getAllMembers(type)) {
201 if (element instanceof ExecutableElement) {
202 final ExecutableElement executableElement = (ExecutableElement) element;
203 if (executableElement.getAnnotation(TagAttribute.class) == null
204 && executableElement.getAnnotation(UIComponentTagAttribute.class) == null) {
205 continue;
206 }
207 final TagAttribute tagAttribute = executableElement.getAnnotation(TagAttribute.class);
208 if (tagAttribute != null) {
209 final String simpleName = executableElement.getSimpleName().toString();
210 if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
211
212 String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
213 if (tagAttribute.name().length() > 0) {
214 attributeStr = tagAttribute.name();
215 }
216 attributes.add(attributeStr);
217 }
218 }
219 }
220 }
221 final String regexp = getRegExpForUndefinedAttributes(taglib, tagName, attributes);
222
223 final String message = "Found an unknown attribute in tag '" + tagName + "'.";
224
225 Element module = createRegexpModule(regexp, message, document);
226 parent.appendChild(module);
227
228 if (taglib.equals("tx")) {
229 final String m2 = "The taglib tx is deprecated, please use tc with labelLayout. Found tag 'tx:" + tagName + "'.";
230 module = createRegexpModule("<" + taglib + ":" + tagName + "\\b", m2, document);
231 parent.appendChild(module);
232 }
233
234 }
235
236 protected void addAttributes(
237 final TypeElement type, final String taglib, final Element tagElement, final String tagName,
238 final Document document)
239 throws ClassNotFoundException {
240
241 for (final javax.lang.model.element.Element element : getAllMembers(type)) {
242 if (element instanceof ExecutableElement) {
243 final ExecutableElement executableElement = (ExecutableElement) element;
244 if (executableElement.getAnnotation(TagAttribute.class) == null
245 && executableElement.getAnnotation(UIComponentTagAttribute.class) == null) {
246 continue;
247 }
248 addAttribute(executableElement, taglib, tagElement, tagName, document);
249 }
250 }
251 }
252
253 protected void addAttribute(
254 final ExecutableElement declaration, final String taglib, final Element parent, final String tagName,
255 final Document document) {
256 final TagAttribute tagAttribute = declaration.getAnnotation(TagAttribute.class);
257 final Deprecated deprecatedAnnotation = declaration.getAnnotation(Deprecated.class);
258 if (tagAttribute != null && deprecatedAnnotation != null) {
259 final String simpleName = declaration.getSimpleName().toString();
260 if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
261
262 String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
263 if (tagAttribute.name().length() > 0) {
264 attributeStr = tagAttribute.name();
265 }
266
267 final String format = "<" + taglib + ":" + tagName + "\\b[^<]*\\b" + attributeStr + "=";
268 final String message = "The attribute '" + attributeStr + "' is deprecated for tag '" + tagName + "'";
269
270 final Element module = createRegexpModule(format, message, document);
271
272 parent.appendChild(module);
273 } else {
274 throw new IllegalArgumentException("Only setter allowed found: " + simpleName);
275 }
276 }
277 }
278
279 private List<? extends javax.lang.model.element.Element> getAllMembers(final TypeElement type) {
280 final List<? extends javax.lang.model.element.Element> members
281 = new ArrayList<javax.lang.model.element.Element>(processingEnv.getElementUtils().getAllMembers(type));
282 members.sort(Comparator.comparing(d -> d.getSimpleName().toString()));
283 return members;
284 }
285
286 private void addLib(final Taglib taglibAnnotation, final Element parent, final Document document) {
287
288 final String shortName = taglibAnnotation.shortName();
289 if (shortName.length() != 2) {
290
291 }
292 final String uri = taglibAnnotation.uri();
293
294 final String format = "(?<!" + shortName + ")=(\"|')" + uri + "(\"|')";
295 final String message = "The taglib declaration is not like 'xmlns:" + shortName + "=\"" + uri + "\"'";
296
297 final Element module = createRegexpModule(format, message, document);
298
299 parent.appendChild(module);
300 }
301
302 protected Element createRegexpModule(final String formatValue, final String messageValue, final Document document) {
303 final Element module = document.createElement("module");
304 module.setAttribute("name", "RegexpMultiline");
305
306 final Element format = document.createElement("property");
307 format.setAttribute("name", "format");
308 format.setAttribute("value", formatValue);
309 module.appendChild(format);
310
311 final Element message = document.createElement("property");
312 message.setAttribute("name", "message");
313 message.setAttribute("value", messageValue);
314 module.appendChild(message);
315
316 final Element severity = document.createElement("property");
317 severity.setAttribute("name", "severity");
318 severity.setAttribute("value", "warning");
319 module.appendChild(severity);
320
321 return module;
322 }
323
324 protected static String getRegExpForUndefinedAttributes(
325 final String taglib, final String tagName, final List<String> attributes) {
326 final StringBuilder builder = new StringBuilder();
327 builder.append("<");
328 builder.append(taglib);
329 builder.append(":");
330 builder.append(tagName);
331 builder.append("(\\s+(");
332 for (final String attribute : attributes) {
333 builder.append(attribute);
334 builder.append('|');
335 }
336 builder.append("xmlns:\\w*)=\\\"([^\"=<>]*)\\\")*\\s+(?!(");
337 for (final String attribute : attributes) {
338 builder.append(attribute);
339 builder.append('|');
340 }
341 builder.append("xmlns:\\w*|\\W))");
342 return builder.toString();
343 }
344
345 }