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