1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
191
192
193
194
195
196
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
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
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 }