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.processor;
21
22 import org.apache.commons.lang3.StringUtils;
23 import org.apache.myfaces.tobago.apt.AnnotationUtils;
24 import org.apache.myfaces.tobago.apt.annotation.ConverterTag;
25 import org.apache.myfaces.tobago.apt.annotation.Facet;
26 import org.apache.myfaces.tobago.apt.annotation.Markup;
27 import org.apache.myfaces.tobago.apt.annotation.Preliminary;
28 import org.apache.myfaces.tobago.apt.annotation.SimpleTag;
29 import org.apache.myfaces.tobago.apt.annotation.Tag;
30 import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
31 import org.apache.myfaces.tobago.apt.annotation.Taglib;
32 import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
33 import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
34 import org.apache.myfaces.tobago.apt.annotation.ValidatorTag;
35 import org.apache.myfaces.tobago.apt.generate.ClassUtils;
36 import org.w3c.dom.Comment;
37 import org.w3c.dom.Document;
38 import org.w3c.dom.Element;
39
40 import javax.annotation.processing.SupportedAnnotationTypes;
41 import javax.annotation.processing.SupportedOptions;
42 import javax.annotation.processing.SupportedSourceVersion;
43 import javax.lang.model.SourceVersion;
44 import javax.lang.model.element.ExecutableElement;
45 import javax.lang.model.element.PackageElement;
46 import javax.lang.model.element.TypeElement;
47 import javax.tools.FileObject;
48 import javax.tools.StandardLocation;
49 import javax.xml.parsers.DocumentBuilder;
50 import javax.xml.parsers.DocumentBuilderFactory;
51 import javax.xml.parsers.ParserConfigurationException;
52 import javax.xml.transform.OutputKeys;
53 import javax.xml.transform.Transformer;
54 import javax.xml.transform.TransformerException;
55 import javax.xml.transform.TransformerFactory;
56 import javax.xml.transform.dom.DOMSource;
57 import javax.xml.transform.stream.StreamResult;
58 import java.io.IOException;
59 import java.io.Writer;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.Comparator;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.Locale;
66 import java.util.Map;
67 import java.util.Set;
68
69 @SupportedSourceVersion(SourceVersion.RELEASE_8)
70 @SupportedAnnotationTypes({
71 "org.apache.myfaces.tobago.apt.annotation.Tag",
72 "org.apache.myfaces.tobago.apt.annotation.TagAttribute",
73 "org.apache.myfaces.tobago.apt.annotation.Taglib"})
74 @SupportedOptions({
75 TaglibGenerator.TARGET_TAGLIB})
76 public class TaglibGenerator extends AbstractGenerator {
77
78 static final String TARGET_TAGLIB = "targetTaglib";
79
80 private Set<String> tagSet = new HashSet<>();
81 private Set<String> attributeSet = new HashSet<>();
82 private String currentTag;
83
84 private String targetTaglib;
85
86 @Override
87 public void configure() {
88 final Map<String, String> options = processingEnv.getOptions();
89 targetTaglib = options.get(TARGET_TAGLIB);
90
91 info("Generating the *.tld and *.taglib.xml");
92 info("Options:");
93 info(TARGET_TAGLIB + ": " + targetTaglib);
94 }
95
96 @Override
97 public void generate()
98 throws IOException, TransformerException, ParserConfigurationException, ClassNotFoundException {
99 for (final PackageElement packageElement : getPackages()) {
100 final Taglib taglibAnnotation = packageElement.getAnnotation(Taglib.class);
101
102 createTaglib(taglibAnnotation, packageElement);
103 }
104 }
105
106 protected void createTaglib(final Taglib taglibAnnotation, final PackageElement packageElement)
107 throws ParserConfigurationException, ClassNotFoundException, IOException, TransformerException {
108 resetDuplicateList();
109 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
110 dbf.setValidating(false);
111
112
113
114 final DocumentBuilder parser = dbf.newDocumentBuilder();
115 final Document document = parser.newDocument();
116
117 final Element taglib = createTaglib(document, taglibAnnotation);
118 final String description = processingEnv.getElementUtils().getDocComment(packageElement);
119
120 addComment("The next tags are commented because of MYFACES-3537. "
121 + "The application will not run with MyFaces before 2.0.14/2.1.8. "
122 + "This also affects WebSphere 8.5", taglib, document);
123 addComment("<description>" + description + "</description>", taglib, document);
124 addComment("<display-name>" + taglibAnnotation.displayName() + "</display-name>", taglib, document);
125
126 if (description != null) {
127 addLeafCDATAElement(description, "description", taglib, document);
128 }
129 addLeafTextElement(taglibAnnotation.displayName(), "display-name", taglib, document);
130
131 addLeafTextElement(taglibAnnotation.uri(), "namespace", taglib, document);
132
133
134 if ("http://myfaces.apache.org/tobago/component".equals(taglibAnnotation.uri())) {
135 for (int i = 1; i < 10; i++) {
136 addFunction(document, taglib, "format" + i, "org.apache.myfaces.tobago.util.MessageFormat",
137 "java.lang.String format(java.lang.String"+ StringUtils.repeat(", java.lang.Object", i) +")");
138 }
139 }
140
141 for (final TypeElement typeElement : getTypes()) {
142 if (processingEnv.getElementUtils().getPackageOf(typeElement).equals(packageElement)) {
143 appendTag(typeElement, taglib, document);
144 }
145 }
146 document.appendChild(taglib);
147
148
149
150 String target = targetTaglib;
151 target = StringUtils.isNotBlank(target) ? target + '/' : "";
152 final String name = target + taglibAnnotation.name() + ".taglib.xml";
153 final FileObject resource = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", name);
154 info("Writing to file: " + resource.toUri());
155
156 try (Writer writer = resource.openWriter()) {
157 final TransformerFactory transFactory = TransformerFactory.newInstance();
158 transFactory.setAttribute("indent-number", 2);
159 final Transformer transformer = transFactory.newTransformer();
160 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
161 transformer.transform(new DOMSource(document), new StreamResult(writer));
162 }
163 }
164
165 private void addFunction(
166 final Document document, final Element taglib, final String functionName, final String functionClass,
167 final String functionSignature) {
168 final Element function = document.createElement("function");
169 taglib.appendChild(function);
170 addLeafTextElement(functionName, "function-name", function, document);
171 addLeafTextElement(functionClass, "function-class", function, document);
172 addLeafTextElement(functionSignature, "function-signature", function, document);
173 }
174
175 protected void appendTag(
176 final TypeElement typeElement, final Element parent, final Document document)
177 throws ClassNotFoundException {
178 final Tag annotationTag = typeElement.getAnnotation(Tag.class);
179 if (annotationTag != null) {
180 checkDuplicates(annotationTag.name());
181 resetAttributeDuplicateList();
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196 final Element tag = createTag(typeElement, annotationTag, document, false);
197 addAttributes(typeElement, tag, document);
198 parent.appendChild(tag);
199 if (annotationTag.deprecatedName() != null && annotationTag.deprecatedName().length() > 0) {
200 final Element deprecatedTag = createTag(typeElement, annotationTag, document, true);
201 addAttributes(typeElement, deprecatedTag, document);
202 parent.appendChild(deprecatedTag);
203 }
204 }
205 }
206
207 protected Element createTag(
208 final TypeElement typeElement, final Tag annotationTag, final Document document,
209 final boolean deprecated) {
210 final Element tagElement = document.createElement("tag");
211 addDescription(typeElement, tagElement, document, deprecated);
212 addTagContent(typeElement, tagElement, document, deprecated, annotationTag);
213 return tagElement;
214 }
215
216 private void checkAttributeDuplicates(final String attributeName) {
217 if (attributeSet.contains(attributeName)) {
218 throw new IllegalArgumentException("Attribute " + attributeName + " in tag " + currentTag + " already defined!");
219 } else {
220 attributeSet.add(attributeName);
221 }
222 }
223
224 private void checkDuplicates(final String tagName) {
225 currentTag = tagName;
226 if (tagSet.contains(tagName)) {
227 throw new IllegalArgumentException("tag with name " + tagName + " already defined!");
228 } else {
229 tagSet.add(tagName);
230 }
231 }
232
233 protected void addDescription(
234 final javax.lang.model.element.Element typeElement, final Element element, final Document document,
235 final boolean deprecated) {
236 final StringBuilder description = new StringBuilder();
237 final Deprecated deprecatedAnnotation = typeElement.getAnnotation(Deprecated.class);
238 String comment = processingEnv.getElementUtils().getDocComment(typeElement);
239 final String deprecationComment = deprecationComment(comment);
240
241 if (deprecatedAnnotation != null || deprecationComment != null) {
242 description.append("<p>**** @deprecated. Will be removed in a future version **** </p>");
243 }
244 if (deprecated) {
245 final Tag annotationTag = typeElement.getAnnotation(Tag.class);
246 description.append("<p>**** @deprecated. Will be removed in a future version. Use ");
247 description.append(annotationTag.name());
248 description.append(" instead. **** </p>");
249 }
250 if (deprecationComment != null) {
251 description.append("<p>").append(deprecationComment).append("</p>");
252 }
253
254 final Preliminary preliminary = typeElement.getAnnotation(Preliminary.class);
255 if (preliminary != null) {
256 description.append("<p>**** Preliminary. Maybe subject to changed in a future version");
257 if (preliminary.value().length() > 0) {
258 description.append(": ");
259 description.append(preliminary.value());
260 }
261 description.append(" **** </p>");
262 }
263 if (comment != null) {
264
265 final int index = comment.indexOf(" @");
266 if (index != -1) {
267 comment = comment.substring(0, index);
268 }
269 comment = comment.trim();
270 if (comment.length() > 0) {
271
272 description.append(comment);
273
274 }
275 }
276 final UIComponentTag componentTag = typeElement.getAnnotation(UIComponentTag.class);
277 if (componentTag != null) {
278 description.append(createDescription(componentTag));
279 }
280 final UIComponentTagAttribute attributeTag = typeElement.getAnnotation(UIComponentTagAttribute.class);
281 if (attributeTag != null) {
282 if (attributeTag.type().length > 0) {
283 description.append("<br />Type: <code>")
284 .append(attributeTag.type().length == 1 ? attributeTag.type()[0] : Arrays.toString(attributeTag.type()))
285 .append("</code>");
286 }
287 if (StringUtils.isNotEmpty(attributeTag.defaultValue())) {
288 description.append("<br />Default: <code>")
289 .append(attributeTag.defaultValue())
290 .append("</code>");
291 }
292 if (attributeTag.allowedValues().length > 0) {
293 description.append("<br />Allowed Values: <code>")
294 .append(Arrays.toString(attributeTag.allowedValues()))
295 .append("</code>");
296 }
297 }
298 if (description.length() > 0) {
299 addLeafCDATAElement(description.toString(), "description", element, document);
300 }
301 }
302
303 private String deprecationComment(final String string) {
304 if (string == null) {
305 return null;
306 }
307 String result = string;
308 final String deprecated = "@deprecated";
309 final int begin = result.indexOf(deprecated);
310 if (begin > -1) {
311 result = result.substring(begin + deprecated.length());
312 final int end = result.indexOf("@");
313 if (end > -1) {
314 result = result.substring(0, end);
315 }
316 return result.trim();
317 } else {
318 return null;
319 }
320 }
321
322 private TypeElement getInterfaceDeclaration(final String name) {
323 for (final TypeElement type : getTypes()) {
324 if (name.equals(type.getQualifiedName().toString())) {
325 return type;
326 }
327 }
328 return null;
329 }
330
331 private String createDescription(final UIComponentTag componentTag) {
332 final StringBuilder description = new StringBuilder();
333 description.append("<p><b>UIComponentClass: </b>");
334 description.append(componentTag.uiComponent());
335 description.append("</p>");
336 description.append("<p><b>RendererType: </b>");
337 description.append("<ul>");
338 boolean first = true;
339 for (final String rendererType : componentTag.rendererType()) {
340 description.append("<li>");
341 description.append(rendererType);
342 if (first) {
343 description.append(" (default)");
344 }
345 description.append("</li>");
346 first = false;
347 }
348 description.append("</ul>");
349 description.append("</p>");
350 final Facet[] facets = componentTag.facets();
351 if (facets.length > 0) {
352 description.append("<p><b>Supported facets:</b></p>");
353 description.append("<dl>");
354 for (final Facet facet : facets) {
355 description.append("<dt><b>");
356 description.append(facet.name());
357 description.append("</b></dt>");
358 description.append("<dd>");
359 description.append(facet.description());
360 description.append("</dd>");
361 }
362 description.append("</dl>");
363 }
364 final Markup[] markups = componentTag.markups();
365 if (markups.length > 0) {
366 description.append("<p><b>Supported markups:</b></p>");
367 description.append("<dl>");
368 for (final Markup markup : markups) {
369 description.append("<dt><b>");
370 description.append(markup.name());
371 description.append("</b></dt>");
372 description.append("<dd>");
373 description.append(markup.description());
374 description.append("</dd>");
375 }
376 description.append("</dl>");
377 }
378 return description.toString();
379 }
380
381 protected void addAttributes(
382 final TypeElement typeElement, final Element tagElement, final Document document)
383 throws ClassNotFoundException {
384
385 for (final javax.lang.model.element.Element element : getAllMembers(typeElement)) {
386 if (element instanceof ExecutableElement) {
387 final ExecutableElement executableElement = (ExecutableElement) element;
388 if (executableElement.getAnnotation(TagAttribute.class) == null
389 && executableElement.getAnnotation(UIComponentTagAttribute.class) == null) {
390 continue;
391 }
392 addAttribute(executableElement, tagElement, document);
393 }
394 }
395 }
396
397 private List<? extends javax.lang.model.element.Element> getAllMembers(final TypeElement type) {
398 final List<? extends javax.lang.model.element.Element> members
399 = new ArrayList<javax.lang.model.element.Element>(processingEnv.getElementUtils().getAllMembers(type));
400 members.sort(Comparator.comparing(d -> d.getSimpleName().toString()));
401 return members;
402 }
403
404 private void resetDuplicateList() {
405 tagSet = new HashSet<>();
406 }
407
408 private void resetAttributeDuplicateList() {
409 attributeSet = new HashSet<>();
410 }
411
412 protected void addAttribute(
413 final ExecutableElement element, final Element tagElement, final Document document)
414 throws ClassNotFoundException {
415 final TagAttribute tagAttribute = element.getAnnotation(TagAttribute.class);
416 if (tagAttribute != null) {
417 final String simpleName = element.getSimpleName().toString();
418 if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
419 final Element attribute = document.createElement("attribute");
420 String attributeName = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
421 if (tagAttribute.name().length() > 0) {
422 attributeName = tagAttribute.name();
423 }
424 checkAttributeDuplicates(attributeName);
425 addDescription(element, attribute, document, false);
426 addLeafTextElement(attributeName, "name", attribute, document);
427
428 addLeafTextElement(Boolean.toString(tagAttribute.required()), "required", attribute, document);
429 final UIComponentTagAttribute componentTagAttribute = element.getAnnotation(UIComponentTagAttribute.class);
430 addAttributeType(attribute, tagAttribute, componentTagAttribute, document);
431 tagElement.appendChild(attribute);
432 } else {
433 throw new IllegalArgumentException("Only setter allowed found: " + simpleName);
434 }
435 }
436 }
437
438 protected void addComment(final String text, final Element parent, final Document document) {
439 final Comment comment = document.createComment(text);
440 parent.appendChild(comment);
441 }
442
443 protected void addLeafTextElement(
444 final String text, final String node, final Element parent, final Document document) {
445 final Element element = document.createElement(node);
446 element.appendChild(document.createTextNode(text));
447 parent.appendChild(element);
448 }
449
450 protected void addLeafCDATAElement(
451 final String text, final String node, final Element parent, final Document document) {
452 final Element element = document.createElement(node);
453 element.appendChild(document.createCDATASection(text));
454 parent.appendChild(element);
455 }
456
457 protected Element createTaglib(final Document document, final Taglib taglibAnnotation) {
458 final Element taglib;
459 taglib = document.createElement("facelet-taglib");
460 taglib.setAttribute("id", taglibAnnotation.shortName());
461 taglib.setAttribute("xmlns", "http://java.sun.com/xml/ns/javaee");
462 taglib.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
463 taglib.setAttribute("xsi:schemaLocation",
464 "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd");
465 taglib.setAttribute("version", "2.0");
466 return taglib;
467 }
468
469 protected void addTagContent(
470 final TypeElement typeElement, final Element tagElement, final Document document, final boolean deprecated,
471 final Tag annotationTag) {
472 if (deprecated) {
473 addLeafTextElement(annotationTag.deprecatedName(), "tag-name", tagElement, document);
474 } else {
475 addLeafTextElement(annotationTag.name(), "tag-name", tagElement, document);
476 }
477
478 final UIComponentTag componentTag = typeElement.getAnnotation(UIComponentTag.class);
479 if (componentTag != null) {
480 final Element componentElement = document.createElement("component");
481 tagElement.appendChild(componentElement);
482 addLeafTextElement(
483 AnnotationUtils.componentType(componentTag), "component-type", componentElement, document);
484 if (componentTag.rendererType().length > 0) {
485 addLeafTextElement(componentTag.rendererType()[0], "renderer-type", componentElement, document);
486 }
487 addLeafTextElement(componentTag.faceletHandler(), "handler-class", componentElement, document);
488 }
489
490 final SimpleTag simpleTag = typeElement.getAnnotation(SimpleTag.class);
491 if (simpleTag != null) {
492 addLeafTextElement(simpleTag.faceletHandler(), "handler-class", tagElement, document);
493 }
494
495 final ConverterTag converterTag = typeElement.getAnnotation(ConverterTag.class);
496 if (converterTag != null) {
497 final Element converterElement = document.createElement("converter");
498 tagElement.appendChild(converterElement);
499 addLeafTextElement(converterTag.converterId(), "converter-id", converterElement, document);
500 if (StringUtils.isNotBlank(converterTag.faceletHandler())) {
501 addLeafTextElement(converterTag.faceletHandler(), "handler-class", converterElement, document);
502 }
503 }
504
505 final ValidatorTag validatorTag = typeElement.getAnnotation(ValidatorTag.class);
506 if (validatorTag != null) {
507 final Element validatorElement = document.createElement("validator");
508 tagElement.appendChild(validatorElement);
509 addLeafTextElement(validatorTag.validatorId(), "validator-id", validatorElement, document);
510 if (StringUtils.isNotBlank(validatorTag.faceletHandler())) {
511 addLeafTextElement(validatorTag.faceletHandler(), "handler-class", validatorElement, document);
512 }
513 }
514 }
515
516 protected void addAttributeType(
517 final Element attribute, final TagAttribute tagAttribute, final UIComponentTagAttribute componentTagAttribute,
518 final Document document) {
519 if (!tagAttribute.rtexprvalue()) {
520 if (componentTagAttribute != null) {
521 if (componentTagAttribute.expression().isMethodExpression()) {
522
523 } else if (componentTagAttribute.expression().isValueExpression()) {
524 String clazz;
525 if (componentTagAttribute.type().length == 1) {
526 clazz = componentTagAttribute.type()[0];
527 final Class wrapper = ClassUtils.getWrapper(clazz);
528 if (wrapper != null) {
529 clazz = wrapper.getName();
530
531
532
533
534
535
536
537
538 }
539 } else {
540 clazz = "java.lang.Object";
541 }
542 addLeafTextElement(clazz, "type", attribute, document);
543 }
544 } else {
545 addLeafTextElement(tagAttribute.type(), "type", attribute, document);
546 }
547 }
548 }
549 }