    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}