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