    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    }