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.commons.lang.StringUtils;
26  import org.apache.myfaces.tobago.apt.AnnotationUtils;
27  import org.apache.myfaces.tobago.apt.annotation.DynamicExpression;
28  import org.apache.myfaces.tobago.apt.annotation.SimpleTag;
29  import org.apache.myfaces.tobago.apt.annotation.Tag;
30  import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
31  import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
32  import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
33  import org.apache.myfaces.tobago.apt.annotation.ValidatorTag;
34  import org.apache.myfaces.tobago.apt.generate.ClassInfo;
35  import org.apache.myfaces.tobago.apt.generate.ComponentInfo;
36  import org.apache.myfaces.tobago.apt.generate.ComponentPropertyInfo;
37  import org.apache.myfaces.tobago.apt.generate.PropertyInfo;
38  import org.apache.myfaces.tobago.apt.generate.RendererInfo;
39  import org.apache.myfaces.tobago.apt.generate.TagInfo;
40  
41  import javax.annotation.processing.SupportedAnnotationTypes;
42  import javax.annotation.processing.SupportedOptions;
43  import javax.faces.component.UIComponent;
44  import javax.lang.model.element.Element;
45  import javax.lang.model.element.ExecutableElement;
46  import javax.lang.model.element.TypeElement;
47  import javax.lang.model.type.TypeKind;
48  import javax.lang.model.type.TypeMirror;
49  import javax.tools.FileObject;
50  import java.io.IOException;
51  import java.io.InputStream;
52  import java.io.InputStreamReader;
53  import java.io.Reader;
54  import java.io.Writer;
55  import java.lang.reflect.Method;
56  import java.lang.reflect.Modifier;
57  import java.util.ArrayList;
58  import java.util.HashMap;
59  import java.util.HashSet;
60  import java.util.List;
61  import java.util.Locale;
62  import java.util.Map;
63  import java.util.Set;
64  
65  @SupportedAnnotationTypes({
66      "org.apache.myfaces.tobago.apt.annotation.Tag",
67      "org.apache.myfaces.tobago.apt.annotation.TagAttribute",
68      "org.apache.myfaces.tobago.apt.annotation.UIComponentTag",
69      "org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute",
70      "org.apache.myfaces.tobago.apt.annotation.Taglib",
71      "org.apache.myfaces.tobago.apt.annotation.SimpleTag"})
72  @SupportedOptions({
73      ClassesGenerator.TAG_VERSION,
74      ClassesGenerator.JSF_VERSION})
75  public class ClassesGenerator extends AbstractGenerator {
76  
77    static final String TAG_VERSION = "tagVersion";
78  
79    private StringTemplateGroup rendererStringTemplateGroup;
80    private StringTemplateGroup tagStringTemplateGroup;
81    private StringTemplateGroup tagAbstractStringTemplateGroup;
82    private StringTemplateGroup componentStringTemplateGroup;
83    private Set<String> renderer = new HashSet<String>();
84    private Set<String> ignoredProperties;
85    private String jsfVersion;
86    private String tagVersion;
87  
88    public void configure() {
89  
90      final Map<String, String> options = processingEnv.getOptions();
91      jsfVersion = options.get(ClassesGenerator.JSF_VERSION);
92      tagVersion = options.get(ClassesGenerator.TAG_VERSION);
93      tagVersion = "1.2"; // XXX
94  
95      info("Generating the classes *Tag, *Component, *Renderer");
96      info("Options:");
97      info(JSF_VERSION + ": " + jsfVersion);
98      info(TAG_VERSION + ": " + tagVersion);
99  
100     InputStream stream = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/renderer.stg");
101     Reader reader = new InputStreamReader(stream);
102     rendererStringTemplateGroup = new StringTemplateGroup(reader);
103     stream = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/tag" + tagVersion + ".stg");
104     reader = new InputStreamReader(stream);
105     tagStringTemplateGroup = new StringTemplateGroup(reader);
106     stream = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/tagAbstract"
107         + tagVersion + ".stg");
108     reader = new InputStreamReader(stream);
109     tagAbstractStringTemplateGroup = new StringTemplateGroup(reader);
110 
111     stream = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/component"
112         + jsfVersion + ".stg");
113     reader = new InputStreamReader(stream);
114     componentStringTemplateGroup = new StringTemplateGroup(reader);
115     ignoredProperties = new HashSet<String>();
116     ignoredProperties.add("id");
117     ignoredProperties.add("rendered");
118     ignoredProperties.add("binding");
119 
120   }
121 
122   public void generate() throws Exception {
123     for (final TypeElement element : getTypes()) {
124       if (element.getAnnotation(UIComponentTag.class) != null) {
125         try {
126           createRenderer(element);
127           createTagOrComponent(element);
128         } catch (final Exception e) {
129           throw new RuntimeException(
130               "Error during processing of " + element.getAnnotation(UIComponentTag.class).uiComponent(), e);
131         }
132       } else if (element.getAnnotation(SimpleTag.class) != null || element.getAnnotation(ValidatorTag.class) != null) {
133         createTag(element);
134       }
135     }
136   }
137 
138   private void createTag(final TypeElement declaration) throws IOException {
139     final List<PropertyInfo> properties = new ArrayList<PropertyInfo>();
140     addPropertiesForTagOnly(declaration, properties);
141     final String className = AnnotationUtils.generatedTagName(declaration);
142 
143     final TagInfo tagInfo = new TagInfo(declaration.getQualifiedName().toString(), className);
144     tagInfo.setSuperClass(declaration.getQualifiedName().toString());
145     final StringTemplate stringTemplate = tagAbstractStringTemplateGroup.getInstanceOf("tag");
146     stringTemplate.setAttribute("tagInfo", tagInfo);
147     tagInfo.getProperties().addAll(properties);
148     tagInfo.addImport("org.slf4j.Logger");
149     tagInfo.addImport("org.slf4j.LoggerFactory");
150     writeFile(tagInfo, stringTemplate);
151   }
152 
153   private void createTagOrComponent(final TypeElement declaration) throws IOException, ClassNotFoundException {
154     final UIComponentTag componentTag = declaration.getAnnotation(UIComponentTag.class);
155     final Tag tag = declaration.getAnnotation(Tag.class);
156     final Map<String, PropertyInfo> properties = new HashMap<String, PropertyInfo>();
157     addProperties(declaration, properties);
158     if (tag != null) {
159       final String className
160           = "org.apache.myfaces.tobago.internal.taglib." + StringUtils.capitalize(tag.name()) + "Tag";
161       final TagInfo tagInfo
162           = new TagInfo(declaration.getQualifiedName().toString(), className, componentTag.rendererType());
163       for (final PropertyInfo property : properties.values()) {
164         if (property.isTagAttribute()) {
165           tagInfo.getProperties().add(property);
166         }
167       }
168       tagInfo.setSuperClass("org.apache.myfaces.tobago.internal.taglib.TobagoELTag");
169       tagInfo.setComponentClassName(componentTag.uiComponent());
170       tagInfo.addImport("org.apache.myfaces.tobago.internal.util.StringUtils");
171       tagInfo.addImport("org.slf4j.Logger");
172       tagInfo.addImport("org.slf4j.LoggerFactory");
173       tagInfo.addImport("javax.faces.application.Application");
174       tagInfo.addImport("javax.faces.component.UIComponent");
175       tagInfo.addImport("javax.faces.context.FacesContext");
176 
177       final StringTemplate stringTemplate = tagStringTemplateGroup.getInstanceOf("tag");
178       stringTemplate.setAttribute("tagInfo", tagInfo);
179       writeFile(tagInfo, stringTemplate);
180     }
181 
182     if (componentTag.generate()) {
183       final StringTemplate componentStringTemplate = componentStringTemplateGroup.getInstanceOf("component");
184       final ComponentInfo componentInfo = new ComponentInfo(declaration, componentTag);
185       componentInfo.setSuperClass(componentTag.uiComponentBaseClass());
186       componentInfo.setDescription(getDescription(declaration));
187       componentInfo.setDeprecated(declaration.getAnnotation(Deprecated.class) != null);
188       for (final String interfaces : componentTag.interfaces()) {
189         componentInfo.addInterface(interfaces);
190       }
191 
192       final Class<? extends UIComponent> facesClass
193           = Class.forName(componentTag.uiComponentFacesClass()).asSubclass(UIComponent.class);
194 
195       for (final PropertyInfo info : properties.values()) {
196         final String infoType = info.getType();
197         final String methodName
198             = ((infoType.equals("java.lang.Boolean") || infoType.equals("boolean")) ? "is" : "get")
199             + info.getUpperCamelCaseName();
200 
201         boolean generate = info.isGenerate();
202         try {
203           final Method method = facesClass.getMethod(methodName);
204           if (!Modifier.isAbstract(method.getModifiers())) {
205             generate = false;
206           }
207         } catch (final NoSuchMethodException e) {
208           // generate = true
209         }
210         if (generate) {
211           addPropertyToComponent(componentInfo, info);
212         }
213 
214       }
215 
216       componentStringTemplate.setAttribute("componentInfo", componentInfo);
217       writeFile(componentInfo, componentStringTemplate);
218     }
219   }
220 
221 /*
222   private Map<String, PropertyInfo> getBaseClassProperties(String baseClass) {
223 
224     for (TypeElement typeElement : getTypes()) {
225       info("bcp " + typeElement);
226       if (typeElement.getAnnotation(UIComponentTag.class) != null) {
227         if (typeElement.getAnnotation(UIComponentTag.class).uiComponent().equals(baseClass)
228             && typeElement.getAnnotation(UIComponentTag.class).generate()) {
229           Map<String, PropertyInfo> properties = new HashMap<String, PropertyInfo>();
230           addProperties(typeElement, properties);
231           return properties;
232         }
233       }
234     }
235     throw new IllegalStateException("No UIComponentTag found for componentClass " + baseClass);
236   }
237 */
238 
239   private ComponentPropertyInfo addPropertyToComponent(final ComponentInfo componentInfo, final PropertyInfo info) {
240 
241     final ComponentPropertyInfo componentPropertyInfo = (ComponentPropertyInfo) info.fill(new ComponentPropertyInfo());
242     componentInfo.addImport(componentPropertyInfo.getUnmodifiedType());
243     componentInfo.addImport("javax.faces.context.FacesContext");
244     if ("markup".equals(info.getName())) {
245       componentInfo.addInterface("org.apache.myfaces.tobago.component.SupportsMarkup");
246     }
247     if ("requiredMessage".equals(info.getName())) {
248       componentInfo.setMessages(true);
249     }
250     componentInfo.addPropertyInfo(componentPropertyInfo);
251     return componentPropertyInfo;
252   }
253 
254   private void createRenderer(final TypeElement declaration) throws IOException {
255 
256     final UIComponentTag componentTag = declaration.getAnnotation(UIComponentTag.class);
257     final String rendererType = componentTag.rendererType();
258 
259     if (rendererType != null && rendererType.length() > 0) {
260       final String className = "org.apache.myfaces.tobago.renderkit." + rendererType + "Renderer";
261       if (renderer.contains(className)) {
262         // already created
263         return;
264       }
265       renderer.add(className);
266       final RendererInfo info = new RendererInfo(declaration.getQualifiedName().toString(), className, rendererType);
267       if (componentTag.isLayout()) {
268         info.setSuperClass("org.apache.myfaces.tobago.renderkit.AbstractLayoutRendererWrapper");
269       } else if (componentTag.isTransparentForLayout()) {
270         info.setSuperClass("org.apache.myfaces.tobago.renderkit.AbstractRendererBaseWrapper");
271       } else {
272         info.setSuperClass("org.apache.myfaces.tobago.renderkit.AbstractLayoutableRendererBaseWrapper");
273       }
274       final StringTemplate stringTemplate = rendererStringTemplateGroup.getInstanceOf("renderer");
275       stringTemplate.setAttribute("renderInfo", info);
276       writeFile(info, stringTemplate);
277     }
278   }
279 
280   protected void addPropertiesForTagOnly(final TypeElement type, final List<PropertyInfo> properties) {
281 
282     final List<? extends Element> members = processingEnv.getElementUtils().getAllMembers(type);
283     for (final Element member : members) {
284       if (member instanceof ExecutableElement) {
285         final ExecutableElement executableElement = (ExecutableElement) member;
286         addPropertyForTagOnly(executableElement, properties);
287       }
288     }
289   }
290 
291   protected void addProperties(final TypeElement type, final Map<String, PropertyInfo> properties) {
292     addProperties(type.getInterfaces(), properties);
293     addProperties(type.getSuperclass(), properties);
294 
295     final List<? extends Element> members = processingEnv.getElementUtils().getAllMembers(type);
296     for (final Element member : members) {
297       if (member instanceof ExecutableElement) {
298         final ExecutableElement executableElement = (ExecutableElement) member;
299         addProperty(executableElement, properties);
300       }
301     }
302   }
303 
304   protected void addProperties(
305       final List<? extends TypeMirror> interfaces, final Map<String, PropertyInfo> properties) {
306     for (final TypeMirror typeMirror : interfaces) {
307       addProperties(typeMirror, properties);
308     }
309   }
310 
311   protected void addProperties(final TypeMirror typeMirror, final Map<String, PropertyInfo> properties) {
312     if (typeMirror.getKind() != TypeKind.NONE) {
313       addProperties((TypeElement) (processingEnv.getTypeUtils().asElement(typeMirror)), properties);
314     }
315   }
316 
317   protected void addProperty(final ExecutableElement declaration, final Map<String, PropertyInfo> properties) {
318     final TagAttribute tagAttribute = declaration.getAnnotation(TagAttribute.class);
319     final UIComponentTagAttribute uiComponentTagAttribute = declaration.getAnnotation(UIComponentTagAttribute.class);
320     if (uiComponentTagAttribute != null) {
321       final String simpleName = declaration.getSimpleName().toString();
322       if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
323         final String name = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
324         if (ignoredProperties.contains(name)) {
325           return;
326         }
327         final PropertyInfo propertyInfo = new PropertyInfo(name);
328         propertyInfo.setAllowedValues(uiComponentTagAttribute.allowedValues());
329         if (tagAttribute != null) {
330           propertyInfo.setBodyContent(tagAttribute.bodyContent());
331           propertyInfo.setTagAttribute(true);
332         }
333         final String type;
334         if (uiComponentTagAttribute.expression().isMethodExpression()) {
335           propertyInfo.setMethodExpressionRequired(true);
336           type = "javax.el.MethodExpression";
337         } else {
338           if (uiComponentTagAttribute.expression() == DynamicExpression.VALUE_EXPRESSION_REQUIRED) {
339             propertyInfo.setValueExpressionRequired(true);
340           } else if (uiComponentTagAttribute.expression() == DynamicExpression.PROHIBITED) {
341             propertyInfo.setLiteralOnly(true);
342           }
343 
344           if (uiComponentTagAttribute.type().length > 1) {
345             type = "java.lang.Object";
346           } else {
347             type = uiComponentTagAttribute.type()[0];
348           }
349         }
350         propertyInfo.setType(type);
351         propertyInfo.setDefaultValue(
352             uiComponentTagAttribute.defaultValue().length() > 0 ? uiComponentTagAttribute.defaultValue() : null);
353         propertyInfo.setDefaultCode(
354             uiComponentTagAttribute.defaultCode().length() > 0 ? uiComponentTagAttribute.defaultCode() : null);
355         propertyInfo.setMethodSignature(uiComponentTagAttribute.methodSignature());
356         propertyInfo.setDeprecated(declaration.getAnnotation(Deprecated.class) != null);
357         propertyInfo.setDescription(getDescription(declaration));
358         propertyInfo.setTransient(uiComponentTagAttribute.isTransient());
359         propertyInfo.setGenerate(uiComponentTagAttribute.generate());
360         if (properties.containsKey(name)) {
361           warn("Redefinition of attribute '" + name + "'.");
362         }
363         properties.put(name, propertyInfo);
364       }
365     }
366   }
367 
368   private String getDescription(final Element element) {
369     String comment = processingEnv.getElementUtils().getDocComment(element);
370     if (comment != null) {
371       final int index = comment.indexOf('@');
372       if (index != -1) {
373         comment = comment.substring(0, index);
374       }
375       comment = comment.trim();
376       if (comment.length() > 0) {
377         return comment;
378       }
379     }
380     return null;
381   }
382 
383   protected void addPropertyForTagOnly(final ExecutableElement declaration, final List<PropertyInfo> properties) {
384     final TagAttribute tagAttribute = declaration.getAnnotation(TagAttribute.class);
385     if (tagAttribute != null) {
386       final String simpleName = declaration.getSimpleName().toString();
387       if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
388         String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
389         if (tagAttribute.name().length() > 0) {
390           attributeStr = tagAttribute.name();
391         }
392         final PropertyInfo propertyInfo = new PropertyInfo(attributeStr);
393         propertyInfo.setType(tagAttribute.type());
394         properties.add(propertyInfo);
395       }
396     }
397   }
398 
399   private void writeFile(final ClassInfo info, final StringTemplate stringTemplate) throws IOException {
400     Writer writer = null;
401     try {
402       final FileObject resource = processingEnv.getFiler().createSourceFile(
403           info.getPackageName() + '.' + info.getClassName());
404       info("Writing to file: " + resource.toUri());
405       writer = resource.openWriter();
406 
407       writer.append(stringTemplate.toString());
408     } finally {
409       IOUtils.closeQuietly(writer);
410     }
411   }
412 }