View Javadoc

1   /*
2    * Copyright 2006-2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.batch.item.file.mapping;
18  
19  import java.beans.PropertyDescriptor;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  
24  import org.springframework.beans.BeanUtils;
25  import org.springframework.util.ObjectUtils;
26  import org.springframework.util.StringUtils;
27  
28  /**
29   * Helper class for calculating bean property matches, according to.
30   * Used by BeanWrapperImpl to suggest alternatives for an invalid property name.<br/>
31   * 
32   * Copied and slightly modified from Spring core,
33   *
34   * @author Alef Arendsen
35   * @author Arjen Poutsma
36   * @author Juergen Hoeller
37   * @author Dave Syer
38   * 
39   * @since 1.0
40   * @see #forProperty(String, Class)
41   */
42  final class PropertyMatches {
43  
44  	//---------------------------------------------------------------------
45  	// Static section
46  	//---------------------------------------------------------------------
47  
48  	/** Default maximum property distance: 2 */
49  	public static final int DEFAULT_MAX_DISTANCE = 2;
50  
51  
52  	/**
53  	 * Create PropertyMatches for the given bean property.
54  	 * @param propertyName the name of the property to find possible matches for
55  	 * @param beanClass the bean class to search for matches
56  	 */
57  	public static PropertyMatches forProperty(String propertyName, Class<?> beanClass) {
58  		return forProperty(propertyName, beanClass, DEFAULT_MAX_DISTANCE);
59  	}
60  
61  	/**
62  	 * Create PropertyMatches for the given bean property.
63  	 * @param propertyName the name of the property to find possible matches for
64  	 * @param beanClass the bean class to search for matches
65  	 * @param maxDistance the maximum property distance allowed for matches
66  	 */
67  	public static PropertyMatches forProperty(String propertyName, Class<?> beanClass, int maxDistance) {
68  		return new PropertyMatches(propertyName, beanClass, maxDistance);
69  	}
70  
71  
72  	//---------------------------------------------------------------------
73  	// Instance section
74  	//---------------------------------------------------------------------
75  
76  	private final String propertyName;
77  
78  	private String[] possibleMatches;
79  
80  
81  	/**
82  	 * Create a new PropertyMatches instance for the given property.
83  	 */
84  	private PropertyMatches(String propertyName, Class<?> beanClass, int maxDistance) {
85  		this.propertyName = propertyName;
86  		this.possibleMatches = calculateMatches(BeanUtils.getPropertyDescriptors(beanClass), maxDistance);
87  	}
88  
89  
90  	/**
91  	 * Return the calculated possible matches.
92  	 */
93  	public String[] getPossibleMatches() {
94  		return possibleMatches;
95  	}
96  
97  	/**
98  	 * Build an error message for the given invalid property name,
99  	 * indicating the possible property matches.
100 	 */
101 	public String buildErrorMessage() {
102 		StringBuffer buf = new StringBuffer();
103 		buf.append("Bean property '");
104 		buf.append(this.propertyName);
105 		buf.append("' is not writable or has an invalid setter method. ");
106 
107 		if (ObjectUtils.isEmpty(this.possibleMatches)) {
108 			buf.append("Does the parameter type of the setter match the return type of the getter?");
109 		}
110 		else {
111 			buf.append("Did you mean ");
112 			for (int i = 0; i < this.possibleMatches.length; i++) {
113 				buf.append('\'');
114 				buf.append(this.possibleMatches[i]);
115 				if (i < this.possibleMatches.length - 2) {
116 					buf.append("', ");
117 				}
118 				else if (i == this.possibleMatches.length - 2){
119 					buf.append("', or ");
120 				}
121 	 		}
122 			buf.append("'?");
123 		}
124 		return buf.toString();
125 	}
126 
127 
128 	/**
129 	 * Generate possible property alternatives for the given property and
130 	 * class. Internally uses the <code>getStringDistance</code> method, which
131 	 * in turn uses the Levenshtein algorithm to determine the distance between
132 	 * two Strings.
133 	 * @param propertyDescriptors the JavaBeans property descriptors to search
134 	 * @param maxDistance the maximum distance to accept
135 	 */
136 	private String[] calculateMatches(PropertyDescriptor[] propertyDescriptors, int maxDistance) {
137 		List<String> candidates = new ArrayList<String>();
138 		for (int i = 0; i < propertyDescriptors.length; i++) {
139 			if (propertyDescriptors[i].getWriteMethod() != null) {
140 				String possibleAlternative = propertyDescriptors[i].getName();
141 				int distance = calculateStringDistance(this.propertyName, possibleAlternative);
142 				if (distance <= maxDistance) {
143 					candidates.add(possibleAlternative);
144 				}
145 			}
146 		}
147 		Collections.sort(candidates);
148 		return StringUtils.toStringArray(candidates);
149 	}
150 
151 	/**
152 	 * Calculate the distance between the given two Strings
153 	 * according to the Levenshtein algorithm.
154 	 * @param s1 the first String
155 	 * @param s2 the second String
156 	 * @return the distance value
157 	 */
158 	private int calculateStringDistance(String s1, String s2) {
159 		if (s1.length() == 0) {
160 			return s2.length();
161 		}
162 		if (s2.length() == 0) {
163 			return s1.length();
164 		}
165 		int d[][] = new int[s1.length() + 1][s2.length() + 1];
166 
167 		for (int i = 0; i <= s1.length(); i++) {
168 			d[i][0] = i;
169 		}
170 		for (int j = 0; j <= s2.length(); j++) {
171 			d[0][j] = j;
172 		}
173 
174 		for (int i = 1; i <= s1.length(); i++) {
175 			char s_i = s1.charAt(i - 1);
176 			for (int j = 1; j <= s2.length(); j++) {
177 				int cost;
178 				char t_j = s2.charAt(j - 1);
179 				if (Character.toLowerCase(s_i) == Character.toLowerCase(t_j)) {
180 					cost = 0;
181 				} else {
182 					cost = 1;
183 				}
184 				d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1),
185 						d[i - 1][j - 1] + cost);
186 			}
187 		}
188 
189 		return d[s1.length()][s2.length()];
190 	}
191 }