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 }