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.lang.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.lang.model.element.ExecutableElement;
39  import javax.lang.model.element.PackageElement;
40  import javax.lang.model.element.TypeElement;
41  import javax.tools.FileObject;
42  import javax.tools.StandardLocation;
43  import javax.xml.parsers.DocumentBuilder;
44  import javax.xml.parsers.DocumentBuilderFactory;
45  import javax.xml.parsers.ParserConfigurationException;
46  import javax.xml.transform.OutputKeys;
47  import javax.xml.transform.Transformer;
48  import javax.xml.transform.TransformerException;
49  import javax.xml.transform.TransformerFactory;
50  import javax.xml.transform.dom.DOMSource;
51  import javax.xml.transform.stream.StreamResult;
52  import java.io.IOException;
53  import java.io.Writer;
54  import java.util.ArrayList;
55  import java.util.Collections;
56  import java.util.Comparator;
57  import java.util.HashSet;
58  import java.util.List;
59  import java.util.Locale;
60  import java.util.Map;
61  import java.util.Set;
62  
63  @SupportedAnnotationTypes({
64      "org.apache.myfaces.tobago.apt.annotation.Tag",
65      "org.apache.myfaces.tobago.apt.annotation.TagAttribute",
66      "org.apache.myfaces.tobago.apt.annotation.Taglib"})
67  @SupportedOptions({
68      CheckstyleConfigGenerator.TARGET_CHECKSTYLE})
69  public class CheckstyleConfigGenerator extends AbstractGenerator {
70  
71    static final String TARGET_CHECKSTYLE = "targetCheckstyle";
72  
73    private Set<String> tagSet = new HashSet<String>();
74  
75    private String targetCheckstyle;
76  
77    @Override
78    public void configure() {
79      final Map<String, String> options = processingEnv.getOptions();
80      targetCheckstyle = options.get(TARGET_CHECKSTYLE);
81  
82      info("Generating the tobago-checkstyle.xml"); // XXX name?
83      info("Options:");
84      info(TARGET_CHECKSTYLE + ": " + targetCheckstyle);
85    }
86  
87    @Override
88    public void generate() throws ParserConfigurationException, IOException, TransformerException,
89        ClassNotFoundException {
90      final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
91      dbf.setValidating(false);
92      final DocumentBuilder parser = dbf.newDocumentBuilder();
93      final Document document = parser.newDocument();
94      final Element module = document.createElement("module");
95      module.setAttribute("name", "Checker");
96  
97      for (final PackageElement packageElement : getPackages()) {
98        final Taglib taglibAnnotation = packageElement.getAnnotation(Taglib.class);
99        createCheckstyleConfig(taglibAnnotation, packageElement, module, document);
100     }
101 
102     document.appendChild(module);
103 
104     writeCheckstyleConfig(document);
105   }
106 
107   private Document createCheckstyleConfig(
108       final Taglib taglibAnnotation, final PackageElement packageElement, final Element module, final Document document)
109       throws ParserConfigurationException, ClassNotFoundException {
110     resetDuplicateList();
111 
112     addLib(taglibAnnotation, module, document);
113 
114     for (final TypeElement typeElement : getTypes()) {
115       if (processingEnv.getElementUtils().getPackageOf(typeElement).equals(packageElement)) {
116         appendTag(typeElement, taglibAnnotation.shortName(), module, document);
117       }
118     }
119     return document;
120   }
121 
122   protected void writeCheckstyleConfig(final Document document) throws IOException, TransformerException {
123     Writer writer = null;
124     try {
125       final String path = "checkstyle-tobago.xml";
126       final String name = (StringUtils.isNotBlank(targetCheckstyle) ? targetCheckstyle + '/' : "") + path;
127       final FileObject resource = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", name);
128       info("Writing to file: " + resource.toUri());
129       writer = resource.openWriter();
130 
131       final TransformerFactory transFactory = TransformerFactory.newInstance();
132       transFactory.setAttribute("indent-number", 2);
133       final Transformer transformer = transFactory.newTransformer();
134       transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Puppy Crawl//DTD Check Configuration 1.2//EN");
135       transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://www.puppycrawl.com/dtds/configuration_1_2.dtd");
136       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
137       transformer.transform(new DOMSource(document), new StreamResult(writer));
138     } finally {
139       IOUtils.closeQuietly(writer);
140     }
141   }
142 
143   protected void appendTag(
144       final TypeElement typeElement, final String taglib, final Element parent, final Document document)
145       throws ClassNotFoundException {
146     final Tag annotationTag = typeElement.getAnnotation(Tag.class);
147     if (annotationTag != null) {
148       checkDuplicates(annotationTag.name());
149       // TODO configure replacement
150       final String className;
151       if (typeElement.getAnnotation(SimpleTag.class) != null || typeElement.getAnnotation(ValidatorTag.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 RuntimeException("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<String>();
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     List<String> attributes = new ArrayList<String>();
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     Collections.sort(members, new Comparator<javax.lang.model.element.Element>() {
283       @Override
284       public int compare(final javax.lang.model.element.Element d1, final javax.lang.model.element.Element d2) {
285         return d1.getSimpleName().toString().compareTo(d2.getSimpleName().toString());
286       }
287     });
288     return members;
289   }
290 
291   private void addLib(final Taglib taglibAnnotation, final Element parent, final Document document) {
292 
293     final String shortName = taglibAnnotation.shortName();
294     if (shortName.length() != 2) {
295 
296     }
297     final String uri = taglibAnnotation.uri();
298 
299     final String format = "(?<!" + shortName + ")=(\"|')" + uri + "(\"|')";
300     final String message = "The taglib declaration is not like 'xmlns:" + shortName + "=\"" + uri + "\"'";
301 
302     final Element module = createRegexpModule(format, message, document);
303 
304     parent.appendChild(module);
305   }
306 
307   protected Element createRegexpModule(final String formatValue, final String messageValue, final Document document) {
308     final Element module = document.createElement("module");
309     module.setAttribute("name", "RegexpMultiline");
310 
311     final Element format = document.createElement("property");
312     format.setAttribute("name", "format");
313     format.setAttribute("value", formatValue);
314     module.appendChild(format);
315 
316     final Element message = document.createElement("property");
317     message.setAttribute("name", "message");
318     message.setAttribute("value", messageValue);
319     module.appendChild(message);
320 
321     final Element severity = document.createElement("property");
322     severity.setAttribute("name", "severity");
323     severity.setAttribute("value", "warning");
324     module.appendChild(severity);
325 
326     return module;
327   }
328 
329   protected static String getRegExpForUndefinedAttributes(
330       String taglib, String tagName, List<String> attributes) {
331     final StringBuilder builder = new StringBuilder();
332     builder.append("<");
333     builder.append(taglib);
334     builder.append(":");
335     builder.append(tagName);
336     builder.append("(\\s+(");
337     for (String attribute : attributes) {
338       builder.append(attribute);
339       builder.append('|');
340     }
341     builder.append("xmlns:\\w*)=\\\"([^\"=<>]*)\\\")*\\s+(?!(");
342     for (String attribute : attributes) {
343       builder.append(attribute);
344       builder.append('|');
345     }
346     builder.append("xmlns:\\w*|\\W))");
347     return builder.toString();
348   }
349 
350 }