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.commons.reflect;
18  
19  import static org.junit.jupiter.api.Assertions.*;
20  
21  import org.apache.juneau.*;
22  import org.junit.jupiter.api.*;
23  
24  class Property_Test extends TestBase {
25  
26  	public static class TestClass {
27  		public String publicField;
28  		private String privateField;
29  		private int intField;
30  
31  		public void setPublicField(String value) {
32  			publicField = value;
33  		}
34  
35  		public String getPublicField() {
36  			return publicField;
37  		}
38  
39  		public void setPrivateField(String value) {
40  			privateField = value;
41  		}
42  
43  		public String getPrivateField() {
44  			return privateField;
45  		}
46  
47  		public void setIntField(int value) {
48  			intField = value;
49  		}
50  
51  		public int getIntField() {
52  			return intField;
53  		}
54  	}
55  
56  	//====================================================================================================
57  	// Property.create() - static factory method
58  	//====================================================================================================
59  	@Test
60  	void a001_create() {
61  		var builder = Property.create();
62  		assertNotNull(builder);
63  	}
64  
65  	//====================================================================================================
66  	// Property.Builder.getter(Function) and setter(BiConsumer) - basic usage
67  	//====================================================================================================
68  	@Test
69  	void a002_functionGetterAndSetter() throws Exception {
70  		var prop = Property.<TestClass, String>create()
71  			.getter(obj -> obj.getPublicField())
72  			.setter((obj, val) -> obj.setPublicField(val))
73  			.build();
74  
75  		var obj = new TestClass();
76  		prop.set(obj, "testValue");
77  		assertEquals("testValue", prop.get(obj));
78  		assertEquals("testValue", obj.getPublicField());
79  	}
80  
81  	//====================================================================================================
82  	// Property.Builder.getter(Function) - getter only
83  	//====================================================================================================
84  	@Test
85  	void a003_getterOnly() throws Exception {
86  		var prop = Property.<TestClass, String>create()
87  			.getter(obj -> obj.getPublicField())
88  			.build();
89  
90  		var obj = new TestClass();
91  		obj.setPublicField("testValue");
92  		assertEquals("testValue", prop.get(obj));
93  		assertTrue(prop.canRead());
94  		assertFalse(prop.canWrite());
95  	}
96  
97  	//====================================================================================================
98  	// Property.Builder.setter(BiConsumer) - setter only
99  	//====================================================================================================
100 	@Test
101 	void a004_setterOnly() throws Exception {
102 		var prop = Property.<TestClass, String>create()
103 			.setter((obj, val) -> obj.setPublicField(val))
104 			.build();
105 
106 		var obj = new TestClass();
107 		prop.set(obj, "testValue");
108 		assertEquals("testValue", obj.getPublicField());
109 		assertFalse(prop.canRead());
110 		assertTrue(prop.canWrite());
111 	}
112 
113 	//====================================================================================================
114 	// Property.get() - with null producer
115 	//====================================================================================================
116 	@Test
117 	void a005_get_withNullProducer() throws Exception {
118 		var prop = Property.<TestClass, String>create()
119 			.setter((obj, val) -> obj.setPublicField(val))
120 			.build();
121 
122 		var obj = new TestClass();
123 		var ex = assertThrows(ExecutableException.class, () -> prop.get(obj));
124 		assertTrue(ex.getMessage().contains("No getter defined"));
125 	}
126 
127 	//====================================================================================================
128 	// Property.set() - with null consumer
129 	//====================================================================================================
130 	@Test
131 	void a006_set_withNullConsumer() throws Exception {
132 		var prop = Property.<TestClass, String>create()
133 			.getter(obj -> obj.getPublicField())
134 			.build();
135 
136 		var obj = new TestClass();
137 		var ex = assertThrows(ExecutableException.class, () -> prop.set(obj, "test"));
138 		assertTrue(ex.getMessage().contains("No setter defined"));
139 	}
140 
141 	//====================================================================================================
142 	// Property.get() - with null object
143 	//====================================================================================================
144 	@Test
145 	void a007_get_withNullObject() {
146 		var prop = Property.<TestClass, String>create()
147 			.getter(obj -> obj.getPublicField())
148 			.build();
149 
150 		assertThrows(IllegalArgumentException.class, () -> prop.get(null));
151 	}
152 
153 	//====================================================================================================
154 	// Property.set() - with null object
155 	//====================================================================================================
156 	@Test
157 	void a008_set_withNullObject() {
158 		var prop = Property.<TestClass, String>create()
159 			.setter((obj, val) -> obj.setPublicField(val))
160 			.build();
161 
162 		assertThrows(IllegalArgumentException.class, () -> prop.set(null, "test"));
163 	}
164 
165 	//====================================================================================================
166 	// Property.Builder.field(FieldInfo) - public field
167 	//====================================================================================================
168 	@Test
169 	void a009_field_publicField() throws Exception {
170 		var field = ClassInfo.of(TestClass.class).getPublicFields().stream()
171 			.filter(f -> f.getName().equals("publicField"))
172 			.findFirst()
173 			.orElseThrow();
174 		var prop = Property.<TestClass, String>create()
175 			.field(field)
176 			.build();
177 
178 		var obj = new TestClass();
179 		prop.set(obj, "testValue");
180 		assertEquals("testValue", prop.get(obj));
181 		assertEquals("testValue", obj.publicField);
182 	}
183 
184 	//====================================================================================================
185 	// Property.Builder.field(FieldInfo) - private field
186 	//====================================================================================================
187 	@Test
188 	void a010_field_privateField() throws Exception {
189 		var field = ClassInfo.of(TestClass.class).getDeclaredFields().stream()
190 			.filter(f -> f.getName().equals("privateField"))
191 			.findFirst()
192 			.orElseThrow();
193 		var prop = Property.<TestClass, String>create()
194 			.field(field)
195 			.build();
196 
197 		var obj = new TestClass();
198 		prop.set(obj, "testValue");
199 		assertEquals("testValue", prop.get(obj));
200 		assertEquals("testValue", obj.getPrivateField());
201 	}
202 
203 	//====================================================================================================
204 	// Property.Builder.field(FieldInfo) - with null field
205 	//====================================================================================================
206 	@Test
207 	void a011_field_withNullField() {
208 		assertThrows(IllegalArgumentException.class, () -> {
209 			Property.<TestClass, String>create()
210 				.field(null)
211 				.build();
212 		});
213 	}
214 
215 	//====================================================================================================
216 	// Property.Builder.getter(MethodInfo) - method getter
217 	//====================================================================================================
218 	@Test
219 	void a012_getter_methodInfo() throws Exception {
220 		var getter = ClassInfo.of(TestClass.class).getPublicMethods().stream()
221 			.filter(m -> m.hasName("getPublicField"))
222 			.findFirst()
223 			.orElseThrow();
224 		var prop = Property.<TestClass, String>create()
225 			.getter(getter)
226 			.setter((obj, val) -> obj.setPublicField(val))
227 			.build();
228 
229 		var obj = new TestClass();
230 		obj.setPublicField("testValue");
231 		assertEquals("testValue", prop.get(obj));
232 	}
233 
234 	//====================================================================================================
235 	// Property.Builder.setter(MethodInfo) - method setter
236 	//====================================================================================================
237 	@Test
238 	void a013_setter_methodInfo() throws Exception {
239 		var setter = ClassInfo.of(TestClass.class).getPublicMethods().stream()
240 			.filter(m -> m.hasName("setPublicField") && m.hasParameterTypes(String.class))
241 			.findFirst()
242 			.orElseThrow();
243 		var prop = Property.<TestClass, String>create()
244 			.getter(obj -> obj.getPublicField())
245 			.setter(setter)
246 			.build();
247 
248 		var obj = new TestClass();
249 		prop.set(obj, "testValue");
250 		assertEquals("testValue", obj.getPublicField());
251 	}
252 
253 	//====================================================================================================
254 	// Property.Builder.getter(MethodInfo) - with null method
255 	//====================================================================================================
256 	@Test
257 	void a014_getter_withNullMethod() {
258 		assertThrows(IllegalArgumentException.class, () -> {
259 			Property.<TestClass, String>create()
260 				.getter((MethodInfo)null)
261 				.build();
262 		});
263 	}
264 
265 	//====================================================================================================
266 	// Property.Builder.setter(MethodInfo) - with null method
267 	//====================================================================================================
268 	@Test
269 	void a015_setter_withNullMethod() {
270 		assertThrows(IllegalArgumentException.class, () -> {
271 			Property.<TestClass, String>create()
272 				.setter((MethodInfo)null)
273 				.build();
274 		});
275 	}
276 
277 	//====================================================================================================
278 	// Property.Builder.field() - with primitive type
279 	//====================================================================================================
280 	@Test
281 	void a016_field_primitiveType() throws Exception {
282 		var field = ClassInfo.of(TestClass.class).getDeclaredFields().stream()
283 			.filter(f -> f.getName().equals("intField"))
284 			.findFirst()
285 			.orElseThrow();
286 		var prop = Property.<TestClass, Integer>create()
287 			.field(field)
288 			.build();
289 
290 		var obj = new TestClass();
291 		prop.set(obj, 42);
292 		assertEquals(Integer.valueOf(42), prop.get(obj));
293 		assertEquals(42, obj.getIntField());
294 	}
295 
296 	//====================================================================================================
297 	// Property.set() - with null value
298 	//====================================================================================================
299 	@Test
300 	void a017_set_withNullValue() throws Exception {
301 		var prop = Property.<TestClass, String>create()
302 			.getter(obj -> obj.getPublicField())
303 			.setter((obj, val) -> obj.setPublicField(val))
304 			.build();
305 
306 		var obj = new TestClass();
307 		obj.setPublicField("initial");
308 		prop.set(obj, null);
309 		assertNull(prop.get(obj));
310 		assertNull(obj.getPublicField());
311 	}
312 
313 	//====================================================================================================
314 	// Property.get() - exception handling
315 	//====================================================================================================
316 	@Test
317 	void a018_get_exceptionHandling() {
318 		var prop = Property.<TestClass, String>create()
319 			.getter(obj -> {
320 				throw new RuntimeException("Test exception");
321 			})
322 			.build();
323 
324 		var obj = new TestClass();
325 		var ex = assertThrows(RuntimeException.class, () -> prop.get(obj));
326 		assertEquals("Test exception", ex.getMessage());
327 	}
328 
329 	//====================================================================================================
330 	// Property.set() - exception handling
331 	//====================================================================================================
332 	@Test
333 	void a019_set_exceptionHandling() {
334 		var prop = Property.<TestClass, String>create()
335 			.setter((obj, val) -> {
336 				throw new RuntimeException("Test exception");
337 			})
338 			.build();
339 
340 		var obj = new TestClass();
341 		var ex = assertThrows(RuntimeException.class, () -> prop.set(obj, "test"));
342 		assertEquals("Test exception", ex.getMessage());
343 	}
344 
345 	//====================================================================================================
346 	// Property.get() - ExecutableException pass-through
347 	//====================================================================================================
348 	@Test
349 	void a020_get_executableExceptionPassThrough() {
350 		var originalEx = new ExecutableException("Original exception");
351 		var prop = Property.<TestClass, String>create()
352 			.getter(obj -> {
353 				throw originalEx;
354 			})
355 			.build();
356 
357 		var obj = new TestClass();
358 		var ex = assertThrows(ExecutableException.class, () -> prop.get(obj));
359 		assertSame(originalEx, ex);
360 	}
361 
362 	//====================================================================================================
363 	// Property.set() - ExecutableException pass-through
364 	//====================================================================================================
365 	@Test
366 	void a021_set_executableExceptionPassThrough() {
367 		var originalEx = new ExecutableException("Original exception");
368 		var prop = Property.<TestClass, String>create()
369 			.setter((obj, val) -> {
370 				throw originalEx;
371 			})
372 			.build();
373 
374 		var obj = new TestClass();
375 		var ex = assertThrows(ExecutableException.class, () -> prop.set(obj, "test"));
376 		assertSame(originalEx, ex);
377 	}
378 
379 	//====================================================================================================
380 	// Property.get() - checked exception wrapping
381 	//====================================================================================================
382 	@Test
383 	void a022_get_checkedExceptionWrapping() {
384 		var prop = Property.<TestClass, String>create()
385 			.getter(obj -> {
386 				throw new java.io.IOException("IO error");
387 			})
388 			.build();
389 
390 		var obj = new TestClass();
391 		var ex = assertThrows(RuntimeException.class, () -> prop.get(obj));
392 		assertNotNull(ex.getCause());
393 		assertTrue(ex.getCause() instanceof java.io.IOException);
394 		assertEquals("IO error", ex.getCause().getMessage());
395 	}
396 
397 	//====================================================================================================
398 	// Property.set() - checked exception wrapping
399 	//====================================================================================================
400 	@Test
401 	void a023_set_checkedExceptionWrapping() {
402 		var prop = Property.<TestClass, String>create()
403 			.setter((obj, val) -> {
404 				throw new java.io.IOException("IO error");
405 			})
406 			.build();
407 
408 		var obj = new TestClass();
409 		var ex = assertThrows(RuntimeException.class, () -> prop.set(obj, "test"));
410 		assertNotNull(ex.getCause());
411 		assertTrue(ex.getCause() instanceof java.io.IOException);
412 		assertEquals("IO error", ex.getCause().getMessage());
413 	}
414 
415 	//====================================================================================================
416 	// Property.Builder - chaining methods
417 	//====================================================================================================
418 	@Test
419 	void a024_builder_chaining() throws Exception {
420 		var prop = Property.<TestClass, String>create()
421 			.getter(obj -> obj.getPublicField())
422 			.setter((obj, val) -> obj.setPublicField(val))
423 			.build();
424 
425 		assertNotNull(prop);
426 		assertTrue(prop.canRead());
427 		assertTrue(prop.canWrite());
428 	}
429 
430 	//====================================================================================================
431 	// Property.Builder.field() - complete round trip
432 	//====================================================================================================
433 	@Test
434 	void a025_field_roundTrip() throws Exception {
435 		var field = ClassInfo.of(TestClass.class).getPublicFields().stream()
436 			.filter(f -> f.getName().equals("publicField"))
437 			.findFirst()
438 			.orElseThrow();
439 		var prop = Property.<TestClass, String>create()
440 			.field(field)
441 			.build();
442 
443 		var obj = new TestClass();
444 		prop.set(obj, "value1");
445 		assertEquals("value1", prop.get(obj));
446 		prop.set(obj, "value2");
447 		assertEquals("value2", prop.get(obj));
448 		prop.set(obj, null);
449 		assertNull(prop.get(obj));
450 	}
451 
452 	//====================================================================================================
453 	// Property.Builder.getter/setter(MethodInfo) - complete round trip
454 	//====================================================================================================
455 	@Test
456 	void a026_methodInfo_roundTrip() throws Exception {
457 		var getter = ClassInfo.of(TestClass.class).getPublicMethods().stream()
458 			.filter(m -> m.hasName("getPublicField"))
459 			.findFirst()
460 			.orElseThrow();
461 		var setter = ClassInfo.of(TestClass.class).getPublicMethods().stream()
462 			.filter(m -> m.hasName("setPublicField") && m.hasParameterTypes(String.class))
463 			.findFirst()
464 			.orElseThrow();
465 		var prop = Property.<TestClass, String>create()
466 			.getter(getter)
467 			.setter(setter)
468 			.build();
469 
470 		var obj = new TestClass();
471 		prop.set(obj, "value1");
472 		assertEquals("value1", prop.get(obj));
473 		prop.set(obj, "value2");
474 		assertEquals("value2", prop.get(obj));
475 		prop.set(obj, null);
476 		assertNull(prop.get(obj));
477 	}
478 }
479