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 }