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 /** 102 * Because of how ASM works, this should only be invoked when the array values are not 103 * primitives and not Class/Type; i.e. the inner values will be either Class/Type, enum, or 104 * nested annotations. All the arrays of strings and primitives are handled by ASM and become 105 * a single call to {@link #visit(String, Object)}. 106 */ 107 @Override 108 public AnnotationVisitor visitArray(final String name) 109 { 110 final List<Object> values = new ArrayList<Object>(); 111 112 final Class componentType = elementTypeForArrayAttribute(name); 113 114 final AbstractAnnotationBuilder outerBuilder = this; 115 116 return new AbstractAnnotationBuilder(pool) 117 { 118 @Override 119 protected void store(String name, Object value) 120 { 121 values.add(value); 122 } 123 124 @Override 125 public void visitEnd() 126 { 127 Object array = Array.newInstance(componentType, values.size()); 128 129 // Now, empty arrays may be primitive types and will not cast to Object[], but 130 // non empty arrays indicate that it was a Class/Enum/Annotation, which can cast 131 // to Object[] 132 133 if (values.size() != 0) 134 array = values.toArray((Object[]) array); 135 136 outerBuilder.store(name, array); 137 } 138 }; 139 } 140 141 @Override 142 public void visitEnd() 143 { 144 // Nothing to do here. Subclasses use this as a chance to store a value into an outer 145 // builder. 146 } 147 148}