Path: blob/master/src/java.desktop/share/classes/java/beans/PropertyDescriptor.java
41152 views
/*1* Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package java.beans;2526import java.lang.ref.Reference;27import java.lang.reflect.Method;28import java.lang.reflect.Constructor;29import java.util.Map.Entry;3031import com.sun.beans.introspect.PropertyInfo;32import sun.reflect.misc.ReflectUtil;3334/**35* A PropertyDescriptor describes one property that a Java Bean36* exports via a pair of accessor methods.37* @since 1.138*/39public class PropertyDescriptor extends FeatureDescriptor {4041private Reference<? extends Class<?>> propertyTypeRef;42private final MethodRef readMethodRef = new MethodRef();43private final MethodRef writeMethodRef = new MethodRef();44private Reference<? extends Class<?>> propertyEditorClassRef;4546private boolean bound;47private boolean constrained;4849// The base name of the method name which will be prefixed with the50// read and write method. If name == "foo" then the baseName is "Foo"51private String baseName;5253private String writeMethodName;54private String readMethodName;5556/**57* Constructs a PropertyDescriptor for a property that follows58* the standard Java convention by having getFoo and setFoo59* accessor methods. Thus if the argument name is "fred", it will60* assume that the writer method is "setFred" and the reader method61* is "getFred" (or "isFred" for a boolean property). Note that the62* property name should start with a lower case character, which will63* be capitalized in the method names.64*65* @param propertyName The programmatic name of the property.66* @param beanClass The Class object for the target bean. For67* example sun.beans.OurButton.class.68* @exception IntrospectionException if an exception occurs during69* introspection.70*/71public PropertyDescriptor(String propertyName, Class<?> beanClass)72throws IntrospectionException {73this(propertyName, beanClass,74Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),75Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));76}7778/**79* This constructor takes the name of a simple property, and method80* names for reading and writing the property.81*82* @param propertyName The programmatic name of the property.83* @param beanClass The Class object for the target bean. For84* example sun.beans.OurButton.class.85* @param readMethodName The name of the method used for reading the property86* value. May be null if the property is write-only.87* @param writeMethodName The name of the method used for writing the property88* value. May be null if the property is read-only.89* @exception IntrospectionException if an exception occurs during90* introspection.91*/92public PropertyDescriptor(String propertyName, Class<?> beanClass,93String readMethodName, String writeMethodName)94throws IntrospectionException {95if (beanClass == null) {96throw new IntrospectionException("Target Bean class is null");97}98if (propertyName == null || propertyName.length() == 0) {99throw new IntrospectionException("bad property name");100}101if ("".equals(readMethodName) || "".equals(writeMethodName)) {102throw new IntrospectionException("read or write method name should not be the empty string");103}104setName(propertyName);105setClass0(beanClass);106107this.readMethodName = readMethodName;108if (readMethodName != null && getReadMethod() == null) {109throw new IntrospectionException("Method not found: " + readMethodName);110}111this.writeMethodName = writeMethodName;112if (writeMethodName != null && getWriteMethod() == null) {113throw new IntrospectionException("Method not found: " + writeMethodName);114}115// If this class or one of its base classes allow PropertyChangeListener,116// then we assume that any properties we discover are "bound".117// See Introspector.getTargetPropertyInfo() method.118Class<?>[] args = { PropertyChangeListener.class };119this.bound = null != Introspector.findMethod(beanClass, "addPropertyChangeListener", args.length, args);120}121122/**123* This constructor takes the name of a simple property, and Method124* objects for reading and writing the property.125*126* @param propertyName The programmatic name of the property.127* @param readMethod The method used for reading the property value.128* May be null if the property is write-only.129* @param writeMethod The method used for writing the property value.130* May be null if the property is read-only.131* @exception IntrospectionException if an exception occurs during132* introspection.133*/134public PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod)135throws IntrospectionException {136if (propertyName == null || propertyName.length() == 0) {137throw new IntrospectionException("bad property name");138}139setName(propertyName);140setReadMethod(readMethod);141setWriteMethod(writeMethod);142}143144/**145* Creates {@code PropertyDescriptor} from the specified property info.146*147* @param entry the pair of values,148* where the {@code key} is the base name of the property (the rest of the method name)149* and the {@code value} is the automatically generated property info150* @param bound the flag indicating whether it is possible to treat this property as a bound property151*152* @since 9153*/154PropertyDescriptor(Entry<String,PropertyInfo> entry, boolean bound) {155String base = entry.getKey();156PropertyInfo info = entry.getValue();157setName(Introspector.decapitalize(base));158setReadMethod0(info.getReadMethod());159setWriteMethod0(info.getWriteMethod());160setPropertyType(info.getPropertyType());161setConstrained(info.isConstrained());162setBound(bound && info.is(PropertyInfo.Name.bound));163164boolean isExpert = info.is(PropertyInfo.Name.expert);165setValue(PropertyInfo.Name.expert.name(), isExpert); // compatibility166setExpert(isExpert);167168boolean isHidden = info.is(PropertyInfo.Name.hidden);169setValue(PropertyInfo.Name.hidden.name(), isHidden); // compatibility170setHidden(isHidden);171172setPreferred(info.is(PropertyInfo.Name.preferred));173174boolean isRequired = info.is(PropertyInfo.Name.required);175setValue(PropertyInfo.Name.required.name(), isRequired);176177boolean visual = info.is(PropertyInfo.Name.visualUpdate);178setValue(PropertyInfo.Name.visualUpdate.name(), visual);179180Object description = info.get(PropertyInfo.Name.description);181if (description != null) {182setShortDescription(description.toString());183}184Object values = info.get(PropertyInfo.Name.enumerationValues);185if (values == null) {186values = new Object[0];187}188setValue(PropertyInfo.Name.enumerationValues.name(), values);189this.baseName = base;190}191192/**193* Returns the Java type info for the property.194* Note that the {@code Class} object may describe195* primitive Java types such as {@code int}.196* This type is returned by the read method197* or is used as the parameter type of the write method.198* Returns {@code null} if the type is an indexed property199* that does not support non-indexed access.200*201* @return the {@code Class} object that represents the Java type info,202* or {@code null} if the type cannot be determined203*/204public synchronized Class<?> getPropertyType() {205Class<?> type = getPropertyType0();206if (type == null) {207try {208type = findPropertyType(getReadMethod(), getWriteMethod());209setPropertyType(type);210} catch (IntrospectionException ex) {211// Fall212}213}214return type;215}216217private void setPropertyType(Class<?> type) {218this.propertyTypeRef = getWeakReference(type);219}220221private Class<?> getPropertyType0() {222return (this.propertyTypeRef != null)223? this.propertyTypeRef.get()224: null;225}226227/**228* Gets the method that should be used to read the property value.229*230* @return The method that should be used to read the property value.231* May return null if the property can't be read.232*/233public synchronized Method getReadMethod() {234Method readMethod = this.readMethodRef.get();235if (readMethod == null) {236Class<?> cls = getClass0();237if (cls == null || (readMethodName == null && !this.readMethodRef.isSet())) {238// The read method was explicitly set to null.239return null;240}241String nextMethodName = Introspector.GET_PREFIX + getBaseName();242if (readMethodName == null) {243Class<?> type = getPropertyType0();244if (type == boolean.class || type == null) {245readMethodName = Introspector.IS_PREFIX + getBaseName();246} else {247readMethodName = nextMethodName;248}249}250251// Since there can be multiple write methods but only one getter252// method, find the getter method first so that you know what the253// property type is. For booleans, there can be "is" and "get"254// methods. If an "is" method exists, this is the official255// reader method so look for this one first.256readMethod = Introspector.findMethod(cls, readMethodName, 0);257if ((readMethod == null) && !readMethodName.equals(nextMethodName)) {258readMethodName = nextMethodName;259readMethod = Introspector.findMethod(cls, readMethodName, 0);260}261try {262setReadMethod(readMethod);263} catch (IntrospectionException ex) {264// fall265}266}267return readMethod;268}269270/**271* Sets the method that should be used to read the property value.272*273* @param readMethod The new read method.274* @throws IntrospectionException if the read method is invalid275* @since 1.2276*/277public synchronized void setReadMethod(Method readMethod)278throws IntrospectionException {279// The property type is determined by the read method.280setPropertyType(findPropertyType(readMethod, this.writeMethodRef.get()));281setReadMethod0(readMethod);282}283284private void setReadMethod0(Method readMethod) {285this.readMethodRef.set(readMethod);286if (readMethod == null) {287readMethodName = null;288return;289}290setClass0(readMethod.getDeclaringClass());291292readMethodName = readMethod.getName();293setTransient(readMethod.getAnnotation(Transient.class));294}295296/**297* Gets the method that should be used to write the property value.298*299* @return The method that should be used to write the property value.300* May return null if the property can't be written.301*/302public synchronized Method getWriteMethod() {303Method writeMethod = this.writeMethodRef.get();304if (writeMethod == null) {305Class<?> cls = getClass0();306if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) {307// The write method was explicitly set to null.308return null;309}310311// We need the type to fetch the correct method.312Class<?> type = getPropertyType0();313if (type == null) {314try {315// Can't use getPropertyType since it will lead to recursive loop.316type = findPropertyType(getReadMethod(), null);317setPropertyType(type);318} catch (IntrospectionException ex) {319// Without the correct property type we can't be guaranteed320// to find the correct method.321return null;322}323}324325if (writeMethodName == null) {326writeMethodName = Introspector.SET_PREFIX + getBaseName();327}328329Class<?>[] args = (type == null) ? null : new Class<?>[] { type };330writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);331if (writeMethod != null) {332if (!writeMethod.getReturnType().equals(void.class)) {333writeMethod = null;334}335}336try {337setWriteMethod(writeMethod);338} catch (IntrospectionException ex) {339// fall through340}341}342return writeMethod;343}344345/**346* Sets the method that should be used to write the property value.347*348* @param writeMethod The new write method.349* @throws IntrospectionException if the write method is invalid350* @since 1.2351*/352public synchronized void setWriteMethod(Method writeMethod)353throws IntrospectionException {354// Set the property type - which validates the method355setPropertyType(findPropertyType(getReadMethod(), writeMethod));356setWriteMethod0(writeMethod);357}358359private void setWriteMethod0(Method writeMethod) {360this.writeMethodRef.set(writeMethod);361if (writeMethod == null) {362writeMethodName = null;363return;364}365setClass0(writeMethod.getDeclaringClass());366367writeMethodName = writeMethod.getName();368setTransient(writeMethod.getAnnotation(Transient.class));369}370371/**372* Overridden to ensure that a super class doesn't take precedent373*/374void setClass0(Class<?> clz) {375if (getClass0() != null && clz.isAssignableFrom(getClass0())) {376// don't replace a subclass with a superclass377return;378}379super.setClass0(clz);380}381382/**383* Updates to "bound" properties will cause a "PropertyChange" event to384* get fired when the property is changed.385*386* @return True if this is a bound property.387*/388public boolean isBound() {389return bound;390}391392/**393* Updates to "bound" properties will cause a "PropertyChange" event to394* get fired when the property is changed.395*396* @param bound True if this is a bound property.397*/398public void setBound(boolean bound) {399this.bound = bound;400}401402/**403* Attempted updates to "Constrained" properties will cause a "VetoableChange"404* event to get fired when the property is changed.405*406* @return True if this is a constrained property.407*/408public boolean isConstrained() {409return constrained;410}411412/**413* Attempted updates to "Constrained" properties will cause a "VetoableChange"414* event to get fired when the property is changed.415*416* @param constrained True if this is a constrained property.417*/418public void setConstrained(boolean constrained) {419this.constrained = constrained;420}421422423/**424* Normally PropertyEditors will be found using the PropertyEditorManager.425* However if for some reason you want to associate a particular426* PropertyEditor with a given property, then you can do it with427* this method.428*429* @param propertyEditorClass The Class for the desired PropertyEditor.430*/431public void setPropertyEditorClass(Class<?> propertyEditorClass) {432this.propertyEditorClassRef = getWeakReference(propertyEditorClass);433}434435/**436* Gets any explicit PropertyEditor Class that has been registered437* for this property.438*439* @return Any explicit PropertyEditor Class that has been registered440* for this property. Normally this will return "null",441* indicating that no special editor has been registered,442* so the PropertyEditorManager should be used to locate443* a suitable PropertyEditor.444*/445public Class<?> getPropertyEditorClass() {446return (this.propertyEditorClassRef != null)447? this.propertyEditorClassRef.get()448: null;449}450451/**452* Constructs an instance of a property editor using the current453* property editor class.454* <p>455* If the property editor class has a public constructor that takes an456* Object argument then it will be invoked using the bean parameter457* as the argument. Otherwise, the default constructor will be invoked.458*459* @param bean the source object460* @return a property editor instance or null if a property editor has461* not been defined or cannot be created462* @since 1.5463*/464@SuppressWarnings("deprecation")465public PropertyEditor createPropertyEditor(Object bean) {466Object editor = null;467468final Class<?> cls = getPropertyEditorClass();469if (cls != null && PropertyEditor.class.isAssignableFrom(cls)470&& ReflectUtil.isPackageAccessible(cls)) {471Constructor<?> ctor = null;472if (bean != null) {473try {474ctor = cls.getConstructor(new Class<?>[] { Object.class });475} catch (Exception ex) {476// Fall through477}478}479try {480if (ctor == null) {481editor = cls.newInstance();482} else {483editor = ctor.newInstance(new Object[] { bean });484}485} catch (Exception ex) {486// Fall through487}488}489return (PropertyEditor)editor;490}491492493/**494* Compares this {@code PropertyDescriptor} against the specified object.495* Returns true if the objects are the same. Two {@code PropertyDescriptor}s496* are the same if the read, write, property types, property editor and497* flags are equivalent.498*499* @since 1.4500*/501public boolean equals(Object obj) {502if (this == obj) {503return true;504}505if (obj != null && obj instanceof PropertyDescriptor) {506PropertyDescriptor other = (PropertyDescriptor)obj;507Method otherReadMethod = other.getReadMethod();508Method otherWriteMethod = other.getWriteMethod();509510if (!compareMethods(getReadMethod(), otherReadMethod)) {511return false;512}513514if (!compareMethods(getWriteMethod(), otherWriteMethod)) {515return false;516}517518if (getPropertyType() == other.getPropertyType() &&519getPropertyEditorClass() == other.getPropertyEditorClass() &&520bound == other.isBound() && constrained == other.isConstrained() &&521writeMethodName == other.writeMethodName &&522readMethodName == other.readMethodName) {523return true;524}525}526return false;527}528529/**530* Package private helper method for Descriptor .equals methods.531*532* @param a first method to compare533* @param b second method to compare534* @return boolean to indicate that the methods are equivalent535*/536boolean compareMethods(Method a, Method b) {537// Note: perhaps this should be a protected method in FeatureDescriptor538if ((a == null) != (b == null)) {539return false;540}541542if (a != null && b != null) {543if (!a.equals(b)) {544return false;545}546}547return true;548}549550/**551* Package-private constructor.552* Merge two property descriptors. Where they conflict, give the553* second argument (y) priority over the first argument (x).554*555* @param x The first (lower priority) PropertyDescriptor556* @param y The second (higher priority) PropertyDescriptor557*/558PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {559super(x,y);560561if (y.baseName != null) {562baseName = y.baseName;563} else {564baseName = x.baseName;565}566567if (y.readMethodName != null) {568readMethodName = y.readMethodName;569} else {570readMethodName = x.readMethodName;571}572573if (y.writeMethodName != null) {574writeMethodName = y.writeMethodName;575} else {576writeMethodName = x.writeMethodName;577}578579if (y.propertyTypeRef != null) {580propertyTypeRef = y.propertyTypeRef;581} else {582propertyTypeRef = x.propertyTypeRef;583}584585// Figure out the merged read method.586Method xr = x.getReadMethod();587Method yr = y.getReadMethod();588589// Normally give priority to y's readMethod.590try {591if (isAssignable(xr, yr)) {592setReadMethod(yr);593} else {594setReadMethod(xr);595}596} catch (IntrospectionException ex) {597// fall through598}599600// However, if both x and y reference read methods in the same class,601// give priority to a boolean "is" method over a boolean "get" method.602if (xr != null && yr != null &&603xr.getDeclaringClass() == yr.getDeclaringClass() &&604getReturnType(getClass0(), xr) == boolean.class &&605getReturnType(getClass0(), yr) == boolean.class &&606xr.getName().indexOf(Introspector.IS_PREFIX) == 0 &&607yr.getName().indexOf(Introspector.GET_PREFIX) == 0) {608try {609setReadMethod(xr);610} catch (IntrospectionException ex) {611// fall through612}613}614615Method xw = x.getWriteMethod();616Method yw = y.getWriteMethod();617618try {619if (yw != null) {620setWriteMethod(yw);621} else {622setWriteMethod(xw);623}624} catch (IntrospectionException ex) {625// Fall through626}627628if (y.getPropertyEditorClass() != null) {629setPropertyEditorClass(y.getPropertyEditorClass());630} else {631setPropertyEditorClass(x.getPropertyEditorClass());632}633634635bound = x.bound | y.bound;636constrained = x.constrained | y.constrained;637}638639/*640* Package-private dup constructor.641* This must isolate the new object from any changes to the old object.642*/643PropertyDescriptor(PropertyDescriptor old) {644super(old);645propertyTypeRef = old.propertyTypeRef;646this.readMethodRef.set(old.readMethodRef.get());647this.writeMethodRef.set(old.writeMethodRef.get());648propertyEditorClassRef = old.propertyEditorClassRef;649650writeMethodName = old.writeMethodName;651readMethodName = old.readMethodName;652baseName = old.baseName;653654bound = old.bound;655constrained = old.constrained;656}657658void updateGenericsFor(Class<?> type) {659setClass0(type);660try {661setPropertyType(findPropertyType(this.readMethodRef.get(), this.writeMethodRef.get()));662}663catch (IntrospectionException exception) {664setPropertyType(null);665}666}667668/**669* Returns the property type that corresponds to the read and write method.670* The type precedence is given to the readMethod.671*672* @return the type of the property descriptor or null if both673* read and write methods are null.674* @throws IntrospectionException if the read or write method is invalid675*/676private Class<?> findPropertyType(Method readMethod, Method writeMethod)677throws IntrospectionException {678Class<?> propertyType = null;679try {680if (readMethod != null) {681Class<?>[] params = getParameterTypes(getClass0(), readMethod);682if (params.length != 0) {683throw new IntrospectionException("bad read method arg count: "684+ readMethod);685}686propertyType = getReturnType(getClass0(), readMethod);687if (propertyType == Void.TYPE) {688throw new IntrospectionException("read method " +689readMethod.getName() + " returns void");690}691}692if (writeMethod != null) {693Class<?>[] params = getParameterTypes(getClass0(), writeMethod);694if (params.length != 1) {695throw new IntrospectionException("bad write method arg count: "696+ writeMethod);697}698if (propertyType != null && !params[0].isAssignableFrom(propertyType)) {699throw new IntrospectionException("type mismatch between read and write methods");700}701propertyType = params[0];702}703} catch (IntrospectionException ex) {704throw ex;705}706return propertyType;707}708709710/**711* Returns a hash code value for the object.712* See {@link java.lang.Object#hashCode} for a complete description.713*714* @return a hash code value for this object.715* @since 1.5716*/717public int hashCode() {718int result = 7;719720result = 37 * result + ((getPropertyType() == null) ? 0 :721getPropertyType().hashCode());722result = 37 * result + ((getReadMethod() == null) ? 0 :723getReadMethod().hashCode());724result = 37 * result + ((getWriteMethod() == null) ? 0 :725getWriteMethod().hashCode());726result = 37 * result + ((getPropertyEditorClass() == null) ? 0 :727getPropertyEditorClass().hashCode());728result = 37 * result + ((writeMethodName == null) ? 0 :729writeMethodName.hashCode());730result = 37 * result + ((readMethodName == null) ? 0 :731readMethodName.hashCode());732result = 37 * result + getName().hashCode();733result = 37 * result + ((bound == false) ? 0 : 1);734result = 37 * result + ((constrained == false) ? 0 : 1);735736return result;737}738739// Calculate once since capitalize() is expensive.740String getBaseName() {741if (baseName == null) {742baseName = NameGenerator.capitalize(getName());743}744return baseName;745}746747void appendTo(StringBuilder sb) {748appendTo(sb, "bound", this.bound);749appendTo(sb, "constrained", this.constrained);750appendTo(sb, "propertyEditorClass", this.propertyEditorClassRef);751appendTo(sb, "propertyType", this.propertyTypeRef);752appendTo(sb, "readMethod", this.readMethodRef.get());753appendTo(sb, "writeMethod", this.writeMethodRef.get());754}755756boolean isAssignable(Method m1, Method m2) {757if (m1 == null) {758return true; // choose second method759}760if (m2 == null) {761return false; // choose first method762}763if (!m1.getName().equals(m2.getName())) {764return true; // choose second method by default765}766Class<?> type1 = m1.getDeclaringClass();767Class<?> type2 = m2.getDeclaringClass();768if (!type1.isAssignableFrom(type2)) {769return false; // choose first method: it declared later770}771type1 = getReturnType(getClass0(), m1);772type2 = getReturnType(getClass0(), m2);773if (!type1.isAssignableFrom(type2)) {774return false; // choose first method: it overrides return type775}776Class<?>[] args1 = getParameterTypes(getClass0(), m1);777Class<?>[] args2 = getParameterTypes(getClass0(), m2);778if (args1.length != args2.length) {779return true; // choose second method by default780}781for (int i = 0; i < args1.length; i++) {782if (!args1[i].isAssignableFrom(args2[i])) {783return false; // choose first method: it overrides parameter784}785}786return true; // choose second method787}788}789790791