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.apache.juneau.TestUtils.*;
20  import static org.apache.juneau.Visibility.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.io.*;
24  import java.lang.reflect.*;
25  import java.time.*;
26  import java.util.*;
27  
28  import org.apache.juneau.annotation.*;
29  import org.apache.juneau.collections.*;
30  import org.apache.juneau.json.*;
31  import org.apache.juneau.parser.*;
32  import org.apache.juneau.swap.*;
33  import org.junit.jupiter.api.*;
34  
35  @SuppressWarnings("rawtypes")
36  class BeanConfig_Test extends TestBase {
37  
38  	//====================================================================================================
39  	// testBasic
40  	//====================================================================================================
41  	@SuppressWarnings("unchecked")
42  	@Test void a01_basic() {
43  
44  		var bc = BeanContext.DEFAULT;
45  
46  		var p1 = new Person();
47  		p1.setName("John Doe");
48  		p1.setAge(25);
49  
50  		var a = new Address("101 Main St.", "Las Vegas", "NV", "89101");
51  		var p2 = new AddressablePerson();
52  		p2.setName("Jane Doe");
53  		p2.setAge(21);
54  		p2.setAddress(a);
55  
56  		// setup the reference results
57  		var m1 = new LinkedHashMap<String,Object>();
58  		m1.put("name", p1.getName());
59  		m1.put("age", Integer.valueOf(p1.getAge()));
60  
61  		var m2 = new LinkedHashMap<String,Object>();
62  		m2.put("street", a.getStreet());
63  		m2.put("city", a.getCity());
64  		m2.put("state", a.getState());
65  		m2.put("zip", a.getZip());
66  
67  		var m3 = new LinkedHashMap<String,Object>();
68  		m3.put("name", p2.getName());
69  		m3.put("age", Integer.valueOf(p2.getAge()));
70  		m3.put("address", p2.getAddress());
71  
72  		var pm1 = bc.toBeanMap(p1);
73  
74  		assertEquals(pm1.size(), m1.size(), fms("Bean Map size failed for: {0} / {1} / {2}", p1, pm1.size(), m1.size()));
75  		assertEquals(pm1.keySet(), m1.keySet(), fms("Bean Map key set equality failed for: {0} / {1} / {2}", p1, pm1.keySet() , m1.keySet()));
76  		assertEquals(m1.keySet(), pm1.keySet(), fms("Bean Map key set reverse equality failed for: {0} / {1} / {2}", p1, pm1.keySet(), m1.keySet()));
77  		assertEquals(pm1, m1, fms("Bean Map equality failed for: {0} / {1} / {2}", p1, pm1, m1));  // NOSONAR
78  		assertThrows(BeanRuntimeException.class, ()->bc.newBeanMap(Address.class));  // Address returned as a new bean type, but shouldn't be since it doesn't have a default constructor.
79  		assertNull(bc.newBeanMap(java.lang.Integer.class), "java.lang.Integer incorrectly designated as bean type.");
80  		assertNull(bc.newBeanMap(java.lang.Class.class), "java.lang.Class incorrectly designated as bean type.");
81  
82  		var bm1 = bc.toBeanMap(new Address("street", "city", "state", "zip"));
83  
84  		assertEquals(bm1.size(), m2.size(), fms("Bean Adapter map's key set has wrong size: {0} / {1} / {2}", a, bm1.size(), m2.size()));
85  
86  		var iter = bm1.keySet().iterator();
87  		var temp = new HashSet<>();
88  		var count = 0;
89  		while (iter.hasNext()) {
90  			temp.add(iter.next());
91  			count++;
92  		}
93  
94  		assertEquals(count, m2.size(), fms("Iteration count over bean adpater key set failed: {0} / {1} / {2}", a, count, m2.size()));
95  		assertEquals(m2.keySet(), temp, fms("Iteration over bean adpater key set failed: {0} / {1} / {2}", a, bm1.keySet(), m2.keySet()));
96  		assertNotNull(bc.toBeanMap(p2), fms("Failed to identify class as bean type: {0}", p2.getClass()));
97  
98  		var m5 = bc.toBeanMap(p2);
99  		@SuppressWarnings("cast")
100 		var es1 = (Set)m5.entrySet();
101 
102 		assertEquals(es1, m3.entrySet(), fms("Entry set equality failed: {0} / {1} / {2}", p2, es1, m3.entrySet()));
103 		assertEquals(m3.entrySet(), es1, fms("Entry set reverse equality failed: {0} / {1} / {2}", p2, es1, m3.entrySet()));
104 
105 		iter = es1.iterator();
106 		temp = new HashSet<>();
107 		count = 0;
108 		while (iter.hasNext()) {
109 			temp.add(iter.next());
110 			count++;
111 		}
112 
113 		assertEquals(count, m3.size(), fms("Iteration count over bean adpater entry set failed: {0} / {1} / {2}", a, count, m3.size()));
114 		assertEquals(m3.entrySet(), temp, fms("Iteration over bean adpater entry set failed: {0} / {1} / {2}", a, es1, m3.entrySet()));
115 	}
116 
117 	public static class Person {
118 
119 		public Person() {
120 			name = null;
121 			age = -1;
122 		}
123 
124 		private String name;
125 		public String getName() { return name; }
126 		public void setName(String v) { name = v; }
127 
128 		private int age;
129 		public int getAge() { return age; }
130 		public void setAge(int v) { age = v; }
131 
132 		@Override /* Object */
133 		public String toString() {
134 			return f("Person(name: {0}, age: {1})", name, age);
135 		}
136 	}
137 
138 	public static class Address {
139 
140 		public Address(String street, String city, String state, String zip) {
141 			this.street = street;
142 			this.city = city;
143 			this.state = state;
144 			this.zip = zip;
145 		}
146 
147 		protected String street;
148 		public String getStreet() { return street; }
149 
150 		protected String city;
151 		public String getCity() { return city; }
152 
153 		protected String state;
154 		public String getState() { return state; }
155 
156 		protected String zip;
157 		public String getZip() { return zip; }
158 
159 		@Override /* Object */
160 		public boolean equals(Object o) {
161 			return eq(this, (Address)o, (x,y) -> eq(x.getStreet(), y.getStreet()) && eq(x.getCity(), y.getCity()) && eq(x.getState(), y.getState()) && eq(x.getZip(), y.getZip()));
162 		}
163 
164 		@Override /* Object */
165 		public int hashCode() {
166 			return hash(street, city, state, zip);
167 		}
168 
169 		@Override /* Object */
170 		public String toString() {
171 			return f("Address(street: {0}, city: {1}, state: {2}, zip: {3})", street, city, state, zip);
172 		}
173 	}
174 
175 	public static class AddressablePerson extends Person {
176 
177 		public AddressablePerson() {
178 			this.address = null;
179 		}
180 
181 		private Address address;
182 		public Address getAddress() { return address; }
183 		public void setAddress(Address v) { address = v; }
184 
185 		@Override /* Object */
186 		public String toString() {
187 			return super.toString() + "@" + this.address;
188 		}
189 	}
190 
191 	//====================================================================================================
192 	// Exhaustive test of BeanContext.convertToType()
193 	//====================================================================================================
194 	@Test void a01_beanContextConvertToType() throws Exception {
195 		var bc = BeanContext.DEFAULT;
196 		var o = (Object)null;
197 
198 		// Primitive nulls.
199 		assertEquals(Integer.valueOf(0), bc.convertToType(o, Integer.TYPE));
200 		assertEquals(Short.valueOf((short) 0), bc.convertToType(o, Short.TYPE));
201 		assertEquals(Long.valueOf(0), bc.convertToType(o, Long.TYPE));
202 		assertEquals(Float.valueOf(0), bc.convertToType(o, Float.TYPE));
203 		assertEquals(Double.valueOf(0), bc.convertToType(o, Double.TYPE));
204 		assertEquals(Byte.valueOf((byte) 0), bc.convertToType(o, Byte.TYPE));
205 		assertEquals(Character.valueOf((char) 0), bc.convertToType(o, Character.TYPE));
206 		assertEquals(Boolean.FALSE, bc.convertToType(o, Boolean.TYPE));
207 
208 		o = "1";
209 
210 		assertEquals(Integer.valueOf(1), bc.convertToType(o, Integer.class));
211 		assertEquals(Short.valueOf((short) 1), bc.convertToType(o, Short.class));
212 		assertEquals(Long.valueOf(1), bc.convertToType(o, Long.class));
213 		assertEquals(Float.valueOf(1), bc.convertToType(o, Float.class));
214 		assertEquals(Double.valueOf(1), bc.convertToType(o, Double.class));
215 		assertEquals(Byte.valueOf((byte) 1), bc.convertToType(o, Byte.class));
216 		assertEquals(Character.valueOf('1'), bc.convertToType(o, Character.class));
217 		assertEquals(Boolean.FALSE, bc.convertToType(o, Boolean.class));
218 
219 		assertEquals(Integer.valueOf(1), bc.convertToType(o, Integer.TYPE));
220 		assertEquals(Short.valueOf((short) 1), bc.convertToType(o, Short.TYPE));
221 		assertEquals(Long.valueOf(1), bc.convertToType(o, Long.TYPE));
222 		assertEquals(Float.valueOf(1), bc.convertToType(o, Float.TYPE));
223 		assertEquals(Double.valueOf(1), bc.convertToType(o, Double.TYPE));
224 		assertEquals(Byte.valueOf((byte) 1), bc.convertToType(o, Byte.TYPE));
225 		assertEquals(Character.valueOf('1'), bc.convertToType(o, Character.TYPE));
226 		assertEquals(Boolean.FALSE, bc.convertToType(o, Boolean.TYPE));
227 
228 		o = Integer.valueOf(1);
229 
230 		assertEquals(Integer.valueOf(1), bc.convertToType(o, Integer.TYPE));
231 		assertEquals(Short.valueOf((short) 1), bc.convertToType(o, Short.TYPE));
232 		assertEquals(Long.valueOf(1), bc.convertToType(o, Long.TYPE));
233 		assertEquals(Float.valueOf(1), bc.convertToType(o, Float.TYPE));
234 		assertEquals(Double.valueOf(1), bc.convertToType(o, Double.TYPE));
235 		assertEquals(Byte.valueOf((byte) 1), bc.convertToType(o, Byte.TYPE));
236 		assertEquals(Character.valueOf('1'), bc.convertToType(o, Character.TYPE));
237 		assertEquals(Boolean.TRUE, bc.convertToType(o, Boolean.TYPE));
238 
239 		o = Integer.valueOf(0);
240 		assertEquals(Boolean.FALSE, bc.convertToType(o, Boolean.TYPE));
241 
242 		// Bean
243 		o = "{name:'x',age:123}";
244 		assertBean(bc.convertToType(o, Person.class), "name,age", "x,123");
245 
246 		// Read-only bean
247 		o = "{name:'x',age:123}";
248 		assertBean(bc.convertToType(o, ReadOnlyPerson.class), "name,age", "x,123");
249 
250 		// Class with forString(String) method.
251 		o = UUID.randomUUID();
252 		assertEquals(o, bc.convertToType(o.toString(), UUID.class));
253 
254 		// Class with Constructor(String).
255 		o = "xxx";
256 		var file = bc.convertToType(o, File.class);
257 		assertEquals("xxx", file.getName());
258 
259 		// List of ints to array
260 		o = JsonList.of(1, 2, 3);
261 		assertEquals(1, bc.convertToType(o, int[].class)[0]);
262 
263 		// List of beans to array
264 		o = JsonList.of(new ReadOnlyPerson("x", 123));
265 		assertEquals("x", bc.convertToType(o, ReadOnlyPerson[].class)[0].getName());
266 
267 		// Multi-dimensional array of beans.
268 		o = JsonList.ofCollections(JsonList.of(new ReadOnlyPerson("x", 123)));
269 		assertEquals("x", bc.convertToType(o, ReadOnlyPerson[][].class)[0][0].getName());
270 
271 		// Array of strings to array of ints
272 		o = a("1", "2", "3");
273 		assertEquals(Integer.valueOf(1), bc.convertToType(o, Integer[].class)[0]);
274 		assertEquals(1, bc.convertToType(o, int[].class)[0]);
275 
276 		// Array to list
277 		o = a(1, 2, 3);
278 		assertEquals(Integer.valueOf(1), bc.convertToType(o, LinkedList.class).get(0));
279 
280 		// HashMap to TreeMap
281 		o = map(1, "foo");
282 		assertEquals("foo", bc.convertToType(o, TreeMap.class).firstEntry().getValue());
283 
284 		// String to TreeMap
285 		o = "{1:'foo'}";
286 		assertEquals("foo", bc.convertToType(o, TreeMap.class).firstEntry().getValue());
287 
288 		// String to generic Map
289 		assertEquals("foo", bc.convertToType(o, Map.class).values().iterator().next());
290 
291 		// Array to String
292 		o = a("a", 1, false);
293 		assertEquals("['a',1,false]", bc.convertToType(o, String.class));
294 		o = new Object[]{a("a", 1, false)};
295 		assertEquals("[['a',1,false]]", bc.convertToType(o, String.class));
296 	}
297 
298 	//====================================================================================================
299 	// Test properties set through a constructor.
300 	//====================================================================================================
301 	@Test void a02_readOnlyProperties() throws Exception {
302 		var bc = BeanContext.DEFAULT;
303 		var o = new ReadOnlyPerson("x", 123);
304 
305 		// Bean to String
306 		assertEquals("{name:'x',age:123}", bc.convertToType(o, String.class));
307 
308 		// List of Maps to array of beans.
309 		var o2 = JsonList.of(JsonMap.ofJson("{name:'x',age:1}"), JsonMap.ofJson("{name:'y',age:2}"));
310 		assertEquals(1, bc.convertToType(o2, ReadOnlyPerson[].class)[0].getAge());
311 	}
312 
313 	@Bean(p="name,age")
314 	public static class ReadOnlyPerson {
315 		private final int age;
316 
317 		@Beanc(properties="name,age")
318 		public ReadOnlyPerson(String name, int age) {
319 			this.name = name;
320 			this.age = age;
321 		}
322 
323 		private final String name;
324 		public String getName() { return name; }
325 		public int getAge() { return age; }
326 
327 		@Override /* Object */
328 		public String toString() {
329 			return f("toString():name={0},age={1}", name, age);
330 		}
331 	}
332 
333 	@Test void a03_readOnlyProperties_usingConfig() throws Exception {
334 		var bc = BeanContext.DEFAULT.copy().applyAnnotations(ReadOnlyPerson2Config.class).build();
335 		var o = new ReadOnlyPerson2("x", 123);
336 
337 		// Bean to String
338 		assertEquals("{name:'x',age:123}", bc.convertToType(o, String.class));
339 
340 		// List of Maps to array of beans.
341 		var o2 = JsonList.of(JsonMap.ofJson("{name:'x',age:1}"), JsonMap.ofJson("{name:'y',age:2}"));
342 		assertEquals(1, bc.convertToType(o2, ReadOnlyPerson2[].class)[0].getAge());
343 	}
344 
345 	public static class ReadOnlyPerson2 {
346 		private final int age;
347 
348 		public ReadOnlyPerson2(String name, int age) {
349 			this.name = name;
350 			this.age = age;
351 		}
352 
353 		private final String name;
354 		public String getName() { return name; }
355 		public int getAge() { return age; }
356 
357 		@Override /* Object */
358 		public String toString() {
359 			return f("toString():name={0},age={1}", name, age);
360 		}
361 	}
362 
363 	@Bean(on="Dummy1",p="dummy")
364 	@Bean(on="ReadOnlyPerson2",p="name,age")
365 	@Bean(on="Dummy2",p="dummy")
366 	@Beanc(on="Dummy1",properties="dummy")
367 	@Beanc(on="ReadOnlyPerson2(String,int)",properties="name,age")
368 	@Beanc(on="Dummy2",properties="dummy")
369 	private static class ReadOnlyPerson2Config {}
370 
371 	//====================================================================================================
372 	// testEnums
373 	//====================================================================================================
374 	@Test void a04_enums() throws Exception {
375 		var bc = BeanContext.DEFAULT;
376 		var o = "ENUM2";
377 
378 		// Enum
379 		assertEquals(TestEnum.ENUM2, bc.convertToType(o, TestEnum.class));
380 		assertEquals("ENUM2", bc.convertToType(TestEnum.ENUM2, String.class));
381 
382 		// Array of enums
383 		var o2 = a("ENUM2");
384 		assertEquals(TestEnum.ENUM2, bc.convertToType(o2, TestEnum[].class)[0]);
385 	}
386 
387 	public enum TestEnum {
388 		ENUM1, ENUM2, ENUM3
389 	}
390 
391 	//====================================================================================================
392 	// testProxyHandler
393 	//====================================================================================================
394 	@Test void a05_proxyHandler() {
395 		var session = BeanContext.DEFAULT_SESSION;
396 
397 		var f1 = (A) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { A.class }, new AHandler());
398 
399 		var bm1 = session.toBeanMap(f1);
400 		assertNotNull(bm1, fms("Failed to obtain bean adapter for proxy: {0}", f1));
401 
402 		var bm2 = session.newBeanMap(A.class);
403 		assertNotNull(bm2, fms("Failed to create dynamic proxy bean for interface: {0}", A.class.getName()));
404 
405 		bm2.put("a", "Hello");
406 		bm2.put("b", Integer.valueOf(50));
407 		f1.setA("Hello");
408 		f1.setB(50);
409 
410 		assertMap(bm2, "a,b", "Hello,50");
411 		assertEquals(bm1, bm2, fms("Failed equality test of dynamic proxies beans: {0} / {1}", bm1, bm2));
412 		assertEquals(bm2, bm1, fms("Failed reverse equality test of dynamic proxies beans: {0} / {1}", bm1, bm2));
413 	}
414 
415 	public interface A {
416 		String getA();
417 		void setA(String a);
418 
419 		int getB();
420 		void setB(int b);
421 	}
422 
423 	public static class AHandler implements InvocationHandler {
424 		private Map<String,Object> map;
425 
426 		public AHandler() {
427 			map = new HashMap<>();
428 			map.put("a", "");
429 			map.put("b", Integer.valueOf(0));
430 		}
431 
432 		@Override /* InvocationHandler */
433 		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
434 			var methodName = method.getName();
435 			if (methodName.equals("getA")) {
436 				return map.get("a");
437 			}
438 			if (methodName.equals("setA")) {
439 				map.put("a", args[0]);
440 				return null;
441 			}
442 			if (methodName.equals("getB")) {
443 				return map.get("b");
444 			}
445 			if (methodName.equals("setB")) {
446 				map.put("b", args[0]);
447 				return null;
448 			}
449 			if (methodName.equals("toString")) {
450 				return map.toString();
451 			}
452 			return null;
453 		}
454 	}
455 
456 	//====================================================================================================
457 	// testFluentStyleSetters
458 	//====================================================================================================
459 	@Test void a06_fluentStyleSetters() {
460 		var t = new B2().init();
461 		var m = BeanContext.DEFAULT.toBeanMap(t);
462 		m.put("f1", 2);
463 		assertEquals(2, t.f1);
464 	}
465 
466 	public static class B {
467 		int f1;
468 		public int getF1() { return f1; }
469 		public B setF1(int v) { f1 = v; return this; }
470 	}
471 
472 	public static class B2 extends B {
473 		@Override /* B */
474 		public B2 setF1(int v) { f1 = v; return this; }
475 		public B2 init() { f1 = 1; return this;}
476 	}
477 
478 	//====================================================================================================
479 	// testClassMetaCaching
480 	//====================================================================================================
481 	@Test void a07_classMetaCaching() {
482 		var p1 = JsonParser.create();
483 		var p2 = JsonParser.create();
484 		assertSameCache(p1, p2);
485 
486 		assertDifferentCache(p1.beansRequireDefaultConstructor(), p2);
487 		assertSameCache(p1, p2.beansRequireDefaultConstructor());
488 
489 		assertDifferentCache(p1.beansRequireSerializable(), p2);
490 		assertSameCache(p1, p2.beansRequireSerializable());
491 
492 		assertDifferentCache(p1.beansRequireSettersForGetters(), p2);
493 		assertSameCache(p1, p2.beansRequireSettersForGetters());
494 
495 		assertDifferentCache(p1.disableBeansRequireSomeProperties(), p2);
496 		assertSameCache(p1, p2.disableBeansRequireSomeProperties());
497 
498 		assertDifferentCache(p1.beanMapPutReturnsOldValue(), p2);
499 		assertSameCache(p1, p2.beanMapPutReturnsOldValue());
500 
501 		assertDifferentCache(p1.beanConstructorVisibility(DEFAULT), p2);
502 		assertSameCache(p1, p2.beanConstructorVisibility(DEFAULT));
503 		assertDifferentCache(p1.beanConstructorVisibility(NONE), p2);
504 		assertSameCache(p1, p2.beanConstructorVisibility(NONE));
505 		assertDifferentCache(p1.beanConstructorVisibility(PRIVATE), p2);
506 		assertSameCache(p1, p2.beanConstructorVisibility(PRIVATE));
507 		assertDifferentCache(p1.beanConstructorVisibility(PROTECTED), p2);
508 		assertSameCache(p1, p2.beanConstructorVisibility(PROTECTED));
509 
510 		assertDifferentCache(p1.beanClassVisibility(DEFAULT), p2);
511 		assertSameCache(p1, p2.beanClassVisibility(DEFAULT));
512 		assertDifferentCache(p1.beanClassVisibility(NONE), p2);
513 		assertSameCache(p1, p2.beanClassVisibility(NONE));
514 		assertDifferentCache(p1.beanClassVisibility(PRIVATE), p2);
515 		assertSameCache(p1, p2.beanClassVisibility(PRIVATE));
516 		assertDifferentCache(p1.beanClassVisibility(PROTECTED), p2);
517 		assertSameCache(p1, p2.beanClassVisibility(PROTECTED));
518 
519 		assertDifferentCache(p1.beanFieldVisibility(DEFAULT), p2);
520 		assertSameCache(p1, p2.beanFieldVisibility(DEFAULT));
521 		assertDifferentCache(p1.beanFieldVisibility(NONE), p2);
522 		assertSameCache(p1, p2.beanFieldVisibility(NONE));
523 		assertDifferentCache(p1.beanFieldVisibility(PRIVATE), p2);
524 		assertSameCache(p1, p2.beanFieldVisibility(PRIVATE));
525 		assertDifferentCache(p1.beanFieldVisibility(PROTECTED), p2);
526 		assertSameCache(p1, p2.beanFieldVisibility(PROTECTED));
527 
528 		assertDifferentCache(p1.beanMethodVisibility(DEFAULT), p2);
529 		assertSameCache(p1, p2.beanMethodVisibility(DEFAULT));
530 		assertDifferentCache(p1.beanMethodVisibility(NONE), p2);
531 		assertSameCache(p1, p2.beanMethodVisibility(NONE));
532 		assertDifferentCache(p1.beanMethodVisibility(PRIVATE), p2);
533 		assertSameCache(p1, p2.beanMethodVisibility(PRIVATE));
534 		assertDifferentCache(p1.beanMethodVisibility(PROTECTED), p2);
535 		assertSameCache(p1, p2.beanMethodVisibility(PROTECTED));
536 
537 		assertDifferentCache(p1.useJavaBeanIntrospector(), p2);
538 		assertSameCache(p1, p2.useJavaBeanIntrospector());
539 
540 		assertDifferentCache(p1.disableInterfaceProxies(), p2);
541 		assertSameCache(p1, p2.disableInterfaceProxies());
542 
543 		assertDifferentCache(p1.ignoreUnknownBeanProperties(), p2);
544 		assertSameCache(p1, p2.ignoreUnknownBeanProperties());
545 
546 		assertDifferentCache(p1.disableIgnoreUnknownNullBeanProperties(), p2);
547 		assertSameCache(p1, p2.disableIgnoreUnknownNullBeanProperties());
548 
549 		assertDifferentCache(p1.disableIgnoreMissingSetters(), p2);
550 		assertSameCache(p1, p2.disableIgnoreMissingSetters());
551 
552 		assertDifferentCache(p1.ignoreInvocationExceptionsOnGetters(), p2);
553 		assertSameCache(p1, p2.ignoreInvocationExceptionsOnGetters());
554 
555 		assertDifferentCache(p1.ignoreInvocationExceptionsOnSetters(), p2);
556 		assertSameCache(p1, p2.ignoreInvocationExceptionsOnSetters());
557 
558 		assertDifferentCache(p1.notBeanPackages("foo"), p2);
559 		assertSameCache(p1, p2.notBeanPackages("foo"));
560 		assertDifferentCache(p1.notBeanPackages("bar"), p2);
561 		assertSameCache(p1, p2.notBeanPackages("bar"));
562 		assertDifferentCache(p1.notBeanPackages("baz").notBeanPackages("bing"), p2);
563 		assertSameCache(p2.notBeanPackages("bing").notBeanPackages("baz"), p2);
564 
565 		p1.beanContext().notBeanPackages().remove("bar");
566 		assertDifferentCache(p1, p2);
567 		p2.beanContext().notBeanPackages().remove("bar");
568 		assertSameCache(p1, p2);
569 
570 		assertDifferentCache(p1.swaps(DummyPojoSwapA.class), p2);
571 		assertSameCache(p1, p2.swaps(DummyPojoSwapA.class));
572 		assertDifferentCache(p1.swaps(DummyPojoSwapB.class,DummyPojoSwapC.class), p2.swaps(DummyPojoSwapC.class,DummyPojoSwapB.class));  // Order of filters is important!
573 	}
574 
575 	public static class DummyPojoSwapA extends MapSwap<A> {}
576 	public static class DummyPojoSwapB extends MapSwap<B> {}
577 	public static class DummyPojoSwapC extends MapSwap<C> {}
578 	public static class C {}
579 
580 	private void assertSameCache(Parser.Builder p1b, Parser.Builder p2b) {
581 		var p1 = p1b.build();
582 		var p2 = p2b.build();
583 		assertTrue(p1.getBeanContext().hasSameCache(p2.getBeanContext()));
584 	}
585 
586 	private void assertDifferentCache(Parser.Builder p1b, Parser.Builder p2b) {
587 		var p1 = p1b.build();
588 		var p2 = p2b.build();
589 		assertFalse(p1.getBeanContext().hasSameCache(p2.getBeanContext()));
590 	}
591 
592 	//====================================================================================================
593 	// testNotABeanReasons
594 	//====================================================================================================
595 	@Test void a08_notABeanNonStaticInnerClass() {
596 		var bc = BeanContext.DEFAULT;
597 		var cm = bc.getClassMeta(C1.class);
598 		assertFalse(cm.canCreateNewInstance());
599 	}
600 
601 	public class C1 {
602 		public int f1;
603 	}
604 
605 	//====================================================================================================
606 	// testAddingToArrayProperty
607 	// This tests the speed of the BeanMap.add() method against array properties.
608 	// For performance reasons, array properties are stored as temporary ArrayLists until the
609 	// BeanMap.getBean() method is called.
610 	//====================================================================================================
611 	// Should be around 100ms at most.
612 	@Test void a09_addingToArrayProperty() {
613 		assertTimeout(Duration.ofSeconds(1), () -> {
614 			var bc = BeanContext.DEFAULT;
615 			var bm = bc.newBeanMap(D.class);
616 			for (var i = 0; i < 5000; i++) {
617 				bm.add("f1", i);
618 				bm.add("f2", i);
619 				bm.add("f3", i);
620 				bm.add("f4", i);
621 			}
622 			var d = bm.getBean();
623 			assertBean(d, "f1{length},f2{length},f3{length},f4{length}", "{5000},{5000},{5003},{5003}");
624 		});
625 	}
626 
627 	public class D {
628 		public int[] f1;
629 		private int[] f2;
630 		public int[] f3 = {1,2,3};
631 		private int[] f4 = {1,2,3};
632 		public int[] getF2() {return f2;}
633 		public void setF2(int[] v) {f2 = v;}
634 		public int[] getF4() {return f4;}
635 		public void setF4(int[] v) {f4 = v;}
636 	}
637 
638 	//====================================================================================================
639 	// testClassClassMeta
640 	// Make sure we can get ClassMeta objects against the Class class.
641 	//====================================================================================================
642 	@Test void a10_classClassMeta() {
643 		assertNotNull(BeanContext.DEFAULT.getClassMeta(Class.class));
644 		assertNotNull(BeanContext.DEFAULT.getClassMeta(Class[].class));
645 	}
646 
647 	//====================================================================================================
648 	// testBlanks
649 	//====================================================================================================
650 	@Test void a11_blanks() throws Exception {
651 		var bc = BeanContext.DEFAULT;
652 
653 		// Blanks get interpreted as the default value for primitives and null for boxed objects.
654 		assertEquals(0, (int)bc.convertToType("", int.class));
655 		assertNull(bc.convertToType("", Integer.class));
656 
657 		// Booleans are handled different since 'Boolean.valueOf("")' is valid and resolves to false
658 		// while 'Integer.valueOf("")' produces an exception.
659 		assertEquals(false, (boolean)bc.convertToType("", boolean.class));
660 		assertEquals(null, bc.convertToType("", Boolean.class));
661 	}
662 }