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    }