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    }