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 }