1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
30
31
32
33
34
35
36
37
38
39
40
41
42 final class PropertyMatches {
43
44
45
46
47
48
49 public static final int DEFAULT_MAX_DISTANCE = 2;
50
51
52
53
54
55
56
57 public static PropertyMatches forProperty(String propertyName, Class<?> beanClass) {
58 return forProperty(propertyName, beanClass, DEFAULT_MAX_DISTANCE);
59 }
60
61
62
63
64
65
66
67 public static PropertyMatches forProperty(String propertyName, Class<?> beanClass, int maxDistance) {
68 return new PropertyMatches(propertyName, beanClass, maxDistance);
69 }
70
71
72
73
74
75
76 private final String propertyName;
77
78 private String[] possibleMatches;
79
80
81
82
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
92
93 public String[] getPossibleMatches() {
94 return possibleMatches;
95 }
96
97
98
99
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
130
131
132
133
134
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
153
154
155
156
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 }