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;
21  
22  import com.sun.mirror.apt.AnnotationProcessorEnvironment;
23  import com.sun.mirror.apt.Filer;
24  import com.sun.mirror.declaration.ClassDeclaration;
25  import com.sun.mirror.declaration.InterfaceDeclaration;
26  import com.sun.mirror.declaration.MethodDeclaration;
27  import com.sun.mirror.declaration.PackageDeclaration;
28  import com.sun.mirror.type.InterfaceType;
29  import org.apache.commons.io.IOUtils;
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.myfaces.tobago.apt.annotation.Tag;
32  import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
33  import org.apache.myfaces.tobago.apt.annotation.Taglib;
34  import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  
38  import javax.xml.parsers.DocumentBuilder;
39  import javax.xml.parsers.DocumentBuilderFactory;
40  import javax.xml.parsers.ParserConfigurationException;
41  import javax.xml.transform.OutputKeys;
42  import javax.xml.transform.Transformer;
43  import javax.xml.transform.TransformerException;
44  import javax.xml.transform.TransformerFactory;
45  import javax.xml.transform.dom.DOMSource;
46  import javax.xml.transform.stream.StreamResult;
47  import java.io.File;
48  import java.io.IOException;
49  import java.io.Writer;
50  import java.util.Collection;
51  import java.util.HashSet;
52  import java.util.Locale;
53  import java.util.Set;
54  
55  public class CheckstyleConfigAnnotationVisitor extends AbstractAnnotationVisitor {
56  
57    private Set<String> tagSet = new HashSet<String>();
58  
59    public CheckstyleConfigAnnotationVisitor(AnnotationProcessorEnvironment env) {
60      super(env);
61    }
62  
63    public void process() throws Exception {
64      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
65      dbf.setValidating(false);
66      DocumentBuilder parser = dbf.newDocumentBuilder();
67      Document document = parser.newDocument();
68      Element module = document.createElement("module");
69      module.setAttribute("name", "Checker");
70  
71      for (PackageDeclaration packageDeclaration : getCollectedPackageDeclarations()) {
72        Taglib taglibAnnotation = packageDeclaration.getAnnotation(Taglib.class);
73        createCheckstyleConfig(taglibAnnotation, packageDeclaration, module, document);
74      }
75  
76      document.appendChild(module);
77  
78      writeCheckstyleConfig(document);
79    }
80  
81    private Document createCheckstyleConfig(
82        Taglib taglibAnnotation, PackageDeclaration packageDeclaration, Element module, Document document)
83        throws ParserConfigurationException {
84      resetDuplicateList();
85  
86  
87      addLib(taglibAnnotation, module, document);
88  
89      for (ClassDeclaration declaration : getCollectedClassDeclarations()) {
90        if (declaration.getPackage().equals(packageDeclaration)) {
91          appendTag(declaration, taglibAnnotation.shortName(), module, document);
92        }
93      }
94      for (InterfaceDeclaration declaration : getCollectedInterfaceDeclarations()) {
95        if (declaration.getPackage().equals(packageDeclaration)) {
96          appendTag(declaration, taglibAnnotation.shortName(), module, document);
97        }
98      }
99  
100     return document;
101   }
102 
103   protected void writeCheckstyleConfig(Document document) throws
104       IOException, TransformerException {
105     Writer writer = null;
106     try {
107       getEnv().getMessager().printNotice("Create DOM");
108       final String path = "checkstyle-tobago.xml";
109       writer = getEnv().getFiler().createTextFile(
110           Filer.Location.SOURCE_TREE,
111           "",
112           new File(path),
113           null);
114       TransformerFactory transFactory = TransformerFactory.newInstance();
115       transFactory.setAttribute("indent-number", 2);
116       Transformer transformer = transFactory.newTransformer();
117 
118       transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "-//Puppy Crawl//DTD Check Configuration 1.2//EN");
119       transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://www.puppycrawl.com/dtds/configuration_1_2.dtd");
120 
121       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
122       transformer.transform(new DOMSource(document), new StreamResult(writer));
123       getEnv().getMessager().printNotice("Write to file '" + path + "'");
124     } finally {
125       IOUtils.closeQuietly(writer);
126     }
127   }
128 
129   protected void appendTag(ClassDeclaration declaration, String taglib, Element parent, Document document) {
130     Tag annotationTag = declaration.getAnnotation(Tag.class);
131     checkDuplicates(annotationTag.name());
132     if (declaration.getAnnotation(Deprecated.class) != null) {
133       addTag(taglib, parent, annotationTag.name(), document);
134     }
135     addAttributes(declaration, taglib, parent, annotationTag.name(), document);
136     if (StringUtils.isNotEmpty(annotationTag.deprecatedName())) {
137       addTag(taglib, parent, annotationTag.deprecatedName(), document);
138       addAttributes(declaration, taglib, parent, annotationTag.deprecatedName(), document);
139     }
140   }
141 
142   protected void appendTag(InterfaceDeclaration declaration, String taglib, Element parent, Document document) {
143     Tag annotationTag = declaration.getAnnotation(Tag.class);
144     if (annotationTag != null) {
145       checkDuplicates(annotationTag.name());
146       // TODO configure replacement
147 
148       String className =
149           declaration.getQualifiedName().substring(0, declaration.getQualifiedName().length() - "Declaration".length());
150       if (declaration.getAnnotation(UIComponentTag.class) != null) {
151         className = "org.apache.myfaces.tobago.internal.taglib." + StringUtils.capitalize(annotationTag.name()) + "Tag";
152       }
153       String msg = "Replacing: " + declaration.getQualifiedName() + " -> " + className;
154       getEnv().getMessager().printNotice(msg);
155       if (declaration.getAnnotation(Deprecated.class) != null) {
156         addTag(taglib, parent, annotationTag.name(), document);
157       }
158       addAttributes(declaration, taglib, parent, annotationTag.name(), document);
159       if (StringUtils.isNotEmpty(annotationTag.deprecatedName())) {
160         addTag(taglib, parent, annotationTag.deprecatedName(), document);
161         addAttributes(declaration, taglib, parent, annotationTag.name(), document);
162       }
163     }
164   }
165 
166   protected void addTag(String taglib, Element parent, String tagName, Document document) {
167 
168     final String format = "<" + taglib + ":" + tagName + "\\b";
169     final String message = "The tag '" + tagName + "' is deprecated.";
170 
171     Element module = createRegexpModule(format, message, document);
172 
173     parent.appendChild(module);
174   }
175 
176   private void checkDuplicates(String tagName) {
177     if (tagSet.contains(tagName)) {
178       throw new IllegalArgumentException("tag with name " + tagName + " already defined!");
179     } else {
180       tagSet.add(tagName);
181     }
182   }
183 
184   protected void addAttributes(
185       Collection<InterfaceType> interfaces, String taglib, Element parent, String tagName, Document document) {
186     for (InterfaceType type : interfaces) {
187       addAttributes(type.getDeclaration(), taglib, parent, tagName, document);
188     }
189   }
190 
191   protected void addAttributes(
192       InterfaceDeclaration type, String taglib, Element parent, String tagName, Document document) {
193     addAttributes(type.getSuperinterfaces(), taglib, parent, tagName, document);
194     for (MethodDeclaration declaration : getCollectedMethodDeclarations()) {
195       if (declaration.getDeclaringType().equals(type)) {
196         addAttribute(declaration, taglib, parent, tagName, document);
197       }
198     }
199   }
200 
201   protected void addAttributes(
202       ClassDeclaration d, String taglib, Element parent, String tagName, Document document) {
203 
204     for (MethodDeclaration declaration : getCollectedMethodDeclarations()) {
205       if (d.getQualifiedName().
206           equals(declaration.getDeclaringType().getQualifiedName())) {
207         addAttribute(declaration, taglib, parent, tagName, document);
208       }
209     }
210     addAttributes(d.getSuperinterfaces(), taglib, parent, tagName, document);
211     if (d.getSuperclass() != null) {
212       addAttributes(d.getSuperclass().getDeclaration(), taglib, parent, tagName, document);
213     }
214   }
215 
216   private void resetDuplicateList() {
217     tagSet = new HashSet<String>();
218   }
219 
220   protected void addAttribute(
221       MethodDeclaration declaration, String taglib, Element parent, String tagName, Document document) {
222     TagAttribute tagAttribute = declaration.getAnnotation(TagAttribute.class);
223     Deprecated deprecatedAnnotation = declaration.getAnnotation(Deprecated.class);
224     if (tagAttribute != null && deprecatedAnnotation != null) {
225       String simpleName = declaration.getSimpleName();
226       if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
227 
228         String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
229         if (tagAttribute.name().length() > 0) {
230           attributeStr = tagAttribute.name();
231         }
232 
233         final String format = "<" + taglib + ":" + tagName + "\\b[^<]*\\b" + attributeStr + "=";
234         final String message = "The attribute '" + attributeStr + "' is deprecated for tag '" + tagName + "'";
235 
236         Element module = createRegexpModule(format, message, document);
237 
238         parent.appendChild(module);
239       } else {
240         throw new IllegalArgumentException("Only setter allowed found: " + simpleName);
241       }
242     }
243   }
244 
245   private void addLib(Taglib taglibAnnotation, Element parent, Document document) {
246 
247     final String shortName = taglibAnnotation.shortName();
248     if (shortName.length() != 2) {
249 
250     }
251     final String uri = taglibAnnotation.uri();
252 
253     final String format = "(?<!" + shortName + ")=(\"|')" + uri + "(\"|')";
254     final String message = "The taglib declaration is not like 'xmlns:" + shortName + "=\"" + uri + "\"'";
255 
256     final Element module = createRegexpModule(format, message, document);
257 
258     parent.appendChild(module);
259   }
260 
261   protected Element createRegexpModule(String formatValue, String messageValue, Document document) {
262     Element module = document.createElement("module");
263     module.setAttribute("name", "RegexpMultiline");
264 
265     Element format = document.createElement("property");
266     format.setAttribute("name", "format");
267     format.setAttribute("value", formatValue);
268     module.appendChild(format);
269 
270     Element message = document.createElement("property");
271     message.setAttribute("name", "message");
272     message.setAttribute("value", messageValue);
273     module.appendChild(message);
274 
275     Element severity = document.createElement("property");
276     severity.setAttribute("name", "severity");
277     severity.setAttribute("value", "warning");
278     module.appendChild(severity);
279 
280     return module;
281   }
282 }