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