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        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}