- /*
- * @(#)PropertyDescriptor.java 1.72 04/05/05
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
- package java.beans;
-
- import java.lang.ref.Reference;
-
- import java.lang.reflect.Method;
- import java.lang.reflect.Constructor;
-
- /**
- * A PropertyDescriptor describes one property that a Java Bean
- * exports via a pair of accessor methods.
- */
- public class PropertyDescriptor extends FeatureDescriptor {
-
- private Reference propertyTypeRef;
- private Reference readMethodRef;
- private Reference writeMethodRef;
- private Reference propertyEditorClassRef;
-
- private boolean bound;
- private boolean constrained;
-
- // The base name of the method name which will be prefixed with the
- // read and write method. If name == "foo" then the baseName is "Foo"
- private String baseName;
-
- private String writeMethodName;
- private String readMethodName;
-
- /**
- * Constructs a PropertyDescriptor for a property that follows
- * the standard Java convention by having getFoo and setFoo
- * accessor methods. Thus if the argument name is "fred", it will
- * assume that the writer method is "setFred" and the reader method
- * is "getFred" (or "isFred" for a boolean property). Note that the
- * property name should start with a lower case character, which will
- * be capitalized in the method names.
- *
- * @param propertyName The programmatic name of the property.
- * @param beanClass The Class object for the target bean. For
- * example sun.beans.OurButton.class.
- * @exception IntrospectionException if an exception occurs during
- * introspection.
- */
- public PropertyDescriptor(String propertyName, Class<?> beanClass)
- throws IntrospectionException {
- this(propertyName, beanClass,
- "is" + capitalize(propertyName),
- "set" + capitalize(propertyName));
- }
-
- /**
- * This constructor takes the name of a simple property, and method
- * names for reading and writing the property.
- *
- * @param propertyName The programmatic name of the property.
- * @param beanClass The Class object for the target bean. For
- * example sun.beans.OurButton.class.
- * @param readMethodName The name of the method used for reading the property
- * value. May be null if the property is write-only.
- * @param writeMethodName The name of the method used for writing the property
- * value. May be null if the property is read-only.
- * @exception IntrospectionException if an exception occurs during
- * introspection.
- */
- public PropertyDescriptor(String propertyName, Class<?> beanClass,
- String readMethodName, String writeMethodName)
- throws IntrospectionException {
- if (beanClass == null) {
- throw new IntrospectionException("Target Bean class is null");
- }
- if (propertyName == null || propertyName.length() == 0) {
- throw new IntrospectionException("bad property name");
- }
- if ("".equals(readMethodName) || "".equals(writeMethodName)) {
- throw new IntrospectionException("read or write method name should not be the empty string");
- }
- setName(propertyName);
- setClass0(beanClass);
-
- this.readMethodName = readMethodName;
- if (readMethodName != null && getReadMethod() == null) {
- throw new IntrospectionException("Method not found: " + readMethodName);
- }
- this.writeMethodName = writeMethodName;
- if (writeMethodName != null && getWriteMethod() == null) {
- throw new IntrospectionException("Method not found: " + writeMethodName);
- }
-
- }
-
- /**
- * This constructor takes the name of a simple property, and Method
- * objects for reading and writing the property.
- *
- * @param propertyName The programmatic name of the property.
- * @param readMethod The method used for reading the property value.
- * May be null if the property is write-only.
- * @param writeMethod The method used for writing the property value.
- * May be null if the property is read-only.
- * @exception IntrospectionException if an exception occurs during
- * introspection.
- */
- public PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod)
- throws IntrospectionException {
- if (propertyName == null || propertyName.length() == 0) {
- throw new IntrospectionException("bad property name");
- }
- setName(propertyName);
- setReadMethod(readMethod);
- setWriteMethod(writeMethod);
- }
-
- /**
- * Gets the Class object for the property.
- *
- * @return The Java type info for the property. Note that
- * the "Class" object may describe a built-in Java type such as "int".
- * The result may be "null" if this is an indexed property that
- * does not support non-indexed access.
- * <p>
- * This is the type that will be returned by the ReadMethod.
- */
- public synchronized Class<?> getPropertyType() {
- Class type = getPropertyType0();
- if (type == null) {
- try {
- type = findPropertyType(getReadMethod(), getWriteMethod());
- setPropertyType(type);
- } catch (IntrospectionException ex) {
- // Fall
- }
- }
- return type;
- }
-
- private void setPropertyType(Class type) {
- propertyTypeRef = createReference(type);
- }
-
- private Class getPropertyType0() {
- return (Class)getObject(propertyTypeRef);
- }
-
- /**
- * Gets the method that should be used to read the property value.
- *
- * @return The method that should be used to read the property value.
- * May return null if the property can't be read.
- */
- public synchronized Method getReadMethod() {
- Method readMethod = getReadMethod0();
- if (readMethod == null) {
- Class cls = getClass0();
- if (cls == null || (readMethodName == null && readMethodRef == null)) {
- // The read method was explicitly set to null.
- return null;
- }
- if (readMethodName == null) {
- Class type = getPropertyType0();
- if (type == boolean.class || type == null) {
- readMethodName = "is" + getBaseName();
- } else {
- readMethodName = "get" + getBaseName();
- }
- }
-
- // Since there can be multiple write methods but only one getter
- // method, find the getter method first so that you know what the
- // property type is. For booleans, there can be "is" and "get"
- // methods. If an "is" method exists, this is the official
- // reader method so look for this one first.
- readMethod = Introspector.findMethod(cls, readMethodName, 0);
- if (readMethod == null) {
- readMethodName = "get" + getBaseName();
- readMethod = Introspector.findMethod(cls, readMethodName, 0);
- }
- try {
- setReadMethod(readMethod);
- } catch (IntrospectionException ex) {
- // fall
- }
- }
- return readMethod;
- }
-
- /**
- * Sets the method that should be used to read the property value.
- *
- * @param readMethod The new read method.
- */
- public synchronized void setReadMethod(Method readMethod)
- throws IntrospectionException {
- if (readMethod == null) {
- readMethodName = null;
- readMethodRef = null;
- return;
- }
- // The property type is determined by the read method.
- setPropertyType(findPropertyType(readMethod, getWriteMethod0()));
- setClass0(readMethod.getDeclaringClass());
-
- readMethodName = readMethod.getName();
- readMethodRef = createReference(readMethod, true);
- }
-
- /**
- * Gets the method that should be used to write the property value.
- *
- * @return The method that should be used to write the property value.
- * May return null if the property can't be written.
- */
- public synchronized Method getWriteMethod() {
- Method writeMethod = getWriteMethod0();
- if (writeMethod == null) {
- Class cls = getClass0();
- if (cls == null || (writeMethodName == null && writeMethodRef == null)) {
- // The write method was explicitly set to null.
- return null;
- }
-
- // We need the type to fetch the correct method.
- Class type = getPropertyType0();
- if (type == null) {
- try {
- // Can't use getPropertyType since it will lead to recursive loop.
- type = findPropertyType(getReadMethod(), null);
- setPropertyType(type);
- } catch (IntrospectionException ex) {
- // Without the correct property type we can't be guaranteed
- // to find the correct method.
- return null;
- }
- }
-
- if (writeMethodName == null) {
- writeMethodName = "set" + getBaseName();
- }
-
- writeMethod = Introspector.findMethod(cls, writeMethodName, 1,
- (type == null) ? null : new Class[] { type });
- try {
- setWriteMethod(writeMethod);
- } catch (IntrospectionException ex) {
- // fall through
- }
- }
- return writeMethod;
- }
-
- /**
- * Sets the method that should be used to write the property value.
- *
- * @param writeMethod The new write method.
- */
- public synchronized void setWriteMethod(Method writeMethod)
- throws IntrospectionException {
- if (writeMethod == null) {
- writeMethodName = null;
- writeMethodRef = null;
- return;
- }
- // Set the property type - which validates the method
- setPropertyType(findPropertyType(getReadMethod(), writeMethod));
- setClass0(writeMethod.getDeclaringClass());
-
- writeMethodName = writeMethod.getName();
- writeMethodRef = createReference(writeMethod, true);
-
- }
-
- private Method getReadMethod0() {
- return (Method)getObject(readMethodRef);
- }
-
- private Method getWriteMethod0() {
- return (Method)getObject(writeMethodRef);
- }
-
- /**
- * Overridden to ensure that a super class doesn't take precedent
- */
- void setClass0(Class clz) {
- if (getClass0() != null && clz.isAssignableFrom(getClass0())) {
- // dont replace a subclass with a superclass
- return;
- }
- super.setClass0(clz);
- }
-
- /**
- * Updates to "bound" properties will cause a "PropertyChange" event to
- * get fired when the property is changed.
- *
- * @return True if this is a bound property.
- */
- public boolean isBound() {
- return bound;
- }
-
- /**
- * Updates to "bound" properties will cause a "PropertyChange" event to
- * get fired when the property is changed.
- *
- * @param bound True if this is a bound property.
- */
- public void setBound(boolean bound) {
- this.bound = bound;
- }
-
- /**
- * Attempted updates to "Constrained" properties will cause a "VetoableChange"
- * event to get fired when the property is changed.
- *
- * @return True if this is a constrained property.
- */
- public boolean isConstrained() {
- return constrained;
- }
-
- /**
- * Attempted updates to "Constrained" properties will cause a "VetoableChange"
- * event to get fired when the property is changed.
- *
- * @param constrained True if this is a constrained property.
- */
- public void setConstrained(boolean constrained) {
- this.constrained = constrained;
- }
-
-
- /**
- * Normally PropertyEditors will be found using the PropertyEditorManager.
- * However if for some reason you want to associate a particular
- * PropertyEditor with a given property, then you can do it with
- * this method.
- *
- * @param propertyEditorClass The Class for the desired PropertyEditor.
- */
- public void setPropertyEditorClass(Class<?> propertyEditorClass) {
- propertyEditorClassRef = createReference(propertyEditorClass);
- }
-
- /**
- * Gets any explicit PropertyEditor Class that has been registered
- * for this property.
- *
- * @return Any explicit PropertyEditor Class that has been registered
- * for this property. Normally this will return "null",
- * indicating that no special editor has been registered,
- * so the PropertyEditorManager should be used to locate
- * a suitable PropertyEditor.
- */
- public Class<?> getPropertyEditorClass() {
- return (Class)getObject(propertyEditorClassRef);
- }
-
- /**
- * Constructs an instance of a property editor using the current
- * property editor class.
- * <p>
- * If the property editor class has a public constructor that takes an
- * Object argument then it will be invoked using the bean parameter
- * as the argument. Otherwise, the default constructor will be invoked.
- *
- * @param bean the source object
- * @return a property editor instance or null if a property editor has
- * not been defined or cannot be created
- * @since 1.5
- */
- public PropertyEditor createPropertyEditor(Object bean) {
- Object editor = null;
-
- Class cls = getPropertyEditorClass();
- if (cls != null) {
- Constructor ctor = null;
- if (bean != null) {
- try {
- ctor = cls.getConstructor(new Class[] { Object.class });
- } catch (Exception ex) {
- // Fall through
- }
- }
- try {
- if (ctor == null) {
- editor = cls.newInstance();
- } else {
- editor = ctor.newInstance(new Object[] { bean });
- }
- } catch (Exception ex) {
- // A serious error has occured.
- // Proably due to an invalid property editor.
- throw new RuntimeException("PropertyEditor not instantiated",
- ex);
- }
- }
- return (PropertyEditor)editor;
- }
-
-
- /**
- * Compares this <code>PropertyDescriptor</code> against the specified object.
- * Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s
- * are the same if the read, write, property types, property editor and
- * flags are equivalent.
- *
- * @since 1.4
- */
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj != null && obj instanceof PropertyDescriptor) {
- PropertyDescriptor other = (PropertyDescriptor)obj;
- Method otherReadMethod = other.getReadMethod();
- Method otherWriteMethod = other.getWriteMethod();
-
- if (!compareMethods(getReadMethod(), otherReadMethod)) {
- return false;
- }
-
- if (!compareMethods(getWriteMethod(), otherWriteMethod)) {
- return false;
- }
-
- if (getPropertyType() == other.getPropertyType() &&
- getPropertyEditorClass() == other.getPropertyEditorClass() &&
- bound == other.isBound() && constrained == other.isConstrained() &&
- writeMethodName == other.writeMethodName &&
- readMethodName == other.readMethodName) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Package private helper method for Descriptor .equals methods.
- *
- * @param a first method to compare
- * @param b second method to compare
- * @return boolean to indicate that the methods are equivalent
- */
- boolean compareMethods(Method a, Method b) {
- // Note: perhaps this should be a protected method in FeatureDescriptor
- if ((a == null) != (b == null)) {
- return false;
- }
-
- if (a != null && b != null) {
- if (!a.equals(b)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Package-private constructor.
- * Merge two property descriptors. Where they conflict, give the
- * second argument (y) priority over the first argument (x).
- *
- * @param x The first (lower priority) PropertyDescriptor
- * @param y The second (higher priority) PropertyDescriptor
- */
- PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
- super(x,y);
-
- if (y.baseName != null) {
- baseName = y.baseName;
- } else {
- baseName = x.baseName;
- }
-
- if (y.readMethodName != null) {
- readMethodName = y.readMethodName;
- } else {
- readMethodName = x.readMethodName;
- }
-
- if (y.writeMethodName != null) {
- writeMethodName = y.writeMethodName;
- } else {
- writeMethodName = x.writeMethodName;
- }
-
- if (y.propertyTypeRef != null) {
- propertyTypeRef = y.propertyTypeRef;
- } else {
- propertyTypeRef = x.propertyTypeRef;
- }
-
- // Figure out the merged read method.
- Method xr = x.getReadMethod();
- Method yr = y.getReadMethod();
-
- // Normally give priority to y's readMethod.
- try {
- if (yr != null && yr.getDeclaringClass() == getClass0()) {
- setReadMethod(yr);
- } else {
- setReadMethod(xr);
- }
- } catch (IntrospectionException ex) {
- // fall through
- }
-
- // However, if both x and y reference read methods in the same class,
- // give priority to a boolean "is" method over a boolean "get" method.
- if (xr != null && yr != null &&
- xr.getDeclaringClass() == yr.getDeclaringClass() &&
- xr.getReturnType() == boolean.class &&
- yr.getReturnType() == boolean.class &&
- xr.getName().indexOf("is") == 0 &&
- yr.getName().indexOf("get") == 0) {
- try {
- setReadMethod(xr);
- } catch (IntrospectionException ex) {
- // fall through
- }
- }
-
- Method xw = x.getWriteMethod();
- Method yw = y.getWriteMethod();
-
- try {
- if (yw != null && yw.getDeclaringClass() == getClass0()) {
- setWriteMethod(yw);
- } else {
- setWriteMethod(xw);
- }
- } catch (IntrospectionException ex) {
- // Fall through
- }
-
- if (y.getPropertyEditorClass() != null) {
- setPropertyEditorClass(y.getPropertyEditorClass());
- } else {
- setPropertyEditorClass(x.getPropertyEditorClass());
- }
-
-
- bound = x.bound | y.bound;
- constrained = x.constrained | y.constrained;
- }
-
- /*
- * Package-private dup constructor.
- * This must isolate the new object from any changes to the old object.
- */
- PropertyDescriptor(PropertyDescriptor old) {
- super(old);
- propertyTypeRef = old.propertyTypeRef;
- readMethodRef = old.readMethodRef;
- writeMethodRef = old.writeMethodRef;
- propertyEditorClassRef = old.propertyEditorClassRef;
-
- writeMethodName = old.writeMethodName;
- readMethodName = old.readMethodName;
- baseName = old.baseName;
-
- bound = old.bound;
- constrained = old.constrained;
- }
-
- /**
- * Returns the property type that corresponds to the read and write method.
- * The type precedence is given to the readMethod.
- *
- * @return the type of the property descriptor or null if both
- * read and write methods are null.
- * @throws IntrospectionException if the read or write method is invalid
- */
- private Class findPropertyType(Method readMethod, Method writeMethod)
- throws IntrospectionException {
- Class propertyType = null;
- try {
- if (readMethod != null) {
- Class[] params = readMethod.getParameterTypes();
- if (params.length != 0) {
- throw new IntrospectionException("bad read method arg count: "
- + readMethod);
- }
- propertyType = readMethod.getReturnType();
- if (propertyType == Void.TYPE) {
- throw new IntrospectionException("read method " +
- readMethod.getName() + " returns void");
- }
- }
- if (writeMethod != null) {
- Class params[] = writeMethod.getParameterTypes();
- if (params.length != 1) {
- throw new IntrospectionException("bad write method arg count: "
- + writeMethod);
- }
- if (propertyType != null && propertyType != params[0]) {
- throw new IntrospectionException("type mismatch between read and write methods");
- }
- propertyType = params[0];
- }
- } catch (IntrospectionException ex) {
- throw ex;
- }
- return propertyType;
- }
-
-
- /**
- * Returns a hash code value for the object.
- * See {@link java.lang.Object#hashCode} for a complete description.
- *
- * @return a hash code value for this object.
- * @since 1.5
- */
- public int hashCode() {
- int result = 7;
-
- result = 37 * result + ((getPropertyType() == null) ? 0 :
- getPropertyType().hashCode());
- result = 37 * result + ((getReadMethod() == null) ? 0 :
- getReadMethod().hashCode());
- result = 37 * result + ((getWriteMethod() == null) ? 0 :
- getWriteMethod().hashCode());
- result = 37 * result + ((getPropertyEditorClass() == null) ? 0 :
- getPropertyEditorClass().hashCode());
- result = 37 * result + ((writeMethodName == null) ? 0 :
- writeMethodName.hashCode());
- result = 37 * result + ((readMethodName == null) ? 0 :
- readMethodName.hashCode());
- result = 37 * result + getName().hashCode();
- result = 37 * result + ((bound == false) ? 0 : 1);
- result = 37 * result + ((constrained == false) ? 0 : 1);
-
- return result;
- }
-
- // Calculate once since capitalize() is expensive.
- String getBaseName() {
- if (baseName == null) {
- baseName = capitalize(getName());
- }
- return baseName;
- }
-
- /*
- public String toString() {
- String message = "name=" + getName();
- message += ", class=" + getClass0();
- message += ", type=" + getPropertyType();
-
- message += ", writeMethod=";
- message += writeMethodName;
-
- message += ", readMethod=";
- message += readMethodName;
-
- message += ", bound=" + bound;
- message += ", constrained=" + constrained;
-
- return message;
- }
- */
- }