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