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