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    }