blob: de31a279a21f3b6f5b1481fa21b77645e1608ac0 [file]
// Copyright 2004, 2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry.enhance;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.Location;
import org.apache.hivemind.service.ClassFabUtils;
import org.apache.hivemind.service.MethodSignature;
import org.apache.hivemind.util.Defense;
import org.apache.tapestry.IBinding;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.engine.IPageLoader;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.spec.IComponentSpecification;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
/**
* Convienience methods needed by various parts of the enhancement subsystem.
*
* @author Howard M. Lewis Ship
* @since 4.0
*/
public final class EnhanceUtils
{
public static final MethodSignature FINISH_LOAD_SIGNATURE = new MethodSignature(void.class,
"finishLoad", new Class[]
{ IRequestCycle.class, IPageLoader.class, IComponentSpecification.class }, null);
public static final MethodSignature PAGE_DETACHED_SIGNATURE = new MethodSignature(void.class,
"pageDetached", new Class[]
{ PageEvent.class }, null);
public static final MethodSignature CLEANUP_AFTER_RENDER_SIGNATURE = new MethodSignature(
void.class, "cleanupAfterRender", new Class[]
{ IRequestCycle.class }, null);
/**
* Used to unwrap primitive types inside the accessor method. In each case, the binding is in a
* variable named "binding", and {0} will be the actual type of the property. The Map is keyed
* on the primtive type.
*/
private static Map _unwrappers = new HashMap();
static
{
_unwrappers.put(boolean.class, "toBoolean");
_unwrappers.put(byte.class, "toByte");
_unwrappers.put(char.class, "toChar");
_unwrappers.put(short.class, "toShort");
_unwrappers.put(int.class, "toInt");
_unwrappers.put(long.class, "toLong");
_unwrappers.put(float.class, "toFloat");
_unwrappers.put(double.class, "toDouble");
}
/* defeat instantiation */
private EnhanceUtils() { }
public static String createMutatorMethodName(String propertyName)
{
return "set" + upcase(propertyName);
}
public static String createAccessorMethodName(String propertyName)
{
return "get" + upcase(propertyName);
}
private static String upcase(String name)
{
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
public static void createSimpleAccessor(EnhancementOperation op, String fieldName,
String propertyName, Class propertyType, Location location)
{
String methodName = op.getAccessorMethodName(propertyName);
op.addMethod( Modifier.PUBLIC, new MethodSignature(propertyType, methodName, null, null),
"return " + fieldName + ";", location);
}
public static void createSimpleMutator(EnhancementOperation op, String fieldName,
String propertyName, Class propertyType, Location location)
{
String methodName = createMutatorMethodName(propertyName);
op.addMethod(Modifier.PUBLIC, new MethodSignature(void.class, methodName,
new Class[] { propertyType }, null), fieldName + " = $1;", location);
}
/**
* Returns the correct class for a property to be enhanced into a class. If a type name is
* non-null, then it is converted to a Class. If the class being enhanced defines a property,
* then the type must be an exact match (this is largely a holdover from Tapestry 3.0, where the
* type had to be provided in the specification). If the type name is null, then the value
* returned is the type of the existing property (if such a property exists), or
* java.lang.Object is no property exists.
*
* @param op
* the enhancement operation, which provides most of this logic
* @param propertyName
* the name of the property (the property may or may not exist)
* @param definedTypeName
* the type indicated for the property, may be null to make the return value match
* the type of an existing property.
*/
public static Class extractPropertyType(EnhancementOperation op, String propertyName, String definedTypeName)
{
return extractPropertyType(op, propertyName, definedTypeName, false);
}
/**
* Does the same thing as {@link #extractPropertyType(EnhancementOperation, String, String)}, with the added
* knowledge of knowing whether or not the type is generic and thus skips over type validation operations
* as generic type checking can't be safely done in this jre 1.4 compatible section of the codebase.
*
* @param op
* the enhancement operation, which provides most of this logic
* @param propertyName
* the name of the property (the property may or may not exist)
* @param definedTypeName
* the type indicated for the property, may be null to make the return value match
* the type of an existing property.
* @param isGeneric
* Whether or not the type was previously discoverd and found to be generic, if true
* type validation is skipped.
*/
public static Class extractPropertyType(EnhancementOperation op, String propertyName,
String definedTypeName, boolean isGeneric)
{
Defense.notNull(op, "op");
Defense.notNull(propertyName, "propertyName");
if (definedTypeName != null)
{
Class propertyType = op.convertTypeName(definedTypeName);
if (!isGeneric)
op.validateProperty(propertyName, propertyType);
return propertyType;
}
Class propertyType = op.getPropertyType(propertyName);
return propertyType == null ? Object.class : propertyType;
}
// The following methods are actually invoked from fabricated methods in
// enhanced classes.
public static boolean toBoolean(IBinding binding)
{
Boolean wrapped = (Boolean) binding.getObject(Boolean.class);
return wrapped == null ? false : wrapped.booleanValue();
}
public static byte toByte(IBinding binding)
{
Byte wrapped = (Byte) binding.getObject(Byte.class);
return wrapped == null ? 0 : wrapped.byteValue();
}
public static char toChar(IBinding binding)
{
Character wrapped = (Character) binding.getObject(Character.class);
return wrapped == null ? 0 : wrapped.charValue();
}
public static short toShort(IBinding binding)
{
Short wrapped = (Short) binding.getObject(Short.class);
return wrapped == null ? 0 : wrapped.shortValue();
}
public static int toInt(IBinding binding)
{
Integer wrapped = (Integer) binding.getObject(Integer.class);
return wrapped == null ? 0 : wrapped.intValue();
}
public static long toLong(IBinding binding)
{
Long wrapped = (Long) binding.getObject(Long.class);
return wrapped == null ? 0 : wrapped.longValue();
}
public static float toFloat(IBinding binding)
{
Float wrapped = (Float) binding.getObject(Float.class);
return wrapped == null ? 0.0f : wrapped.floatValue();
}
public static double toDouble(IBinding binding)
{
Double wrapped = (Double) binding.getObject(Double.class);
return wrapped == null ? 0.0d : wrapped.doubleValue();
}
/**
* Returns the name of the static method, within EnhanceUtils, used to unwrap a binding to a
* primitive type. Returns null if the type is not a primitve.
*/
public static String getUnwrapperMethodName(Class type)
{
Defense.notNull(type, "type");
return (String) _unwrappers.get(type);
}
/**
* Builds a Javassist expression for unwrapping a binding's value to a type (either primitive or
* a class type).
*
* @param op
* the enhancement operation
* @param bindingName
* the name of the field (or an expression) that will evaluate to the binding from
* which a value will be extracted.
* @param valueType
* the type of value to be extracted from the binding.
*/
public static String createUnwrapExpression(EnhancementOperation op, String bindingName, Class valueType)
{
Defense.notNull(op, "op");
Defense.notNull(bindingName, "bindingName");
Defense.notNull(valueType, "valueType");
StringBuffer buffer = new StringBuffer();
String unwrapper = getUnwrapperMethodName(valueType);
if (unwrapper == null)
{
String propertyTypeRef = op.getClassReference(valueType);
buffer.append("(");
buffer.append(ClassFabUtils.getJavaClassName(valueType));
buffer.append(") ");
buffer.append(bindingName);
buffer.append(".getObject(");
buffer.append(propertyTypeRef);
buffer.append(")");
}
else
{
buffer.append(EnhanceUtils.class.getName());
buffer.append(".");
buffer.append(unwrapper);
buffer.append("(");
buffer.append(bindingName);
buffer.append(")");
}
return buffer.toString();
}
/**
* Verifies that a property type can be assigned a particular type of value.
*
* @param op
* the enhancement operation
* @param propertyName
* the name of the property to check
* @param requiredType
* the type of value that will be assigned to the property
* @return the property type, or java.lang.Object if the class does not define the property
*/
public static Class verifyPropertyType(EnhancementOperation op, String propertyName,
Class requiredType)
{
Defense.notNull(op, "op");
Defense.notNull(propertyName, "propertyName");
Defense.notNull(requiredType, "requiredType");
Class propertyType = op.getPropertyType(propertyName);
// When the property type is not defined, it will end up being
if (propertyType == null)
return Object.class;
// Make sure that an object of the required type is assignable
// to the property type.
if (!propertyType.isAssignableFrom(requiredType))
throw new ApplicationRuntimeException(EnhanceMessages.wrongTypeForProperty(
propertyName,
propertyType,
requiredType));
return propertyType;
}
/**
* Determines whether or not the specified class type is elligable for proxying. This generally
* means it needs a default constructor, can't be final / primitive / array.
*
* @param type
* The class to check for proxying elligibility.
* @return True if the type can be proxied, false otherwise.
*/
public static boolean canProxyPropertyType(Class type)
{
// if it's already enhanced it must be by someone else
if (type.isInterface())
return true;
if (!hasEmptyConstructor(type))
return false;
if (type.isArray() || type.isPrimitive() || Modifier.isFinal(type.getModifiers()) || Object.class == type)
return false;
return true;
}
/**
* Checks if the specified class type has an empty constructor.
*
* @param type
* The class to check, can't be null.
*
* @return True if a no args constructor exists.
*/
public static boolean hasEmptyConstructor(Class type)
{
Defense.notNull(type, "type");
try {
return type.getConstructor(null) != null;
} catch (Throwable t) {
return false;
}
}
}