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  import org.apache.commons.io.IOUtils;
23  import org.apache.myfaces.tobago.apt.annotation.Converter;
24  import org.apache.myfaces.tobago.apt.annotation.Facet;
25  import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
26  import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
27  import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
28  import org.apache.myfaces.tobago.apt.annotation.Validator;
29  import org.apache.myfaces.tobago.apt.generate.ComponentInfo;
30  import org.jdom2.Attribute;
31  import org.jdom2.Comment;
32  import org.jdom2.Document;
33  import org.jdom2.Namespace;
34  import org.jdom2.filter.ContentFilter;
35  import org.jdom2.input.SAXBuilder;
36  import org.jdom2.output.Format;
37  import org.jdom2.output.XMLOutputter;
38  
39  import javax.annotation.processing.SupportedAnnotationTypes;
40  import javax.annotation.processing.SupportedOptions;
41  import javax.annotation.processing.SupportedSourceVersion;
42  import javax.lang.model.SourceVersion;
43  import javax.lang.model.element.ExecutableElement;
44  import javax.lang.model.element.TypeElement;
45  import javax.tools.FileObject;
46  import javax.tools.StandardLocation;
47  import java.io.FileInputStream;
48  import java.io.IOException;
49  import java.io.StringReader;
50  import java.io.StringWriter;
51  import java.io.Writer;
52  import java.nio.charset.StandardCharsets;
53  import java.util.ArrayList;
54  import java.util.Collections;
55  import java.util.Comparator;
56  import java.util.HashSet;
57  import java.util.Iterator;
58  import java.util.List;
59  import java.util.Locale;
60  import java.util.Map;
61  import java.util.Set;
62  
63  @SupportedSourceVersion(SourceVersion.RELEASE_8)
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      "org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute",
69      "org.apache.myfaces.tobago.apt.annotation.UIComponentTag",
70      "org.apache.myfaces.tobago.apt.annotation.Facet",
71      "org.apache.myfaces.tobago.apt.annotation.Preliminary",
72      "org.apache.myfaces.tobago.apt.annotation.Converter",
73      "org.apache.myfaces.tobago.apt.annotation.Validator"})
74  @SupportedOptions({
75      FacesConfigGenerator.SOURCE_FACES_CONFIG,
76      FacesConfigGenerator.TARGET_FACES_CONFIG})
77  public class FacesConfigGenerator extends AbstractGenerator {
78  
79    static final String SOURCE_FACES_CONFIG = "sourceFacesConfig";
80    static final String TARGET_FACES_CONFIG = "targetFacesConfig";
81  
82    private static final String SEPARATOR = System.getProperty("line.separator");
83    private static final String COMPONENT = "component";
84    private static final String COMPONENT_TYPE = "component-type";
85    private static final String COMPONENT_CLASS = "component-class";
86    private static final String COMPONENT_EXTENSION = "component-extension";
87    private static final String ALLOWED_CHILD_COMPONENTS = "allowed-child-components";
88    private static final String CATEGORY = "category";
89    private static final String DEPRECATED = "deprecated";
90    private static final String HIDDEN = "hidden";
91    private static final String FACET = "facet";
92    private static final String DISPLAY_NAME = "display-name";
93    private static final String DESCRIPTION = "description";
94    private static final String FACET_NAME = "facet-name";
95    private static final String FACET_EXTENSION = "facet-extension";
96    private static final String PROPERTY = "property";
97    private static final String PROPERTY_NAME = "property-name";
98    private static final String PROPERTY_CLASS = "property-class";
99    private static final String PROPERTY_EXTENSION = "property-extension";
100   private static final String VALUE_EXPRESSION = "value-expression"; //UIComponentTagAttribute.valueExpression()
101   private static final String PROPERTY_VALUES = "property-values"; //UIComponentTagAttribute.allowedValues()
102   private static final String READONLY = "read-only";
103   private static final String REQUIRED = "required"; //UITagAttribute.required()
104   private static final String DEFAULT_VALUE = "default-value";
105   private static final String ATTRIBUTE = "attribute";
106   private static final String ATTRIBUTE_NAME = "attribute-name";
107   private static final String ATTRIBUTE_CLASS = "attribute-class";
108   private static final String ATTRIBUTE_EXTENSION = "attribute-extension";
109   private static final String APPLICATION = "application";
110   private static final String FACTORY = "factory";
111   private static final String CONVERTER = "converter";
112   private static final String CONVERTER_ID = "converter-id";
113   private static final String CONVERTER_FOR_CLASS = "converter-for-class";
114   private static final String CONVERTER_CLASS = "converter-class";
115   private static final String VALIDATOR = "validator";
116   private static final String VALIDATOR_ID = "validator-id";
117   private static final String VALIDATOR_FOR_CLASS = "validator-for-class";
118   private static final String VALIDATOR_CLASS = "validator-class";
119   private static final String RENDERER = "renderer";
120   private static final String COMPONENT_FAMILY = "component-family";
121   private static final String RENDER_KIT = "render-kit";
122   private static final String RENDER_KIT_ID = "render-kit-id";
123   private static final String RENDER_KIT_CLASS = "render-kit-class";
124   private static final String RENDERER_TYPE = "renderer-type";
125   private static final String RENDERER_CLASS = "renderer-class";
126   private static final String BEHAVIOR = "behavior";
127 /* XXX
128   private static final String BEHAVIOR_ID = "behavior-id";
129   private static final String BEHAVIOR_CLASS = "behavior-class";
130   private static final String CLIENT_BEHAVIOR_RENDERER = "client-behavior-renderer";
131   private static final String CLIENT_BEHAVIOR_RENDERER_TYPE = "client-behavior-renderer-type";
132   private static final String CLIENT_BEHAVIOR_RENDERER_CLASS = "client-behavior-renderer-class";
133 */
134 
135   private static final Set<String> IGNORED_PROPERTIES = new HashSet<>(Collections.singletonList("binding"));
136 
137   private String sourceFacesConfigFile;
138   private String targetFacesConfigFile;
139 
140   @Override
141   public void configure() {
142     final Map<String, String> options = processingEnv.getOptions();
143     sourceFacesConfigFile = options.get(SOURCE_FACES_CONFIG);
144     targetFacesConfigFile = options.get(TARGET_FACES_CONFIG);
145 
146     info("Generating the faces-config.xml");
147     info("Options:");
148     info(SOURCE_FACES_CONFIG + ": " + sourceFacesConfigFile);
149     info(TARGET_FACES_CONFIG + ": " + targetFacesConfigFile);
150   }
151 
152   @Override
153   protected void generate() throws Exception {
154     final Document document;
155     final String content = IOUtils.toString(new FileInputStream(sourceFacesConfigFile), StandardCharsets.UTF_8);
156     final SAXBuilder builder = new SAXBuilder();
157     document = builder.build(new StringReader(content));
158 
159     // Normalise line endings. For some reason, JDOM replaces \r\n inside a comment with \n.
160     normaliseLineEndings(document);
161 
162     // rewrite DOM as a string to find differences, since text outside the root element is not tracked
163 
164     final org.jdom2.Element rootElement = document.getRootElement();
165 
166     rootElement.setNamespace(Namespace.getNamespace("http://xmlns.jcp.org/xml/ns/javaee"));
167     final Namespace xsi = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
168 //    rootElement.addNamespaceDeclaration(Namespace.getNamespace("xi", "http://www.w3.org/2001/XInclude"));
169     rootElement.setAttribute(new Attribute("schemaLocation",
170         "http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd", xsi));
171     rootElement.setAttribute("version", "2.3");
172 
173     final Namespace namespace = rootElement.getNamespace();
174     applyNamespace(rootElement, namespace);
175     final List<org.jdom2.Element> components = rootElement.getChildren(COMPONENT, namespace);
176 
177     final List<org.jdom2.Element> newComponents = new ArrayList<>();
178     final List<org.jdom2.Element> newRenderer = new ArrayList<>();
179     final List<org.jdom2.Element> newConverters = new ArrayList<>();
180     final List<org.jdom2.Element> newValidators = new ArrayList<>();
181 
182     for (final TypeElement element : getTypes()) {
183       if (element.getAnnotation(UIComponentTag.class) != null) {
184         addElement(element, newComponents, newRenderer, namespace);
185       } else if (element.getAnnotation(Converter.class) != null) {
186         addConverter(element, newConverters, namespace);
187       } else if (element.getAnnotation(Validator.class) != null) {
188         addValidator(element, newValidators, namespace);
189       }
190     }
191 
192     final List<org.jdom2.Element> elementsToAdd = new ArrayList<>();
193     // sort out duplicates
194     for (final org.jdom2.Element newElement : newComponents) {
195       final boolean found = containsElement(components, newElement);
196       if (!found) {
197         elementsToAdd.add(newElement);
198       }
199     }
200     if (!elementsToAdd.isEmpty()) {
201       // if faces-config contains no component section add the components after factory or application
202       final int lastIndex = getIndexAfter(rootElement, COMPONENT, FACTORY, APPLICATION);
203       rootElement.addContent(lastIndex, elementsToAdd);
204     }
205     if (!newRenderer.isEmpty()) {
206       org.jdom2.Element renderKit = getFirstElementByName(rootElement, RENDER_KIT);
207       if (renderKit == null) {
208         renderKit = new org.jdom2.Element(RENDER_KIT, namespace);
209         final int last = getIndexAfter(rootElement, CONVERTER, COMPONENT, FACTORY, APPLICATION, BEHAVIOR);
210         rootElement.addContent(last, renderKit);
211       }
212       final org.jdom2.Element renderKitId = new org.jdom2.Element(RENDER_KIT_ID, namespace);
213       renderKitId.setText("tobago");
214       renderKit.addContent(0, renderKitId);
215       final org.jdom2.Element renderKitClass = new org.jdom2.Element(RENDER_KIT_CLASS, namespace);
216       renderKitClass.setText("org.apache.myfaces.tobago.renderkit.TobagoRenderKit");
217       renderKit.addContent(1, renderKitClass);
218       renderKit.addContent(2, newRenderer);
219     }
220     if (!newConverters.isEmpty()) {
221       final int last = getIndexAfter(rootElement, RENDER_KIT, CONVERTER, COMPONENT, FACTORY, APPLICATION, BEHAVIOR);
222       rootElement.addContent(last, newConverters);
223     }
224     if (!newValidators.isEmpty()) {
225       rootElement.addContent(newValidators);
226     }
227     final FileObject resource = processingEnv.getFiler().createResource(
228         StandardLocation.SOURCE_OUTPUT, "", targetFacesConfigFile);
229     info("Writing to file: " + resource.toUri());
230 
231     try (Writer writer = resource.openWriter()) {
232       final StringWriter facesConfig = new StringWriter(1024);
233       final Format format = Format.getPrettyFormat();
234       format.setLineSeparator(SEPARATOR);
235       final XMLOutputter out = new XMLOutputter(format);
236       out.output(document, facesConfig);
237       writer.append(facesConfig.toString());
238     }
239   }
240 
241   private void applyNamespace(final org.jdom2.Element parent, final Namespace namespace) {
242     for (final org.jdom2.Element element : parent.getChildren()) {
243       element.setNamespace(namespace);
244       applyNamespace(element, namespace);
245     }
246   }
247 
248   private void addConverter(
249       final TypeElement typeElement, final List<org.jdom2.Element> newConverters, final Namespace namespace) {
250     final Converter converterAnn = typeElement.getAnnotation(Converter.class);
251     final org.jdom2.Element converter = new org.jdom2.Element(CONVERTER, namespace);
252     if (converterAnn.id().length() > 0) {
253       final org.jdom2.Element converterId = new org.jdom2.Element(CONVERTER_ID, namespace);
254       converterId.setText(converterAnn.id());
255       converter.addContent(converterId);
256     } else if (converterAnn.forClass().length() > 0) {
257       final org.jdom2.Element converterForClass = new org.jdom2.Element(CONVERTER_FOR_CLASS, namespace);
258       converterForClass.setText(converterAnn.forClass());
259       converter.addContent(converterForClass);
260     }
261 
262     final org.jdom2.Element converterClass = new org.jdom2.Element(CONVERTER_CLASS, namespace);
263     converterClass.setText(typeElement.getQualifiedName().toString());
264     converter.addContent(converterClass);
265     newConverters.add(converter);
266   }
267 
268   private void addValidator(
269       final TypeElement typeElement, final List<org.jdom2.Element> newValidators, final Namespace namespace) {
270     final Validator validatorAnn = typeElement.getAnnotation(Validator.class);
271     final org.jdom2.Element validator = new org.jdom2.Element(VALIDATOR, namespace);
272     if (validatorAnn.id().length() > 0) {
273       final org.jdom2.Element validatorId = new org.jdom2.Element(VALIDATOR_ID, namespace);
274       validatorId.setText(validatorAnn.id());
275       validator.addContent(validatorId);
276     } else if (validatorAnn.forClass().length() > 0) {
277       final org.jdom2.Element validatorForClass = new org.jdom2.Element(VALIDATOR_FOR_CLASS, namespace);
278       validatorForClass.setText(validatorAnn.forClass());
279       validator.addContent(validatorForClass);
280     }
281 
282     final org.jdom2.Element validatorClass = new org.jdom2.Element(VALIDATOR_CLASS, namespace);
283     validatorClass.setText(typeElement.getQualifiedName().toString());
284     validator.addContent(validatorClass);
285     newValidators.add(validator);
286   }
287 
288   private boolean containsElement(final List<org.jdom2.Element> components, final org.jdom2.Element newElement) {
289     return getEqualElement(components, newElement) != null;
290   }
291 
292   private org.jdom2.Element getEqualElement(
293       final List<org.jdom2.Element> components, final org.jdom2.Element newElement) {
294     for (final org.jdom2.Element element : components) {
295       if (equals(element, newElement)) {
296         return element;
297       }
298     }
299     return null;
300   }
301 
302   private org.jdom2.Element getFirstElementByName(final org.jdom2.Element rootElement, final String tagName) {
303     final List<org.jdom2.Element> elements = rootElement.getChildren(tagName, rootElement.getNamespace());
304     if (elements.isEmpty()) {
305       return null;
306     } else {
307       return elements.get(0);
308     }
309   }
310 
311   private int getIndexAfter(final org.jdom2.Element rootElement, final String... tagNames) {
312     for (final String tagName : tagNames) {
313       final int index = getIndexAfter(rootElement, tagName);
314       if (index != 0) {
315         return index;
316       }
317     }
318     return 0;
319   }
320 
321   private int getIndexAfter(final org.jdom2.Element rootElement, final String tagName) {
322     final List<org.jdom2.Element> components = rootElement.getChildren(tagName, rootElement.getNamespace());
323     if (components.isEmpty()) {
324       return 0;
325     } else {
326       return rootElement.indexOf(components.get(components.size() - 1)) + 1;
327     }
328   }
329 
330   public boolean equals(final org.jdom2.Element element1, final org.jdom2.Element element2) {
331     final Namespace namespace = element1.getNamespace();
332     if (element1.getName().equals(element2.getName()) && element1.getNamespace().equals(element2.getNamespace())) {
333       if (element1.getChildText(COMPONENT_CLASS, namespace).equals(element2.getChildText(COMPONENT_CLASS, namespace))) {
334         if (element1.getChildText(COMPONENT_TYPE, namespace).equals(element2.getChildText(COMPONENT_TYPE, namespace))) {
335           return true;
336         }
337       }
338     }
339     return false;
340   }
341 
342   protected org.jdom2.Element createComponentElement(
343       final ComponentInfo componentInfo, final UIComponentTag componentTag, final Namespace namespace)
344       throws IOException, NoSuchFieldException, IllegalAccessException {
345     final org.jdom2.Element element = new org.jdom2.Element(COMPONENT, namespace);
346     final org.jdom2.Element elementDisplayName = new org.jdom2.Element(DISPLAY_NAME, namespace);
347     elementDisplayName.setText(componentInfo.getComponentClassName());
348     element.addContent(elementDisplayName);
349     final org.jdom2.Element elementType = new org.jdom2.Element(COMPONENT_TYPE, namespace);
350     elementType.setText(componentInfo.getComponentType());
351     element.addContent(elementType);
352     final org.jdom2.Element elementClass = new org.jdom2.Element(COMPONENT_CLASS, namespace);
353     elementClass.setText(componentTag.uiComponent());
354     element.addContent(elementClass);
355 
356     return element;
357   }
358 
359   protected void addRendererElement(
360       final ComponentInfo componentInfo, final UIComponentTag componentTag, final List<org.jdom2.Element> renderer,
361       final Namespace namespace)
362       throws IOException, NoSuchFieldException, IllegalAccessException {
363     for (final String rendererType : componentTag.rendererType()) {
364       final org.jdom2.Element element = new org.jdom2.Element(RENDERER, namespace);
365       String displayName = componentTag.displayName();
366       if (displayName.equals("")) {
367         displayName = componentInfo.getComponentClassName();
368       }
369       final org.jdom2.Element elementDisplayName = new org.jdom2.Element(DISPLAY_NAME, namespace);
370       elementDisplayName.setText(displayName);
371       element.addContent(elementDisplayName);
372       final org.jdom2.Element elementComponentFamily = new org.jdom2.Element(COMPONENT_FAMILY, namespace);
373       elementComponentFamily.addContent(componentInfo.getComponentFamily());
374       element.addContent(elementComponentFamily);
375       final org.jdom2.Element elementType = new org.jdom2.Element(RENDERER_TYPE, namespace);
376       elementType.setText(rendererType);
377       element.addContent(elementType);
378       final org.jdom2.Element elementClass = new org.jdom2.Element(RENDERER_CLASS, namespace);
379       final String className = "org.apache.myfaces.tobago.internal.renderkit.renderer." + rendererType + "Renderer";
380       elementClass.setText(className);
381       element.addContent(elementClass);
382       renderer.add(element);
383     }
384   }
385 
386 
387   private org.jdom2.Element createElementExtension(
388       final TypeElement typeElement, final UIComponentTag uiComponentTag,
389       final Namespace namespace) {
390     final org.jdom2.Element elementExtension = new org.jdom2.Element(COMPONENT_EXTENSION, namespace);
391     final org.jdom2.Element elementAllowedChildComponents = new org.jdom2.Element(ALLOWED_CHILD_COMPONENTS, namespace);
392     final String[] allowedChildComponents = uiComponentTag.allowedChildComponenents();
393     final StringBuilder allowedComponentTypes = new StringBuilder();
394     for (final String componentType : allowedChildComponents) {
395       allowedComponentTypes.append(componentType).append(" ");
396     }
397     elementAllowedChildComponents.setText(allowedComponentTypes.toString());
398     elementExtension.addContent(elementAllowedChildComponents);
399     final org.jdom2.Element elementCategory = new org.jdom2.Element(CATEGORY, namespace);
400     elementCategory.setText(uiComponentTag.category().toString());
401     elementExtension.addContent(elementCategory);
402     final Deprecated deprecated = typeElement.getAnnotation(Deprecated.class);
403     if (deprecated != null) {
404       final org.jdom2.Element elementDeprecated = new org.jdom2.Element(DEPRECATED, namespace);
405       elementDeprecated.setText("Warning: This component is deprecated!");
406       elementExtension.addContent(elementDeprecated);
407     }
408     final org.jdom2.Element elementHidden = new org.jdom2.Element(HIDDEN, namespace);
409     elementHidden.setText(Boolean.toString(uiComponentTag.isHidden()));
410     elementExtension.addContent(elementHidden);
411 
412     return elementExtension;
413   }
414 
415   protected void addAttribute(
416       final ExecutableElement executableElement, final List<org.jdom2.Element> attributes,
417       final List<org.jdom2.Element> properties,
418       final Namespace namespace) {
419     final UIComponentTagAttribute componentAttribute = executableElement.getAnnotation(UIComponentTagAttribute.class);
420     if (componentAttribute != null) {
421       final String simpleName = executableElement.getSimpleName().toString();
422       if (simpleName.startsWith("set")) {
423         final String name = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
424         if (IGNORED_PROPERTIES.contains(name)) {
425           final org.jdom2.Element attribute = new org.jdom2.Element(ATTRIBUTE, namespace);
426           final org.jdom2.Element attributeName = new org.jdom2.Element(ATTRIBUTE_NAME, namespace);
427           final org.jdom2.Element attributeClass = new org.jdom2.Element(ATTRIBUTE_CLASS, namespace);
428 
429           attributeName.setText(name);
430           addClass(componentAttribute, attributeClass);
431 
432           addDescription(executableElement, attribute, namespace);
433 
434           attribute.addContent(attributeName);
435           attribute.addContent(attributeClass);
436           if (componentAttribute.defaultValue().length() > 0) {
437             final org.jdom2.Element defaultValue = new org.jdom2.Element(DEFAULT_VALUE, namespace);
438             defaultValue.setText(componentAttribute.defaultValue());
439             attribute.addContent(defaultValue);
440           }
441 
442           attribute.addContent(createPropertyOrAttributeExtension(ATTRIBUTE_EXTENSION, executableElement,
443               componentAttribute, namespace));
444 
445           attributes.add(attribute);
446         } else {
447           final org.jdom2.Element property = new org.jdom2.Element(PROPERTY, namespace);
448           final org.jdom2.Element propertyName = new org.jdom2.Element(PROPERTY_NAME, namespace);
449           final org.jdom2.Element propertyClass = new org.jdom2.Element(PROPERTY_CLASS, namespace);
450 
451           propertyName.setText(name);
452           addClass(componentAttribute, propertyClass);
453 
454           addDescription(executableElement, property, namespace);
455 
456           property.addContent(propertyName);
457           property.addContent(propertyClass);
458           if (componentAttribute.defaultValue().length() > 0) {
459             final org.jdom2.Element defaultValue = new org.jdom2.Element(DEFAULT_VALUE, namespace);
460             defaultValue.setText(componentAttribute.defaultValue());
461             property.addContent(defaultValue);
462           }
463 
464           property.addContent(
465               createPropertyOrAttributeExtension(PROPERTY_EXTENSION, executableElement, componentAttribute, namespace));
466           properties.add(property);
467         }
468       } else {
469         throw new IllegalArgumentException("Only setter allowed found: " + simpleName);
470       }
471     }
472   }
473 
474   private void addClass(final UIComponentTagAttribute componentAttribute, final org.jdom2.Element attributeClass) {
475     if (componentAttribute.type().length > 1) {
476       attributeClass.setText(Object.class.getName());
477     } else if (componentAttribute.type().length == 1) {
478       String className = componentAttribute.type()[0];
479       if (componentAttribute.expression().isMethodExpression()) {
480         className = "javax.el.MethodExpression";
481       }
482       attributeClass.setText(className);
483     } else {
484       if (componentAttribute.expression().isMethodExpression()) {
485         attributeClass.setText("javax.el.MethodExpression");
486       }
487     }
488   }
489 
490   private void addDescription(
491       final ExecutableElement element, final org.jdom2.Element attribute, final Namespace namespace) {
492     String comment = processingEnv.getElementUtils().getDocComment(element);
493     if (comment != null) {
494       final int index = comment.indexOf('@');
495       if (index != -1) {
496         comment = comment.substring(0, index);
497       }
498       comment = comment.trim();
499       if (comment.length() > 0) {
500         final org.jdom2.Element description = new org.jdom2.Element(DESCRIPTION, namespace);
501         description.setText(comment);
502         attribute.addContent(description);
503       }
504     }
505   }
506 
507   private org.jdom2.Element createPropertyOrAttributeExtension(
508       final String extensionType, final ExecutableElement executableElement,
509       final UIComponentTagAttribute uiComponentTagAttribute,
510       final Namespace namespace)
511       throws IllegalArgumentException {
512     final org.jdom2.Element extensionElement = new org.jdom2.Element(extensionType, namespace);
513     final org.jdom2.Element valueExpression = new org.jdom2.Element(VALUE_EXPRESSION, namespace);
514     valueExpression.setText(uiComponentTagAttribute.expression().toMetaDataString());
515     extensionElement.addContent(valueExpression);
516     final String[] allowedValues = uiComponentTagAttribute.allowedValues();
517     if (allowedValues.length > 0) {
518       final org.jdom2.Element propertyValues = new org.jdom2.Element(PROPERTY_VALUES, namespace);
519       final StringBuilder values = new StringBuilder();
520       for (final String value : allowedValues) {
521         values.append(value).append(" ");
522       }
523       propertyValues.setText(values.toString());
524       extensionElement.addContent(propertyValues);
525     }
526     final Deprecated deprecated = executableElement.getAnnotation(Deprecated.class);
527     if (deprecated != null) {
528       final org.jdom2.Element elementDeprecated = new org.jdom2.Element(DEPRECATED, namespace);
529       elementDeprecated.setText("Warning: This property is deprecated!");
530       extensionElement.addContent(elementDeprecated);
531     }
532     final org.jdom2.Element hidden = new org.jdom2.Element(HIDDEN, namespace);
533     hidden.setText(Boolean.toString(uiComponentTagAttribute.isHidden()));
534     extensionElement.addContent(hidden);
535     final org.jdom2.Element readOnly = new org.jdom2.Element(READONLY, namespace);
536     readOnly.setText(Boolean.toString(uiComponentTagAttribute.isReadOnly()));
537     extensionElement.addContent(readOnly);
538     final TagAttribute tagAttribute = executableElement.getAnnotation(TagAttribute.class);
539     if (tagAttribute != null) {
540       final org.jdom2.Element required = new org.jdom2.Element(REQUIRED, namespace);
541       required.setText(Boolean.toString(tagAttribute.required()));
542       extensionElement.addContent(required);
543     }
544 
545     return extensionElement;
546   }
547 
548   protected void addAttributes(
549       final TypeElement typeElement, final List<org.jdom2.Element> attributes, final List<org.jdom2.Element> properties,
550       final Namespace namespace) {
551 
552     for (final javax.lang.model.element.Element element : processingEnv.getElementUtils().getAllMembers(typeElement)) {
553       final ExecutableElement executableElement = (ExecutableElement) element;
554       if (executableElement.getAnnotation(TagAttribute.class) == null
555           && executableElement.getAnnotation(UIComponentTagAttribute.class) == null) {
556         continue;
557       }
558 
559       addAttribute(executableElement, attributes, properties, namespace);
560     }
561   }
562 
563   private void addFacets(
564       final UIComponentTag componentTag, final Namespace namespace, final org.jdom2.Element element) {
565     final Facet[] facets = componentTag.facets();
566     for (final Facet facet : facets) {
567       final org.jdom2.Element facetElement = new org.jdom2.Element(FACET, namespace);
568       final String description = facet.description();
569       if (description.length() > 0) {
570         final org.jdom2.Element facetDescription = new org.jdom2.Element(DESCRIPTION, namespace);
571         facetDescription.setText(description);
572         facetElement.addContent(facetDescription);
573       }
574       final org.jdom2.Element facetName = new org.jdom2.Element(FACET_NAME, namespace);
575       facetName.setText(facet.name());
576       facetElement.addContent(facetName);
577       final org.jdom2.Element facetExtension = new org.jdom2.Element(FACET_EXTENSION, namespace);
578       final org.jdom2.Element elementAllowedChildComponents
579           = new org.jdom2.Element(ALLOWED_CHILD_COMPONENTS, namespace);
580       final String[] allowedChildComponents = facet.allowedChildComponenents();
581       final StringBuilder allowedComponentTypes = new StringBuilder();
582       for (final String componentType : allowedChildComponents) {
583         allowedComponentTypes.append(componentType).append(" ");
584       }
585       elementAllowedChildComponents.setText(allowedComponentTypes.toString());
586       facetExtension.addContent(elementAllowedChildComponents);
587       facetElement.addContent(facetExtension);
588       element.addContent(facetElement);
589     }
590   }
591 
592   protected void addElement(
593       final TypeElement typeElement, final List<org.jdom2.Element> components, final List<org.jdom2.Element> renderer,
594       final Namespace namespace) throws Exception {
595     final UIComponentTag componentTag = typeElement.getAnnotation(UIComponentTag.class);
596     if (componentTag != null) {
597       final ComponentInfo componentInfo = new ComponentInfo(typeElement, componentTag);
598       if (!componentTag.isComponentAlreadyDefined()) {
599         final org.jdom2.Element element = createComponentElement(componentInfo, componentTag, namespace);
600         if (element != null) {
601           if (!containsElement(components, element)) {
602             addFacets(componentTag, namespace, element);
603             final List<org.jdom2.Element> attributes = new ArrayList<>();
604             final List<org.jdom2.Element> properties = new ArrayList<>();
605             addAttributes(typeElement, attributes, properties, namespace);
606             if (!attributes.isEmpty()) {
607               attributes.sort(Comparator.comparing(d -> d.getChildText(ATTRIBUTE_NAME, namespace)));
608               element.addContent(attributes);
609             }
610             if (!properties.isEmpty()) {
611               properties.sort(Comparator.comparing(d -> d.getChildText(PROPERTY_NAME, namespace)));
612               element.addContent(properties);
613             }
614             element.addContent(createElementExtension(typeElement, componentTag, namespace));
615             components.add(element);
616           } else {
617             // TODO add facet and attributes
618           }
619         }
620       }
621       addRendererElement(componentInfo, componentTag, renderer, namespace);
622     }
623   }
624 
625   private void normaliseLineEndings(final Document document) {
626     final Iterator i = document.getDescendants(new ContentFilter(ContentFilter.COMMENT));
627     while (i.hasNext()) {
628       final Comment c = (Comment) i.next();
629       c.setText(c.getText().replaceAll("\n", SEPARATOR));
630     }
631   }
632 }