001: /*
002: * Copyright 2002-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.beans;
018:
019: import java.beans.PropertyDescriptor;
020: import java.util.ArrayList;
021: import java.util.Collections;
022: import java.util.List;
023:
024: import org.springframework.util.ObjectUtils;
025: import org.springframework.util.StringUtils;
026:
027: /**
028: * Helper class for calculating bean property matches, according to.
029: * Used by BeanWrapperImpl to suggest alternatives for an invalid property name.
030: *
031: * @author Alef Arendsen
032: * @author Arjen Poutsma
033: * @author Juergen Hoeller
034: * @since 2.0
035: * @see #forProperty(String, Class)
036: */
037: final class PropertyMatches {
038:
039: //---------------------------------------------------------------------
040: // Static section
041: //---------------------------------------------------------------------
042:
043: /** Default maximum property distance: 2 */
044: public static final int DEFAULT_MAX_DISTANCE = 2;
045:
046: /**
047: * Create PropertyMatches for the given bean property.
048: * @param propertyName the name of the property to find possible matches for
049: * @param beanClass the bean class to search for matches
050: */
051: public static PropertyMatches forProperty(String propertyName,
052: Class beanClass) {
053: return forProperty(propertyName, beanClass,
054: DEFAULT_MAX_DISTANCE);
055: }
056:
057: /**
058: * Create PropertyMatches for the given bean property.
059: * @param propertyName the name of the property to find possible matches for
060: * @param beanClass the bean class to search for matches
061: * @param maxDistance the maximum property distance allowed for matches
062: */
063: public static PropertyMatches forProperty(String propertyName,
064: Class beanClass, int maxDistance) {
065: return new PropertyMatches(propertyName, beanClass, maxDistance);
066: }
067:
068: //---------------------------------------------------------------------
069: // Instance section
070: //---------------------------------------------------------------------
071:
072: private final String propertyName;
073:
074: private String[] possibleMatches;
075:
076: /**
077: * Create a new PropertyMatches instance for the given property.
078: */
079: private PropertyMatches(String propertyName, Class beanClass,
080: int maxDistance) {
081: this .propertyName = propertyName;
082: this .possibleMatches = calculateMatches(BeanUtils
083: .getPropertyDescriptors(beanClass), maxDistance);
084: }
085:
086: /**
087: * Return the calculated possible matches.
088: */
089: public String[] getPossibleMatches() {
090: return possibleMatches;
091: }
092:
093: /**
094: * Build an error message for the given invalid property name,
095: * indicating the possible property matches.
096: */
097: public String buildErrorMessage() {
098: StringBuffer buf = new StringBuffer();
099: buf.append("Bean property '");
100: buf.append(this .propertyName);
101: buf
102: .append("' is not writable or has an invalid setter method. ");
103:
104: if (ObjectUtils.isEmpty(this .possibleMatches)) {
105: buf
106: .append("Does the parameter type of the setter match the return type of the getter?");
107: } else {
108: buf.append("Did you mean ");
109: for (int i = 0; i < this .possibleMatches.length; i++) {
110: buf.append('\'');
111: buf.append(this .possibleMatches[i]);
112: if (i < this .possibleMatches.length - 2) {
113: buf.append("', ");
114: } else if (i == this .possibleMatches.length - 2) {
115: buf.append("', or ");
116: }
117: }
118: buf.append("'?");
119: }
120: return buf.toString();
121: }
122:
123: /**
124: * Generate possible property alternatives for the given property and
125: * class. Internally uses the <code>getStringDistance</code> method, which
126: * in turn uses the Levenshtein algorithm to determine the distance between
127: * two Strings.
128: * @param propertyDescriptors the JavaBeans property descriptors to search
129: * @param maxDistance the maximum distance to accept
130: */
131: private String[] calculateMatches(
132: PropertyDescriptor[] propertyDescriptors, int maxDistance) {
133: List candidates = new ArrayList();
134: for (int i = 0; i < propertyDescriptors.length; i++) {
135: if (propertyDescriptors[i].getWriteMethod() != null) {
136: String possibleAlternative = propertyDescriptors[i]
137: .getName();
138: if (calculateStringDistance(this .propertyName,
139: possibleAlternative) <= maxDistance) {
140: candidates.add(possibleAlternative);
141: }
142: }
143: }
144: Collections.sort(candidates);
145: return StringUtils.toStringArray(candidates);
146: }
147:
148: /**
149: * Calculate the distance between the given two Strings
150: * according to the Levenshtein algorithm.
151: * @param s1 the first String
152: * @param s2 the second String
153: * @return the distance value
154: */
155: private int calculateStringDistance(String s1, String s2) {
156: if (s1.length() == 0) {
157: return s2.length();
158: }
159: if (s2.length() == 0) {
160: return s1.length();
161: }
162: int d[][] = new int[s1.length() + 1][s2.length() + 1];
163:
164: for (int i = 0; i <= s1.length(); i++) {
165: d[i][0] = i;
166: }
167: for (int j = 0; j <= s2.length(); j++) {
168: d[0][j] = j;
169: }
170:
171: for (int i = 1; i <= s1.length(); i++) {
172: char s_i = s1.charAt(i - 1);
173: for (int j = 1; j <= s2.length(); j++) {
174: int cost;
175: char t_j = s2.charAt(j - 1);
176: if (s_i == t_j) {
177: cost = 0;
178: } else {
179: cost = 1;
180: }
181: d[i][j] = Math.min(Math.min(d[i - 1][j] + 1,
182: d[i][j - 1] + 1), d[i - 1][j - 1] + cost);
183: }
184: }
185:
186: return d[s1.length()][s2.length()];
187: }
188:
189: }
|