    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
    015    package org.apache.tapestry5.internal.plastic;
    016
    017    import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor;
    018    import org.apache.tapestry5.internal.plastic.asm.Opcodes;
    019    import org.apache.tapestry5.internal.plastic.asm.Type;
    020
    021    import java.lang.reflect.Array;
    022    import java.util.ArrayList;
    023    import java.util.List;
    024
    025    @SuppressWarnings(
    026    { "rawtypes", "unchecked" })
    027    public 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    }