001// Copyright 2011 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.internal.plastic; 016 017import java.lang.reflect.InvocationHandler; 018import java.lang.reflect.Method; 019import java.lang.reflect.Proxy; 020import java.util.Map; 021 022@SuppressWarnings( 023{ "rawtypes", "unchecked" }) 024public class AnnotationBuilder extends AbstractAnnotationBuilder 025{ 026 private static final class AnnotationValueHandler implements InvocationHandler 027 { 028 private final Class annotationType; 029 030 private final Map<String, Object> attributes; 031 032 public AnnotationValueHandler(final Class annotationType, Map<String, Object> attributes) 033 { 034 this.annotationType = annotationType; 035 this.attributes = attributes; 036 } 037 038 @Override 039 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 040 { 041 // args is null for no-arguments methods 042 if (args == null) 043 { 044 String attributeName = method.getName(); 045 046 if (attributes.containsKey(attributeName)) { return attributes.get(attributeName); } 047 } 048 049 // TODO: Handling of equals() and hashCode() and toString(), plus other methods 050 // inherited from Object 051 052 throw new RuntimeException(String.format("Annotation proxy for class %s does not handle method %s.", 053 annotationType.getName(), method)); 054 } 055 } 056 057 private final Class annotationType; 058 059 final Map<String, Object> attributes = PlasticInternalUtils.newMap(); 060 061 public AnnotationBuilder(Class annotationType, PlasticClassPool pool) 062 { 063 super(pool); 064 065 this.annotationType = annotationType; 066 067 attributes.put("annotationType", annotationType); 068 069 // Annotation attributes are represented as methods, and for each method there may be a 070 // default value. Preload the default values, which may be overwritten by explicit 071 // values. 072 073 for (Method m : annotationType.getMethods()) 074 { 075 Object defaultValue = m.getDefaultValue(); 076 077 if (defaultValue != null) 078 { 079 attributes.put(m.getName(), defaultValue); 080 } 081 } 082 083 if (!attributes.containsKey("toString")) 084 { 085 attributes.put("toString", "@" + annotationType.getName()); 086 } 087 088 } 089 090 @Override 091 protected void store(String name, Object value) 092 { 093 attributes.put(name, value); 094 } 095 096 @Override 097 protected Class elementTypeForArrayAttribute(String name) 098 { 099 try 100 { 101 return annotationType.getMethod(name).getReturnType().getComponentType(); 102 } 103 catch (Exception ex) 104 { 105 throw new RuntimeException(String.format( 106 "Unable to determine element type for attribute '%s' of annotation %s: %s", name, 107 annotationType.getName(), PlasticInternalUtils.toMessage(ex)), ex); 108 } 109 } 110 111 public Object createAnnotation() 112 { 113 // Use a static inner class to keep the AnnotationBuilder from being retained 114 115 InvocationHandler handler = new AnnotationValueHandler(annotationType, attributes); 116 117 try 118 { 119 return Proxy.newProxyInstance(pool.loader, new Class[] 120 { annotationType }, handler); 121 } 122 catch (IllegalArgumentException ex) 123 { 124 throw new IllegalArgumentException(String.format("Unable to create instance of annotation type %s: %s", 125 annotationType.getName(), PlasticInternalUtils.toMessage(ex)), ex); 126 } 127 } 128 129}