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