Skip to content

Commit f75be73

Browse files
committed
Ambiguous setters/getters should throw exception only when they are invoked
Lower impact fix for #1201 , Unlike #1335 , this version throws exception only when the ambiguous setter/getter is actually invoked.
1 parent 1c0cad1 commit f75be73

File tree

3 files changed

+162
-45
lines changed

3 files changed

+162
-45
lines changed

src/main/java/org/apache/ibatis/reflection/Reflector.java

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.lang.reflect.ParameterizedType;
2525
import java.lang.reflect.ReflectPermission;
2626
import java.lang.reflect.Type;
27+
import java.text.MessageFormat;
2728
import java.util.ArrayList;
2829
import java.util.Arrays;
2930
import java.util.Collection;
@@ -33,6 +34,7 @@
3334
import java.util.Map;
3435
import java.util.Map.Entry;
3536

37+
import org.apache.ibatis.reflection.invoker.AmbiguousMethodInvoker;
3638
import org.apache.ibatis.reflection.invoker.GetFieldInvoker;
3739
import org.apache.ibatis.reflection.invoker.Invoker;
3840
import org.apache.ibatis.reflection.invoker.MethodInvoker;
@@ -92,6 +94,7 @@ private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters
9294
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
9395
Method winner = null;
9496
String propName = entry.getKey();
97+
boolean isAmbiguous = false;
9598
for (Method candidate : entry.getValue()) {
9699
if (winner == null) {
97100
winner = candidate;
@@ -101,10 +104,8 @@ private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters
101104
Class<?> candidateType = candidate.getReturnType();
102105
if (candidateType.equals(winnerType)) {
103106
if (!boolean.class.equals(candidateType)) {
104-
throw new ReflectionException(
105-
"Illegal overloaded getter method with ambiguous type for property "
106-
+ propName + " in class " + winner.getDeclaringClass()
107-
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
107+
isAmbiguous = true;
108+
break;
108109
} else if (candidate.getName().startsWith("is")) {
109110
winner = candidate;
110111
}
@@ -113,22 +114,25 @@ private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters
113114
} else if (winnerType.isAssignableFrom(candidateType)) {
114115
winner = candidate;
115116
} else {
116-
throw new ReflectionException(
117-
"Illegal overloaded getter method with ambiguous type for property "
118-
+ propName + " in class " + winner.getDeclaringClass()
119-
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
117+
isAmbiguous = true;
118+
break;
120119
}
121120
}
122-
addGetMethod(propName, winner);
121+
if (winner != null) {
122+
addGetMethod(propName, winner, isAmbiguous);
123+
}
123124
}
124125
}
125126

126-
private void addGetMethod(String name, Method method) {
127-
if (isValidPropertyName(name)) {
128-
getMethods.put(name, new MethodInvoker(method));
129-
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
130-
getTypes.put(name, typeToClass(returnType));
131-
}
127+
private void addGetMethod(String name, Method method, boolean isAmbiguous) {
128+
MethodInvoker invoker = isAmbiguous
129+
? new AmbiguousMethodInvoker(method, MessageFormat.format(
130+
"Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
131+
name, method.getDeclaringClass().getName()))
132+
: new MethodInvoker(method);
133+
getMethods.put(name, invoker);
134+
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
135+
getTypes.put(name, typeToClass(returnType));
132136
}
133137

134138
private void addSetMethods(Class<?> clazz) {
@@ -140,35 +144,26 @@ private void addSetMethods(Class<?> clazz) {
140144
}
141145

142146
private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
143-
List<Method> list = conflictingMethods.computeIfAbsent(name, k -> new ArrayList<>());
144-
list.add(method);
147+
if (isValidPropertyName(name)) {
148+
List<Method> list = conflictingMethods.computeIfAbsent(name, k -> new ArrayList<>());
149+
list.add(method);
150+
}
145151
}
146152

147153
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
148154
for (String propName : conflictingSetters.keySet()) {
149155
List<Method> setters = conflictingSetters.get(propName);
150156
Class<?> getterType = getTypes.get(propName);
151157
Method match = null;
152-
ReflectionException exception = null;
153158
for (Method setter : setters) {
154159
if (setter.getParameterTypes()[0].equals(getterType)) {
155160
// should be the best match
156161
match = setter;
157162
break;
158163
}
159-
if (exception == null) {
160-
try {
161-
match = pickBetterSetter(match, setter, propName);
162-
} catch (ReflectionException e) {
163-
// there could still be the 'best match'
164-
match = null;
165-
exception = e;
166-
}
167-
}
164+
match = pickBetterSetter(match, setter, propName);
168165
}
169-
if (match == null) {
170-
throw exception;
171-
} else {
166+
if (match != null) {
172167
addSetMethod(propName, match);
173168
}
174169
}
@@ -185,17 +180,21 @@ private Method pickBetterSetter(Method setter1, Method setter2, String property)
185180
} else if (paramType2.isAssignableFrom(paramType1)) {
186181
return setter1;
187182
}
188-
throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
189-
+ setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
190-
+ paramType2.getName() + "'.");
183+
MethodInvoker invoker = new AmbiguousMethodInvoker(setter1,
184+
MessageFormat.format(
185+
"Ambiguous setters defined for property ''{0}'' in class ''{1}'' with types ''{2}'' and ''{3}''.",
186+
property, setter2.getDeclaringClass().getName(), paramType1.getName(), paramType2.getName()));
187+
setMethods.put(property, invoker);
188+
Type[] paramTypes = TypeParameterResolver.resolveParamTypes(setter1, type);
189+
setTypes.put(property, typeToClass(paramTypes[0]));
190+
return null;
191191
}
192192

193193
private void addSetMethod(String name, Method method) {
194-
if (isValidPropertyName(name)) {
195-
setMethods.put(name, new MethodInvoker(method));
196-
Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
197-
setTypes.put(name, typeToClass(paramTypes[0]));
198-
}
194+
MethodInvoker invoker = new MethodInvoker(method);
195+
setMethods.put(name, invoker);
196+
Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
197+
setTypes.put(name, typeToClass(paramTypes[0]));
199198
}
200199

201200
private Class<?> typeToClass(Type src) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright 2009-2019 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.apache.ibatis.reflection.invoker;
18+
19+
import java.lang.reflect.InvocationTargetException;
20+
import java.lang.reflect.Method;
21+
22+
import org.apache.ibatis.reflection.ReflectionException;
23+
24+
public class AmbiguousMethodInvoker extends MethodInvoker {
25+
private final String exceptionMessage;
26+
27+
public AmbiguousMethodInvoker(Method method, String exceptionMessage) {
28+
super(method);
29+
this.exceptionMessage = exceptionMessage;
30+
}
31+
32+
@Override
33+
public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
34+
throw new ReflectionException(exceptionMessage);
35+
}
36+
}

src/test/java/org/apache/ibatis/reflection/ReflectorTest.java

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818
import static org.junit.jupiter.api.Assertions.*;
1919

2020
import java.io.Serializable;
21+
import java.util.Arrays;
2122
import java.util.List;
2223

24+
import org.apache.ibatis.reflection.invoker.Invoker;
2325
import org.junit.jupiter.api.Assertions;
2426
import org.junit.jupiter.api.Test;
27+
2528
import static com.googlecode.catchexception.apis.BDDCatchException.*;
2629
import static org.assertj.core.api.BDDAssertions.then;
2730

@@ -189,22 +192,101 @@ interface BeanInterface<T> {
189192
}
190193

191194
@Test
192-
void shouldSettersWithUnrelatedArgTypesThrowException() {
195+
void shouldSettersWithUnrelatedArgTypesThrowException() throws Exception {
193196
@SuppressWarnings("unused")
194197
class BeanClass {
195-
public void setTheProp(String arg) {}
196-
public void setTheProp(Integer arg) {}
198+
public void setProp1(String arg) {}
199+
public void setProp2(String arg) {}
200+
public void setProp2(Integer arg) {}
197201
}
198-
199202
ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
200-
when(reflectorFactory).findForClass(BeanClass.class);
203+
Reflector reflector = reflectorFactory.findForClass(BeanClass.class);
204+
205+
List<String> setableProps = Arrays.asList(reflector.getSetablePropertyNames());
206+
assertTrue(setableProps.contains("prop1"));
207+
assertTrue(setableProps.contains("prop2"));
208+
assertEquals("prop1", reflector.findPropertyName("PROP1"));
209+
assertEquals("prop2", reflector.findPropertyName("PROP2"));
210+
211+
assertEquals(String.class, reflector.getSetterType("prop1"));
212+
assertNotNull(reflector.getSetInvoker("prop1"));
213+
214+
Class<?> paramType = reflector.getSetterType("prop2");
215+
assertTrue(String.class.equals(paramType) || Integer.class.equals(paramType));
216+
217+
Invoker ambiguousInvoker = reflector.getSetInvoker("prop2");
218+
Object[] param = String.class.equals(paramType)? new String[]{"x"} : new Integer[]{1};
219+
when(ambiguousInvoker).invoke(new BeanClass(), param);
201220
then(caughtException()).isInstanceOf(ReflectionException.class)
202-
.hasMessageContaining("theProp")
203-
.hasMessageContaining("BeanClass")
221+
.hasMessageContaining("Ambiguous setters defined for property 'prop2' in class '" + BeanClass.class.getName() + "' with types")
204222
.hasMessageContaining("java.lang.String")
205223
.hasMessageContaining("java.lang.Integer");
206224
}
207225

226+
@Test
227+
public void shouldTwoGettersForNonBooleanPropertyThrowException() throws Exception {
228+
@SuppressWarnings("unused")
229+
class BeanClass {
230+
public Integer getProp1() {return 1;}
231+
public int getProp2() {return 0;}
232+
public int isProp2() {return 0;}
233+
}
234+
ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
235+
Reflector reflector = reflectorFactory.findForClass(BeanClass.class);
236+
237+
List<String> getableProps = Arrays.asList(reflector.getGetablePropertyNames());
238+
assertTrue(getableProps.contains("prop1"));
239+
assertTrue(getableProps.contains("prop2"));
240+
assertEquals("prop1", reflector.findPropertyName("PROP1"));
241+
assertEquals("prop2", reflector.findPropertyName("PROP2"));
242+
243+
assertEquals(Integer.class, reflector.getGetterType("prop1"));
244+
Invoker getInvoker = reflector.getGetInvoker("prop1");
245+
assertEquals(Integer.valueOf(1), getInvoker.invoke(new BeanClass(), null));
246+
247+
Class<?> paramType = reflector.getGetterType("prop2");
248+
assertEquals(int.class, paramType);
249+
250+
Invoker ambiguousInvoker = reflector.getGetInvoker("prop2");
251+
when(ambiguousInvoker).invoke(new BeanClass(), new Integer[] {1});
252+
then(caughtException()).isInstanceOf(ReflectionException.class)
253+
.hasMessageContaining("Illegal overloaded getter method with ambiguous type for property 'prop2' in class '"
254+
+ BeanClass.class.getName()
255+
+ "'. This breaks the JavaBeans specification and can cause unpredictable results.");
256+
}
257+
258+
@Test
259+
public void shouldTwoGettersWithDifferentTypesThrowException() throws Exception {
260+
@SuppressWarnings("unused")
261+
class BeanClass {
262+
public Integer getProp1() {return 1;}
263+
public Integer getProp2() {return 1;}
264+
public boolean isProp2() {return false;}
265+
}
266+
ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
267+
Reflector reflector = reflectorFactory.findForClass(BeanClass.class);
268+
269+
List<String> getableProps = Arrays.asList(reflector.getGetablePropertyNames());
270+
assertTrue(getableProps.contains("prop1"));
271+
assertTrue(getableProps.contains("prop2"));
272+
assertEquals("prop1", reflector.findPropertyName("PROP1"));
273+
assertEquals("prop2", reflector.findPropertyName("PROP2"));
274+
275+
assertEquals(Integer.class, reflector.getGetterType("prop1"));
276+
Invoker getInvoker = reflector.getGetInvoker("prop1");
277+
assertEquals(Integer.valueOf(1), getInvoker.invoke(new BeanClass(), null));
278+
279+
Class<?> returnType = reflector.getGetterType("prop2");
280+
assertTrue(Integer.class.equals(returnType) || boolean.class.equals(returnType));
281+
282+
Invoker ambiguousInvoker = reflector.getGetInvoker("prop2");
283+
when(ambiguousInvoker).invoke(new BeanClass(), null);
284+
then(caughtException()).isInstanceOf(ReflectionException.class)
285+
.hasMessageContaining("Illegal overloaded getter method with ambiguous type for property 'prop2' in class '"
286+
+ BeanClass.class.getName()
287+
+ "'. This breaks the JavaBeans specification and can cause unpredictable results.");
288+
}
289+
208290
@Test
209291
void shouldAllowTwoBooleanGetters() throws Exception {
210292
@SuppressWarnings("unused")

0 commit comments

Comments
 (0)