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
015package org.apache.tapestry.enhance;
016
017import java.lang.reflect.Modifier;
018import java.util.HashMap;
019import java.util.Map;
020
021import org.apache.hivemind.ApplicationRuntimeException;
022import org.apache.hivemind.Location;
023import org.apache.hivemind.service.ClassFabUtils;
024import org.apache.hivemind.service.MethodSignature;
025import org.apache.hivemind.util.Defense;
026import org.apache.tapestry.IBinding;
027import org.apache.tapestry.IRequestCycle;
028import org.apache.tapestry.engine.IPageLoader;
029import org.apache.tapestry.event.PageEvent;
030import 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 */
038public 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}