001 // Copyright 2004, 2005 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.tapestry.enhance; 016 017 import java.lang.reflect.Modifier; 018 import java.util.HashMap; 019 import java.util.Map; 020 021 import org.apache.hivemind.ApplicationRuntimeException; 022 import org.apache.hivemind.Location; 023 import org.apache.hivemind.service.ClassFabUtils; 024 import org.apache.hivemind.service.MethodSignature; 025 import org.apache.hivemind.util.Defense; 026 import org.apache.tapestry.IBinding; 027 import org.apache.tapestry.IRequestCycle; 028 import org.apache.tapestry.engine.IPageLoader; 029 import org.apache.tapestry.event.PageEvent; 030 import org.apache.tapestry.spec.IComponentSpecification; 031 032 /** 033 * Convienience methods needed by various parts of the enhancement subsystem. 034 * 035 * @author Howard M. Lewis Ship 036 * @since 4.0 037 */ 038 public class EnhanceUtils 039 { 040 public static final MethodSignature FINISH_LOAD_SIGNATURE = new MethodSignature(void.class, 041 "finishLoad", new Class[] 042 { IRequestCycle.class, IPageLoader.class, IComponentSpecification.class }, null); 043 044 public static final MethodSignature PAGE_DETACHED_SIGNATURE = new MethodSignature(void.class, 045 "pageDetached", new Class[] 046 { PageEvent.class }, null); 047 048 public static final MethodSignature CLEANUP_AFTER_RENDER_SIGNATURE = new MethodSignature( 049 void.class, "cleanupAfterRender", new Class[] 050 { IRequestCycle.class }, null); 051 052 public static String createMutatorMethodName(String propertyName) 053 { 054 return "set" + upcase(propertyName); 055 } 056 057 public static String createAccessorMethodName(String propertyName) 058 { 059 return "get" + upcase(propertyName); 060 } 061 062 private static String upcase(String name) 063 { 064 return name.substring(0, 1).toUpperCase() + name.substring(1); 065 } 066 067 public static void createSimpleAccessor(EnhancementOperation op, String fieldName, 068 String propertyName, Class propertyType, Location location) 069 { 070 String methodName = op.getAccessorMethodName(propertyName); 071 072 op.addMethod( 073 Modifier.PUBLIC, 074 new MethodSignature(propertyType, methodName, null, null), 075 "return " + fieldName + ";", 076 location); 077 } 078 079 public static void createSimpleMutator(EnhancementOperation op, String fieldName, 080 String propertyName, Class propertyType, Location location) 081 { 082 String methodName = createMutatorMethodName(propertyName); 083 084 op.addMethod(Modifier.PUBLIC, new MethodSignature(void.class, methodName, new Class[] 085 { propertyType }, null), fieldName + " = $1;", location); 086 } 087 088 /** 089 * Returns the correct class for a property to be enhanced into a class. If a type name is 090 * non-null, then it is converted to a Class. If the class being enhanced defines a property, 091 * then the type must be an exact match (this is largely a holdover from Tapestry 3.0, where the 092 * type had to be provided in the specification). If the type name is null, then the value 093 * returned is the type of the existing property (if such a property exists), or 094 * java.lang.Object is no property exists. 095 * 096 * @param op 097 * the enhancement operation, which provides most of this logic 098 * @param propertyName 099 * the name of the property (the property may or may not exist) 100 * @param definedTypeName 101 * the type indicated for the property, may be null to make the return value match 102 * the type of an existing property. 103 */ 104 105 public static Class extractPropertyType(EnhancementOperation op, String propertyName, 106 String definedTypeName) 107 { 108 Defense.notNull(op, "op"); 109 Defense.notNull(propertyName, "propertyName"); 110 111 if (definedTypeName != null) 112 { 113 Class propertyType = op.convertTypeName(definedTypeName); 114 115 op.validateProperty(propertyName, propertyType); 116 117 return propertyType; 118 } 119 120 Class propertyType = op.getPropertyType(propertyName); 121 122 return propertyType == null ? Object.class : propertyType; 123 } 124 125 // The following methods are actually invoked from fabricated methods in 126 // enhanced classes. 127 128 public static boolean toBoolean(IBinding binding) 129 { 130 Boolean wrapped = (Boolean) binding.getObject(Boolean.class); 131 132 return wrapped == null ? false : wrapped.booleanValue(); 133 } 134 135 public static byte toByte(IBinding binding) 136 { 137 Byte wrapped = (Byte) binding.getObject(Byte.class); 138 139 return wrapped == null ? 0 : wrapped.byteValue(); 140 } 141 142 public static char toChar(IBinding binding) 143 { 144 Character wrapped = (Character) binding.getObject(Character.class); 145 146 return wrapped == null ? 0 : wrapped.charValue(); 147 } 148 149 public static short toShort(IBinding binding) 150 { 151 Short wrapped = (Short) binding.getObject(Short.class); 152 153 return wrapped == null ? 0 : wrapped.shortValue(); 154 } 155 156 public static int toInt(IBinding binding) 157 { 158 Integer wrapped = (Integer) binding.getObject(Integer.class); 159 160 return wrapped == null ? 0 : wrapped.intValue(); 161 } 162 163 public static long toLong(IBinding binding) 164 { 165 Long wrapped = (Long) binding.getObject(Long.class); 166 167 return wrapped == null ? 0 : wrapped.longValue(); 168 } 169 170 public static float toFloat(IBinding binding) 171 { 172 Float wrapped = (Float) binding.getObject(Float.class); 173 174 return wrapped == null ? 0.0f : wrapped.floatValue(); 175 } 176 177 public static double toDouble(IBinding binding) 178 { 179 Double wrapped = (Double) binding.getObject(Double.class); 180 181 return wrapped == null ? 0.0d : wrapped.doubleValue(); 182 } 183 184 /** 185 * Used to unwrap primitive types inside the accessor method. In each case, the binding is in a 186 * variable named "binding", and {0} will be the actual type of the property. The Map is keyed 187 * on the primtive type. 188 */ 189 190 private static Map _unwrappers = new HashMap(); 191 192 static 193 { 194 _unwrappers.put(boolean.class, "toBoolean"); 195 _unwrappers.put(byte.class, "toByte"); 196 _unwrappers.put(char.class, "toChar"); 197 _unwrappers.put(short.class, "toShort"); 198 _unwrappers.put(int.class, "toInt"); 199 _unwrappers.put(long.class, "toLong"); 200 _unwrappers.put(float.class, "toFloat"); 201 _unwrappers.put(double.class, "toDouble"); 202 } 203 204 /** 205 * Returns the name of the static method, within EnhanceUtils, used to unwrap a binding to a 206 * primitive type. Returns null if the type is not a primitve. 207 */ 208 209 public static String getUnwrapperMethodName(Class type) 210 { 211 Defense.notNull(type, "type"); 212 213 return (String) _unwrappers.get(type); 214 } 215 216 /** 217 * Builds a Javassist expression for unwrapping a binding's value to a type (either primitive or 218 * a class type). 219 * 220 * @param op 221 * the enhancement operation 222 * @param bindingName 223 * the name of the field (or an expression) that will evaluate to the binding from 224 * which a value will be extracted. 225 * @param valueType 226 * the type of value to be extracted from the binding. 227 */ 228 229 public static String createUnwrapExpression(EnhancementOperation op, String bindingName, 230 Class valueType) 231 { 232 Defense.notNull(op, "op"); 233 Defense.notNull(bindingName, "bindingName"); 234 Defense.notNull(valueType, "valueType"); 235 236 StringBuffer buffer = new StringBuffer(); 237 238 String unwrapper = getUnwrapperMethodName(valueType); 239 240 if (unwrapper == null) 241 { 242 String propertyTypeRef = op.getClassReference(valueType); 243 244 buffer.append("("); 245 buffer.append(ClassFabUtils.getJavaClassName(valueType)); 246 buffer.append(") "); 247 buffer.append(bindingName); 248 buffer.append(".getObject("); 249 buffer.append(propertyTypeRef); 250 buffer.append(")"); 251 } 252 else 253 { 254 buffer.append(EnhanceUtils.class.getName()); 255 buffer.append("."); 256 buffer.append(unwrapper); 257 buffer.append("("); 258 buffer.append(bindingName); 259 buffer.append(")"); 260 } 261 262 return buffer.toString(); 263 } 264 265 /** 266 * Verifies that a property type can be assigned a particular type of value. 267 * 268 * @param op 269 * the enhancement operation 270 * @param propertyName 271 * the name of the property to check 272 * @param requiredType 273 * the type of value that will be assigned to the property 274 * @return the property type, or java.lang.Object if the class does not define the property 275 */ 276 public static Class verifyPropertyType(EnhancementOperation op, String propertyName, 277 Class requiredType) 278 { 279 Defense.notNull(op, "op"); 280 Defense.notNull(propertyName, "propertyName"); 281 Defense.notNull(requiredType, "requiredType"); 282 283 Class propertyType = op.getPropertyType(propertyName); 284 285 // When the property type is not defined, it will end up being 286 if (propertyType == null) 287 return Object.class; 288 289 // Make sure that an object of the required type is assignable 290 // to the property type. 291 292 if (!propertyType.isAssignableFrom(requiredType)) 293 throw new ApplicationRuntimeException(EnhanceMessages.wrongTypeForProperty( 294 propertyName, 295 propertyType, 296 requiredType)); 297 298 return propertyType; 299 } 300 }