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}