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 org.apache.hivemind.ApplicationRuntimeException; 018 import org.apache.hivemind.Location; 019 import org.apache.hivemind.service.ClassFabUtils; 020 import org.apache.hivemind.service.MethodSignature; 021 import org.apache.hivemind.util.Defense; 022 import org.apache.tapestry.IBinding; 023 import org.apache.tapestry.IRequestCycle; 024 import org.apache.tapestry.engine.IPageLoader; 025 import org.apache.tapestry.event.PageEvent; 026 import org.apache.tapestry.spec.IComponentSpecification; 027 028 import java.lang.reflect.Modifier; 029 import java.util.HashMap; 030 import java.util.Map; 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 final 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 /** 053 * Used to unwrap primitive types inside the accessor method. In each case, the binding is in a 054 * variable named "binding", and {0} will be the actual type of the property. The Map is keyed 055 * on the primtive type. 056 */ 057 058 private static Map _unwrappers = new HashMap(); 059 060 static 061 { 062 _unwrappers.put(boolean.class, "toBoolean"); 063 _unwrappers.put(byte.class, "toByte"); 064 _unwrappers.put(char.class, "toChar"); 065 _unwrappers.put(short.class, "toShort"); 066 _unwrappers.put(int.class, "toInt"); 067 _unwrappers.put(long.class, "toLong"); 068 _unwrappers.put(float.class, "toFloat"); 069 _unwrappers.put(double.class, "toDouble"); 070 } 071 072 /* defeat instantiation */ 073 private EnhanceUtils() { } 074 075 public static String createMutatorMethodName(String propertyName) 076 { 077 return "set" + upcase(propertyName); 078 } 079 080 public static String createAccessorMethodName(String propertyName) 081 { 082 return "get" + upcase(propertyName); 083 } 084 085 private static String upcase(String name) 086 { 087 return name.substring(0, 1).toUpperCase() + name.substring(1); 088 } 089 090 public static void createSimpleAccessor(EnhancementOperation op, String fieldName, 091 String propertyName, Class propertyType, Location location) 092 { 093 String methodName = op.getAccessorMethodName(propertyName); 094 095 op.addMethod( Modifier.PUBLIC, new MethodSignature(propertyType, methodName, null, null), 096 "return " + fieldName + ";", location); 097 } 098 099 public static void createSimpleMutator(EnhancementOperation op, String fieldName, 100 String propertyName, Class propertyType, Location location) 101 { 102 String methodName = createMutatorMethodName(propertyName); 103 104 op.addMethod(Modifier.PUBLIC, new MethodSignature(void.class, methodName, 105 new Class[] { propertyType }, null), fieldName + " = $1;", location); 106 } 107 108 /** 109 * Returns the correct class for a property to be enhanced into a class. If a type name is 110 * non-null, then it is converted to a Class. If the class being enhanced defines a property, 111 * then the type must be an exact match (this is largely a holdover from Tapestry 3.0, where the 112 * type had to be provided in the specification). If the type name is null, then the value 113 * returned is the type of the existing property (if such a property exists), or 114 * java.lang.Object is no property exists. 115 * 116 * @param op 117 * the enhancement operation, which provides most of this logic 118 * @param propertyName 119 * the name of the property (the property may or may not exist) 120 * @param definedTypeName 121 * the type indicated for the property, may be null to make the return value match 122 * the type of an existing property. 123 */ 124 125 public static Class extractPropertyType(EnhancementOperation op, String propertyName, String definedTypeName) 126 { 127 return extractPropertyType(op, propertyName, definedTypeName, false); 128 } 129 130 /** 131 * Does the same thing as {@link #extractPropertyType(EnhancementOperation, String, String)}, with the added 132 * knowledge of knowing whether or not the type is generic and thus skips over type validation operations 133 * as generic type checking can't be safely done in this jre 1.4 compatible section of the codebase. 134 * 135 * @param op 136 * the enhancement operation, which provides most of this logic 137 * @param propertyName 138 * the name of the property (the property may or may not exist) 139 * @param definedTypeName 140 * the type indicated for the property, may be null to make the return value match 141 * the type of an existing property. 142 * @param isGeneric 143 * Whether or not the type was previously discoverd and found to be generic, if true 144 * type validation is skipped. 145 */ 146 147 public static Class extractPropertyType(EnhancementOperation op, String propertyName, 148 String definedTypeName, boolean isGeneric) 149 { 150 Defense.notNull(op, "op"); 151 Defense.notNull(propertyName, "propertyName"); 152 153 if (definedTypeName != null) 154 { 155 Class propertyType = op.convertTypeName(definedTypeName); 156 157 if (!isGeneric) 158 op.validateProperty(propertyName, propertyType); 159 160 return propertyType; 161 } 162 163 Class propertyType = op.getPropertyType(propertyName); 164 165 return propertyType == null ? Object.class : propertyType; 166 } 167 168 // The following methods are actually invoked from fabricated methods in 169 // enhanced classes. 170 171 public static boolean toBoolean(IBinding binding) 172 { 173 Boolean wrapped = (Boolean) binding.getObject(Boolean.class); 174 175 return wrapped == null ? false : wrapped.booleanValue(); 176 } 177 178 public static byte toByte(IBinding binding) 179 { 180 Byte wrapped = (Byte) binding.getObject(Byte.class); 181 182 return wrapped == null ? 0 : wrapped.byteValue(); 183 } 184 185 public static char toChar(IBinding binding) 186 { 187 Character wrapped = (Character) binding.getObject(Character.class); 188 189 return wrapped == null ? 0 : wrapped.charValue(); 190 } 191 192 public static short toShort(IBinding binding) 193 { 194 Short wrapped = (Short) binding.getObject(Short.class); 195 196 return wrapped == null ? 0 : wrapped.shortValue(); 197 } 198 199 public static int toInt(IBinding binding) 200 { 201 Integer wrapped = (Integer) binding.getObject(Integer.class); 202 203 return wrapped == null ? 0 : wrapped.intValue(); 204 } 205 206 public static long toLong(IBinding binding) 207 { 208 Long wrapped = (Long) binding.getObject(Long.class); 209 210 return wrapped == null ? 0 : wrapped.longValue(); 211 } 212 213 public static float toFloat(IBinding binding) 214 { 215 Float wrapped = (Float) binding.getObject(Float.class); 216 217 return wrapped == null ? 0.0f : wrapped.floatValue(); 218 } 219 220 public static double toDouble(IBinding binding) 221 { 222 Double wrapped = (Double) binding.getObject(Double.class); 223 224 return wrapped == null ? 0.0d : wrapped.doubleValue(); 225 } 226 227 /** 228 * Returns the name of the static method, within EnhanceUtils, used to unwrap a binding to a 229 * primitive type. Returns null if the type is not a primitve. 230 */ 231 232 public static String getUnwrapperMethodName(Class type) 233 { 234 Defense.notNull(type, "type"); 235 236 return (String) _unwrappers.get(type); 237 } 238 239 /** 240 * Builds a Javassist expression for unwrapping a binding's value to a type (either primitive or 241 * a class type). 242 * 243 * @param op 244 * the enhancement operation 245 * @param bindingName 246 * the name of the field (or an expression) that will evaluate to the binding from 247 * which a value will be extracted. 248 * @param valueType 249 * the type of value to be extracted from the binding. 250 */ 251 252 public static String createUnwrapExpression(EnhancementOperation op, String bindingName, Class valueType) 253 { 254 Defense.notNull(op, "op"); 255 Defense.notNull(bindingName, "bindingName"); 256 Defense.notNull(valueType, "valueType"); 257 258 StringBuffer buffer = new StringBuffer(); 259 260 String unwrapper = getUnwrapperMethodName(valueType); 261 262 if (unwrapper == null) 263 { 264 String propertyTypeRef = op.getClassReference(valueType); 265 266 buffer.append("("); 267 buffer.append(ClassFabUtils.getJavaClassName(valueType)); 268 buffer.append(") "); 269 buffer.append(bindingName); 270 buffer.append(".getObject("); 271 buffer.append(propertyTypeRef); 272 buffer.append(")"); 273 } 274 else 275 { 276 buffer.append(EnhanceUtils.class.getName()); 277 buffer.append("."); 278 buffer.append(unwrapper); 279 buffer.append("("); 280 buffer.append(bindingName); 281 buffer.append(")"); 282 } 283 284 return buffer.toString(); 285 } 286 287 /** 288 * Verifies that a property type can be assigned a particular type of value. 289 * 290 * @param op 291 * the enhancement operation 292 * @param propertyName 293 * the name of the property to check 294 * @param requiredType 295 * the type of value that will be assigned to the property 296 * @return the property type, or java.lang.Object if the class does not define the property 297 */ 298 public static Class verifyPropertyType(EnhancementOperation op, String propertyName, 299 Class requiredType) 300 { 301 Defense.notNull(op, "op"); 302 Defense.notNull(propertyName, "propertyName"); 303 Defense.notNull(requiredType, "requiredType"); 304 305 Class propertyType = op.getPropertyType(propertyName); 306 307 // When the property type is not defined, it will end up being 308 if (propertyType == null) 309 return Object.class; 310 311 // Make sure that an object of the required type is assignable 312 // to the property type. 313 314 if (!propertyType.isAssignableFrom(requiredType)) 315 throw new ApplicationRuntimeException(EnhanceMessages.wrongTypeForProperty( 316 propertyName, 317 propertyType, 318 requiredType)); 319 320 return propertyType; 321 } 322 323 /** 324 * Determines whether or not the specified class type is elligable for proxying. This generally 325 * means it needs a default constructor, can't be final / primitive / array. 326 * 327 * @param type 328 * The class to check for proxying elligibility. 329 * @return True if the type can be proxied, false otherwise. 330 */ 331 public static boolean canProxyPropertyType(Class type) 332 { 333 // if it's already enhanced it must be by someone else 334 335 if (type.isInterface()) 336 return true; 337 338 if (!hasEmptyConstructor(type)) 339 return false; 340 341 if (type.isArray() || type.isPrimitive() || Modifier.isFinal(type.getModifiers()) || Object.class == type) 342 return false; 343 344 return true; 345 } 346 347 /** 348 * Checks if the specified class type has an empty constructor. 349 * 350 * @param type 351 * The class to check, can't be null. 352 * 353 * @return True if a no args constructor exists. 354 */ 355 public static boolean hasEmptyConstructor(Class type) 356 { 357 Defense.notNull(type, "type"); 358 359 try { 360 361 return type.getConstructor(null) != null; 362 } catch (Throwable t) { 363 return false; 364 } 365 } 366 }