001// Copyright 2011, 2012 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 org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor;
018import org.apache.tapestry5.internal.plastic.asm.Opcodes;
019import org.apache.tapestry5.internal.plastic.asm.Type;
020
021import java.lang.reflect.Array;
022import java.util.ArrayList;
023import java.util.List;
024
025@SuppressWarnings(
026{ "rawtypes", "unchecked" })
027public abstract class AbstractAnnotationBuilder extends AnnotationVisitor
028{
029    protected final PlasticClassPool pool;
030
031    public AbstractAnnotationBuilder(PlasticClassPool pool)
032    {
033        super(Opcodes.ASM4);
034
035        this.pool = pool;
036    }
037
038    protected abstract void store(String name, Object value);
039
040    protected Class elementTypeForArrayAttribute(String name)
041    {
042        throw new IllegalStateException("elementTypeForArrayAttribute() may not be invoked here.");
043    }
044
045    public void visit(String name, Object value)
046    {
047        if (value instanceof Type)
048        {
049            Type type = (Type) value;
050
051            Class valueType = pool.loadClass(type.getClassName());
052            store(name, valueType);
053            return;
054        }
055
056        store(name, value);
057    }
058
059    public void visitEnum(String name, String desc, String value)
060    {
061
062        try
063        {
064            String enumClassName = PlasticInternalUtils.objectDescriptorToClassName(desc);
065
066            Class enumClass = pool.loader.loadClass(enumClassName);
067
068            Object enumValue = Enum.valueOf(enumClass, value);
069
070            store(name, enumValue);
071        }
072        catch (Exception ex)
073        {
074            throw new IllegalArgumentException(String.format("Unable to convert enum annotation attribute %s %s: %s",
075                    value, desc, PlasticInternalUtils.toMessage(ex)), ex);
076        }
077    }
078
079    public AnnotationVisitor visitAnnotation(final String name, String desc)
080    {
081        final AbstractAnnotationBuilder outerBuilder = this;
082
083        final Class nestedAnnotationType = pool.loadClass(PlasticInternalUtils.objectDescriptorToClassName(desc));
084
085        // Return a nested builder that constructs the inner annotation and, at the end of
086        // construction, pushes the final Annotation object into this builder's attributes.
087
088        return new AnnotationBuilder(nestedAnnotationType, pool)
089        {
090            @Override
091            public void visitEnd()
092            {
093                outerBuilder.store(name, createAnnotation());
094            };
095        };
096    }
097
098    /**
099     * Because of how ASM works, this should only be invoked when the array values are not
100     * primitives and not Class/Type; i.e. the inner values will be either Class/Type, enum, or
101     * nested annotations. All the arrays of strings and primitives are handled by ASM and become
102     * a single call to {@link #visit(String, Object)}.
103     */
104    public AnnotationVisitor visitArray(final String name)
105    {
106        final List<Object> values = new ArrayList<Object>();
107
108        final Class componentType = elementTypeForArrayAttribute(name);
109
110        final AbstractAnnotationBuilder outerBuilder = this;
111
112        return new AbstractAnnotationBuilder(pool)
113        {
114            @Override
115            protected void store(String name, Object value)
116            {
117                values.add(value);
118            }
119
120            @Override
121            public void visitEnd()
122            {
123                Object array = Array.newInstance(componentType, values.size());
124
125                // Now, empty arrays may be primitive types and will not cast to Object[], but
126                // non empty arrays indicate that it was a Class/Enum/Annotation, which can cast
127                // to Object[]
128
129                if (values.size() != 0)
130                    array = values.toArray((Object[]) array);
131
132                outerBuilder.store(name, array);
133            }
134        };
135    }
136
137    public void visitEnd()
138    {
139        // Nothing to do here. Subclasses use this as a chance to store a value into an outer
140        // builder.
141    }
142
143}