    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}