ExtendedBeanInfo.java 15.1 KB
Newer Older
C
Chris Beams 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * Copyright 2002-2011 the original author or authors.
 *
 * 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.springframework.beans;

19 20
import static java.lang.String.format;

C
Chris Beams 已提交
21 22 23 24 25 26 27 28 29 30 31 32 33 34
import java.awt.Image;
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.EventSetDescriptor;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;

35 36 37
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
C
Chris Beams 已提交
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
 * Decorates a standard {@link BeanInfo} object (likely created created by
 * {@link Introspector#getBeanInfo(Class)}) by including non-void returning setter
 * methods in the collection of {@link #getPropertyDescriptors() property descriptors}.
 * Both regular and
 * <a href="http://download.oracle.com/javase/tutorial/javabeans/properties/indexed.html">
 * indexed properties</a> are fully supported.
 *
 * <p>The wrapped {@code BeanInfo} object is not modified in any way.
 *
 * @author Chris Beams
 * @since 3.1
 * @see CachedIntrospectionResults
 */
55
class ExtendedBeanInfo implements BeanInfo {
56 57 58

	private final Log logger = LogFactory.getLog(getClass());

C
Chris Beams 已提交
59
	private final BeanInfo delegate;
60

C
Chris Beams 已提交
61 62 63
	private final SortedSet<PropertyDescriptor> propertyDescriptors =
		new TreeSet<PropertyDescriptor>(new PropertyDescriptorComparator());

64

C
Chris Beams 已提交
65 66 67 68
	/**
	 * Wrap the given delegate {@link BeanInfo} instance and find any non-void returning
	 * setter methods, creating and adding a {@link PropertyDescriptor} for each.
	 *
69
	 * <p>Note that the wrapped {@code BeanInfo} is modified by this process.
C
Chris Beams 已提交
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
	 *
	 * @see #getPropertyDescriptors()
	 * @throws IntrospectionException if any problems occur creating and adding new {@code PropertyDescriptors}
	 */
	public ExtendedBeanInfo(BeanInfo delegate) throws IntrospectionException {
		this.delegate = delegate;

		// PropertyDescriptor instances from the delegate object are never added directly, but always
		// copied to the local collection of #propertyDescriptors and returned by calls to
		// #getPropertyDescriptors(). this algorithm iterates through all methods (method descriptors)
		// in the wrapped BeanInfo object, copying any existing PropertyDescriptor or creating a new
		// one for any non-standard setter methods found.

		ALL_METHODS:
		for (MethodDescriptor md : delegate.getMethodDescriptors()) {
			Method method = md.getMethod();

			// bypass non-getter java.lang.Class methods for efficiency
			if (ReflectionUtils.isObjectMethod(method) && !method.getName().startsWith("get")) {
				continue ALL_METHODS;
			}

			// is the method a NON-INDEXED setter? ignore return type in order to capture non-void signatures
			if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
				String propertyName = propertyNameFor(method);
95 96 97
				if(propertyName.length() == 0) {
					continue ALL_METHODS;
				}
C
Chris Beams 已提交
98 99 100 101 102 103 104
				for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) {
					Method readMethod = pd.getReadMethod();
					Method writeMethod = pd.getWriteMethod();
					// has the setter already been found by the wrapped BeanInfo?
					if (writeMethod != null
							&& writeMethod.getName().equals(method.getName())) {
						// yes -> copy it, including corresponding getter method (if any -- may be null)
105
						this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod);
C
Chris Beams 已提交
106 107 108 109 110 111
						continue ALL_METHODS;
					}
					// has a getter corresponding to this setter already been found by the wrapped BeanInfo?
					if (readMethod != null
							&& readMethod.getName().equals(getterMethodNameFor(propertyName))
							&& readMethod.getReturnType().equals(method.getParameterTypes()[0])) {
112
						this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, method);
C
Chris Beams 已提交
113 114 115 116 117
						continue ALL_METHODS;
					}
				}
				// the setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor for it
				// no corresponding getter was detected, so the 'read method' parameter is null.
118
				this.addOrUpdatePropertyDescriptor(null, propertyName, null, method);
C
Chris Beams 已提交
119 120 121 122 123 124
				continue ALL_METHODS;
			}

			// is the method an INDEXED setter? ignore return type in order to capture non-void signatures
			if (method.getName().startsWith("set") && method.getParameterTypes().length == 2 && method.getParameterTypes()[0].equals(int.class)) {
				String propertyName = propertyNameFor(method);
125 126 127
				if(propertyName.length() == 0) {
					continue ALL_METHODS;
				}
C
Chris Beams 已提交
128 129 130 131 132 133 134 135 136 137 138 139 140 141
				DELEGATE_PD:
				for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) {
					if (!(pd instanceof IndexedPropertyDescriptor)) {
						continue DELEGATE_PD;
					}
					IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
					Method readMethod = ipd.getReadMethod();
					Method writeMethod = ipd.getWriteMethod();
					Method indexedReadMethod = ipd.getIndexedReadMethod();
					Method indexedWriteMethod = ipd.getIndexedWriteMethod();
					// has the setter already been found by the wrapped BeanInfo?
					if (indexedWriteMethod != null
							&& indexedWriteMethod.getName().equals(method.getName())) {
						// yes -> copy it, including corresponding getter method (if any -- may be null)
142
						this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod);
C
Chris Beams 已提交
143 144 145 146 147 148
						continue ALL_METHODS;
					}
					// has a getter corresponding to this setter already been found by the wrapped BeanInfo?
					if (indexedReadMethod != null
							&& indexedReadMethod.getName().equals(getterMethodNameFor(propertyName))
							&& indexedReadMethod.getReturnType().equals(method.getParameterTypes()[1])) {
149
						this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod, indexedReadMethod, method);
C
Chris Beams 已提交
150 151 152 153 154
						continue ALL_METHODS;
					}
				}
				// the INDEXED setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor
				// for it. no corresponding INDEXED getter was detected, so the 'indexed read method' parameter is null.
155
				this.addOrUpdatePropertyDescriptor(null, propertyName, null, null, null, method);
C
Chris Beams 已提交
156 157 158 159 160 161 162 163 164 165 166
				continue ALL_METHODS;
			}

			// the method is not a setter, but is it a getter?
			for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) {
				// have we already copied this read method to a property descriptor locally?
				for (PropertyDescriptor existingPD : this.propertyDescriptors) {
					if (method.equals(pd.getReadMethod())
							&& existingPD.getName().equals(pd.getName())) {
						if (existingPD.getReadMethod() == null) {
							// no -> add it now
167
							this.addOrUpdatePropertyDescriptor(pd, pd.getName(), method, pd.getWriteMethod());
C
Chris Beams 已提交
168 169 170 171 172 173 174 175 176
						}
						// yes -> do not add a duplicate
						continue ALL_METHODS;
					}
				}
				if (method == pd.getReadMethod()
						|| (pd instanceof IndexedPropertyDescriptor && method == ((IndexedPropertyDescriptor) pd).getIndexedReadMethod())) {
					// yes -> copy it, including corresponding setter method (if any -- may be null)
					if (pd instanceof IndexedPropertyDescriptor) {
177
						this.addOrUpdatePropertyDescriptor(pd, pd.getName(), pd.getReadMethod(), pd.getWriteMethod(), ((IndexedPropertyDescriptor)pd).getIndexedReadMethod(), ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod());
C
Chris Beams 已提交
178
					} else {
179
						this.addOrUpdatePropertyDescriptor(pd, pd.getName(), pd.getReadMethod(), pd.getWriteMethod());
C
Chris Beams 已提交
180 181 182 183 184 185 186
					}
					continue ALL_METHODS;
				}
			}
		}
	}

187 188
	private void addOrUpdatePropertyDescriptor(PropertyDescriptor pd, String propertyName, Method readMethod, Method writeMethod) throws IntrospectionException {
		addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, writeMethod, null, null);
C
Chris Beams 已提交
189 190
	}

191 192 193
	private void addOrUpdatePropertyDescriptor(PropertyDescriptor pd, String propertyName, Method readMethod, Method writeMethod, Method indexedReadMethod, Method indexedWriteMethod) throws IntrospectionException {
		Assert.notNull(propertyName, "propertyName may not be null");
		propertyName = pd == null ? propertyName : pd.getName();
C
Chris Beams 已提交
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
		for (PropertyDescriptor existingPD : this.propertyDescriptors) {
			if (existingPD.getName().equals(propertyName)) {
				// is there already a descriptor that captures this read method or its corresponding write method?
				if (existingPD.getReadMethod() != null) {
					if (readMethod != null && existingPD.getReadMethod().getReturnType() != readMethod.getReturnType()
							|| writeMethod != null && existingPD.getReadMethod().getReturnType() != writeMethod.getParameterTypes()[0]) {
						// no -> add a new descriptor for it below
						break;
					}
				}
				// update the existing descriptor's read method
				if (readMethod != null) {
					try {
						existingPD.setReadMethod(readMethod);
					} catch (IntrospectionException ex) {
						// there is a conflicting setter method present -> null it out and try again
						existingPD.setWriteMethod(null);
						existingPD.setReadMethod(readMethod);
					}
				}

				// is there already a descriptor that captures this write method or its corresponding read method?
				if (existingPD.getWriteMethod() != null) {
					if (readMethod != null && existingPD.getWriteMethod().getParameterTypes()[0] != readMethod.getReturnType()
							|| writeMethod != null && existingPD.getWriteMethod().getParameterTypes()[0] != writeMethod.getParameterTypes()[0]) {
						// no -> add a new descriptor for it below
						break;
					}
				}
				// update the existing descriptor's write method
				if (writeMethod != null) {
					existingPD.setWriteMethod(writeMethod);
				}

				// is this descriptor indexed?
				if (existingPD instanceof IndexedPropertyDescriptor) {
					IndexedPropertyDescriptor existingIPD = (IndexedPropertyDescriptor) existingPD;

					// is there already a descriptor that captures this indexed read method or its corresponding indexed write method?
					if (existingIPD.getIndexedReadMethod() != null) {
						if (indexedReadMethod != null && existingIPD.getIndexedReadMethod().getReturnType() != indexedReadMethod.getReturnType()
								|| indexedWriteMethod != null && existingIPD.getIndexedReadMethod().getReturnType() != indexedWriteMethod.getParameterTypes()[1]) {
							// no -> add a new descriptor for it below
							break;
						}
					}
					// update the existing descriptor's indexed read method
					try {
242 243 244
						if (indexedReadMethod != null) {
							existingIPD.setIndexedReadMethod(indexedReadMethod);
						}
C
Chris Beams 已提交
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
					} catch (IntrospectionException ex) {
						// there is a conflicting indexed setter method present -> null it out and try again
						existingIPD.setIndexedWriteMethod(null);
						existingIPD.setIndexedReadMethod(indexedReadMethod);
					}

					// is there already a descriptor that captures this indexed write method or its corresponding indexed read method?
					if (existingIPD.getIndexedWriteMethod() != null) {
						if (indexedReadMethod != null && existingIPD.getIndexedWriteMethod().getParameterTypes()[1] != indexedReadMethod.getReturnType()
								|| indexedWriteMethod != null && existingIPD.getIndexedWriteMethod().getParameterTypes()[1] != indexedWriteMethod.getParameterTypes()[1]) {
							// no -> add a new descriptor for it below
							break;
						}
					}
					// update the existing descriptor's indexed write method
					if (indexedWriteMethod != null) {
						existingIPD.setIndexedWriteMethod(indexedWriteMethod);
					}
				}

				// the descriptor has been updated -> return immediately
				return;
			}
		}

		// we haven't yet seen read or write methods for this property -> add a new descriptor
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
		if (pd == null) {
			try {
				if (indexedReadMethod == null && indexedWriteMethod == null) {
					pd = new PropertyDescriptor(propertyName, readMethod, writeMethod);
				}
				else {
					pd = new IndexedPropertyDescriptor(propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod);
				}
				this.propertyDescriptors.add(pd);
			} catch (IntrospectionException ex) {
				logger.warn(format("Could not create new PropertyDescriptor for readMethod [%s] writeMethod [%s] " +
						"indexedReadMethod [%s] indexedWriteMethod [%s] for property [%s]. Reason: %s",
						readMethod, writeMethod, indexedReadMethod, indexedWriteMethod, propertyName, ex.getMessage()));
				// suppress exception and attempt to continue
			}
		}
		else {
			pd.setReadMethod(readMethod);
			try {
				pd.setWriteMethod(writeMethod);
			} catch (IntrospectionException ex) {
				logger.warn(format("Could not add write method [%s] for property [%s]. Reason: %s",
						writeMethod, propertyName, ex.getMessage()));
				// fall through -> add property descriptor as best we can
			}
			this.propertyDescriptors.add(pd);
C
Chris Beams 已提交
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
		}
	}

	private String propertyNameFor(Method method) {
		return Introspector.decapitalize(method.getName().substring(3,method.getName().length()));
	}

	private Object getterMethodNameFor(String name) {
		return "get" + StringUtils.capitalize(name);
	}

	public BeanInfo[] getAdditionalBeanInfo() {
		return delegate.getAdditionalBeanInfo();
	}

	public BeanDescriptor getBeanDescriptor() {
		return delegate.getBeanDescriptor();
	}

	public int getDefaultEventIndex() {
		return delegate.getDefaultEventIndex();
	}

	public int getDefaultPropertyIndex() {
		return delegate.getDefaultPropertyIndex();
	}

	public EventSetDescriptor[] getEventSetDescriptors() {
		return delegate.getEventSetDescriptors();
	}

	public Image getIcon(int arg0) {
		return delegate.getIcon(arg0);
	}

	public MethodDescriptor[] getMethodDescriptors() {
		return delegate.getMethodDescriptors();
	}

	/**
	 * Return the set of {@link PropertyDescriptor}s from the wrapped {@link BeanInfo}
	 * object as well as {@code PropertyDescriptor}s for each non-void returning setter
	 * method found during construction.
	 * @see #ExtendedBeanInfo(BeanInfo)
	 */
	public PropertyDescriptor[] getPropertyDescriptors() {
		return this.propertyDescriptors.toArray(new PropertyDescriptor[this.propertyDescriptors.size()]);
	}


	/**
	 * Sorts PropertyDescriptor instances alphanumerically to emulate the behavior of {@link java.beans.BeanInfo#getPropertyDescriptors()}.
	 *
	 * @see ExtendedBeanInfo#propertyDescriptors
	 */
	static class PropertyDescriptorComparator implements Comparator<PropertyDescriptor> {
		public int compare(PropertyDescriptor desc1, PropertyDescriptor desc2) {
			String left = desc1.getName();
			String right = desc2.getName();
			for (int i = 0; i < left.length(); i++) {
				if (right.length() == i) {
					return 1;
				}
				int result = left.getBytes()[i] - right.getBytes()[i];
				if (result != 0) {
					return result;
				}
			}
			return left.length() - right.length();
		}
	}
}