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