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    
015    package org.apache.tapestry5.internal.plastic;
016    
017    import java.lang.reflect.InvocationHandler;
018    import java.lang.reflect.Method;
019    import java.lang.reflect.Proxy;
020    import java.util.Map;
021    
022    @SuppressWarnings(
023    { "rawtypes", "unchecked" })
024    public 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            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
039            {
040                // args is null for no-arguments methods
041                if (args == null)
042                {
043                    String attributeName = method.getName();
044    
045                    if (attributes.containsKey(attributeName)) { return attributes.get(attributeName); }
046                }
047    
048                // TODO: Handling of equals() and hashCode() and toString(), plus other methods
049                // inherited from Object
050    
051                throw new RuntimeException(String.format("Annotation proxy for class %s does not handle method %s.",
052                        annotationType.getName(), method));
053            }
054        }
055    
056        private final Class annotationType;
057    
058        final Map<String, Object> attributes = PlasticInternalUtils.newMap();
059    
060        public AnnotationBuilder(Class annotationType, PlasticClassPool pool)
061        {
062            super(pool);
063    
064            this.annotationType = annotationType;
065    
066            attributes.put("annotationType", annotationType);
067    
068            // Annotation attributes are represented as methods, and for each method there may be a
069            // default value. Preload the default values, which may be overwritten by explicit
070            // values.
071    
072            for (Method m : annotationType.getMethods())
073            {
074                Object defaultValue = m.getDefaultValue();
075    
076                if (defaultValue != null)
077                {
078                    attributes.put(m.getName(), defaultValue);
079                }
080            }
081    
082            if (!attributes.containsKey("toString"))
083            {
084                attributes.put("toString", "@" + annotationType.getName());
085            }
086    
087        }
088    
089        protected void store(String name, Object value)
090        {
091            attributes.put(name, value);
092        }
093    
094        protected Class elementTypeForArrayAttribute(String name)
095        {
096            try
097            {
098                return annotationType.getMethod(name).getReturnType().getComponentType();
099            }
100            catch (Exception ex)
101            {
102                throw new RuntimeException(String.format(
103                        "Unable to determine element type for attribute '%s' of annotation %s: %s", name,
104                        annotationType.getName(), PlasticInternalUtils.toMessage(ex)), ex);
105            }
106        }
107    
108        public Object createAnnotation()
109        {
110            // Use a static inner class to keep the AnnotationBuilder from being retained
111    
112            InvocationHandler handler = new AnnotationValueHandler(annotationType, attributes);
113    
114            try
115            {
116                return Proxy.newProxyInstance(pool.loader, new Class[]
117                { annotationType }, handler);
118            }
119            catch (IllegalArgumentException ex)
120            {
121                throw new IllegalArgumentException(String.format("Unable to create instance of annotation type %s: %s",
122                        annotationType.getName(), PlasticInternalUtils.toMessage(ex)), ex);
123            }
124        }
125    
126    }