524 lines
22 KiB
Java
524 lines
22 KiB
Java
/*
|
|
* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package java.beans;
|
|
|
|
import java.lang.ref.Reference;
|
|
import java.lang.reflect.Method;
|
|
|
|
/**
|
|
* An IndexedPropertyDescriptor describes a property that acts like an
|
|
* array and has an indexed read and/or indexed write method to access
|
|
* specific elements of the array.
|
|
* <p>
|
|
* An indexed property may also provide simple non-indexed read and write
|
|
* methods. If these are present, they read and write arrays of the type
|
|
* returned by the indexed read method.
|
|
*/
|
|
|
|
public class IndexedPropertyDescriptor extends PropertyDescriptor {
|
|
|
|
private Reference<? extends Class<?>> indexedPropertyTypeRef;
|
|
private final MethodRef indexedReadMethodRef = new MethodRef();
|
|
private final MethodRef indexedWriteMethodRef = new MethodRef();
|
|
|
|
private String indexedReadMethodName;
|
|
private String indexedWriteMethodName;
|
|
|
|
/**
|
|
* This constructor constructs an IndexedPropertyDescriptor for a property
|
|
* that follows the standard Java conventions by having getFoo and setFoo
|
|
* accessor methods, for both indexed access and array access.
|
|
* <p>
|
|
* Thus if the argument name is "fred", it will assume that there
|
|
* is an indexed reader method "getFred", a non-indexed (array) reader
|
|
* method also called "getFred", an indexed writer method "setFred",
|
|
* and finally a non-indexed writer method "setFred".
|
|
*
|
|
* @param propertyName The programmatic name of the property.
|
|
* @param beanClass The Class object for the target bean.
|
|
* @exception IntrospectionException if an exception occurs during
|
|
* introspection.
|
|
*/
|
|
public IndexedPropertyDescriptor(String propertyName, Class<?> beanClass)
|
|
throws IntrospectionException {
|
|
this(propertyName, beanClass,
|
|
Introspector.GET_PREFIX + NameGenerator.capitalize(propertyName),
|
|
Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName),
|
|
Introspector.GET_PREFIX + NameGenerator.capitalize(propertyName),
|
|
Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));
|
|
}
|
|
|
|
/**
|
|
* This constructor takes the name of a simple property, and method
|
|
* names for reading and writing the property, both indexed
|
|
* and non-indexed.
|
|
*
|
|
* @param propertyName The programmatic name of the property.
|
|
* @param beanClass The Class object for the target bean.
|
|
* @param readMethodName The name of the method used for reading the property
|
|
* values as an array. May be null if the property is write-only
|
|
* or must be indexed.
|
|
* @param writeMethodName The name of the method used for writing the property
|
|
* values as an array. May be null if the property is read-only
|
|
* or must be indexed.
|
|
* @param indexedReadMethodName The name of the method used for reading
|
|
* an indexed property value.
|
|
* May be null if the property is write-only.
|
|
* @param indexedWriteMethodName The name of the method used for writing
|
|
* an indexed property value.
|
|
* May be null if the property is read-only.
|
|
* @exception IntrospectionException if an exception occurs during
|
|
* introspection.
|
|
*/
|
|
public IndexedPropertyDescriptor(String propertyName, Class<?> beanClass,
|
|
String readMethodName, String writeMethodName,
|
|
String indexedReadMethodName, String indexedWriteMethodName)
|
|
throws IntrospectionException {
|
|
super(propertyName, beanClass, readMethodName, writeMethodName);
|
|
|
|
this.indexedReadMethodName = indexedReadMethodName;
|
|
if (indexedReadMethodName != null && getIndexedReadMethod() == null) {
|
|
throw new IntrospectionException("Method not found: " + indexedReadMethodName);
|
|
}
|
|
|
|
this.indexedWriteMethodName = indexedWriteMethodName;
|
|
if (indexedWriteMethodName != null && getIndexedWriteMethod() == null) {
|
|
throw new IntrospectionException("Method not found: " + indexedWriteMethodName);
|
|
}
|
|
// Implemented only for type checking.
|
|
findIndexedPropertyType(getIndexedReadMethod(), getIndexedWriteMethod());
|
|
}
|
|
|
|
/**
|
|
* 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 values as an array.
|
|
* May be null if the property is write-only or must be indexed.
|
|
* @param writeMethod The method used for writing the property values as an array.
|
|
* May be null if the property is read-only or must be indexed.
|
|
* @param indexedReadMethod The method used for reading an indexed property value.
|
|
* May be null if the property is write-only.
|
|
* @param indexedWriteMethod The method used for writing an indexed property value.
|
|
* May be null if the property is read-only.
|
|
* @exception IntrospectionException if an exception occurs during
|
|
* introspection.
|
|
*/
|
|
public IndexedPropertyDescriptor(String propertyName, Method readMethod, Method writeMethod,
|
|
Method indexedReadMethod, Method indexedWriteMethod)
|
|
throws IntrospectionException {
|
|
super(propertyName, readMethod, writeMethod);
|
|
|
|
setIndexedReadMethod0(indexedReadMethod);
|
|
setIndexedWriteMethod0(indexedWriteMethod);
|
|
|
|
// Type checking
|
|
setIndexedPropertyType(findIndexedPropertyType(indexedReadMethod, indexedWriteMethod));
|
|
}
|
|
|
|
/**
|
|
* Creates <code>PropertyDescriptor</code> for the specified bean
|
|
* with the specified name and methods to read/write the property value.
|
|
*
|
|
* @param bean the type of the target bean
|
|
* @param base the base name of the property (the rest of the method name)
|
|
* @param read the method used for reading the property value
|
|
* @param write the method used for writing the property value
|
|
* @param readIndexed the method used for reading an indexed property value
|
|
* @param writeIndexed the method used for writing an indexed property value
|
|
* @exception IntrospectionException if an exception occurs during introspection
|
|
*
|
|
* @since 1.7
|
|
*/
|
|
IndexedPropertyDescriptor(Class<?> bean, String base, Method read, Method write, Method readIndexed, Method writeIndexed) throws IntrospectionException {
|
|
super(bean, base, read, write);
|
|
|
|
setIndexedReadMethod0(readIndexed);
|
|
setIndexedWriteMethod0(writeIndexed);
|
|
|
|
// Type checking
|
|
setIndexedPropertyType(findIndexedPropertyType(readIndexed, writeIndexed));
|
|
}
|
|
|
|
/**
|
|
* Gets the method that should be used to read an indexed
|
|
* property value.
|
|
*
|
|
* @return The method that should be used to read an indexed
|
|
* property value.
|
|
* May return null if the property isn't indexed or is write-only.
|
|
*/
|
|
public synchronized Method getIndexedReadMethod() {
|
|
Method indexedReadMethod = this.indexedReadMethodRef.get();
|
|
if (indexedReadMethod == null) {
|
|
Class<?> cls = getClass0();
|
|
if (cls == null ||
|
|
(indexedReadMethodName == null && !this.indexedReadMethodRef.isSet())) {
|
|
// the Indexed readMethod was explicitly set to null.
|
|
return null;
|
|
}
|
|
String nextMethodName = Introspector.GET_PREFIX + getBaseName();
|
|
if (indexedReadMethodName == null) {
|
|
Class<?> type = getIndexedPropertyType0();
|
|
if (type == boolean.class || type == null) {
|
|
indexedReadMethodName = Introspector.IS_PREFIX + getBaseName();
|
|
} else {
|
|
indexedReadMethodName = nextMethodName;
|
|
}
|
|
}
|
|
|
|
Class<?>[] args = { int.class };
|
|
indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
|
|
if ((indexedReadMethod == null) && !indexedReadMethodName.equals(nextMethodName)) {
|
|
// no "is" method, so look for a "get" method.
|
|
indexedReadMethodName = nextMethodName;
|
|
indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
|
|
}
|
|
setIndexedReadMethod0(indexedReadMethod);
|
|
}
|
|
return indexedReadMethod;
|
|
}
|
|
|
|
/**
|
|
* Sets the method that should be used to read an indexed property value.
|
|
*
|
|
* @param readMethod The new indexed read method.
|
|
* @throws IntrospectionException if an exception occurs during
|
|
* introspection.
|
|
*/
|
|
public synchronized void setIndexedReadMethod(Method readMethod)
|
|
throws IntrospectionException {
|
|
|
|
// the indexed property type is set by the reader.
|
|
setIndexedPropertyType(findIndexedPropertyType(readMethod,
|
|
this.indexedWriteMethodRef.get()));
|
|
setIndexedReadMethod0(readMethod);
|
|
}
|
|
|
|
private void setIndexedReadMethod0(Method readMethod) {
|
|
this.indexedReadMethodRef.set(readMethod);
|
|
if (readMethod == null) {
|
|
indexedReadMethodName = null;
|
|
return;
|
|
}
|
|
setClass0(readMethod.getDeclaringClass());
|
|
|
|
indexedReadMethodName = readMethod.getName();
|
|
setTransient(readMethod.getAnnotation(Transient.class));
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the method that should be used to write an indexed property value.
|
|
*
|
|
* @return The method that should be used to write an indexed
|
|
* property value.
|
|
* May return null if the property isn't indexed or is read-only.
|
|
*/
|
|
public synchronized Method getIndexedWriteMethod() {
|
|
Method indexedWriteMethod = this.indexedWriteMethodRef.get();
|
|
if (indexedWriteMethod == null) {
|
|
Class<?> cls = getClass0();
|
|
if (cls == null ||
|
|
(indexedWriteMethodName == null && !this.indexedWriteMethodRef.isSet())) {
|
|
// the Indexed writeMethod was explicitly set to null.
|
|
return null;
|
|
}
|
|
|
|
// We need the indexed type to ensure that we get the correct method.
|
|
// Cannot use the getIndexedPropertyType method since that could
|
|
// result in an infinite loop.
|
|
Class<?> type = getIndexedPropertyType0();
|
|
if (type == null) {
|
|
try {
|
|
type = findIndexedPropertyType(getIndexedReadMethod(), null);
|
|
setIndexedPropertyType(type);
|
|
} catch (IntrospectionException ex) {
|
|
// Set iprop type to be the classic type
|
|
Class<?> propType = getPropertyType();
|
|
if (propType.isArray()) {
|
|
type = propType.getComponentType();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (indexedWriteMethodName == null) {
|
|
indexedWriteMethodName = Introspector.SET_PREFIX + getBaseName();
|
|
}
|
|
|
|
Class<?>[] args = (type == null) ? null : new Class<?>[] { int.class, type };
|
|
indexedWriteMethod = Introspector.findMethod(cls, indexedWriteMethodName, 2, args);
|
|
if (indexedWriteMethod != null) {
|
|
if (!indexedWriteMethod.getReturnType().equals(void.class)) {
|
|
indexedWriteMethod = null;
|
|
}
|
|
}
|
|
setIndexedWriteMethod0(indexedWriteMethod);
|
|
}
|
|
return indexedWriteMethod;
|
|
}
|
|
|
|
/**
|
|
* Sets the method that should be used to write an indexed property value.
|
|
*
|
|
* @param writeMethod The new indexed write method.
|
|
* @throws IntrospectionException if an exception occurs during
|
|
* introspection.
|
|
*/
|
|
public synchronized void setIndexedWriteMethod(Method writeMethod)
|
|
throws IntrospectionException {
|
|
|
|
// If the indexed property type has not been set, then set it.
|
|
Class<?> type = findIndexedPropertyType(getIndexedReadMethod(),
|
|
writeMethod);
|
|
setIndexedPropertyType(type);
|
|
setIndexedWriteMethod0(writeMethod);
|
|
}
|
|
|
|
private void setIndexedWriteMethod0(Method writeMethod) {
|
|
this.indexedWriteMethodRef.set(writeMethod);
|
|
if (writeMethod == null) {
|
|
indexedWriteMethodName = null;
|
|
return;
|
|
}
|
|
setClass0(writeMethod.getDeclaringClass());
|
|
|
|
indexedWriteMethodName = writeMethod.getName();
|
|
setTransient(writeMethod.getAnnotation(Transient.class));
|
|
}
|
|
|
|
/**
|
|
* Returns the Java type info for the indexed property.
|
|
* Note that the {@code Class} object may describe
|
|
* primitive Java types such as {@code int}.
|
|
* This type is returned by the indexed read method
|
|
* or is used as the parameter type of the indexed write method.
|
|
*
|
|
* @return the {@code Class} object that represents the Java type info,
|
|
* or {@code null} if the type cannot be determined
|
|
*/
|
|
public synchronized Class<?> getIndexedPropertyType() {
|
|
Class<?> type = getIndexedPropertyType0();
|
|
if (type == null) {
|
|
try {
|
|
type = findIndexedPropertyType(getIndexedReadMethod(),
|
|
getIndexedWriteMethod());
|
|
setIndexedPropertyType(type);
|
|
} catch (IntrospectionException ex) {
|
|
// fall
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
// Private methods which set get/set the Reference objects
|
|
|
|
private void setIndexedPropertyType(Class<?> type) {
|
|
this.indexedPropertyTypeRef = getWeakReference(type);
|
|
}
|
|
|
|
private Class<?> getIndexedPropertyType0() {
|
|
return (this.indexedPropertyTypeRef != null)
|
|
? this.indexedPropertyTypeRef.get()
|
|
: null;
|
|
}
|
|
|
|
private Class<?> findIndexedPropertyType(Method indexedReadMethod,
|
|
Method indexedWriteMethod)
|
|
throws IntrospectionException {
|
|
Class<?> indexedPropertyType = null;
|
|
|
|
if (indexedReadMethod != null) {
|
|
Class params[] = getParameterTypes(getClass0(), indexedReadMethod);
|
|
if (params.length != 1) {
|
|
throw new IntrospectionException("bad indexed read method arg count");
|
|
}
|
|
if (params[0] != Integer.TYPE) {
|
|
throw new IntrospectionException("non int index to indexed read method");
|
|
}
|
|
indexedPropertyType = getReturnType(getClass0(), indexedReadMethod);
|
|
if (indexedPropertyType == Void.TYPE) {
|
|
throw new IntrospectionException("indexed read method returns void");
|
|
}
|
|
}
|
|
if (indexedWriteMethod != null) {
|
|
Class params[] = getParameterTypes(getClass0(), indexedWriteMethod);
|
|
if (params.length != 2) {
|
|
throw new IntrospectionException("bad indexed write method arg count");
|
|
}
|
|
if (params[0] != Integer.TYPE) {
|
|
throw new IntrospectionException("non int index to indexed write method");
|
|
}
|
|
if (indexedPropertyType == null || params[1].isAssignableFrom(indexedPropertyType)) {
|
|
indexedPropertyType = params[1];
|
|
} else if (!indexedPropertyType.isAssignableFrom(params[1])) {
|
|
throw new IntrospectionException(
|
|
"type mismatch between indexed read and indexed write methods: "
|
|
+ getName());
|
|
}
|
|
}
|
|
Class<?> propertyType = getPropertyType();
|
|
if (propertyType != null && (!propertyType.isArray() ||
|
|
propertyType.getComponentType() != indexedPropertyType)) {
|
|
throw new IntrospectionException("type mismatch between indexed and non-indexed methods: "
|
|
+ getName());
|
|
}
|
|
return indexedPropertyType;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
// Note: This would be identical to PropertyDescriptor but they don't
|
|
// share the same fields.
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
|
|
if (obj != null && obj instanceof IndexedPropertyDescriptor) {
|
|
IndexedPropertyDescriptor other = (IndexedPropertyDescriptor)obj;
|
|
Method otherIndexedReadMethod = other.getIndexedReadMethod();
|
|
Method otherIndexedWriteMethod = other.getIndexedWriteMethod();
|
|
|
|
if (!compareMethods(getIndexedReadMethod(), otherIndexedReadMethod)) {
|
|
return false;
|
|
}
|
|
|
|
if (!compareMethods(getIndexedWriteMethod(), otherIndexedWriteMethod)) {
|
|
return false;
|
|
}
|
|
|
|
if (getIndexedPropertyType() != other.getIndexedPropertyType()) {
|
|
return false;
|
|
}
|
|
return super.equals(obj);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
|
|
IndexedPropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
|
|
super(x,y);
|
|
Method tr = null;
|
|
Method tw = null;
|
|
|
|
if (x instanceof IndexedPropertyDescriptor) {
|
|
IndexedPropertyDescriptor ix = (IndexedPropertyDescriptor) x;
|
|
tr = ix.getIndexedReadMethod();
|
|
tw = ix.getIndexedWriteMethod();
|
|
}
|
|
if (y instanceof IndexedPropertyDescriptor) {
|
|
IndexedPropertyDescriptor iy = (IndexedPropertyDescriptor) y;
|
|
Method yr = iy.getIndexedReadMethod();
|
|
if (isAssignable(tr, yr)) {
|
|
tr = yr;
|
|
}
|
|
|
|
Method yw = iy.getIndexedWriteMethod();
|
|
if (isAssignable(tw, yw)) {
|
|
tw = yw;
|
|
}
|
|
}
|
|
|
|
try {
|
|
if(tr != null) {
|
|
setIndexedReadMethod(tr);
|
|
}
|
|
if(tw != null) {
|
|
setIndexedWriteMethod(tw);
|
|
}
|
|
} catch(IntrospectionException ex) {
|
|
// Should not happen
|
|
throw new AssertionError(ex);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Package-private dup constructor
|
|
* This must isolate the new object from any changes to the old object.
|
|
*/
|
|
IndexedPropertyDescriptor(IndexedPropertyDescriptor old) {
|
|
super(old);
|
|
this.indexedReadMethodRef.set(old.indexedReadMethodRef.get());
|
|
this.indexedWriteMethodRef.set(old.indexedWriteMethodRef.get());
|
|
indexedPropertyTypeRef = old.indexedPropertyTypeRef;
|
|
indexedWriteMethodName = old.indexedWriteMethodName;
|
|
indexedReadMethodName = old.indexedReadMethodName;
|
|
}
|
|
|
|
void updateGenericsFor(Class<?> type) {
|
|
super.updateGenericsFor(type);
|
|
try {
|
|
setIndexedPropertyType(findIndexedPropertyType(this.indexedReadMethodRef.get(), this.indexedWriteMethodRef.get()));
|
|
}
|
|
catch (IntrospectionException exception) {
|
|
setIndexedPropertyType(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 = super.hashCode();
|
|
|
|
result = 37 * result + ((indexedWriteMethodName == null) ? 0 :
|
|
indexedWriteMethodName.hashCode());
|
|
result = 37 * result + ((indexedReadMethodName == null) ? 0 :
|
|
indexedReadMethodName.hashCode());
|
|
result = 37 * result + ((getIndexedPropertyType() == null) ? 0 :
|
|
getIndexedPropertyType().hashCode());
|
|
|
|
return result;
|
|
}
|
|
|
|
void appendTo(StringBuilder sb) {
|
|
super.appendTo(sb);
|
|
appendTo(sb, "indexedPropertyType", this.indexedPropertyTypeRef);
|
|
appendTo(sb, "indexedReadMethod", this.indexedReadMethodRef.get());
|
|
appendTo(sb, "indexedWriteMethod", this.indexedWriteMethodRef.get());
|
|
}
|
|
}
|