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    @Override
102    public AnnotationVisitor visitArray(final String name)
103    {
104        final List<Object> values = new ArrayList<Object>();
105
106        final Class componentType = elementTypeForArrayAttribute(name);
107
108        final AbstractAnnotationBuilder outerBuilder = this;
109
110        return new AbstractAnnotationBuilder(pool)
111        {
112            @Override
113            protected void store(String name, Object value)
114            {
115                values.add(value);
116            }
117
118            @Override
119            public void visitEnd()
120            {
121                Object array = Array.newInstance(componentType, values.size());
122
123                // Now, empty arrays may be primitive types and will not cast to Object[], but
124                // non empty arrays indicate that it was a Class/Enum/Annotation, which can cast
125                // to Object[]
126
127                if (values.size() != 0)
128                {
129                    for (int i = 0; i<values.size(); i++)
130                    {
131                        Array.set(array, i, values.get(i));
132                    }
133                }
134                outerBuilder.store(name, array);
135            }
136        };
137    }
138
139    @Override
140    public void visitEnd()
141    {
142        // Nothing to do here. Subclasses use this as a chance to store a value into an outer
143        // builder.
144    }
145
146}