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.antlr.stringtemplate.StringTemplate;
23  import org.antlr.stringtemplate.StringTemplateGroup;
24  import org.apache.commons.io.IOUtils;
25  import org.apache.myfaces.tobago.apt.annotation.DynamicExpression;
26  import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
27  import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
28  import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
29  import org.apache.myfaces.tobago.apt.generate.ClassInfo;
30  import org.apache.myfaces.tobago.apt.generate.ComponentInfo;
31  import org.apache.myfaces.tobago.apt.generate.ComponentPropertyInfo;
32  import org.apache.myfaces.tobago.apt.generate.PropertyInfo;
33  import org.apache.myfaces.tobago.apt.generate.RendererInfo;
34  
35  import javax.annotation.processing.SupportedAnnotationTypes;
36  import javax.faces.component.UIComponent;
37  import javax.lang.model.element.Element;
38  import javax.lang.model.element.ExecutableElement;
39  import javax.lang.model.element.TypeElement;
40  import javax.lang.model.type.TypeKind;
41  import javax.lang.model.type.TypeMirror;
42  import javax.tools.FileObject;
43  import java.io.IOException;
44  import java.io.InputStream;
45  import java.io.InputStreamReader;
46  import java.io.Reader;
47  import java.io.Writer;
48  import java.lang.reflect.Method;
49  import java.lang.reflect.Modifier;
50  import java.util.HashMap;
51  import java.util.HashSet;
52  import java.util.List;
53  import java.util.Locale;
54  import java.util.Map;
55  import java.util.Set;
56  
57  @SupportedAnnotationTypes({
58      "org.apache.myfaces.tobago.apt.annotation.Tag",
59      "org.apache.myfaces.tobago.apt.annotation.TagAttribute",
60      "org.apache.myfaces.tobago.apt.annotation.UIComponentTag",
61      "org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute",
62      "org.apache.myfaces.tobago.apt.annotation.Taglib",
63      "org.apache.myfaces.tobago.apt.annotation.SimpleTag"})
64  public class ClassesGenerator extends AbstractGenerator {
65  
66    private StringTemplateGroup rendererStringTemplateGroup;
67    private StringTemplateGroup componentStringTemplateGroup;
68    private Set<String> renderer = new HashSet<String>();
69    private Set<String> ignoredProperties;
70  
71    @Override
72    public void configure() {
73  
74      info("Generating the classes *Component, *Renderer");
75  
76      final InputStream rendererStream
77          = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/renderer.stg");
78      final Reader rendererReader = new InputStreamReader(rendererStream);
79      rendererStringTemplateGroup = new StringTemplateGroup(rendererReader);
80  
81      final InputStream componentStream
82          = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/component.stg");
83      final Reader componentReader = new InputStreamReader(componentStream);
84      componentStringTemplateGroup = new StringTemplateGroup(componentReader);
85  
86      ignoredProperties = new HashSet<String>();
87      ignoredProperties.add("id");
88      ignoredProperties.add("rendered");
89      ignoredProperties.add("binding");
90    }
91  
92    @Override
93    public void generate() throws Exception {
94      for (final TypeElement element : getTypes()) {
95        if (element.getAnnotation(UIComponentTag.class) != null) {
96          try {
97            createRenderer(element);
98            createTagOrComponent(element);
99          } catch (final Exception e) {
100           throw new RuntimeException(
101               "Error during processing of " + element.getAnnotation(UIComponentTag.class).uiComponent(), e);
102         }
103       }
104     }
105   }
106 
107   private void createTagOrComponent(final TypeElement declaration) throws IOException, ClassNotFoundException {
108     final UIComponentTag componentTag = declaration.getAnnotation(UIComponentTag.class);
109     final Map<String, PropertyInfo> properties = new HashMap<String, PropertyInfo>();
110     addProperties(declaration, properties);
111 
112     if (componentTag.generate()) {
113       final StringTemplate componentStringTemplate = componentStringTemplateGroup.getInstanceOf("component");
114       final ComponentInfo componentInfo = new ComponentInfo(declaration, componentTag);
115       componentInfo.setSuperClass(componentTag.uiComponentBaseClass());
116       componentInfo.setDescription(getDescription(declaration));
117       componentInfo.setDeprecated(declaration.getAnnotation(Deprecated.class) != null);
118       for (final String interfaces : componentTag.interfaces()) {
119         componentInfo.addInterface(interfaces);
120       }
121 
122       final Class<? extends UIComponent> facesClass
123           = Class.forName(componentTag.uiComponentFacesClass()).asSubclass(UIComponent.class);
124 
125       for (final PropertyInfo info : properties.values()) {
126         final String infoType = info.getType();
127         final String methodName
128             = ((infoType.equals("java.lang.Boolean") || infoType.equals("boolean")) ? "is" : "get")
129             + info.getUpperCamelCaseName();
130 
131         boolean generate = info.isGenerate();
132         try {
133           final Method method = facesClass.getMethod(methodName);
134           if (!Modifier.isAbstract(method.getModifiers())) {
135             generate = false;
136           }
137         } catch (final NoSuchMethodException e) {
138           // generate = true
139         }
140         if (generate) {
141           addPropertyToComponent(componentInfo, info);
142         }
143 
144       }
145 
146       componentStringTemplate.setAttribute("componentInfo", componentInfo);
147       writeFile(componentInfo, componentStringTemplate);
148     }
149   }
150 
151   private ComponentPropertyInfo addPropertyToComponent(final ComponentInfo componentInfo, final PropertyInfo info) {
152 
153     final ComponentPropertyInfo componentPropertyInfo = (ComponentPropertyInfo) info.fill(new ComponentPropertyInfo());
154     componentInfo.addImport(componentPropertyInfo.getUnmodifiedType());
155     componentInfo.addImport("javax.faces.context.FacesContext");
156     if ("markup".equals(info.getName())) {
157       componentInfo.addInterface("org.apache.myfaces.tobago.component.Visual");
158     }
159     if ("requiredMessage".equals(info.getName())) {
160       componentInfo.setMessages(true);
161     }
162     componentInfo.addPropertyInfo(componentPropertyInfo);
163     return componentPropertyInfo;
164   }
165 
166   private void createRenderer(final TypeElement declaration) throws IOException {
167 
168     final UIComponentTag componentTag = declaration.getAnnotation(UIComponentTag.class);
169     final String rendererType = componentTag.rendererType();
170 
171     if (rendererType != null && rendererType.length() > 0) {
172       final String className = "org.apache.myfaces.tobago.renderkit." + rendererType + "Renderer";
173       if (renderer.contains(className)) {
174         // already created
175         return;
176       }
177       renderer.add(className);
178       final RendererInfo info = new RendererInfo(declaration.getQualifiedName().toString(), className, rendererType);
179       info.setSuperClass("org.apache.myfaces.tobago.renderkit.AbstractRendererBaseWrapper");
180       final StringTemplate stringTemplate = rendererStringTemplateGroup.getInstanceOf("renderer");
181       stringTemplate.setAttribute("renderInfo", info);
182       writeFile(info, stringTemplate);
183     }
184   }
185 
186   protected void addProperties(final TypeElement type, final Map<String, PropertyInfo> properties) {
187     addProperties(type.getInterfaces(), properties);
188     addProperties(type.getSuperclass(), properties);
189 
190     final List<? extends Element> members = processingEnv.getElementUtils().getAllMembers(type);
191     for (final Element member : members) {
192       if (member instanceof ExecutableElement) {
193         final ExecutableElement executableElement = (ExecutableElement) member;
194         addProperty(executableElement, properties);
195       }
196     }
197   }
198 
199   protected void addProperties(
200       final List<? extends TypeMirror> interfaces, final Map<String, PropertyInfo> properties) {
201     for (final TypeMirror typeMirror : interfaces) {
202       addProperties(typeMirror, properties);
203     }
204   }
205 
206   protected void addProperties(final TypeMirror typeMirror, final Map<String, PropertyInfo> properties) {
207     if (typeMirror.getKind() != TypeKind.NONE) {
208       addProperties((TypeElement) (processingEnv.getTypeUtils().asElement(typeMirror)), properties);
209     }
210   }
211 
212   protected void addProperty(final ExecutableElement declaration, final Map<String, PropertyInfo> properties) {
213     final TagAttribute tagAttribute = declaration.getAnnotation(TagAttribute.class);
214     final UIComponentTagAttribute uiComponentTagAttribute = declaration.getAnnotation(UIComponentTagAttribute.class);
215     if (uiComponentTagAttribute != null) {
216       final String simpleName = declaration.getSimpleName().toString();
217       if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
218         final String name = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
219         if (ignoredProperties.contains(name)) {
220           return;
221         }
222         final PropertyInfo propertyInfo = new PropertyInfo(name);
223         propertyInfo.setAllowedValues(uiComponentTagAttribute.allowedValues());
224         if (tagAttribute != null) {
225           propertyInfo.setBodyContent(tagAttribute.bodyContent());
226           propertyInfo.setTagAttribute(true);
227         }
228         final String type;
229         if (uiComponentTagAttribute.expression().isMethodExpression()) {
230           propertyInfo.setMethodExpressionRequired(true);
231           type = "javax.el.MethodExpression";
232         } else {
233           if (uiComponentTagAttribute.expression() == DynamicExpression.VALUE_EXPRESSION_REQUIRED) {
234             propertyInfo.setValueExpressionRequired(true);
235           } else if (uiComponentTagAttribute.expression() == DynamicExpression.PROHIBITED) {
236             propertyInfo.setLiteralOnly(true);
237           }
238 
239           if (uiComponentTagAttribute.type().length > 1) {
240             type = "java.lang.Object";
241           } else {
242             type = uiComponentTagAttribute.type()[0];
243           }
244         }
245         propertyInfo.setType(type);
246         propertyInfo.setDefaultValue(
247             uiComponentTagAttribute.defaultValue().length() > 0 ? uiComponentTagAttribute.defaultValue() : null);
248         propertyInfo.setDefaultCode(
249             uiComponentTagAttribute.defaultCode().length() > 0 ? uiComponentTagAttribute.defaultCode() : null);
250         propertyInfo.setMethodSignature(uiComponentTagAttribute.methodSignature());
251         propertyInfo.setDeprecated(declaration.getAnnotation(Deprecated.class) != null);
252         propertyInfo.setDescription(getDescription(declaration));
253         propertyInfo.setTransient(uiComponentTagAttribute.isTransient());
254         propertyInfo.setGenerate(uiComponentTagAttribute.generate());
255         if (properties.containsKey(name)) {
256           warn("Redefinition of attribute '" + name + "'.");
257         }
258         properties.put(name, propertyInfo);
259       }
260     }
261   }
262 
263   private String getDescription(final Element element) {
264     String comment = processingEnv.getElementUtils().getDocComment(element);
265     if (comment != null) {
266       final int index = comment.indexOf('@');
267       if (index != -1) {
268         comment = comment.substring(0, index);
269       }
270       comment = comment.trim();
271       if (comment.length() > 0) {
272         return comment;
273       }
274     }
275     return null;
276   }
277 
278   private void writeFile(final ClassInfo info, final StringTemplate stringTemplate) throws IOException {
279     Writer writer = null;
280     try {
281       final FileObject resource = processingEnv.getFiler().createSourceFile(
282           info.getPackageName() + '.' + info.getClassName());
283       info("Writing to file: " + resource.toUri());
284       writer = resource.openWriter();
285 
286       writer.append(stringTemplate.toString());
287     } finally {
288       IOUtils.closeQuietly(writer);
289     }
290   }
291 }