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;
21  
22  import com.sun.mirror.apt.AnnotationProcessorEnvironment;
23  import com.sun.mirror.apt.Filer;
24  import com.sun.mirror.declaration.ClassDeclaration;
25  import com.sun.mirror.declaration.InterfaceDeclaration;
26  import com.sun.mirror.declaration.MethodDeclaration;
27  import com.sun.mirror.declaration.TypeDeclaration;
28  import com.sun.mirror.type.InterfaceType;
29  import org.apache.commons.io.IOUtils;
30  import org.apache.myfaces.tobago.apt.annotation.Converter;
31  import org.apache.myfaces.tobago.apt.annotation.Facet;
32  import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
33  import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
34  import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
35  import org.apache.myfaces.tobago.apt.annotation.Validator;
36  import org.codehaus.plexus.util.FileUtils;
37  import org.jdom.Attribute;
38  import org.jdom.Comment;
39  import org.jdom.DocType;
40  import org.jdom.Document;
41  import org.jdom.Element;
42  import org.jdom.JDOMException;
43  import org.jdom.Namespace;
44  import org.jdom.filter.ContentFilter;
45  import org.jdom.input.SAXBuilder;
46  import org.jdom.output.Format;
47  import org.jdom.output.XMLOutputter;
48  import org.xml.sax.EntityResolver;
49  import org.xml.sax.InputSource;
50  import org.xml.sax.SAXException;
51  
52  import javax.xml.parsers.ParserConfigurationException;
53  import java.io.File;
54  import java.io.IOException;
55  import java.io.InputStream;
56  import java.io.StringReader;
57  import java.io.StringWriter;
58  import java.io.Writer;
59  import java.lang.reflect.Field;
60  import java.util.ArrayList;
61  import java.util.Collection;
62  import java.util.Iterator;
63  import java.util.List;
64  import java.util.Locale;
65  import java.util.Map;
66  
67  /*
68   * Date: Sep 25, 2006
69   * Time: 9:31:09 PM
70   */
71  public class FacesConfigAnnotationVisitor extends AbstractAnnotationVisitor {
72    public static final String SOURCE_FACES_CONFIG_KEY = "sourceFacesConfig";
73    public static final String TARGET_FACES_CONFIG_KEY = "targetFacesConfig";
74  
75    private String jsfVersion = "1.1";
76  
77    private static final String SEPARATOR = System.getProperty("line.separator");
78    private static final String COMPONENT = "component";
79    private static final String COMPONENT_TYPE = "component-type";
80    private static final String COMPONENT_CLASS = "component-class";
81    private static final String COMPONENT_EXTENSION = "component-extension";
82    private static final String ALLOWED_CHILD_COMPONENTS = "allowed-child-components";
83    private static final String CATEGORY = "category";
84    private static final String DEPRECATED = "deprecated";
85    private static final String HIDDEN = "hidden";
86    private static final String FACET = "facet";
87    private static final String DISPLAY_NAME = "display-name";
88    private static final String DESCRIPTION = "description";
89    private static final String FACET_NAME = "facet-name";
90    private static final String FACET_EXTENSION = "facet-extension";
91    private static final String PROPERTY = "property";
92    private static final String PROPERTY_NAME = "property-name";
93    private static final String PROPERTY_CLASS = "property-class";
94    private static final String PROPERTY_EXTENSION = "property-extension";
95    private static final String VALUE_EXPRESSION = "value-expression"; //UIComponentTagAttribute.valueExpression()
96    private static final String PROPERTY_VALUES = "property-values"; //UIComponentTagAttribute.allowedValues()
97    private static final String READONLY = "read-only";
98    private static final String REQUIRED = "required"; //UITagAttribute.required()
99    private static final String DEFAULT_VALUE = "default-value";
100   private static final String ATTRIBUTE = "attribute";
101   private static final String ATTRIBUTE_NAME = "attribute-name";
102   private static final String ATTRIBUTE_CLASS = "attribute-class";
103   private static final String ATTRIBUTE_EXTENSION = "attribute-extension";
104   private static final String APPLICATION = "application";
105   private static final String FACTORY = "factory";
106   private static final String CONVERTER = "converter";
107   private static final String CONVERTER_ID = "converter-id";
108   private static final String CONVERTER_FOR_CLASS = "converter-for-class";
109   private static final String CONVERTER_CLASS = "converter-class";
110   private static final String VALIDATOR = "validator";
111   private static final String VALIDATOR_ID = "validator-id";
112   private static final String VALIDATOR_FOR_CLASS = "validator-for-class";
113   private static final String VALIDATOR_CLASS = "validator-class";
114   private static final String RENDERER = "renderer";
115   private static final String COMPONENT_FAMILY = "component-family";
116   private static final String RENDER_KIT = "render-kit";
117   private static final String RENDER_KIT_ID = "render-kit-id";
118   private static final String RENDER_KIT_CLASS = "render-kit-class";
119   private static final String RENDERER_TYPE = "renderer-type";
120   private static final String RENDERER_CLASS = "renderer-class";
121 
122   public FacesConfigAnnotationVisitor(AnnotationProcessorEnvironment env) {
123     super(env);
124   }
125 
126   public void process() throws ParserConfigurationException, IOException {
127     String sourceFacesConfigFile = null;
128     String targetFacesConfigFile = null;
129     for (Map.Entry<String, String> entry : getEnv().getOptions().entrySet()) {
130       if (entry.getKey().startsWith("-A" + SOURCE_FACES_CONFIG_KEY + "=")) {
131         sourceFacesConfigFile = entry.getKey().substring(SOURCE_FACES_CONFIG_KEY.length() + 3);
132       }
133       if (entry.getKey().startsWith("-A" + TARGET_FACES_CONFIG_KEY + "=")) {
134         targetFacesConfigFile = entry.getKey().substring(TARGET_FACES_CONFIG_KEY.length() + 3);
135       }
136       if (entry.getKey().startsWith("-Ajsf-version=")) {
137         String version = entry.getKey().substring("-Ajsf-version=".length());
138         if ("1.2".equals(version)) {
139           jsfVersion = "1.2";
140         }
141         if ("2.0".equals(version)) {
142           jsfVersion = "2.0";
143         }
144       }
145     }
146     Document document;
147     Writer writer = null;
148     try {
149       String content = FileUtils.fileRead(sourceFacesConfigFile);
150       SAXBuilder builder = new SAXBuilder();
151       builder.setEntityResolver(new EntityResolver() {
152         public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
153           if ("-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN".equals(publicId)) {
154             InputStream stream = FacesConfigAnnotationVisitor.class.getResourceAsStream(
155                 "/org/apache/myfaces/tobago/dtd/web-facesconfig_1_1.dtd");
156             return new InputSource(stream);
157           }
158           return null;
159         }
160       });
161       document = builder.build(new StringReader(content));
162 
163       // Normalise line endings. For some reason, JDOM replaces \r\n inside a comment with \n.
164       normaliseLineEndings(document);
165 
166       // rewrite DOM as a string to find differences, since text outside the root element is not tracked
167 
168       Element rootElement = document.getRootElement();
169       if (is12()) {
170         rootElement.setNamespace(Namespace.getNamespace("http://java.sun.com/xml/ns/javaee"));
171         Namespace xsi = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
172         rootElement.addNamespaceDeclaration(Namespace.getNamespace("xi", "http://www.w3.org/2001/XInclude"));
173         rootElement.setAttribute(new Attribute("schemaLocation",
174             "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd", xsi));
175         rootElement.setAttribute("version", "1.2");
176       } else if (is20()) {
177         rootElement.setNamespace(Namespace.getNamespace("http://java.sun.com/xml/ns/javaee"));
178         Namespace xsi = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
179         rootElement.addNamespaceDeclaration(Namespace.getNamespace("xi", "http://www.w3.org/2001/XInclude"));
180         rootElement.setAttribute(new Attribute("schemaLocation",
181             "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd", xsi));
182         rootElement.setAttribute("version", "2.0");
183       }
184       Namespace namespace = rootElement.getNamespace();
185       if (is12()||is20()) {
186         applyNamespace(rootElement, namespace);
187       }
188       List<Element> components = rootElement.getChildren(COMPONENT, namespace);
189 
190       List<Element> newComponents = new ArrayList<Element>();
191       List<Element> newRenderer = new ArrayList<Element>();
192       List<Element> newConverters = new ArrayList<Element>();
193       List<Element> newValidators = new ArrayList<Element>();
194 
195       for (ClassDeclaration decl : getCollectedClassDeclarations()) {
196         if (decl.getAnnotation(UIComponentTag.class) != null) {
197           addElement(decl, newComponents, newRenderer, namespace);
198         } else if (decl.getAnnotation(Converter.class) != null) {
199           addConverter(decl, newConverters, namespace);
200         } else if (decl.getAnnotation(Validator.class) != null) {
201           addValidator(decl, newValidators, namespace);
202         }
203       }
204 
205       for (InterfaceDeclaration decl : getCollectedInterfaceDeclarations()) {
206         if (decl.getAnnotation(UIComponentTag.class) != null) {
207           addElement(decl, newComponents, newRenderer, namespace);
208         }
209       }
210       List<Element> elementsToAdd = new ArrayList<Element>();
211       // sort out duplicates
212       for (Element newElement : newComponents) {
213         boolean found = containsElement(components, newElement);
214         if (!found) {
215           elementsToAdd.add(newElement);
216         }
217       }
218       if (!elementsToAdd.isEmpty()) {
219         // if facesconfig contains no component section add the components after factory or application
220         int lastIndex = getIndexAfter(rootElement, COMPONENT, FACTORY, APPLICATION);
221         rootElement.addContent(lastIndex, elementsToAdd);
222       }
223       if (!newRenderer.isEmpty()) {
224         Element renderKit = new Element(RENDER_KIT, namespace);
225         Element renderKitId = new Element(RENDER_KIT_ID, namespace);
226         renderKitId.setText("tobago");
227         renderKit.addContent(renderKitId);
228         Element renderKitClass = new Element(RENDER_KIT_CLASS, namespace);
229         renderKitClass.setText("org.apache.myfaces.tobago.renderkit.TobagoRenderKit");
230         renderKit.addContent(renderKitClass);
231         renderKit.addContent(newRenderer);
232         int lastIndex = getIndexAfter(rootElement, CONVERTER, COMPONENT, FACTORY, APPLICATION);
233         rootElement.addContent(lastIndex, renderKit);
234       }
235       if (!newConverters.isEmpty()) {
236         int lastIndex = getIndexAfter(rootElement, RENDER_KIT, CONVERTER, COMPONENT, FACTORY, APPLICATION);
237         rootElement.addContent(lastIndex, newConverters);
238       }
239       if (!newValidators.isEmpty()) {
240         rootElement.addContent(newValidators);
241       }
242       if (is11()) {
243         document.setDocType(new DocType("faces-config",
244             "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN",
245            "http://java.sun.com/dtd/web-facesconfig_1_1.dtd"));
246       }
247       writer =
248           getEnv().getFiler().createTextFile(Filer.Location.SOURCE_TREE, "", new File(targetFacesConfigFile), null);
249 
250       StringWriter facesConfig = new StringWriter(1024);
251       Format format = Format.getPrettyFormat();
252       format.setLineSeparator(SEPARATOR);
253       XMLOutputter out = new XMLOutputter(format);
254       out.output(document, facesConfig);
255       if (is11()) {
256         // TODO: is this replace really necessary?
257         String facesConfigStr =
258             facesConfig.toString().replaceFirst(" xmlns=\"http://java.sun.com/JSF/Configuration\"", "");
259         // TODO: Find a better way
260         facesConfigStr = facesConfigStr.replaceFirst("\"http://java.sun.com/dtd/web-facesconfig_1_1.dtd\"",
261             "\"http://java.sun.com/dtd/web-facesconfig_1_1.dtd\"[\n"
262                 + "<!ELEMENT allowed-child-components (#PCDATA)>\n"
263                 + "<!ELEMENT category (#PCDATA)>\n"
264                 + "<!ELEMENT deprecated (#PCDATA)>\n"
265                 + "<!ELEMENT hidden (#PCDATA)>\n"
266                 + "<!ELEMENT preferred (#PCDATA)>\n"
267                 + "<!ELEMENT read-only (#PCDATA)>\n"
268                 + "<!ELEMENT value-expression (#PCDATA)>\n"
269                 + "<!ELEMENT property-values (#PCDATA)>\n"
270                 + "<!ELEMENT required (#PCDATA)>\n"
271                 + "]");
272         writer.append(facesConfigStr);
273       } else {
274         writer.append(facesConfig.toString());
275       }
276 
277     } catch (JDOMException e) {
278       e.printStackTrace();
279     } catch (IOException e) {
280       e.printStackTrace();
281     } finally {
282       IOUtils.closeQuietly(writer);
283     }
284   }
285 
286   private void applyNamespace(Element parent, Namespace namespace) {
287     for (Element element : (List<Element>) parent.getChildren()) {
288       element.setNamespace(namespace);
289       applyNamespace(element, namespace);
290     }
291   }
292 
293   private void addConverter(ClassDeclaration decl, List<Element> newConverters, Namespace namespace) {
294     Converter converterAnn = decl.getAnnotation(Converter.class);
295     Element converter = new Element(CONVERTER, namespace);
296     if (converterAnn.id().length() > 0) {
297       Element converterId = new Element(CONVERTER_ID, namespace);
298       converterId.setText(converterAnn.id());
299       converter.addContent(converterId);
300     } else if (converterAnn.forClass().length() > 0) {
301       Element converterForClass = new Element(CONVERTER_FOR_CLASS, namespace);
302       converterForClass.setText(converterAnn.forClass());
303       converter.addContent(converterForClass);
304     }
305 
306     Element converterClass = new Element(CONVERTER_CLASS, namespace);
307     converterClass.setText(decl.getQualifiedName());
308     converter.addContent(converterClass);
309     newConverters.add(converter);
310   }
311 
312   private void addValidator(ClassDeclaration decl, List<Element> newValidators, Namespace namespace) {
313     Validator validatorAnn = decl.getAnnotation(Validator.class);
314     Element validator = new Element(VALIDATOR, namespace);
315     if (validatorAnn.id().length() > 0) {
316       Element validatorId = new Element(VALIDATOR_ID, namespace);
317       validatorId.setText(validatorAnn.id());
318       validator.addContent(validatorId);
319     } else if (validatorAnn.forClass().length() > 0) {
320       Element validatorForClass = new Element(VALIDATOR_FOR_CLASS, namespace);
321       validatorForClass.setText(validatorAnn.forClass());
322       validator.addContent(validatorForClass);
323     }
324 
325     Element validatorClass = new Element(VALIDATOR_CLASS, namespace);
326     validatorClass.setText(decl.getQualifiedName());
327     validator.addContent(validatorClass);
328     newValidators.add(validator);
329   }
330 
331   private boolean containsElement(List<Element> components, Element newElement) {
332     return getEqualElement(components, newElement) != null;
333   }
334 
335   private Element getEqualElement(List<Element> components, Element newElement) {
336     for (Element element : components) {
337       if (equals(element, newElement)) {
338         return element;
339       }
340     }
341     return null;
342   }
343 
344   private int getIndexAfter(Element rootElement, String... tagNames) {
345     for (String tagName : tagNames) {
346       int index = getIndexAfter(rootElement, tagName);
347       if (index != 0) {
348         return index;
349       }
350     }
351     return 0;
352   }
353 
354   private int getIndexAfter(Element rootElement, String tagName) {
355     List<Element> components = rootElement.getChildren(tagName, rootElement.getNamespace());
356     if (!components.isEmpty()) {
357       return rootElement.indexOf(components.get(components.size() - 1)) + 1;
358     }
359     return 0;
360   }
361 
362   public boolean equals(Element element1, Element element2) {
363     Namespace namespace = element1.getNamespace();
364     if (element1.getName().equals(element2.getName()) && element1.getNamespace().equals(element2.getNamespace())) {
365       if (element1.getChildText(COMPONENT_CLASS, namespace).equals(element2.getChildText(COMPONENT_CLASS, namespace))) {
366         if (element1.getChildText(COMPONENT_TYPE, namespace).equals(element2.getChildText(COMPONENT_TYPE, namespace))) {
367           return true;
368         }
369       }
370     }
371     return false;
372   }
373 
374   protected Element createComponentElement(TypeDeclaration decl, UIComponentTag componentTag,
375       Class uiComponentClass, Namespace namespace) throws IOException, NoSuchFieldException, IllegalAccessException {
376     Field componentField = uiComponentClass.getField("COMPONENT_TYPE");
377     String componentType = (String) componentField.get(null);
378     Element element = new Element(COMPONENT, namespace);
379     String displayName = componentTag.displayName();
380     if (displayName.equals("")) {
381       displayName = uiComponentClass.getName().substring(uiComponentClass.getName().lastIndexOf(".") + 1);
382     }
383     Element elementDisplayName = new Element(DISPLAY_NAME, namespace);
384     elementDisplayName.setText(displayName);
385     element.addContent(elementDisplayName);
386     Element elementType = new Element(COMPONENT_TYPE, namespace);
387     elementType.setText(componentType);
388     element.addContent(elementType);
389     Element elementClass = new Element(COMPONENT_CLASS, namespace);
390     elementClass.setText(componentTag.uiComponent());
391     element.addContent(elementClass);
392 
393     return element;
394   }
395 
396   protected void addRendererElement(TypeDeclaration decl, UIComponentTag componentTag,
397       Class uiComponentClass, List<Element> renderer, Namespace namespace)
398       throws IOException, NoSuchFieldException, IllegalAccessException {
399     String rendererType = componentTag.rendererType();
400     if (rendererType != null && rendererType.length() > 0) {
401       Field componentField = uiComponentClass.getField("COMPONENT_FAMILY");
402       String componentFamily = (String) componentField.get(null);
403       Element element = new Element(RENDERER, namespace);
404       String displayName = componentTag.displayName();
405       if (displayName.equals("")) {
406         displayName = uiComponentClass.getName().substring(uiComponentClass.getName().lastIndexOf(".") + 1);
407       }
408       Element elementDisplayName = new Element(DISPLAY_NAME, namespace);
409       elementDisplayName.setText(displayName);
410       element.addContent(elementDisplayName);
411       Element elementComponentFamily = new Element(COMPONENT_FAMILY, namespace);
412       elementComponentFamily.addContent(componentFamily);
413       element.addContent(elementComponentFamily);
414       Element elementType = new Element(RENDERER_TYPE, namespace);
415       elementType.setText(rendererType);
416       element.addContent(elementType);
417       Element elementClass = new Element(RENDERER_CLASS, namespace);
418       String className = "org.apache.myfaces.tobago.renderkit." + rendererType + "Renderer";
419       elementClass.setText(className);
420       element.addContent(elementClass);
421       renderer.add(element);
422     }
423   }
424 
425 
426   private Element createElementExtension(TypeDeclaration decl, UIComponentTag uiComponentTag,
427       Namespace namespace) {
428     Element elementExtension = new Element(COMPONENT_EXTENSION, namespace);
429     Element elementAllowedChildComponents = new Element(ALLOWED_CHILD_COMPONENTS, namespace);
430     String[] allowedChildComponents = uiComponentTag.allowedChildComponenents();
431     String allowedComponentTypes = "";
432     for (String componentType : allowedChildComponents) {
433       allowedComponentTypes += componentType + " ";
434     }
435     elementAllowedChildComponents.setText(allowedComponentTypes);
436     elementExtension.addContent(elementAllowedChildComponents);
437     Element elementCategory = new Element(CATEGORY, namespace);
438     elementCategory.setText(uiComponentTag.category().toString());
439     elementExtension.addContent(elementCategory);
440     Deprecated deprecated = decl.getAnnotation(Deprecated.class);
441     if (deprecated != null) {
442       Element elementDeprecated = new Element(DEPRECATED, namespace);
443       elementDeprecated.setText("Warning: This component is deprecated!");
444       elementExtension.addContent(elementDeprecated);
445     }
446     Element elementHidden = new Element(HIDDEN, namespace);
447     elementHidden.setText(Boolean.toString(uiComponentTag.isHidden()));
448     elementExtension.addContent(elementHidden);
449 
450     return elementExtension;
451   }
452 
453   protected void addAttribute(MethodDeclaration d, Class uiComponentClass, List properties, List attributes,
454       Namespace namespace) {
455     UIComponentTagAttribute componentAttribute = d.getAnnotation(UIComponentTagAttribute.class);
456     if (componentAttribute != null) {
457       String simpleName = d.getSimpleName();
458       if (simpleName.startsWith("set")) {
459         String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
460         String methodStr;
461         if (componentAttribute.type().length == 1
462             && (componentAttribute.type()[0].equals(Boolean.class.getName())
463             || componentAttribute.type()[0].equals("boolean"))) {
464           methodStr = "is" + simpleName.substring(3);
465         } else {
466           methodStr = "get" + simpleName.substring(3);
467         }
468         try {
469           uiComponentClass.getMethod(methodStr, new Class[0]);
470           Element property = new Element(PROPERTY, namespace);
471           Element propertyName = new Element(PROPERTY_NAME, namespace);
472           Element propertyClass = new Element(PROPERTY_CLASS, namespace);
473 
474           propertyName.setText(attributeStr);
475           addClass(componentAttribute, propertyClass);
476 
477           addDescription(d, property, namespace);
478 
479           property.addContent(propertyName);
480           property.addContent(propertyClass);
481           if (componentAttribute.defaultValue().length() > 0) {
482             Element defaultValue = new Element(DEFAULT_VALUE, namespace);
483             defaultValue.setText(componentAttribute.defaultValue());
484             property.addContent(defaultValue);
485           }
486 
487           property.addContent(createPropertyOrAttributeExtension(PROPERTY_EXTENSION, d, componentAttribute, namespace));
488           properties.add(property);
489         } catch (NoSuchMethodException e) {
490           // if property not found should be attribute
491           Element attribute = new Element(ATTRIBUTE, namespace);
492           Element attributeName = new Element(ATTRIBUTE_NAME, namespace);
493           Element attributeClass = new Element(ATTRIBUTE_CLASS, namespace);
494 
495           attributeName.setText(attributeStr);
496           addClass(componentAttribute, attributeClass);
497 
498           addDescription(d, attribute, namespace);
499 
500           attribute.addContent(attributeName);
501           attribute.addContent(attributeClass);
502           if (componentAttribute.defaultValue().length() > 0) {
503             Element defaultValue = new Element(DEFAULT_VALUE, namespace);
504             defaultValue.setText(componentAttribute.defaultValue());
505             attribute.addContent(defaultValue);
506           }
507 
508           attribute.addContent(createPropertyOrAttributeExtension(ATTRIBUTE_EXTENSION, d,
509               componentAttribute, namespace));
510 
511           attributes.add(attribute);
512         }
513       } else {
514         throw new IllegalArgumentException("Only setter allowed found: " + simpleName);
515       }
516     }
517   }
518 
519   private void addClass(UIComponentTagAttribute componentAttribute, Element attributeClass) {
520     if (componentAttribute.type().length > 1) {
521       attributeClass.setText(Object.class.getName());
522     } else if (componentAttribute.type().length == 1) {
523       String className = componentAttribute.type()[0];
524       if (componentAttribute.expression().isMethodExpression() && isUnifiedEL()) {
525         className = "javax.el.MethodExpression";
526       }
527       attributeClass.setText((className.equals(Boolean.class.getName()) && !isUnifiedEL()) ? "boolean" : className);
528     } else {
529       if (componentAttribute.expression().isMethodExpression()) {
530         String className = "";
531         if (isUnifiedEL()) {
532           className = "javax.el.MethodExpression";
533         } else {
534           className = "javax.faces.el.MethodBinding";
535         }
536         attributeClass.setText(className);
537       }
538     }
539   }
540 
541   private void addDescription(MethodDeclaration d, Element attribute, Namespace namespace) {
542     String comment = d.getDocComment();
543     if (comment != null) {
544       int index = comment.indexOf('@');
545       if (index != -1) {
546         comment = comment.substring(0, index);
547       }
548       comment = comment.trim();
549       if (comment.length() > 0) {
550         Element description = new Element(DESCRIPTION, namespace);
551         description.setText(comment);
552         attribute.addContent(description);
553       }
554     }
555   }
556 
557   private Element createPropertyOrAttributeExtension(String extensionType, MethodDeclaration methodDeclaration,
558       UIComponentTagAttribute uiComponentTagAttribute, Namespace namespace) throws IllegalArgumentException {
559     Element extensionElement = new Element(extensionType, namespace);
560     Element valueExpression = new Element(VALUE_EXPRESSION, namespace);
561     valueExpression.setText(uiComponentTagAttribute.expression().toMetaDataString());
562     extensionElement.addContent(valueExpression);
563     String[] allowedValues = uiComponentTagAttribute.allowedValues();
564     if (allowedValues.length > 0) {
565       Element propertyValues = new Element(PROPERTY_VALUES, namespace);
566       String values = "";
567       for (String value : allowedValues) {
568         values += value + " ";
569       }
570       propertyValues.setText(values);
571       extensionElement.addContent(propertyValues);
572     }
573     Deprecated deprecated = methodDeclaration.getAnnotation(Deprecated.class);
574     if (deprecated != null) {
575       Element elementDeprecated = new Element(DEPRECATED, namespace);
576       elementDeprecated.setText("Warning: This property is deprecated!");
577       extensionElement.addContent(elementDeprecated);
578     }
579     Element hidden = new Element(HIDDEN, namespace);
580     hidden.setText(Boolean.toString(uiComponentTagAttribute.isHidden()));
581     extensionElement.addContent(hidden);
582     Element readOnly = new Element(READONLY, namespace);
583     readOnly.setText(Boolean.toString(uiComponentTagAttribute.isReadOnly()));
584     extensionElement.addContent(readOnly);
585     TagAttribute tagAttribute = methodDeclaration.getAnnotation(TagAttribute.class);
586     if (tagAttribute != null) {
587       Element required = new Element(REQUIRED, namespace);
588       required.setText(Boolean.toString(tagAttribute.required()));
589       extensionElement.addContent(required);
590     }
591 
592     return extensionElement;
593   }
594 
595   protected void addAttributes(InterfaceDeclaration type, Class uiComponentClass, List properties, List attributes,
596       Namespace namespace) {
597     addAttributes(type.getSuperinterfaces(), uiComponentClass, properties, attributes, namespace);
598     for (MethodDeclaration decl : getCollectedMethodDeclarations()) {
599       if (decl.getDeclaringType().equals(type)) {
600         addAttribute(decl, uiComponentClass, properties, attributes, namespace);
601       }
602     }
603   }
604 
605   protected void addAttributes(Collection<InterfaceType> interfaces, Class uiComponentClass, List properties,
606       List attributes, Namespace namespace) {
607     for (InterfaceType type : interfaces) {
608       addAttributes(type.getDeclaration(), uiComponentClass, properties, attributes, namespace);
609     }
610   }
611 
612   protected void addAttributes(ClassDeclaration d, Class uiComponentClass, List properties, List attributes,
613       Namespace namespace) {
614     for (MethodDeclaration decl : getCollectedMethodDeclarations()) {
615       if (d.getQualifiedName().
616           equals(decl.getDeclaringType().getQualifiedName())) {
617         addAttribute(decl, uiComponentClass, properties, attributes, namespace);
618       }
619     }
620     addAttributes(d.getSuperinterfaces(), uiComponentClass, properties, attributes, namespace);
621     if (d.getSuperclass() != null) {
622       addAttributes(d.getSuperclass().getDeclaration(), uiComponentClass, properties, attributes, namespace);
623     }
624   }
625 
626 
627   private void addFacets(UIComponentTag componentTag, Namespace namespace, Element element) {
628     Facet[] facets = componentTag.facets();
629     for (Facet facet : facets) {
630       Element facetElement = new Element(FACET, namespace);
631       String description = facet.description();
632       if (description != null && description.length() > 0) {
633         Element facetDescription = new Element(DESCRIPTION, namespace);
634         facetDescription.setText(description);
635         facetElement.addContent(facetDescription);
636       }
637       Element facetName = new Element(FACET_NAME, namespace);
638       facetName.setText(facet.name());
639       facetElement.addContent(facetName);
640       Element facetExtension = new Element(FACET_EXTENSION, namespace);
641       Element elementAllowedChildComponents = new Element(ALLOWED_CHILD_COMPONENTS, namespace);
642       String[] allowedChildComponents = facet.allowedChildComponenents();
643       String allowedComponentTypes = "";
644       for (String componentType : allowedChildComponents) {
645         allowedComponentTypes += componentType + " ";
646       }
647       elementAllowedChildComponents.setText(allowedComponentTypes);
648       facetExtension.addContent(elementAllowedChildComponents);
649       facetElement.addContent(facetExtension);
650       element.addContent(facetElement);
651     }
652   }
653 
654   protected void addElement(ClassDeclaration decl, List<Element> components, List<Element> renderer,
655       Namespace namespace) throws IOException {
656     UIComponentTag componentTag = decl.getAnnotation(UIComponentTag.class);
657     if (componentTag != null) {
658       try {
659         Class<?> uiComponentClass = Class.forName(componentTag.uiComponent());
660         if (!componentTag.isComponentAlreadyDefined()) {
661           Element element = createComponentElement(decl, componentTag, uiComponentClass, namespace);
662           if (element != null) {
663             if (!containsElement(components, element)) {
664               addFacets(componentTag, namespace, element);
665               List attributes = new ArrayList();
666               List properties = new ArrayList();
667               addAttributes(decl, uiComponentClass, attributes, properties, namespace);
668               if (!attributes.isEmpty()) {
669                 element.addContent(attributes);
670               }
671               if (!properties.isEmpty()) {
672                 element.addContent(properties);
673               }
674               element.addContent(createElementExtension(decl, componentTag, namespace));
675               components.add(element);
676             } else {
677               // TODO add facet and attributes
678             }
679           }
680         }
681         addRendererElement(decl, componentTag, uiComponentClass, renderer, namespace);
682       } catch (Exception e) {
683         e.printStackTrace();
684       }
685     }
686   }
687 
688   protected void addElement(InterfaceDeclaration decl, List<Element> components, List<Element> renderer,
689       Namespace namespace) throws IOException {
690     UIComponentTag componentTag = decl.getAnnotation(UIComponentTag.class);
691     if (componentTag != null) {
692       try {
693         Class<?> uiComponentClass = Class.forName(componentTag.uiComponent());
694         if (!componentTag.isComponentAlreadyDefined()) {
695           Element element = createComponentElement(decl, componentTag, uiComponentClass, namespace);
696           if (element != null) {
697             if (!containsElement(components, element)) {
698               addFacets(componentTag, namespace, element);
699               List attributes = new ArrayList();
700               List properties = new ArrayList();
701               addAttributes(decl, uiComponentClass, properties, attributes, namespace);
702               if (!attributes.isEmpty()) {
703                 element.addContent(attributes);
704               }
705               if (!properties.isEmpty()) {
706                 element.addContent(properties);
707               }
708               element.addContent(createElementExtension(decl, componentTag, namespace));
709               components.add(element);
710             } else {
711               // TODO add facet and attributes
712             }
713           }
714         }
715         addRendererElement(decl, componentTag, uiComponentClass, renderer, namespace);
716       } catch (Exception e) {
717         e.printStackTrace();
718       }
719     }
720   }
721 
722   private void normaliseLineEndings(Document document) {
723     for (Iterator i = document.getDescendants(new ContentFilter(ContentFilter.COMMENT)); i.hasNext();) {
724       Comment c = (Comment) i.next();
725       c.setText(c.getText().replaceAll("\n", SEPARATOR));
726     }
727   }
728 
729   private boolean is12() {
730     return "1.2".equals(jsfVersion);
731   }
732 
733   private boolean is11() {
734     return "1.1".equals(jsfVersion);
735   }
736 
737   private boolean is20() {
738     return "2.0".equals(jsfVersion);
739   }
740 
741   private boolean isUnifiedEL() {
742     return !"1.1".equals(jsfVersion);
743   }
744 }