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.Declaration;
26  import com.sun.mirror.declaration.InterfaceDeclaration;
27  import com.sun.mirror.declaration.MethodDeclaration;
28  import com.sun.mirror.declaration.TypeDeclaration;
29  import com.sun.mirror.type.InterfaceType;
30  import org.antlr.stringtemplate.StringTemplate;
31  import org.antlr.stringtemplate.StringTemplateGroup;
32  import org.apache.commons.io.IOUtils;
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.myfaces.tobago.apt.annotation.DynamicExpression;
35  import org.apache.myfaces.tobago.apt.annotation.Tag;
36  import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
37  import org.apache.myfaces.tobago.apt.annotation.TagGeneration;
38  import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
39  import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
40  import org.apache.myfaces.tobago.apt.generate.ClassInfo;
41  import org.apache.myfaces.tobago.apt.generate.ComponentInfo;
42  import org.apache.myfaces.tobago.apt.generate.ComponentPropertyInfo;
43  import org.apache.myfaces.tobago.apt.generate.PropertyInfo;
44  import org.apache.myfaces.tobago.apt.generate.RendererInfo;
45  import org.apache.myfaces.tobago.apt.generate.TagInfo;
46  
47  import java.io.File;
48  import java.io.IOException;
49  import java.io.InputStream;
50  import java.io.InputStreamReader;
51  import java.io.Reader;
52  import java.io.Writer;
53  import java.lang.reflect.Method;
54  import java.lang.reflect.Modifier;
55  import java.util.ArrayList;
56  import java.util.Collection;
57  import java.util.Collections;
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  public class CreateComponentAnnotationVisitor extends AbstractAnnotationVisitor {
66  
67    private StringTemplateGroup rendererStringTemplateGroup;
68    private StringTemplateGroup tagStringTemplateGroup;
69    private StringTemplateGroup tagAbstractStringTemplateGroup;
70    private StringTemplateGroup componentStringTemplateGroup;
71    private Set<String> renderer = new HashSet<String>();
72    private Set<String> ignoredProperties;
73    private String jsfVersion = "1.1";
74    private String tagVersion = "1.1";
75  
76    public CreateComponentAnnotationVisitor(AnnotationProcessorEnvironment env) {
77      super(env);
78  
79      for (Map.Entry<String, String> entry : getEnv().getOptions().entrySet()) {
80        if (entry.getKey().startsWith("-Ajsf-version=")) {
81          String version = entry.getKey().substring("-Ajsf-version=".length());
82          if ("1.2".equals(version)) {
83            jsfVersion = "1.2";
84            tagVersion = "1.2";
85          }
86          if ("2.0".equals(version)) {
87            jsfVersion = "2.0";
88            tagVersion = "1.2";
89          }
90        }
91      }
92      InputStream stream = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/renderer.stg");
93      Reader reader = new InputStreamReader(stream);
94      rendererStringTemplateGroup = new StringTemplateGroup(reader);
95      stream = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/tag" + tagVersion + ".stg");
96      reader = new InputStreamReader(stream);
97      tagStringTemplateGroup = new StringTemplateGroup(reader);
98      stream = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/tagAbstract"
99          + tagVersion + ".stg");
100     reader = new InputStreamReader(stream);
101     tagAbstractStringTemplateGroup = new StringTemplateGroup(reader);
102 
103     stream = getClass().getClassLoader().getResourceAsStream("org/apache/myfaces/tobago/apt/component"
104         + jsfVersion + ".stg");
105     reader = new InputStreamReader(stream);
106     componentStringTemplateGroup = new StringTemplateGroup(reader);
107     ignoredProperties = new HashSet<String>();
108     ignoredProperties.add("id");
109     ignoredProperties.add("rendered");
110     ignoredProperties.add("binding");
111 
112   }
113 
114   public void process() {
115     for (InterfaceDeclaration declaration : getCollectedInterfaceDeclarations()) {
116       if (declaration.getAnnotation(UIComponentTag.class) != null) {
117         try {
118           createRenderer(declaration);
119           createTagOrComponent(declaration);
120         } catch (IllegalArgumentException e) {
121           getEnv().getMessager().printError("Error during processing of "
122                   + declaration.getAnnotation(UIComponentTag.class).uiComponent());
123           throw e;
124         }
125       }
126     }
127     for (ClassDeclaration declaration : getCollectedClassDeclarations()) {
128       if (declaration.getAnnotation(Tag.class) != null && declaration.getAnnotation(TagGeneration.class) != null) {
129         createTag(declaration);
130       }
131     }
132   }
133 
134   private void createTag(ClassDeclaration declaration) {
135     List<PropertyInfo> properties = new ArrayList<PropertyInfo>();
136     addPropertiesForTagOnly(declaration, properties);
137     TagGeneration tagGeneration = declaration.getAnnotation(TagGeneration.class);
138 
139     TagInfo tagInfo = new TagInfo(declaration.getQualifiedName(), tagGeneration.className());
140     tagInfo.setSuperClass(declaration.getQualifiedName());
141     StringTemplate stringTemplate = tagAbstractStringTemplateGroup.getInstanceOf("tag");
142     stringTemplate.setAttribute("tagInfo", tagInfo);
143     tagInfo.getProperties().addAll(properties);
144     tagInfo.addImport("org.slf4j.Logger");
145     tagInfo.addImport("org.slf4j.LoggerFactory");
146     writeFile(tagInfo, stringTemplate);
147   }
148 
149   private void createTagOrComponent(InterfaceDeclaration declaration) {
150     UIComponentTag componentTag = declaration.getAnnotation(UIComponentTag.class);
151     Tag tag = declaration.getAnnotation(Tag.class);
152     Map<String, PropertyInfo> properties = new HashMap<String, PropertyInfo>();
153     addProperties(declaration, properties);
154     if (tag != null) {
155       String className = "org.apache.myfaces.tobago.internal.taglib." + StringUtils.capitalize(tag.name()) + "Tag";
156       TagInfo tagInfo = new TagInfo(declaration.getQualifiedName(), className, componentTag.rendererType());
157       for (PropertyInfo property : properties.values()) {
158         if (property.isTagAttribute()) {
159           tagInfo.getProperties().add(property);
160         }
161       }
162       if (isUnifiedEL()) {
163         tagInfo.setSuperClass("org.apache.myfaces.tobago.internal.taglib.TobagoELTag");
164       } else {
165         if (tagInfo.getBodyContent() != null) {
166           tagInfo.setSuperClass("org.apache.myfaces.tobago.internal.taglib.TobagoBodyTag");
167         } else {
168           tagInfo.setSuperClass("org.apache.myfaces.tobago.internal.taglib.TobagoTag");
169         }
170       }
171       tagInfo.setComponentClassName(componentTag.uiComponent());
172       tagInfo.addImport("org.apache.commons.lang.StringUtils");
173       tagInfo.addImport("org.slf4j.Logger");
174       tagInfo.addImport("org.slf4j.LoggerFactory");
175       tagInfo.addImport("javax.faces.application.Application");
176       tagInfo.addImport("javax.faces.component.UIComponent");
177       tagInfo.addImport("javax.faces.context.FacesContext");
178 
179       StringTemplate stringTemplate = tagStringTemplateGroup.getInstanceOf("tag");
180       stringTemplate.setAttribute("tagInfo", tagInfo);
181       writeFile(tagInfo, stringTemplate);
182     }
183 
184     if (componentTag.generate()) {
185       StringTemplate componentStringTemplate = componentStringTemplateGroup.getInstanceOf("component");
186       ComponentInfo componentInfo 
187           = new ComponentInfo(declaration.getQualifiedName(), componentTag.uiComponent(), componentTag.rendererType());
188       
189 /*
190       String p = componentTag.uiComponentBaseClass();
191       String c = componentTag.uiComponent();
192       String m = c.substring(0, 36) + "Abstract" + c.substring(36);
193       if (p.equals(m)) {
194         getEnv().getMessager().printNotice("*********** ok   " + c);
195       } else {
196         getEnv().getMessager().printNotice("*********** diff " + c + " " + p);
197       }
198 */
199       
200       componentInfo.setSuperClass(componentTag.uiComponentBaseClass());
201       componentInfo.setComponentFamily(componentTag.componentFamily());
202       componentInfo.setDescription(getDescription(declaration));
203       componentInfo.setDeprecated(declaration.getAnnotation(Deprecated.class) != null);
204       List<String> elMethods = Collections.emptyList();
205       if (isUnifiedEL()) {
206         elMethods = checkForElMethods(componentInfo, componentTag.interfaces());
207       }
208       for (String interfaces : componentTag.interfaces()) {
209         componentInfo.addInterface(interfaces);
210       }
211       if (componentTag.componentType().length() > 0) {
212         componentInfo.setComponentType(componentTag.componentType());
213       } else {
214         componentInfo.setComponentType(componentTag.uiComponent().replace(".component.UI", "."));
215       }
216       try {
217         Class componentBaseClass = Class.forName(componentTag.uiComponentBaseClass());
218         for (PropertyInfo info : properties.values()) {
219           String methodName
220               = (info.getType().equals("java.lang.Boolean") ? "is" : "get") + info.getUpperCamelCaseName();
221 
222           try {
223             Method method = componentBaseClass.getMethod(methodName);
224             if (Modifier.isAbstract(method.getModifiers())) {
225               addPropertyToComponent(componentInfo, info, elMethods, false);
226             }
227           } catch (NoSuchMethodException e) {
228             addPropertyToComponent(componentInfo, info, elMethods, false);
229           }
230         }
231         boolean found = false;
232         for (Method method : componentBaseClass.getMethods()) {
233           if ("invokeOnComponent".equals(method.getName())) {
234             found = true;
235           }
236         }
237         if (!found) {
238           componentInfo.setInvokeOnComponent(true);
239           componentInfo.addImport("javax.faces.context.FacesContext");
240           componentInfo.addImport("javax.faces.FacesException");
241           componentInfo.addImport("javax.faces.component.ContextCallback");
242           componentInfo.addImport("org.apache.myfaces.tobago.compat.FacesUtils");
243           componentInfo.addInterface("org.apache.myfaces.tobago.compat.InvokeOnComponent");
244         }
245 
246       } catch (ClassNotFoundException e) {
247         Map<String, PropertyInfo> baseClassProperties = getBaseClassProperties(componentTag.uiComponentBaseClass());
248         for (PropertyInfo info : properties.values()) {
249           if (!baseClassProperties.containsValue(info)) {
250             addPropertyToComponent(componentInfo, info, elMethods, false);
251           }
252         }
253       }
254 
255       componentStringTemplate.setAttribute("componentInfo", componentInfo);
256       writeFile(componentInfo, componentStringTemplate);
257     }
258 
259   }
260 
261   private List<String> checkForElMethods(ComponentInfo info, String[] interfaces) {
262     List<String> elMethods = new ArrayList<String>();
263     for (String interfaceName : interfaces) {
264       try {
265         Class.forName(interfaceName);
266         Class interfaceClass2 = Class.forName(interfaceName + "2");
267         info.addInterface(interfaceClass2.getName());
268         for (Method method : interfaceClass2.getMethods()) {
269           Class[] parameter = method.getParameterTypes();
270           if (parameter.length == 1 && "javax.el.MethodExpression".equals(parameter[0].getName())) {
271             elMethods.add(method.getName());
272           }
273         }
274       } catch (ClassNotFoundException e) {
275         // ignore
276       }
277     }
278     return elMethods;
279 
280   }
281 
282   private Map<String, PropertyInfo> getBaseClassProperties(String baseClass) {
283     for (InterfaceDeclaration declaration : getCollectedInterfaceDeclarations()) {
284       if (declaration.getAnnotation(UIComponentTag.class) != null) {
285         if (declaration.getAnnotation(UIComponentTag.class).uiComponent().equals(baseClass)
286             && declaration.getAnnotation(UIComponentTag.class).generate()) {
287           Map<String, PropertyInfo> properties = new HashMap<String, PropertyInfo>();
288           addProperties(declaration, properties);
289           return properties;
290         }
291       }
292     }
293     throw new IllegalStateException("No UIComponentTag found for componentClass " + baseClass);
294   }
295 
296   private ComponentPropertyInfo addPropertyToComponent(
297       ComponentInfo componentInfo, PropertyInfo info, List<String> elMethods, boolean methodExpression) {
298     ComponentPropertyInfo componentPropertyInfo = (ComponentPropertyInfo) info.fill(new ComponentPropertyInfo());
299     String possibleUnifiedElAlternative = "set" + info.getUpperCamelCaseName() + "Expression";
300     ComponentPropertyInfo elAlternative = null;
301     if (elMethods.contains(possibleUnifiedElAlternative) && !methodExpression) {
302       elAlternative = addPropertyToComponent(componentInfo, info, elMethods, true);
303       componentPropertyInfo.setElAlternativeAvailable(true);
304     }
305     componentInfo.addImport(componentPropertyInfo.getUnmodifiedType());
306     componentInfo.addImport("javax.faces.context.FacesContext");
307 
308     if ("markup".equals(info.getName())) {
309       componentInfo.addInterface("org.apache.myfaces.tobago.component.SupportsMarkup");
310     }
311     if ("requiredMessage".equals(info.getName())) {
312       componentInfo.setMessages(true);
313     }
314     if (methodExpression) {
315       componentPropertyInfo.setType("javax.el.MethodExpression");
316       componentPropertyInfo.setName(info.getName() + "Expression");
317     } else {
318       componentInfo.addPropertyInfo(componentPropertyInfo, elAlternative);
319     }
320     return componentPropertyInfo;
321   }
322 
323   private void createRenderer(TypeDeclaration declaration) {
324 
325     UIComponentTag componentTag = declaration.getAnnotation(UIComponentTag.class);
326     String rendererType = componentTag.rendererType();
327 
328     if (rendererType != null && rendererType.length() > 0) {
329       String className = "org.apache.myfaces.tobago.renderkit." + rendererType + "Renderer";
330       if (renderer.contains(className)) {
331         // already created
332         return;
333       }
334       renderer.add(className);
335       RendererInfo info = new RendererInfo(declaration.getQualifiedName(), className, rendererType);
336       if (componentTag.isLayout()) {
337         info.setSuperClass("org.apache.myfaces.tobago.renderkit.AbstractLayoutRendererWrapper");
338       } else if (componentTag.isTransparentForLayout()) {
339         info.setSuperClass("org.apache.myfaces.tobago.renderkit.AbstractRendererBaseWrapper");
340       } else {
341         info.setSuperClass("org.apache.myfaces.tobago.renderkit.AbstractLayoutableRendererBaseWrapper");
342       }
343       StringTemplate stringTemplate = rendererStringTemplateGroup.getInstanceOf("renderer");
344       stringTemplate.setAttribute("renderInfo", info);
345       writeFile(info, stringTemplate);
346     }
347   }
348 
349   protected void addPropertiesForTagOnly(ClassDeclaration type, List<PropertyInfo> properties) {
350     for (MethodDeclaration declaration : getCollectedMethodDeclarations()) {
351       if (declaration.getDeclaringType().equals(type)) {
352         addPropertyForTagOnly(declaration, properties);
353       }
354     }
355   }
356 
357   protected void addProperties(InterfaceDeclaration type, Map<String, PropertyInfo> properties) {
358     addProperties(type.getSuperinterfaces(), properties);
359     for (MethodDeclaration declaration : getCollectedMethodDeclarations()) {
360       if (declaration.getDeclaringType().equals(type)) {
361         addProperty(declaration, properties);
362       }
363     }
364   }
365 
366   protected void addProperties(Collection<InterfaceType> interfaces, Map<String, PropertyInfo> properties) {
367     for (InterfaceType type : interfaces) {
368       addProperties(type.getDeclaration(), properties);
369     }
370   }
371 
372   protected void addProperty(MethodDeclaration declaration, Map<String, PropertyInfo> properties) {
373     TagAttribute tagAttribute = declaration.getAnnotation(TagAttribute.class);
374     UIComponentTagAttribute uiComponentTagAttribute = declaration.getAnnotation(UIComponentTagAttribute.class);
375     if (uiComponentTagAttribute != null) {
376       String simpleName = declaration.getSimpleName();
377       if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
378         String name = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
379         if (ignoredProperties.contains(name)) {
380           return;
381         }
382         PropertyInfo propertyInfo = new PropertyInfo(name);
383         propertyInfo.setAllowedValues(uiComponentTagAttribute.allowedValues());
384         if (tagAttribute != null) {
385           propertyInfo.setBodyContent(tagAttribute.bodyContent());
386           propertyInfo.setTagAttribute(true);
387         }
388         final String type;
389         if (uiComponentTagAttribute.expression().isMethodExpression()) {
390           propertyInfo.setMethodExpressionRequired(true);
391           type = "javax.faces.el.MethodBinding";
392         } else {
393           if (uiComponentTagAttribute.expression() == DynamicExpression.VALUE_BINDING_REQUIRED) {
394             propertyInfo.setValueExpressionRequired(true);
395           } else if (uiComponentTagAttribute.expression() == DynamicExpression.PROHIBITED) {
396             propertyInfo.setLiteralOnly(true);
397           }
398 
399           if (uiComponentTagAttribute.type().length > 1) {
400             type = "java.lang.Object";
401           } else {
402             type = uiComponentTagAttribute.type()[0];
403           }
404         }
405         propertyInfo.setType(type);
406         propertyInfo.setDefaultValue(
407             uiComponentTagAttribute.defaultValue().length() > 0 ? uiComponentTagAttribute.defaultValue() : null);
408         propertyInfo.setDefaultCode(
409             uiComponentTagAttribute.defaultCode().length() > 0 ? uiComponentTagAttribute.defaultCode() : null);
410         propertyInfo.setMethodSignature(uiComponentTagAttribute.methodSignature());
411         propertyInfo.setDeprecated(declaration.getAnnotation(Deprecated.class) != null);
412         propertyInfo.setDescription(getDescription(declaration));
413         propertyInfo.setTransient(uiComponentTagAttribute.isTransient());
414         if (properties.containsKey(name)) {
415           getEnv().getMessager().printWarning("Redefinition of attribute '" + name + "'.");
416         }
417         properties.put(name, propertyInfo);
418       }
419     }
420   }
421   private String getDescription(Declaration d) {
422     String comment = d.getDocComment();
423     if (comment != null) {
424       int index = comment.indexOf('@');
425       if (index != -1) {
426         comment = comment.substring(0, index);
427       }
428       comment = comment.trim();
429       if (comment.length() > 0) {
430         return comment;
431       }
432     }
433     return null;
434   }
435 
436   protected void addPropertyForTagOnly(MethodDeclaration declaration, List<PropertyInfo> properties) {
437     TagAttribute tagAttribute = declaration.getAnnotation(TagAttribute.class);
438     if (tagAttribute != null) {
439       String simpleName = declaration.getSimpleName();
440       if (simpleName.startsWith("set") || simpleName.startsWith("get")) {
441         String attributeStr = simpleName.substring(3, 4).toLowerCase(Locale.ENGLISH) + simpleName.substring(4);
442         if (tagAttribute.name().length() > 0) {
443           attributeStr = tagAttribute.name();
444         }
445         PropertyInfo propertyInfo = new PropertyInfo(attributeStr);
446         propertyInfo.setType(tagAttribute.type());
447         properties.add(propertyInfo);
448       }
449     }
450   }
451 
452   private void writeFile(ClassInfo info, StringTemplate stringTemplate) {
453     Writer writer = null;
454     try {
455       writer = getEnv().getFiler().createTextFile(Filer.Location.SOURCE_TREE, info.getPackageName(),
456           new File(info.getClassName() + ".java"), null);
457       writer.append(stringTemplate.toString());
458     } catch (IOException e) {
459       e.printStackTrace();
460     } finally {
461       IOUtils.closeQuietly(writer);
462     }
463   }
464 
465   private boolean isUnifiedEL() {
466     return !"1.1".equals(jsfVersion);
467   }
468 }