1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
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
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
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
237 addLeafTextElement(tagExtraInfo, "tei-class", tagElement, document);
238 }
239 BodyContent bodyContent = annotationTag.bodyContent();
240 BodyContentDescription contentDescription = decl.getAnnotation(BodyContentDescription.class);
241
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
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
314 description.append(comment);
315
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
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 }