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