Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/beans/PropertyDescriptor.java
38829 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*/2425package java.beans;2627import java.lang.ref.Reference;28import java.lang.reflect.Method;29import java.lang.reflect.Constructor;30import sun.reflect.misc.ReflectUtil;3132/**33* A PropertyDescriptor describes one property that a Java Bean34* exports via a pair of accessor methods.35*/36public class PropertyDescriptor extends FeatureDescriptor {3738private Reference<? extends Class<?>> propertyTypeRef;39private final MethodRef readMethodRef = new MethodRef();40private final MethodRef writeMethodRef = new MethodRef();41private Reference<? extends Class<?>> propertyEditorClassRef;4243private boolean bound;44private boolean constrained;4546// The base name of the method name which will be prefixed with the47// read and write method. If name == "foo" then the baseName is "Foo"48private String baseName;4950private String writeMethodName;51private String readMethodName;5253/**54* Constructs a PropertyDescriptor for a property that follows55* the standard Java convention by having getFoo and setFoo56* accessor methods. Thus if the argument name is "fred", it will57* assume that the writer method is "setFred" and the reader method58* is "getFred" (or "isFred" for a boolean property). Note that the59* property name should start with a lower case character, which will60* be capitalized in the method names.61*62* @param propertyName The programmatic name of the property.63* @param beanClass The Class object for the target bean. For64* example sun.beans.OurButton.class.65* @exception IntrospectionException if an exception occurs during66* introspection.67*/68public PropertyDescriptor(String propertyName, Class<?> beanClass)69throws IntrospectionException {70this(propertyName, beanClass,71Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),72Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));73}7475/**76* This constructor takes the name of a simple property, and method77* names for reading and writing the property.78*79* @param propertyName The programmatic name of the property.80* @param beanClass The Class object for the target bean. For81* example sun.beans.OurButton.class.82* @param readMethodName The name of the method used for reading the property83* value. May be null if the property is write-only.84* @param writeMethodName The name of the method used for writing the property85* value. May be null if the property is read-only.86* @exception IntrospectionException if an exception occurs during87* introspection.88*/89public PropertyDescriptor(String propertyName, Class<?> beanClass,90String readMethodName, String writeMethodName)91throws IntrospectionException {92if (beanClass == null) {93throw new IntrospectionException("Target Bean class is null");94}95if (propertyName == null || propertyName.length() == 0) {96throw new IntrospectionException("bad property name");97}98if ("".equals(readMethodName) || "".equals(writeMethodName)) {99throw new IntrospectionException("read or write method name should not be the empty string");100}101setName(propertyName);102setClass0(beanClass);103104this.readMethodName = readMethodName;105if (readMethodName != null && getReadMethod() == null) {106throw new IntrospectionException("Method not found: " + readMethodName);107}108this.writeMethodName = writeMethodName;109if (writeMethodName != null && getWriteMethod() == null) {110throw new IntrospectionException("Method not found: " + writeMethodName);111}112// If this class or one of its base classes allow PropertyChangeListener,113// then we assume that any properties we discover are "bound".114// See Introspector.getTargetPropertyInfo() method.115Class[] args = { PropertyChangeListener.class };116this.bound = null != Introspector.findMethod(beanClass, "addPropertyChangeListener", args.length, args);117}118119/**120* This constructor takes the name of a simple property, and Method121* objects for reading and writing the property.122*123* @param propertyName The programmatic name of the property.124* @param readMethod The method used for reading the property value.125* May be null if the property is write-only.126* @param writeMethod The method used for writing the property value.127* May be null if the property is read-only.128* @exception IntrospectionException if an exception occurs during129* introspection.130*/131public PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod)132throws IntrospectionException {133if (propertyName == null || propertyName.length() == 0) {134throw new IntrospectionException("bad property name");135}136setName(propertyName);137setReadMethod(readMethod);138setWriteMethod(writeMethod);139}140141/**142* Creates <code>PropertyDescriptor</code> for the specified bean143* with the specified name and methods to read/write the property value.144*145* @param bean the type of the target bean146* @param base the base name of the property (the rest of the method name)147* @param read the method used for reading the property value148* @param write the method used for writing the property value149* @exception IntrospectionException if an exception occurs during introspection150*151* @since 1.7152*/153PropertyDescriptor(Class<?> bean, String base, Method read, Method write) throws IntrospectionException {154if (bean == null) {155throw new IntrospectionException("Target Bean class is null");156}157setClass0(bean);158setName(Introspector.decapitalize(base));159setReadMethod(read);160setWriteMethod(write);161this.baseName = base;162}163164/**165* Returns the Java type info for the property.166* Note that the {@code Class} object may describe167* primitive Java types such as {@code int}.168* This type is returned by the read method169* or is used as the parameter type of the write method.170* Returns {@code null} if the type is an indexed property171* that does not support non-indexed access.172*173* @return the {@code Class} object that represents the Java type info,174* or {@code null} if the type cannot be determined175*/176public synchronized Class<?> getPropertyType() {177Class<?> type = getPropertyType0();178if (type == null) {179try {180type = findPropertyType(getReadMethod(), getWriteMethod());181setPropertyType(type);182} catch (IntrospectionException ex) {183// Fall184}185}186return type;187}188189private void setPropertyType(Class<?> type) {190this.propertyTypeRef = getWeakReference(type);191}192193private Class<?> getPropertyType0() {194return (this.propertyTypeRef != null)195? this.propertyTypeRef.get()196: null;197}198199/**200* Gets the method that should be used to read the property value.201*202* @return The method that should be used to read the property value.203* May return null if the property can't be read.204*/205public synchronized Method getReadMethod() {206Method readMethod = this.readMethodRef.get();207if (readMethod == null) {208Class<?> cls = getClass0();209if (cls == null || (readMethodName == null && !this.readMethodRef.isSet())) {210// The read method was explicitly set to null.211return null;212}213String nextMethodName = Introspector.GET_PREFIX + getBaseName();214if (readMethodName == null) {215Class<?> type = getPropertyType0();216if (type == boolean.class || type == null) {217readMethodName = Introspector.IS_PREFIX + getBaseName();218} else {219readMethodName = nextMethodName;220}221}222223// Since there can be multiple write methods but only one getter224// method, find the getter method first so that you know what the225// property type is. For booleans, there can be "is" and "get"226// methods. If an "is" method exists, this is the official227// reader method so look for this one first.228readMethod = Introspector.findMethod(cls, readMethodName, 0);229if ((readMethod == null) && !readMethodName.equals(nextMethodName)) {230readMethodName = nextMethodName;231readMethod = Introspector.findMethod(cls, readMethodName, 0);232}233try {234setReadMethod(readMethod);235} catch (IntrospectionException ex) {236// fall237}238}239return readMethod;240}241242/**243* Sets the method that should be used to read the property value.244*245* @param readMethod The new read method.246* @throws IntrospectionException if the read method is invalid247*/248public synchronized void setReadMethod(Method readMethod)249throws IntrospectionException {250this.readMethodRef.set(readMethod);251if (readMethod == null) {252readMethodName = null;253return;254}255// The property type is determined by the read method.256setPropertyType(findPropertyType(readMethod, this.writeMethodRef.get()));257setClass0(readMethod.getDeclaringClass());258259readMethodName = readMethod.getName();260setTransient(readMethod.getAnnotation(Transient.class));261}262263/**264* Gets the method that should be used to write the property value.265*266* @return The method that should be used to write the property value.267* May return null if the property can't be written.268*/269public synchronized Method getWriteMethod() {270Method writeMethod = this.writeMethodRef.get();271if (writeMethod == null) {272Class<?> cls = getClass0();273if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) {274// The write method was explicitly set to null.275return null;276}277278// We need the type to fetch the correct method.279Class<?> type = getPropertyType0();280if (type == null) {281try {282// Can't use getPropertyType since it will lead to recursive loop.283type = findPropertyType(getReadMethod(), null);284setPropertyType(type);285} catch (IntrospectionException ex) {286// Without the correct property type we can't be guaranteed287// to find the correct method.288return null;289}290}291292if (writeMethodName == null) {293writeMethodName = Introspector.SET_PREFIX + getBaseName();294}295296Class<?>[] args = (type == null) ? null : new Class<?>[] { type };297writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);298if (writeMethod != null) {299if (!writeMethod.getReturnType().equals(void.class)) {300writeMethod = null;301}302}303try {304setWriteMethod(writeMethod);305} catch (IntrospectionException ex) {306// fall through307}308}309return writeMethod;310}311312/**313* Sets the method that should be used to write the property value.314*315* @param writeMethod The new write method.316* @throws IntrospectionException if the write method is invalid317*/318public synchronized void setWriteMethod(Method writeMethod)319throws IntrospectionException {320this.writeMethodRef.set(writeMethod);321if (writeMethod == null) {322writeMethodName = null;323return;324}325// Set the property type - which validates the method326setPropertyType(findPropertyType(getReadMethod(), writeMethod));327setClass0(writeMethod.getDeclaringClass());328329writeMethodName = writeMethod.getName();330setTransient(writeMethod.getAnnotation(Transient.class));331}332333/**334* Overridden to ensure that a super class doesn't take precedent335*/336void setClass0(Class<?> clz) {337if (getClass0() != null && clz.isAssignableFrom(getClass0())) {338// don't replace a subclass with a superclass339return;340}341super.setClass0(clz);342}343344/**345* Updates to "bound" properties will cause a "PropertyChange" event to346* get fired when the property is changed.347*348* @return True if this is a bound property.349*/350public boolean isBound() {351return bound;352}353354/**355* Updates to "bound" properties will cause a "PropertyChange" event to356* get fired when the property is changed.357*358* @param bound True if this is a bound property.359*/360public void setBound(boolean bound) {361this.bound = bound;362}363364/**365* Attempted updates to "Constrained" properties will cause a "VetoableChange"366* event to get fired when the property is changed.367*368* @return True if this is a constrained property.369*/370public boolean isConstrained() {371return constrained;372}373374/**375* Attempted updates to "Constrained" properties will cause a "VetoableChange"376* event to get fired when the property is changed.377*378* @param constrained True if this is a constrained property.379*/380public void setConstrained(boolean constrained) {381this.constrained = constrained;382}383384385/**386* Normally PropertyEditors will be found using the PropertyEditorManager.387* However if for some reason you want to associate a particular388* PropertyEditor with a given property, then you can do it with389* this method.390*391* @param propertyEditorClass The Class for the desired PropertyEditor.392*/393public void setPropertyEditorClass(Class<?> propertyEditorClass) {394this.propertyEditorClassRef = getWeakReference(propertyEditorClass);395}396397/**398* Gets any explicit PropertyEditor Class that has been registered399* for this property.400*401* @return Any explicit PropertyEditor Class that has been registered402* for this property. Normally this will return "null",403* indicating that no special editor has been registered,404* so the PropertyEditorManager should be used to locate405* a suitable PropertyEditor.406*/407public Class<?> getPropertyEditorClass() {408return (this.propertyEditorClassRef != null)409? this.propertyEditorClassRef.get()410: null;411}412413/**414* Constructs an instance of a property editor using the current415* property editor class.416* <p>417* If the property editor class has a public constructor that takes an418* Object argument then it will be invoked using the bean parameter419* as the argument. Otherwise, the default constructor will be invoked.420*421* @param bean the source object422* @return a property editor instance or null if a property editor has423* not been defined or cannot be created424* @since 1.5425*/426public PropertyEditor createPropertyEditor(Object bean) {427Object editor = null;428429final Class<?> cls = getPropertyEditorClass();430if (cls != null && PropertyEditor.class.isAssignableFrom(cls)431&& ReflectUtil.isPackageAccessible(cls)) {432Constructor<?> ctor = null;433if (bean != null) {434try {435ctor = cls.getConstructor(new Class<?>[] { Object.class });436} catch (Exception ex) {437// Fall through438}439}440try {441if (ctor == null) {442editor = cls.newInstance();443} else {444editor = ctor.newInstance(new Object[] { bean });445}446} catch (Exception ex) {447// Fall through448}449}450return (PropertyEditor)editor;451}452453454/**455* Compares this <code>PropertyDescriptor</code> against the specified object.456* Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s457* are the same if the read, write, property types, property editor and458* flags are equivalent.459*460* @since 1.4461*/462public boolean equals(Object obj) {463if (this == obj) {464return true;465}466if (obj != null && obj instanceof PropertyDescriptor) {467PropertyDescriptor other = (PropertyDescriptor)obj;468Method otherReadMethod = other.getReadMethod();469Method otherWriteMethod = other.getWriteMethod();470471if (!compareMethods(getReadMethod(), otherReadMethod)) {472return false;473}474475if (!compareMethods(getWriteMethod(), otherWriteMethod)) {476return false;477}478479if (getPropertyType() == other.getPropertyType() &&480getPropertyEditorClass() == other.getPropertyEditorClass() &&481bound == other.isBound() && constrained == other.isConstrained() &&482writeMethodName == other.writeMethodName &&483readMethodName == other.readMethodName) {484return true;485}486}487return false;488}489490/**491* Package private helper method for Descriptor .equals methods.492*493* @param a first method to compare494* @param b second method to compare495* @return boolean to indicate that the methods are equivalent496*/497boolean compareMethods(Method a, Method b) {498// Note: perhaps this should be a protected method in FeatureDescriptor499if ((a == null) != (b == null)) {500return false;501}502503if (a != null && b != null) {504if (!a.equals(b)) {505return false;506}507}508return true;509}510511/**512* Package-private constructor.513* Merge two property descriptors. Where they conflict, give the514* second argument (y) priority over the first argument (x).515*516* @param x The first (lower priority) PropertyDescriptor517* @param y The second (higher priority) PropertyDescriptor518*/519PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {520super(x,y);521522if (y.baseName != null) {523baseName = y.baseName;524} else {525baseName = x.baseName;526}527528if (y.readMethodName != null) {529readMethodName = y.readMethodName;530} else {531readMethodName = x.readMethodName;532}533534if (y.writeMethodName != null) {535writeMethodName = y.writeMethodName;536} else {537writeMethodName = x.writeMethodName;538}539540if (y.propertyTypeRef != null) {541propertyTypeRef = y.propertyTypeRef;542} else {543propertyTypeRef = x.propertyTypeRef;544}545546// Figure out the merged read method.547Method xr = x.getReadMethod();548Method yr = y.getReadMethod();549550// Normally give priority to y's readMethod.551try {552if (isAssignable(xr, yr)) {553setReadMethod(yr);554} else {555setReadMethod(xr);556}557} catch (IntrospectionException ex) {558// fall through559}560561// However, if both x and y reference read methods in the same class,562// give priority to a boolean "is" method over a boolean "get" method.563if (xr != null && yr != null &&564xr.getDeclaringClass() == yr.getDeclaringClass() &&565getReturnType(getClass0(), xr) == boolean.class &&566getReturnType(getClass0(), yr) == boolean.class &&567xr.getName().indexOf(Introspector.IS_PREFIX) == 0 &&568yr.getName().indexOf(Introspector.GET_PREFIX) == 0) {569try {570setReadMethod(xr);571} catch (IntrospectionException ex) {572// fall through573}574}575576Method xw = x.getWriteMethod();577Method yw = y.getWriteMethod();578579try {580if (yw != null) {581setWriteMethod(yw);582} else {583setWriteMethod(xw);584}585} catch (IntrospectionException ex) {586// Fall through587}588589if (y.getPropertyEditorClass() != null) {590setPropertyEditorClass(y.getPropertyEditorClass());591} else {592setPropertyEditorClass(x.getPropertyEditorClass());593}594595596bound = x.bound | y.bound;597constrained = x.constrained | y.constrained;598}599600/*601* Package-private dup constructor.602* This must isolate the new object from any changes to the old object.603*/604PropertyDescriptor(PropertyDescriptor old) {605super(old);606propertyTypeRef = old.propertyTypeRef;607this.readMethodRef.set(old.readMethodRef.get());608this.writeMethodRef.set(old.writeMethodRef.get());609propertyEditorClassRef = old.propertyEditorClassRef;610611writeMethodName = old.writeMethodName;612readMethodName = old.readMethodName;613baseName = old.baseName;614615bound = old.bound;616constrained = old.constrained;617}618619void updateGenericsFor(Class<?> type) {620setClass0(type);621try {622setPropertyType(findPropertyType(this.readMethodRef.get(), this.writeMethodRef.get()));623}624catch (IntrospectionException exception) {625setPropertyType(null);626}627}628629/**630* Returns the property type that corresponds to the read and write method.631* The type precedence is given to the readMethod.632*633* @return the type of the property descriptor or null if both634* read and write methods are null.635* @throws IntrospectionException if the read or write method is invalid636*/637private Class<?> findPropertyType(Method readMethod, Method writeMethod)638throws IntrospectionException {639Class<?> propertyType = null;640try {641if (readMethod != null) {642Class<?>[] params = getParameterTypes(getClass0(), readMethod);643if (params.length != 0) {644throw new IntrospectionException("bad read method arg count: "645+ readMethod);646}647propertyType = getReturnType(getClass0(), readMethod);648if (propertyType == Void.TYPE) {649throw new IntrospectionException("read method " +650readMethod.getName() + " returns void");651}652}653if (writeMethod != null) {654Class<?>[] params = getParameterTypes(getClass0(), writeMethod);655if (params.length != 1) {656throw new IntrospectionException("bad write method arg count: "657+ writeMethod);658}659if (propertyType != null && !params[0].isAssignableFrom(propertyType)) {660throw new IntrospectionException("type mismatch between read and write methods");661}662propertyType = params[0];663}664} catch (IntrospectionException ex) {665throw ex;666}667return propertyType;668}669670671/**672* Returns a hash code value for the object.673* See {@link java.lang.Object#hashCode} for a complete description.674*675* @return a hash code value for this object.676* @since 1.5677*/678public int hashCode() {679int result = 7;680681result = 37 * result + ((getPropertyType() == null) ? 0 :682getPropertyType().hashCode());683result = 37 * result + ((getReadMethod() == null) ? 0 :684getReadMethod().hashCode());685result = 37 * result + ((getWriteMethod() == null) ? 0 :686getWriteMethod().hashCode());687result = 37 * result + ((getPropertyEditorClass() == null) ? 0 :688getPropertyEditorClass().hashCode());689result = 37 * result + ((writeMethodName == null) ? 0 :690writeMethodName.hashCode());691result = 37 * result + ((readMethodName == null) ? 0 :692readMethodName.hashCode());693result = 37 * result + getName().hashCode();694result = 37 * result + ((bound == false) ? 0 : 1);695result = 37 * result + ((constrained == false) ? 0 : 1);696697return result;698}699700// Calculate once since capitalize() is expensive.701String getBaseName() {702if (baseName == null) {703baseName = NameGenerator.capitalize(getName());704}705return baseName;706}707708void appendTo(StringBuilder sb) {709appendTo(sb, "bound", this.bound);710appendTo(sb, "constrained", this.constrained);711appendTo(sb, "propertyEditorClass", this.propertyEditorClassRef);712appendTo(sb, "propertyType", this.propertyTypeRef);713appendTo(sb, "readMethod", this.readMethodRef.get());714appendTo(sb, "writeMethod", this.writeMethodRef.get());715}716717boolean isAssignable(Method m1, Method m2) {718if (m1 == null) {719return true; // choose second method720}721if (m2 == null) {722return false; // choose first method723}724if (!m1.getName().equals(m2.getName())) {725return true; // choose second method by default726}727Class<?> type1 = m1.getDeclaringClass();728Class<?> type2 = m2.getDeclaringClass();729if (!type1.isAssignableFrom(type2)) {730return false; // choose first method: it declared later731}732type1 = getReturnType(getClass0(), m1);733type2 = getReturnType(getClass0(), m2);734if (!type1.isAssignableFrom(type2)) {735return false; // choose first method: it overrides return type736}737Class<?>[] args1 = getParameterTypes(getClass0(), m1);738Class<?>[] args2 = getParameterTypes(getClass0(), m2);739if (args1.length != args2.length) {740return true; // choose second method by default741}742for (int i = 0; i < args1.length; i++) {743if (!args1[i].isAssignableFrom(args2[i])) {744return false; // choose first method: it overrides parameter745}746}747return true; // choose second method748}749}750751752