Java tutorial
// Copyright 2004 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 java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tapestry.ApplicationRuntimeException; import org.apache.tapestry.IBinding; import org.apache.tapestry.ILocation; import org.apache.tapestry.IResourceResolver; import org.apache.tapestry.Tapestry; import org.apache.tapestry.spec.Direction; import org.apache.tapestry.spec.IComponentSpecification; import org.apache.tapestry.spec.IParameterSpecification; import org.apache.tapestry.spec.IPropertySpecification; /** * Contains the logic for analyzing and enhancing a single component class. * Internally, this class makes use of {@link IEnhancedClassFactory}. * * @author Howard Lewis Ship * @since 3.0 * **/ public class ComponentClassFactory { private static final Log LOG = LogFactory.getLog(ComponentClassFactory.class); /** * Package prefix to be added if the enhanced object is in a 'sysem' package */ private static final String PACKAGE_PREFIX = "org.apache.tapestry."; /** * UID used to generate new class names. **/ private static int _uid = 0; /** * Mapping between a primitive type and its Java VM representation * Used for the encoding of array types **/ private static Map _primitiveTypes = new HashMap(); static { _primitiveTypes.put("boolean", "Z"); _primitiveTypes.put("short", "S"); _primitiveTypes.put("int", "I"); _primitiveTypes.put("long", "J"); _primitiveTypes.put("float", "F"); _primitiveTypes.put("double", "D"); _primitiveTypes.put("char", "C"); _primitiveTypes.put("byte", "B"); } private IResourceResolver _resolver; private IEnhancedClassFactory _enhancedClassFactory; private IEnhancedClass _enhancedClass; private Map _beanProperties = new HashMap(); private IComponentSpecification _specification; private Class _componentClass; private JavaClassMapping _classMapping = new JavaClassMapping(); public ComponentClassFactory(IResourceResolver resolver, IComponentSpecification specification, Class componentClass, IEnhancedClassFactory enhancedClassFactory) { _resolver = resolver; _specification = specification; _componentClass = componentClass; _enhancedClassFactory = enhancedClassFactory; buildBeanProperties(); } private void buildBeanProperties() { BeanInfo info = null; try { info = Introspector.getBeanInfo(_componentClass); } catch (IntrospectionException ex) { throw new ApplicationRuntimeException( Tapestry.format("ComponentClassFactory.unable-to-introspect-class", _componentClass.getName()), ex); } PropertyDescriptor[] descriptors = info.getPropertyDescriptors(); for (int i = 0; i < descriptors.length; i++) { _beanProperties.put(descriptors[i].getName(), descriptors[i]); } } protected PropertyDescriptor getPropertyDescriptor(String name) { return (PropertyDescriptor) _beanProperties.get(name); } /** * Invokes {@link #scanForEnhancements()} to identify any * enhancements needed on the class, returning true * if there are any enhancements to be performed. * **/ public boolean needsEnhancement() { scanForEnhancements(); return _enhancedClass != null && _enhancedClass.hasModifications(); } /** * @return true if pd is not null and both read/write methods are implemented */ public boolean isImplemented(PropertyDescriptor pd) { if (pd == null) return false; return isImplemented(pd.getReadMethod()) && isImplemented(pd.getWriteMethod()); } /** * @return true if m is not null and is abstract. */ public boolean isAbstract(Method m) { if (m == null) return false; return Modifier.isAbstract(m.getModifiers()); } /** * @return true if m is not null and not abstract */ public boolean isImplemented(Method m) { if (m == null) return false; return !Modifier.isAbstract(m.getModifiers()); } /** * Given a class name, returns the corresponding class. In addition, * scalar types, arrays of scalar types, java.lang.Object[] and * java.lang.String[] are supported. * * @param type to convert to a Class * @param location of the involved specification element (for exception reporting) * **/ public Class convertPropertyType(String type, ILocation location) { Class result = _classMapping.getType(type); if (result == null) { try { String typeName = translateClassName(type); result = _resolver.findClass(typeName); } catch (Exception ex) { throw new ApplicationRuntimeException( Tapestry.format("ComponentClassFactory.bad-property-type", type), location, ex); } _classMapping.recordType(type, result); } return result; } /** * Translates types from standard Java format to Java VM format. * For example, java.util.Locale remains java.util.Locale, but * int[][] is translated to [[I and java.lang.Object[] to * [Ljava.lang.Object; * This method and its static Map should go into a utility class */ protected String translateClassName(String type) { // if it is not an array, just return the type itself if (!type.endsWith("[]")) return type; // if it is an array, convert it to JavaVM-style format StringBuffer javaType = new StringBuffer(); while (type.endsWith("[]")) { javaType.append("["); type = type.substring(0, type.length() - 2); } String primitiveIdentifier = (String) _primitiveTypes.get(type); if (primitiveIdentifier != null) javaType.append(primitiveIdentifier); else javaType.append("L" + type + ";"); return javaType.toString(); } protected void checkPropertyType(PropertyDescriptor pd, Class propertyType, ILocation location) { if (!pd.getPropertyType().equals(propertyType)) throw new ApplicationRuntimeException(Tapestry.format("ComponentClassFactory.property-type-mismatch", new Object[] { _componentClass.getName(), pd.getName(), pd.getPropertyType().getName(), propertyType.getName() }), location, null); } /** * Checks to see that that class either doesn't provide the property, or does * but the accessor(s) are abstract. Returns the name of the read accessor, * or null if there is no such accessor (this is helpful if the beanClass * defines a boolean property, where the name of the accessor may be isXXX or * getXXX). * **/ protected String checkAccessors(String propertyName, Class propertyType, ILocation location) { PropertyDescriptor d = getPropertyDescriptor(propertyName); if (d == null) return null; checkPropertyType(d, propertyType, location); Method write = d.getWriteMethod(); Method read = d.getReadMethod(); if (isImplemented(write)) throw new ApplicationRuntimeException(Tapestry.format("ComponentClassFactory.non-abstract-write", write.getDeclaringClass().getName(), propertyName), location, null); if (isImplemented(read)) throw new ApplicationRuntimeException(Tapestry.format("ComponentClassFactory.non-abstract-read", read.getDeclaringClass().getName(), propertyName), location, null); return read == null ? null : read.getName(); } protected boolean isMissingProperty(String propertyName) { PropertyDescriptor pd = getPropertyDescriptor(propertyName); return !isImplemented(pd); } /** * Invoked by {@link org.apache.tapestry.enhance.DefaultComponentClassEnhancer} to * create an enahanced * subclass of the component class. This means creating a default constructor, * new fields, and new accessor and mutator methods. Properties are created * for connected parameters, for all formal parameters (the binding property), * and for all specified parameters (which may be transient or persistent). * **/ public Class createEnhancedSubclass() { IEnhancedClass enhancedClass = getEnhancedClass(); String startClassName = _componentClass.getName(); String subclassName = enhancedClass.getClassName(); if (LOG.isDebugEnabled()) LOG.debug("Enhancing subclass of " + startClassName + " for " + _specification.getSpecificationLocation()); Class result = enhancedClass.createEnhancedSubclass(); if (LOG.isDebugEnabled()) LOG.debug("Finished creating enhanced class " + subclassName); return result; } /** * Invoked by {@link #needsEnhancement()} to find any enhancements * that may be needed. Should create an {@link org.apache.tapestry.enhance.IEnhancer} * for each one, and add it to the queue. * **/ protected void scanForEnhancements() { scanForParameterEnhancements(); scanForSpecifiedPropertyEnhancements(); scanForAbstractClass(); } protected void scanForAbstractClass() { if (Modifier.isAbstract(_componentClass.getModifiers())) getEnhancedClass().addEnhancer(new NoOpEnhancer()); } /** * Invoked by {@link #scanForEnhancements()} to locate * any enhancements needed for component parameters (this includes * binding properties and connected parameter property). * **/ protected void scanForParameterEnhancements() { List names = _specification.getParameterNames(); int count = names.size(); for (int i = 0; i < count; i++) { String name = (String) names.get(i); IParameterSpecification ps = _specification.getParameter(name); scanForBindingProperty(name, ps); scanForParameterProperty(name, ps); } } protected void scanForSpecifiedPropertyEnhancements() { List names = _specification.getPropertySpecificationNames(); int count = names.size(); for (int i = 0; i < count; i++) { String name = (String) names.get(i); IPropertySpecification ps = _specification.getPropertySpecification(name); scanForSpecifiedProperty(ps); } } protected void scanForBindingProperty(String parameterName, IParameterSpecification ps) { String propertyName = parameterName + Tapestry.PARAMETER_PROPERTY_NAME_SUFFIX; PropertyDescriptor pd = getPropertyDescriptor(propertyName); // only enhance custom parameter binding properties if they are declared abstract if (ps.getDirection() == Direction.CUSTOM) { if (pd == null) return; if (!(isAbstract(pd.getReadMethod()) || isAbstract(pd.getWriteMethod()))) return; } if (isImplemented(pd)) return; // Need to create the property. getEnhancedClass().createProperty(propertyName, IBinding.class.getName()); } protected void scanForParameterProperty(String parameterName, IParameterSpecification ps) { Direction direction = ps.getDirection(); if (direction == Direction.CUSTOM) return; if (direction == Direction.AUTO) { addAutoParameterEnhancer(parameterName, ps); return; } String propertyName = ps.getPropertyName(); // Yes, but does it *need* a property created? if (!isMissingProperty(propertyName)) return; ILocation location = ps.getLocation(); Class propertyType = convertPropertyType(ps.getType(), location); String readMethodName = checkAccessors(propertyName, propertyType, location); getEnhancedClass().createProperty(propertyName, ps.getType(), readMethodName, false); } protected void addAutoParameterEnhancer(String parameterName, IParameterSpecification ps) { ILocation location = ps.getLocation(); String propertyName = ps.getPropertyName(); if (!ps.isRequired() && ps.getDefaultValue() == null) throw new ApplicationRuntimeException( Tapestry.format("ComponentClassFactory.auto-must-be-required", parameterName), location, null); Class propertyType = convertPropertyType(ps.getType(), location); String readMethodName = checkAccessors(propertyName, propertyType, location); getEnhancedClass().createAutoParameter(propertyName, parameterName, ps.getType(), readMethodName); } protected void scanForSpecifiedProperty(IPropertySpecification ps) { String propertyName = ps.getName(); ILocation location = ps.getLocation(); Class propertyType = convertPropertyType(ps.getType(), location); PropertyDescriptor pd = getPropertyDescriptor(propertyName); if (isImplemented(pd)) { // Make sure the property is at least the right type. checkPropertyType(pd, propertyType, location); return; } String readMethodName = checkAccessors(propertyName, propertyType, location); getEnhancedClass().createProperty(propertyName, ps.getType(), readMethodName, ps.isPersistent()); } public IEnhancedClass getEnhancedClass() { if (_enhancedClass == null) { String startClassName = _componentClass.getName(); String subclassName = startClassName + "$Enhance_" + generateUID(); // If the new class is located in a 'restricted' package, // add a neutral package prefix to the name. // The class enhancement will likely fail anyway, since the original object // would not implement IComponent, but we do not know what the enhancement // will do in the future -- it might implement that interface automatically. if (subclassName.startsWith("java.") || subclassName.startsWith("javax.")) subclassName = PACKAGE_PREFIX + subclassName; _enhancedClass = _enhancedClassFactory.createEnhancedClass(subclassName, _componentClass); } return _enhancedClass; } private static synchronized int generateUID() { return _uid++; } }