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.Declaration;
26  import com.sun.mirror.declaration.InterfaceDeclaration;
27  import com.sun.mirror.declaration.MethodDeclaration;
28  import com.sun.mirror.declaration.PackageDeclaration;
29  import com.sun.mirror.type.InterfaceType;
30  import org.apache.commons.io.IOUtils;
31  import org.apache.commons.lang.StringUtils;
32  import org.apache.myfaces.tobago.apt.annotation.BodyContent;
33  import org.apache.myfaces.tobago.apt.annotation.BodyContentDescription;
34  import org.apache.myfaces.tobago.apt.annotation.ExtensionTag;
35  import org.apache.myfaces.tobago.apt.annotation.Facet;
36  import org.apache.myfaces.tobago.apt.annotation.Preliminary;
37  import org.apache.myfaces.tobago.apt.annotation.Tag;
38  import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
39  import org.apache.myfaces.tobago.apt.annotation.TagGeneration;
40  import org.apache.myfaces.tobago.apt.annotation.Taglib;
41  import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
42  import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
43  import org.w3c.dom.Document;
44  import org.w3c.dom.Element;
45  
46  import javax.xml.parsers.DocumentBuilder;
47  import javax.xml.parsers.DocumentBuilderFactory;
48  import javax.xml.parsers.ParserConfigurationException;
49  import javax.xml.transform.OutputKeys;
50  import javax.xml.transform.Transformer;
51  import javax.xml.transform.TransformerException;
52  import javax.xml.transform.TransformerFactory;
53  import javax.xml.transform.dom.DOMSource;
54  import javax.xml.transform.stream.StreamResult;
55  import java.io.File;
56  import java.io.IOException;
57  import java.io.Writer;
58  import java.util.Arrays;
59  import java.util.Collection;
60  import java.util.HashSet;
61  import java.util.Locale;
62  import java.util.Map;
63  import java.util.Set;
64  
65  /*
66   * Created: Mar 22, 2005 8:18:35 PM
67   */
68  public class TaglibAnnotationVisitor extends AbstractAnnotationVisitor {
69  
70    private Set<String> tagSet = new HashSet<String>();
71    private Set<String> attributeSet = new HashSet<String>();
72    private String currentTag;
73    private String jsfVersion = "1.1";
74  
75    public TaglibAnnotationVisitor(AnnotationProcessorEnvironment env) {
76      super(env);
77      for (Map.Entry<String, String> entry : getEnv().getOptions().entrySet()) {
78        if (entry.getKey().startsWith("-Ajsf-version=")) {
79          String version = entry.getKey().substring("-Ajsf-version=".length());
80          if ("1.2".equals(version)) {
81            jsfVersion = "1.2";
82          }
83        }
84      }
85    }
86  
87    public void process() throws Exception {
88      for (PackageDeclaration packageDeclaration : getCollectedPackageDeclarations()) {
89        Taglib taglibAnnotation = packageDeclaration.getAnnotation(Taglib.class);
90        Document document = createTaglib(taglibAnnotation, packageDeclaration);
91        writeTaglib(packageDeclaration, taglibAnnotation, document);
92      }
93    }
94  
95    private Document createTaglib(Taglib taglibAnnotation, PackageDeclaration packageDeclaration) throws
96        ParserConfigurationException {
97      resetDuplicateList();
98      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
99      dbf.setValidating(false);
100     DocumentBuilder parser = dbf.newDocumentBuilder();
101 
102     Document document = parser.newDocument();
103 
104     Element taglib = document.createElement("taglib");
105     if (isMinium12()) {
106       taglib.setAttribute("xmlns", "http://java.sun.com/xml/ns/javaee");
107       taglib.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
108       taglib.setAttribute("xsi:schemaLocation",
109         "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd");
110       taglib.setAttribute("version", "2.1");
111     }
112     if (isMinium12()) {
113       addLeafTextElement("1.2", "tlib-version", taglib, document);
114     } else {
115       addLeafTextElement(taglibAnnotation.tlibVersion(), "tlib-version", taglib, document);
116     }
117     if (!isMinium12()) {
118       addLeafTextElement(taglibAnnotation.jspVersion(), "jsp-version", taglib, document);
119     }
120     addLeafTextElement(taglibAnnotation.shortName(), "short-name", taglib, document);
121     addLeafTextElement(taglibAnnotation.uri(), "uri", taglib, document);
122     String displayName = taglibAnnotation.displayName();
123     if (displayName == null || displayName.length() == 0) {
124       displayName = taglibAnnotation.shortName();
125     }
126     addLeafTextElement(displayName, "display-name", taglib, document);
127     String description = packageDeclaration.getDocComment();
128     if (description != null) {
129       addLeafCDATAElement(description, "description", taglib, document);
130     }
131     for (String listenerClass : taglibAnnotation.listener()) {
132       Element listener = document.createElement("listener");
133       // TODO check listenerClass implements ServletContextListener !!
134       addLeafTextElement(listenerClass, "listener-class", listener, document);
135       taglib.appendChild(listener);
136     }
137 
138     for (ClassDeclaration decl : getCollectedClassDeclarations()) {
139       if (decl.getPackage().equals(packageDeclaration)) {
140         appendTag(decl, taglib, document);
141       }
142     }
143     for (InterfaceDeclaration decl : getCollectedInterfaceDeclarations()) {
144       if (decl.getPackage().equals(packageDeclaration)) {
145         appendTag(decl, taglib, document);
146       }
147     }
148     document.appendChild(taglib);
149     return document;
150   }
151 
152   protected void writeTaglib(PackageDeclaration packageDeclaration, Taglib taglibAnnotation, Document document) throws
153       IOException, TransformerException {
154     Writer writer = null;
155     try {
156       getEnv().getMessager().printNotice("Create DOM");
157       writer = getEnv().getFiler().createTextFile(Filer.Location.SOURCE_TREE,
158           packageDeclaration.getQualifiedName(),
159           new File(taglibAnnotation.fileName()), null);
160       TransformerFactory transFactory = TransformerFactory.newInstance();
161       transFactory.setAttribute("indent-number", 2);
162       Transformer transformer = transFactory.newTransformer();
163       if (!isMinium12()) {
164         transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
165             "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN");
166         transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
167             "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"
168         );
169       }
170       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
171       transformer.transform(new DOMSource(document),
172           new StreamResult(writer));
173       getEnv().getMessager().printNotice("Write to file " + packageDeclaration.getQualifiedName()
174           + " " + taglibAnnotation.fileName());
175     } finally {
176       IOUtils.closeQuietly(writer);
177     }
178   }
179 
180   protected void appendTag(ClassDeclaration decl, Element parent, Document document) {
181     Tag annotationTag = decl.getAnnotation(Tag.class);
182     checkDuplicates(annotationTag.name());
183     resetAttributeDuplicateList();
184     String className = decl.getQualifiedName();
185     TagGeneration tagGeneration = decl.getAnnotation(TagGeneration.class);
186     if (tagGeneration != null) {
187       className = tagGeneration.className();
188     }
189     Element tag = createTag(decl, annotationTag, className, document, false);
190     addAttributes(decl, tag, document);
191     parent.appendChild(tag);
192     if (annotationTag.deprecatedName() != null&&annotationTag.deprecatedName().length() > 0) {
193       Element deprecatedTag = createTag(decl, annotationTag, className, document, true);
194       addAttributes(decl, deprecatedTag, document);
195       parent.appendChild(deprecatedTag);
196     }
197   }
198 
199   protected void appendTag(InterfaceDeclaration decl, Element parent, Document document) {
200     Tag annotationTag = decl.getAnnotation(Tag.class);
201     if (annotationTag != null) {
202       checkDuplicates(annotationTag.name());
203       resetAttributeDuplicateList();
204       // TODO configure replacement
205 
206       String className =
207           decl.getQualifiedName().substring(0, decl.getQualifiedName().length() - "Declaration".length());
208       if (decl.getAnnotation(UIComponentTag.class) != null) {
209         className = "org.apache.myfaces.tobago.internal.taglib." + StringUtils.capitalize(annotationTag.name()) + "Tag";
210       }
211       //decl.getQualifiedName().replaceAll("Declaration", "");
212       String msg = "Replacing: " + decl.getQualifiedName() + " -> " + className;
213       getEnv().getMessager().printNotice(msg);
214       Element tag = createTag(decl, annotationTag, className, document, false);
215       addAttributes(decl, tag, document);
216       parent.appendChild(tag);
217       if (annotationTag.deprecatedName() != null&&annotationTag.deprecatedName().length() > 0) {
218         Element deprecatedTag = createTag(decl, annotationTag, className, document, true);
219         addAttributes(decl, deprecatedTag, document);
220         parent.appendChild(deprecatedTag);
221       }
222     }
223   }
224 
225   protected Element createTag(
226       Declaration decl, Tag annotationTag, String className, Document document, boolean deprecated) {
227     Element tagElement = document.createElement("tag");
228     if (deprecated) {
229       addLeafTextElement(annotationTag.deprecatedName(), "name", tagElement, document);
230     } else {
231       addLeafTextElement(annotationTag.name(), "name", tagElement, document);
232     }
233     addLeafTextElement(className, "tag-class", tagElement, document);
234     String tagExtraInfo = annotationTag.tagExtraInfoClassName();
235     if (tagExtraInfo != null&& tagExtraInfo.length() > 0) {
236       // TODO check tagExtraInfo extends TagExtraInfo         
237       addLeafTextElement(tagExtraInfo, "tei-class", tagElement, document);
238     }
239     BodyContent bodyContent = annotationTag.bodyContent();
240     BodyContentDescription contentDescription = decl.getAnnotation(BodyContentDescription.class);
241     // TODO more error checking
242     if (contentDescription != null) {
243       if (bodyContent.equals(BodyContent.JSP) && contentDescription.contentType().length() > 0) {
244         throw new IllegalArgumentException(
245             "contentType " + contentDescription.contentType() + " for bodyContent JSP not allowed!");
246       } else if (bodyContent.equals(BodyContent.TAGDEPENDENT) && contentDescription.contentType().length() == 0) {
247         throw new IllegalArgumentException("contentType should set for tagdependent bodyContent");
248       }
249     }
250     addLeafTextElement(bodyContent.toString(), "body-content", tagElement, document);
251     addDescription(decl, tagElement, document, deprecated);
252     return tagElement;
253   }
254 
255   private void checkAttributeDuplicates(String attributeName) {
256     if (attributeSet.contains(attributeName)) {
257       throw new IllegalArgumentException("Attribute " + attributeName + " in tag " +  currentTag + " already defined!");
258     } else {
259       attributeSet.add(attributeName);
260     }
261   }
262 
263   private void checkDuplicates(String tagName) {
264     currentTag = tagName;
265     if (tagSet.contains(tagName)) {
266       throw new IllegalArgumentException("tag with name " + tagName + " already defined!");
267     } else {
268       tagSet.add(tagName);
269     }
270   }
271 
272   protected void addDescription(Declaration decl, Element element, Document document) {
273     addDescription(decl, element, document, false);
274   }
275 
276 
277   protected void addDescription(Declaration decl, Element element, Document document, boolean deprecated) {
278     final StringBuilder description = new StringBuilder();
279     final Deprecated deprecatedAnnotation = decl.getAnnotation(Deprecated.class);
280     String comment = decl.getDocComment();
281     final String deprecationComment = deprecationComment(comment);
282 
283     if (deprecatedAnnotation != null || deprecationComment != null) {
284       description.append("<p>**** @deprecated. Will be removed in a future version **** </p>");
285     }
286     if (deprecated) {
287       Tag annotationTag = decl.getAnnotation(Tag.class);
288       description.append("<p>**** @deprecated. Will be removed in a future version. Use ");
289       description.append(annotationTag.name());
290       description.append(" instead. **** </p>");
291     }
292     if (deprecationComment != null) {
293       description.append("<p>" + deprecationComment + "</p>");
294     }
295 
296     Preliminary preliminary = decl.getAnnotation(Preliminary.class);
297     if (preliminary != null) {
298       description.append("<p>**** Preliminary. Maybe subject to changed in a future version");
299       if (preliminary.value().length() > 0) {
300         description.append(": ");
301         description.append(preliminary.value());
302       }
303       description.append(" **** </p>");
304     }
305     if (comment != null) {
306       // remove @param section
307       int index = comment.indexOf(" @");
308       if (index != -1) {
309         comment = comment.substring(0, index);
310       }
311       comment = comment.trim();
312       if (comment.length() > 0) {
313         //description.append("<p>");
314         description.append(comment);
315         //description.append("</p>");
316       }
317     }
318     UIComponentTag componentTag = decl.getAnnotation(UIComponentTag.class);
319     if (componentTag != null) {
320       description.append(createDescription(componentTag));
321     }
322     UIComponentTagAttribute attributeTag = decl.getAnnotation(UIComponentTagAttribute.class);
323     if (attributeTag != null) {
324       if (null != attributeTag.type() && attributeTag.type().length > 0) {
325         description.append("<br />Type: <code>")
326             .append(attributeTag.type().length == 1 ? attributeTag.type()[0] : Arrays.toString(attributeTag.type()))
327             .append("</code>");
328       }
329       if (StringUtils.isNotEmpty(attributeTag.defaultValue())) {
330         description.append("<br />Default: <code>")
331             .append(attributeTag.defaultValue())
332             .append("</code>");
333       }
334       if (attributeTag.allowedValues().length > 0) {
335         description.append("<br />Allowed Values: <code>")
336             .append(Arrays.toString(attributeTag.allowedValues()))
337             .append("</code>");
338       }
339     }
340     ExtensionTag extensionTag = decl.getAnnotation(ExtensionTag.class);
341     if (extensionTag != null) {
342       String baseName = extensionTag.baseClassName();
343       description.append("<p><b>Extended tag: </b>");
344       description.append(baseName);
345       description.append("</p>");
346 
347       InterfaceDeclaration declaration = getInterfaceDeclaration(baseName + "Declaration");
348       if (declaration != null) {
349         UIComponentTag baseComponentTag = declaration.getAnnotation(UIComponentTag.class);
350         if (baseComponentTag != null) {
351           description.append(createDescription(baseComponentTag));
352         }
353       }
354     }
355     if (description.length() > 0) {
356       addLeafCDATAElement(description.toString(), "description", element, document);
357     }
358   }
359 
360   private String deprecationComment(String string) {
361     if (string == null) {
362       return null;
363     }
364     final String deprecated = "@deprecated";
365     final int begin = string.indexOf(deprecated);
366     if (begin > -1) {
367       string = string.substring(begin + deprecated.length());
368       final int end = string.indexOf("@");
369       if (end > -1) {
370         string = string.substring(0, end);
371       }
372       return string.trim();
373     } else {
374       return null;
375     }
376   }
377 
378   private InterfaceDeclaration getInterfaceDeclaration(String name) {
379     for (InterfaceDeclaration declaration : getCollectedInterfaceDeclarations()) {
380       if (name.equals(declaration.getQualifiedName())) {
381         return declaration;
382       }
383     }
384     return null;
385   }
386 
387   private String createDescription(UIComponentTag componentTag) {
388     StringBuilder description = new StringBuilder();
389     description.append("<p><b>UIComponentClass: </b>");
390     description.append(componentTag.uiComponent());
391     description.append("</p>");
392     description.append("<p><b>RendererType: </b>");
393     description.append(componentTag.rendererType());
394     description.append("</p>");
395     Facet[] facets = componentTag.facets();
396     if (facets.length > 0) {
397       description.append("<p><b>Supported facets:</b></p>");
398       description.append("<dl>");
399       for (Facet facet : facets) {
400         description.append("<dt><b>");
401         description.append(facet.name());
402         description.append("</b></dt>");
403         description.append("<dd>");
404         description.append(facet.description());
405         description.append("</dd>");
406       }
407       description.append("</dl>");
408     }
409     return description.toString();
410   }
411 
412   protected void addAttributes(Collection<InterfaceType> interfaces, Element tagElement, Document document) {
413     for (InterfaceType type : interfaces) {
414       addAttributes(type.getDeclaration(), tagElement, document);
415     }
416   }
417 
418   protected void addAttributes(InterfaceDeclaration type, Element tagElement, Document document) {
419     addAttributes(type.getSuperinterfaces(), tagElement, document);
420     for (MethodDeclaration decl : getCollectedMethodDeclarations()) {
421       if (decl.getDeclaringType().equals(type)) {
422         addAttribute(decl, tagElement, document);
423       }
424     }
425   }
426 
427   protected void addAttributes(ClassDeclaration d, Element tagElement, Document document) {
428 
429     for (MethodDeclaration decl : getCollectedMethodDeclarations()) {
430       if (d.getQualifiedName().
431           equals(decl.getDeclaringType().getQualifiedName())) {
432         addAttribute(decl, tagElement, document);
433       }
434     }
435     addAttributes(d.getSuperinterfaces(), tagElement, document);
436     if (d.getSuperclass() != null) {
437       addAttributes(d.getSuperclass().getDeclaration(), tagElement, document);
438     }
439   }
440   
441   private void resetDuplicateList() {
442     tagSet = new HashSet<String>();
443   }
444   
445   private void resetAttributeDuplicateList() {
446     attributeSet = new HashSet<String>();
447   }
448 
449   protected void addAttribute(MethodDeclaration d, Element tagElement,
450       Document document) {
451     TagAttribute tagAttribute = d.getAnnotation(TagAttribute.class);
452     if (tagAttribute != null) {
453       String simpleName = d.getSimpleName();
454       if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
455         Element attribute = document.createElement("attribute");
456         String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
457         if (tagAttribute.name().length() > 0) {
458           attributeStr = tagAttribute.name();
459         }
460         checkAttributeDuplicates(attributeStr);
461         addLeafTextElement(attributeStr, "name", attribute, document);
462 
463         addLeafTextElement(Boolean.toString(tagAttribute.required()), "required", attribute, document);
464         UIComponentTagAttribute componentTagAttribute = d.getAnnotation(UIComponentTagAttribute.class);
465         if (isMinium12() && !tagAttribute.rtexprvalue()) {
466           if (componentTagAttribute != null) {
467             if (componentTagAttribute.expression().isMethodExpression()) {
468               Element deferredMethod = document.createElement("deferred-method");
469               StringBuilder signature = new StringBuilder();
470               signature.append(componentTagAttribute.methodReturnType());
471               signature.append(" ");
472               signature.append(attributeStr);
473               signature.append("(");
474               signature.append(StringUtils.join(componentTagAttribute.methodSignature(), ", "));
475               signature.append(")");
476               addLeafTextElement(signature.toString(), "method-signature", deferredMethod, document);
477               attribute.appendChild(deferredMethod);
478             } else if (componentTagAttribute != null && componentTagAttribute.expression().isValueExpression()) {
479               Element deferredValue = document.createElement("deferred-value");
480               String type = "java.lang.Object";
481               if (componentTagAttribute.expression().isValueExpression()) {
482                 if (componentTagAttribute.type().length == 1
483                     // XXX fix me hack
484                     && !"org.apache.myfaces.tobago.layout.Measure".equals(componentTagAttribute.type()[0])) {
485                   type = componentTagAttribute.type()[0];
486                 }
487               } else {
488                 type = componentTagAttribute.type()[0];
489               }
490               addLeafTextElement(type, "type", deferredValue, document);
491               attribute.appendChild(deferredValue);
492             } 
493           } else {
494             Element deferredValue = document.createElement("deferred-value");
495             addLeafTextElement(tagAttribute.type(), "type", deferredValue, document);
496             attribute.appendChild(deferredValue);
497           }
498         }
499         if (tagAttribute.rtexprvalue()) {
500           addLeafTextElement(Boolean.toString(tagAttribute.rtexprvalue()), "rtexprvalue", attribute, document);
501         }
502         addDescription(d, attribute, document, false);
503         tagElement.appendChild(attribute);
504       } else {
505         throw new IllegalArgumentException("Only setter allowed found: " + simpleName);
506       }
507     }
508   }
509 
510   private boolean isMinium12() {
511     return !"1.1".equals(jsfVersion);
512   }
513 }