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  
20  package org.apache.myfaces.tobago.apt.processor;
21  
22  
23  import org.apache.commons.io.IOUtils;
24  import org.apache.commons.lang3.StringUtils;
25  import org.apache.myfaces.tobago.apt.AnnotationUtils;
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"); // XXX name?
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     Writer writer = null;
126     try {
127       final String path = "checkstyle-tobago.xml";
128       final String name = (StringUtils.isNotBlank(targetCheckstyle) ? targetCheckstyle + '/' : "") + path;
129       final FileObject resource = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", name);
130       info("Writing to file: " + resource.toUri());
131       writer = resource.openWriter();
132 
133       final TransformerFactory transFactory = TransformerFactory.newInstance();
134       transFactory.setAttribute("indent-number", 2);
135       final Transformer transformer = transFactory.newTransformer();
136       transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Puppy Crawl//DTD Check Configuration 1.2//EN");
137       transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://www.puppycrawl.com/dtds/configuration_1_2.dtd");
138       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
139       transformer.transform(new DOMSource(document), new StreamResult(writer));
140     } finally {
141       IOUtils.closeQuietly(writer);
142     }
143   }
144 
145   protected void appendTag(
146       final TypeElement typeElement, final String taglib, final Element parent, final Document document)
147       throws ClassNotFoundException {
148     final Tag annotationTag = typeElement.getAnnotation(Tag.class);
149     if (annotationTag != null) {
150       checkDuplicates(annotationTag.name());
151       // TODO configure replacement
152       final String className;
153       if (typeElement.getAnnotation(SimpleTag.class) != null || typeElement.getAnnotation(ValidatorTag.class) != null) {
154         className = AnnotationUtils.generatedTagName(typeElement);
155       } else if (typeElement.getAnnotation(UIComponentTag.class) != null) {
156         className = "org.apache.myfaces.tobago.internal.taglib." + StringUtils.capitalize(annotationTag.name())
157             + "Tag";
158       } else {
159         throw new TobagoGeneratorException("Not supported: " + typeElement.getQualifiedName());
160       }
161       info("Replacing: " + typeElement.getQualifiedName() + " -> " + className);
162       if (typeElement.getAnnotation(Deprecated.class) != null) {
163         addTag(taglib, parent, annotationTag.name(), document);
164       }
165       addAttributes(typeElement, taglib, parent, annotationTag.name(), document);
166       if (annotationTag.deprecatedName() != null && annotationTag.deprecatedName().length() > 0) {
167         addTag(taglib, parent, annotationTag.deprecatedName(), document);
168         addAttributes(typeElement, taglib, parent, annotationTag.name(), document);
169       }
170       addAttributesForTag(typeElement, taglib, parent, annotationTag.name(), document);
171     }
172   }
173 
174   protected void addTag(final String taglib, final Element parent, final String tagName, final Document document) {
175 
176     final String format = "<" + taglib + ":" + tagName + "\\b";
177     final String message = "The tag '" + tagName + "' is deprecated.";
178 
179     final Element tag = createRegexpModule(format, message, document);
180 
181     parent.appendChild(tag);
182   }
183 
184   private void checkDuplicates(final String tagName) {
185     if (tagSet.contains(tagName)) {
186       throw new IllegalArgumentException("tag with name " + tagName + " already defined!");
187     } else {
188       tagSet.add(tagName);
189     }
190   }
191 
192   private void resetDuplicateList() {
193     tagSet = new HashSet<>();
194   }
195 
196   protected void addAttributesForTag(
197       final TypeElement type, final String taglib, final Element parent, final String tagName,
198       final Document document)
199       throws ClassNotFoundException {
200 
201     final List<String> attributes = new ArrayList<>();
202     for (final javax.lang.model.element.Element element : getAllMembers(type)) {
203       if (element instanceof ExecutableElement) {
204         final ExecutableElement executableElement = (ExecutableElement) element;
205         if (executableElement.getAnnotation(TagAttribute.class) == null
206             && executableElement.getAnnotation(UIComponentTagAttribute.class) == null) {
207           continue;
208         }
209         final TagAttribute tagAttribute = executableElement.getAnnotation(TagAttribute.class);
210         if (tagAttribute != null) {
211           final String simpleName = executableElement.getSimpleName().toString();
212           if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
213 
214             String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
215             if (tagAttribute.name().length() > 0) {
216               attributeStr = tagAttribute.name();
217             }
218             attributes.add(attributeStr);
219           }
220         }
221       }
222     }
223     final String regexp = getRegExpForUndefinedAttributes(taglib, tagName, attributes);
224 
225     final String message = "Found an unknown attribute in tag '" + tagName + "'.";
226 
227     Element module = createRegexpModule(regexp, message, document);
228     parent.appendChild(module);
229 
230     if (taglib.equals("tx")) {
231       final String m2 = "The taglib tx is deprecated, please use tc with labelLayout. Found tag 'tx:" + tagName + "'.";
232       module = createRegexpModule("<" + taglib + ":" + tagName + "\\b", m2, document);
233       parent.appendChild(module);
234     }
235 
236   }
237 
238   protected void addAttributes(
239       final TypeElement type, final String taglib, final Element tagElement, final String tagName,
240       final Document document)
241       throws ClassNotFoundException {
242 
243     for (final javax.lang.model.element.Element element : getAllMembers(type)) {
244       if (element instanceof ExecutableElement) {
245         final ExecutableElement executableElement = (ExecutableElement) element;
246         if (executableElement.getAnnotation(TagAttribute.class) == null
247             && executableElement.getAnnotation(UIComponentTagAttribute.class) == null) {
248           continue;
249         }
250         addAttribute(executableElement, taglib, tagElement, tagName, document);
251       }
252     }
253   }
254 
255   protected void addAttribute(
256       final ExecutableElement declaration, final String taglib, final Element parent, final String tagName,
257       final Document document) {
258     final TagAttribute tagAttribute = declaration.getAnnotation(TagAttribute.class);
259     final Deprecated deprecatedAnnotation = declaration.getAnnotation(Deprecated.class);
260     if (tagAttribute != null && deprecatedAnnotation != null) {
261       final String simpleName = declaration.getSimpleName().toString();
262       if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
263 
264         String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
265         if (tagAttribute.name().length() > 0) {
266           attributeStr = tagAttribute.name();
267         }
268 
269         final String format = "<" + taglib + ":" + tagName + "\\b[^<]*\\b" + attributeStr + "=";
270         final String message = "The attribute '" + attributeStr + "' is deprecated for tag '" + tagName + "'";
271 
272         final Element module = createRegexpModule(format, message, document);
273 
274         parent.appendChild(module);
275       } else {
276         throw new IllegalArgumentException("Only setter allowed found: " + simpleName);
277       }
278     }
279   }
280 
281   private List<? extends javax.lang.model.element.Element> getAllMembers(final TypeElement type) {
282     final List<? extends javax.lang.model.element.Element> members
283         = new ArrayList<javax.lang.model.element.Element>(processingEnv.getElementUtils().getAllMembers(type));
284     members.sort(Comparator.comparing(d -> d.getSimpleName().toString()));
285     return members;
286   }
287 
288   private void addLib(final Taglib taglibAnnotation, final Element parent, final Document document) {
289 
290     final String shortName = taglibAnnotation.shortName();
291     if (shortName.length() != 2) {
292 
293     }
294     final String uri = taglibAnnotation.uri();
295 
296     final String format = "(?<!" + shortName + ")=(\"|')" + uri + "(\"|')";
297     final String message = "The taglib declaration is not like 'xmlns:" + shortName + "=\"" + uri + "\"'";
298 
299     final Element module = createRegexpModule(format, message, document);
300 
301     parent.appendChild(module);
302   }
303 
304   protected Element createRegexpModule(final String formatValue, final String messageValue, final Document document) {
305     final Element module = document.createElement("module");
306     module.setAttribute("name", "RegexpMultiline");
307 
308     final Element format = document.createElement("property");
309     format.setAttribute("name", "format");
310     format.setAttribute("value", formatValue);
311     module.appendChild(format);
312 
313     final Element message = document.createElement("property");
314     message.setAttribute("name", "message");
315     message.setAttribute("value", messageValue);
316     module.appendChild(message);
317 
318     final Element severity = document.createElement("property");
319     severity.setAttribute("name", "severity");
320     severity.setAttribute("value", "warning");
321     module.appendChild(severity);
322 
323     return module;
324   }
325 
326   protected static String getRegExpForUndefinedAttributes(
327       final String taglib, final String tagName, final List<String> attributes) {
328     final StringBuilder builder = new StringBuilder();
329     builder.append("<");
330     builder.append(taglib);
331     builder.append(":");
332     builder.append(tagName);
333     builder.append("(\\s+(");
334     for (final String attribute : attributes) {
335       builder.append(attribute);
336       builder.append('|');
337     }
338     builder.append("xmlns:\\w*)=\\\"([^\"=<>]*)\\\")*\\s+(?!(");
339     for (final String attribute : attributes) {
340       builder.append(attribute);
341       builder.append('|');
342     }
343     builder.append("xmlns:\\w*|\\W))");
344     return builder.toString();
345   }
346 
347 }