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