View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau;
18  
19  import static org.junit.jupiter.api.Assertions.*;
20  
21  import java.lang.reflect.*;
22  
23  import org.junit.jupiter.api.*;
24  
25  class BeanProxyInvocationHandler_Test extends TestBase {
26  
27  	public interface TestInterface {
28  		String getName();
29  		void setName(String name);
30  		int getAge();
31  		void setAge(int age);
32  		boolean isActive();
33  		void setActive(boolean active);
34  	}
35  
36  	// Helper class with methods that have same names as Object methods but wrong signatures
37  	// We can't put these in an interface because they conflict with Object methods
38  	public static class HelperClassWithWrongSignatures {
39  		public boolean equals(String other) { return false; }  // Wrong: should be equals(Object)
40  		public int hashCode(int dummy) { return 0; }  // Wrong: should be hashCode()
41  		public String toString(String format) { return ""; }  // Wrong: should be toString()
42  	}
43  
44  	BeanContext bc = BeanContext.DEFAULT;
45  
46  	//====================================================================================================
47  	// Constructor
48  	//====================================================================================================
49  
50  	@Test void a01_constructor() {
51  		var bm = bc.getBeanMeta(TestInterface.class);
52  		var handler = new BeanProxyInvocationHandler<>(bm);
53  		assertNotNull(handler);
54  	}
55  
56  	//====================================================================================================
57  	// invoke() - equals() method
58  	//====================================================================================================
59  
60  	@Test void b01_equals_null() throws Exception {
61  		var bm = bc.getBeanMeta(TestInterface.class);
62  		var handler = new BeanProxyInvocationHandler<>(bm);
63  		var proxy = createProxy(TestInterface.class, handler);
64  
65  		var method = Object.class.getMethod("equals", Object.class);
66  		var result = handler.invoke(proxy, method, new Object[]{null});
67  		assertFalse((Boolean)result);
68  	}
69  
70  	@Test void b02_equals_sameProxy() throws Exception {
71  		var bm = bc.getBeanMeta(TestInterface.class);
72  		var handler = new BeanProxyInvocationHandler<>(bm);
73  		var proxy = createProxy(TestInterface.class, handler);
74  
75  		var method = Object.class.getMethod("equals", Object.class);
76  		var result = handler.invoke(proxy, method, new Object[]{proxy});
77  		assertTrue((Boolean)result);
78  	}
79  
80  	@Test void b03_equals_sameClassDifferentHandler() throws Exception {
81  		var bm = bc.getBeanMeta(TestInterface.class);
82  		var handler1 = new BeanProxyInvocationHandler<>(bm);
83  		var handler2 = new BeanProxyInvocationHandler<>(bm);
84  		var proxy1 = createProxy(TestInterface.class, handler1);
85  		var proxy2 = createProxy(TestInterface.class, handler2);
86  
87  		// Set same values
88  		proxy1.setName("John");
89  		proxy1.setAge(25);
90  		proxy2.setName("John");
91  		proxy2.setAge(25);
92  
93  		var method = Object.class.getMethod("equals", Object.class);
94  		var result = handler1.invoke(proxy1, method, new Object[]{proxy2});
95  		assertTrue((Boolean)result);
96  	}
97  
98  	@Test void b04_equals_differentValues() throws Exception {
99  		var bm = bc.getBeanMeta(TestInterface.class);
100 		var handler1 = new BeanProxyInvocationHandler<>(bm);
101 		var handler2 = new BeanProxyInvocationHandler<>(bm);
102 		var proxy1 = createProxy(TestInterface.class, handler1);
103 		var proxy2 = createProxy(TestInterface.class, handler2);
104 
105 		// Set different values
106 		proxy1.setName("John");
107 		proxy1.setAge(25);
108 		proxy2.setName("Jane");
109 		proxy2.setAge(30);
110 
111 		var method = Object.class.getMethod("equals", Object.class);
112 		var result = handler1.invoke(proxy1, method, new Object[]{proxy2});
113 		assertFalse((Boolean)result);
114 	}
115 
116 	@Test void b05_equals_withBeanMap() throws Exception {
117 		var bm = bc.getBeanMeta(TestInterface.class);
118 		var handler = new BeanProxyInvocationHandler<>(bm);
119 		var proxy = createProxy(TestInterface.class, handler);
120 
121 		proxy.setName("John");
122 		proxy.setAge(25);
123 
124 		// Create a regular bean with same values
125 		var bean = new TestBean();
126 		bean.name = "John";
127 		bean.age = 25;
128 
129 		var method = Object.class.getMethod("equals", Object.class);
130 		var result = handler.invoke(proxy, method, new Object[]{bean});
131 		assertTrue((Boolean)result);
132 	}
133 
134 	public static class TestBean {
135 		public String name;
136 		public int age;
137 	}
138 
139 	//====================================================================================================
140 	// invoke() - hashCode() method
141 	//====================================================================================================
142 
143 	@Test void c01_hashCode() throws Exception {
144 		var bm = bc.getBeanMeta(TestInterface.class);
145 		var handler = new BeanProxyInvocationHandler<>(bm);
146 		var proxy = createProxy(TestInterface.class, handler);
147 
148 		proxy.setName("John");
149 		proxy.setAge(25);
150 
151 		var method = Object.class.getMethod("hashCode");
152 		var result = handler.invoke(proxy, method, null);
153 		assertNotNull(result);
154 		assertTrue(result instanceof Integer);
155 	}
156 
157 	@Test void c02_hashCode_sameValues() throws Exception {
158 		var bm = bc.getBeanMeta(TestInterface.class);
159 		var handler1 = new BeanProxyInvocationHandler<>(bm);
160 		var handler2 = new BeanProxyInvocationHandler<>(bm);
161 		var proxy1 = createProxy(TestInterface.class, handler1);
162 		var proxy2 = createProxy(TestInterface.class, handler2);
163 
164 		proxy1.setName("John");
165 		proxy1.setAge(25);
166 		proxy2.setName("John");
167 		proxy2.setAge(25);
168 
169 		var method = Object.class.getMethod("hashCode");
170 		var hashCode1 = handler1.invoke(proxy1, method, null);
171 		var hashCode2 = handler2.invoke(proxy2, method, null);
172 		assertEquals(hashCode1, hashCode2);
173 	}
174 
175 	//====================================================================================================
176 	// invoke() - toString() method
177 	//====================================================================================================
178 
179 	@Test void d01_toString() throws Exception {
180 		var bm = bc.getBeanMeta(TestInterface.class);
181 		var handler = new BeanProxyInvocationHandler<>(bm);
182 		var proxy = createProxy(TestInterface.class, handler);
183 
184 		proxy.setName("John");
185 		proxy.setAge(25);
186 		proxy.setActive(true);
187 
188 		var method = Object.class.getMethod("toString");
189 		var result = handler.invoke(proxy, method, null);
190 		assertNotNull(result);
191 		assertTrue(result instanceof String);
192 		var str = (String)result;
193 		assertTrue(str.contains("John") || str.contains("25") || str.contains("true"));
194 	}
195 
196 	@Test void d02_toString_empty() throws Exception {
197 		var bm = bc.getBeanMeta(TestInterface.class);
198 		var handler = new BeanProxyInvocationHandler<>(bm);
199 		var proxy = createProxy(TestInterface.class, handler);
200 
201 		var method = Object.class.getMethod("toString");
202 		var result = handler.invoke(proxy, method, null);
203 		assertNotNull(result);
204 		assertTrue(result instanceof String);
205 	}
206 
207 	//====================================================================================================
208 	// invoke() - getter methods
209 	//====================================================================================================
210 
211 	@Test void e01_getter_string() throws Exception {
212 		var bm = bc.getBeanMeta(TestInterface.class);
213 		var handler = new BeanProxyInvocationHandler<>(bm);
214 		var proxy = createProxy(TestInterface.class, handler);
215 
216 		proxy.setName("John");
217 
218 		var method = TestInterface.class.getMethod("getName");
219 		var result = handler.invoke(proxy, method, null);
220 		assertEquals("John", result);
221 	}
222 
223 	@Test void e02_getter_int() throws Exception {
224 		var bm = bc.getBeanMeta(TestInterface.class);
225 		var handler = new BeanProxyInvocationHandler<>(bm);
226 		var proxy = createProxy(TestInterface.class, handler);
227 
228 		proxy.setAge(25);
229 
230 		var method = TestInterface.class.getMethod("getAge");
231 		var result = handler.invoke(proxy, method, null);
232 		assertEquals(25, result);
233 	}
234 
235 	@Test void e03_getter_boolean() throws Exception {
236 		var bm = bc.getBeanMeta(TestInterface.class);
237 		var handler = new BeanProxyInvocationHandler<>(bm);
238 		var proxy = createProxy(TestInterface.class, handler);
239 
240 		proxy.setActive(true);
241 
242 		var method = TestInterface.class.getMethod("isActive");
243 		var result = handler.invoke(proxy, method, null);
244 		assertEquals(true, result);
245 	}
246 
247 	@Test void e04_getter_null() throws Exception {
248 		var bm = bc.getBeanMeta(TestInterface.class);
249 		var handler = new BeanProxyInvocationHandler<>(bm);
250 		var proxy = createProxy(TestInterface.class, handler);
251 
252 		// Don't set name, should return null
253 		var method = TestInterface.class.getMethod("getName");
254 		var result = handler.invoke(proxy, method, null);
255 		assertNull(result);
256 	}
257 
258 	//====================================================================================================
259 	// invoke() - setter methods
260 	//====================================================================================================
261 
262 	@Test void f01_setter_string() throws Exception {
263 		var bm = bc.getBeanMeta(TestInterface.class);
264 		var handler = new BeanProxyInvocationHandler<>(bm);
265 		var proxy = createProxy(TestInterface.class, handler);
266 
267 		var method = TestInterface.class.getMethod("setName", String.class);
268 		var result = handler.invoke(proxy, method, new Object[]{"John"});
269 		assertNull(result);
270 
271 		// Verify value was set
272 		assertEquals("John", proxy.getName());
273 	}
274 
275 	@Test void f02_setter_int() throws Exception {
276 		var bm = bc.getBeanMeta(TestInterface.class);
277 		var handler = new BeanProxyInvocationHandler<>(bm);
278 		var proxy = createProxy(TestInterface.class, handler);
279 
280 		var method = TestInterface.class.getMethod("setAge", int.class);
281 		var result = handler.invoke(proxy, method, new Object[]{25});
282 		assertNull(result);
283 
284 		// Verify value was set
285 		assertEquals(25, proxy.getAge());
286 	}
287 
288 	@Test void f03_setter_boolean() throws Exception {
289 		var bm = bc.getBeanMeta(TestInterface.class);
290 		var handler = new BeanProxyInvocationHandler<>(bm);
291 		var proxy = createProxy(TestInterface.class, handler);
292 
293 		var method = TestInterface.class.getMethod("setActive", boolean.class);
294 		var result = handler.invoke(proxy, method, new Object[]{true});
295 		assertNull(result);
296 
297 		// Verify value was set
298 		assertTrue(proxy.isActive());
299 	}
300 
301 	@Test void f04_setter_null() throws Exception {
302 		var bm = bc.getBeanMeta(TestInterface.class);
303 		var handler = new BeanProxyInvocationHandler<>(bm);
304 		var proxy = createProxy(TestInterface.class, handler);
305 
306 		var method = TestInterface.class.getMethod("setName", String.class);
307 		var result = handler.invoke(proxy, method, new Object[]{null});
308 		assertNull(result);
309 
310 		// Verify null was set
311 		assertNull(proxy.getName());
312 	}
313 
314 	//====================================================================================================
315 	// invoke() - unsupported method
316 	//====================================================================================================
317 
318 	@Test void g01_unsupportedMethod() throws Exception {
319 		var bm = bc.getBeanMeta(TestInterface.class);
320 		var handler = new BeanProxyInvocationHandler<>(bm);
321 		var proxy = createProxy(TestInterface.class, handler);
322 
323 		// Create a method that's not a getter, setter, equals, hashCode, or toString
324 		var method = Object.class.getMethod("getClass");
325 		var exception = assertThrows(UnsupportedOperationException.class, () -> {
326 			handler.invoke(proxy, method, null);
327 		});
328 		assertNotNull(exception.getMessage());
329 		assertTrue(exception.getMessage().contains("Unsupported bean method"));
330 	}
331 
332 	//====================================================================================================
333 	// invoke() - methods with same name but wrong signatures (lines 59, 74, 77 coverage)
334 	//====================================================================================================
335 
336 	@Test void h01_equals_wrongSignature() throws Exception {
337 		// Test equals(String) - should not match the equals(Object) check on line 59
338 		// Get the method from a helper class since we can't put it in an interface
339 		var method = HelperClassWithWrongSignatures.class.getMethod("equals", String.class);
340 
341 		// Create handler with TestInterface (any interface will do for testing)
342 		var bm = bc.getBeanMeta(TestInterface.class);
343 		var handler = new BeanProxyInvocationHandler<>(bm);
344 		var proxy = createProxy(TestInterface.class, handler);
345 
346 		// This should fall through to getter/setter/unsupported logic since it doesn't match line 59
347 		// (has name "equals" but wrong parameter type - String instead of Object)
348 		var exception = assertThrows(UnsupportedOperationException.class, () -> {
349 			handler.invoke(proxy, method, new Object[]{"test"});
350 		});
351 		assertNotNull(exception.getMessage());
352 		assertTrue(exception.getMessage().contains("Unsupported bean method"));
353 	}
354 
355 	@Test void h02_hashCode_wrongSignature() throws Exception {
356 		// Test hashCode(int) - should not match the hashCode() check on line 74
357 		// Get the method from a helper class since we can't put it in an interface
358 		var method = HelperClassWithWrongSignatures.class.getMethod("hashCode", int.class);
359 
360 		// Create handler with TestInterface (any interface will do for testing)
361 		var bm = bc.getBeanMeta(TestInterface.class);
362 		var handler = new BeanProxyInvocationHandler<>(bm);
363 		var proxy = createProxy(TestInterface.class, handler);
364 
365 		// This should fall through to getter/setter/unsupported logic since it doesn't match line 74
366 		// (has name "hashCode" but wrong parameter count - 1 instead of 0)
367 		var exception = assertThrows(UnsupportedOperationException.class, () -> {
368 			handler.invoke(proxy, method, new Object[]{42});
369 		});
370 		assertNotNull(exception.getMessage());
371 		assertTrue(exception.getMessage().contains("Unsupported bean method"));
372 	}
373 
374 	@Test void h03_toString_wrongSignature() throws Exception {
375 		// Test toString(String) - should not match the toString() check on line 77
376 		// Get the method from a helper class since we can't put it in an interface
377 		var method = HelperClassWithWrongSignatures.class.getMethod("toString", String.class);
378 
379 		// Create handler with TestInterface (any interface will do for testing)
380 		var bm = bc.getBeanMeta(TestInterface.class);
381 		var handler = new BeanProxyInvocationHandler<>(bm);
382 		var proxy = createProxy(TestInterface.class, handler);
383 
384 		// This should fall through to getter/setter/unsupported logic since it doesn't match line 77
385 		// (has name "toString" but wrong parameter count - 1 instead of 0)
386 		var exception = assertThrows(UnsupportedOperationException.class, () -> {
387 			handler.invoke(proxy, method, new Object[]{"format"});
388 		});
389 		assertNotNull(exception.getMessage());
390 		assertTrue(exception.getMessage().contains("Unsupported bean method"));
391 	}
392 
393 	//====================================================================================================
394 	// Helper methods
395 	//====================================================================================================
396 
397 	private static <T> T createProxy(Class<T> interfaceClass, InvocationHandler handler) {
398 		return (T)Proxy.newProxyInstance(
399 			interfaceClass.getClassLoader(),
400 			new Class[]{interfaceClass},
401 			handler
402 		);
403 	}
404 }
405