001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau; 018 019import static org.apache.juneau.common.utils.Utils.*; 020import static org.apache.juneau.internal.ArrayUtils.*; 021import static org.apache.juneau.internal.ClassUtils.*; 022import static org.apache.juneau.internal.CollectionUtils.copyOf; 023 024import java.beans.*; 025import java.util.*; 026 027import org.apache.juneau.annotation.*; 028import org.apache.juneau.common.utils.*; 029import org.apache.juneau.cp.*; 030import org.apache.juneau.swap.*; 031 032/** 033 * Parent class for all bean filters. 034 * 035 * <p> 036 * Bean filters are used to control aspects of how beans are handled during serialization and parsing. 037 * 038 * <p> 039 * Bean filters are created by {@link Builder} which is the programmatic equivalent to the {@link Bean @Bean} 040 * annotation. 041 * 042 * <h5 class='section'>See Also:</h5><ul> 043 044 * </ul> 045 */ 046public class BeanFilter { 047 048 //----------------------------------------------------------------------------------------------------------------- 049 // Static 050 //----------------------------------------------------------------------------------------------------------------- 051 052 /** 053 * Create a new builder for this object. 054 * 055 * @param <T> The bean class being filtered. 056 * @param beanClass The bean class being filtered. 057 * @return A new builder. 058 */ 059 public static <T> Builder create(Class<T> beanClass) { 060 return new Builder(beanClass); 061 } 062 063 //----------------------------------------------------------------------------------------------------------------- 064 // Builder 065 //----------------------------------------------------------------------------------------------------------------- 066 067 /** 068 * Builder class. 069 */ 070 public static class Builder { 071 072 Class<?> beanClass; 073 String typeName, example; 074 Set<String> 075 properties = set(), 076 excludeProperties = set(), 077 readOnlyProperties = set(), 078 writeOnlyProperties = set(); 079 Class<?> implClass, interfaceClass, stopClass; 080 boolean sortProperties, fluentSetters; 081 BeanCreator<PropertyNamer> propertyNamer = BeanCreator.of(PropertyNamer.class); 082 List<Class<?>> dictionary; 083 @SuppressWarnings("rawtypes") 084 BeanCreator<BeanInterceptor> interceptor = BeanCreator.of(BeanInterceptor.class); 085 086 /** 087 * Constructor. 088 * 089 * @param beanClass The bean class that this filter applies to. 090 */ 091 protected Builder(Class<?> beanClass) { 092 this.beanClass = beanClass; 093 } 094 095 /** 096 * Applies the information in the specified list of {@link Bean @Bean} annotations to this filter. 097 * 098 * @param annotations The annotations to apply. 099 * @return This object. 100 */ 101 public Builder applyAnnotations(List<Bean> annotations) { 102 103 annotations.forEach(x -> { 104 if (isNotEmpty(x.properties()) || isNotEmpty(x.p())) properties(x.properties(), x.p()); 105 if (x.sort()) sortProperties(true); 106 if (x.findFluentSetters()) findFluentSetters(); 107 if (isNotEmpty(x.excludeProperties()) || isNotEmpty(x.xp())) excludeProperties(x.excludeProperties(), x.xp()); 108 if (isNotEmpty(x.readOnlyProperties()) || isNotEmpty(x.ro())) readOnlyProperties(x.readOnlyProperties(), x.ro()); 109 if (isNotEmpty(x.writeOnlyProperties()) || isNotEmpty(x.wo())) writeOnlyProperties(x.writeOnlyProperties(), x.wo()); 110 if (isNotEmpty(x.typeName())) typeName(x.typeName()); 111 if (isNotVoid(x.propertyNamer())) propertyNamer(x.propertyNamer()); 112 if (isNotVoid(x.interfaceClass())) interfaceClass(x.interfaceClass()); 113 if (isNotVoid(x.stopClass())) stopClass(x.stopClass()); 114 if (isNotVoid(x.interceptor())) interceptor(x.interceptor()); 115 if (isNotVoid(x.implClass())) implClass(x.implClass()); 116 if (isNotEmptyArray(x.dictionary())) dictionary(x.dictionary()); 117 if (isNotEmpty(x.example())) example(x.example()); 118 }); 119 return this; 120 } 121 122 /** 123 * Bean dictionary type name. 124 * 125 * <p> 126 * Specifies the dictionary type name for this bean. 127 * 128 * <h5 class='section'>Example:</h5> 129 * <p class='bjava'> 130 * <jc>// Define our filter.</jc> 131 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 132 * <jk>public</jk> MyFilter() { 133 * typeName(<js>"mybean"</js>); 134 * } 135 * } 136 * 137 * <jc>// Register it with a serializer or parser.</jc> 138 * WriterSerializer <jv>serializer<jv> = JsonSerializer 139 * .<jsm>create</jsm>() 140 * .beanFilters(MyFilter.<jk>class</jk>) 141 * .build(); 142 * 143 * <jc>// Produces: "{_type:'mybean', ...}"</jc> 144 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean()); 145 * </p> 146 * 147 * <h5 class='section'>See Also:</h5><ul> 148 * <li class='ja'>{@link Bean#typeName()} 149 * </ul> 150 * 151 * @param value The new value for this setting. 152 * @return This object. 153 */ 154 public Builder typeName(String value) { 155 this.typeName = value; 156 return this; 157 } 158 159 /** 160 * Bean implementation class. 161 * 162 * @param value The new value for this setting. 163 * @return This object. 164 */ 165 public Builder implClass(Class<?> value) { 166 this.implClass = value; 167 return this; 168 } 169 170 /** 171 * Bean interface class. 172 * 173 * Identifies a class to be used as the interface class for this and all subclasses. 174 * 175 * <p> 176 * When specified, only the list of properties defined on the interface class will be used during serialization. 177 * <br>Additional properties on subclasses will be ignored. 178 * 179 * <p class='bjava'> 180 * <jc>// Parent class</jc> 181 * <jk>public abstract class</jk> A { 182 * <jk>public</jk> String <jf>f0</jf> = <js>"f0"</js>; 183 * } 184 * 185 * <jc>// Sub class</jc> 186 * <jk>public class</jk> A1 <jk>extends</jk> A { 187 * <jk>public</jk> String <jf>f1</jf> = <js>"f1"</js>; 188 * } 189 * 190 * <jc>// Define our filter.</jc> 191 * <jk>public class</jk> AFilter <jk>extends</jk> Builder<A> { 192 * <jk>public</jk> AFilter() { 193 * interfaceClass(A.<jk>class</jk>); 194 * } 195 * } 196 * 197 * <jc>// Register it with a serializer.</jc> 198 * WriterSerializer <jv>serializer</jv> = JsonSerializer 199 * .<jsm>create</jsm>() 200 * .beanFilters(AFilter.<jk>class</jk>) 201 * .build(); 202 * 203 * <jc>// Use it.</jc> 204 * A1 <jv>a1</jv> = <jk>new</jk> A1(); 205 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jv>a1</jv>); 206 * <jsm>assertEquals</jsm>(<js>"{f0:'f0'}"</js>, <jv>json</jv>); <jc>// Note f1 is not serialized</jc> 207 * </p> 208 * 209 * <p> 210 * Note that this filter can be used on the parent class so that it filters to all child classes, or can be set 211 * individually on the child classes. 212 * 213 * <h5 class='section'>See Also:</h5><ul> 214 * <li class='ja'>{@link Bean#interfaceClass()} 215 * </ul> 216 * 217 * @param value The new value for this setting. 218 * @return This object. 219 */ 220 public Builder interfaceClass(Class<?> value) { 221 this.interfaceClass = value; 222 return this; 223 } 224 225 /** 226 * Bean stop class. 227 * 228 * <p> 229 * Identifies a stop class for this class and all subclasses. 230 * 231 * <p> 232 * Identical in purpose to the stop class specified by {@link Introspector#getBeanInfo(Class, Class)}. 233 * <br>Any properties in the stop class or in its base classes will be ignored during analysis. 234 * 235 * <p> 236 * For example, in the following class hierarchy, instances of <c>C3</c> will include property <c>p3</c>, 237 * but not <c>p1</c> or <c>p2</c>. 238 * 239 * <h5 class='section'>Example:</h5> 240 * <p class='bjava'> 241 * <jk>public class</jk> C1 { 242 * <jk>public int</jk> getP1(); 243 * } 244 * 245 * <jk>public class</jk> C2 <jk>extends</jk> C1 { 246 * <jk>public int</jk> getP2(); 247 * } 248 * 249 * <jk>public class</jk> C3 <jk>extends</jk> C2 { 250 * <jk>public int</jk> getP3(); 251 * } 252 * 253 * <jc>// Define our filter.</jc> 254 * <jk>public class</jk> C3Filter <jk>extends</jk> Builder<C3> { 255 * <jk>public</jk> C3Filter() { 256 * stopClass(C2.<jk>class</jk>); 257 * } 258 * } 259 * 260 * <jc>// Register it with a serializer.</jc> 261 * WriterSerializer <jv>serializer</jv> = JsonSerializer 262 * .<jsm>create</jsm>() 263 * .beanFilters(C3Filter.<jk>class</jk>) 264 * .build(); 265 * 266 * <jc>// Serializes property 'p3', but NOT 'p1' or 'p2'.</jc> 267 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> C3()); 268 * </p> 269 * 270 * <h5 class='section'>See Also:</h5><ul> 271 * <li class='ja'>{@link Bean#stopClass()} 272 * </ul> 273 * 274 * @param value The new value for this setting. 275 * @return This object. 276 */ 277 public Builder stopClass(Class<?> value) { 278 this.stopClass = value; 279 return this; 280 } 281 282 /** 283 * Sort bean properties. 284 * 285 * <p> 286 * When <jk>true</jk>, all bean properties will be serialized and access in alphabetical order. 287 * <br>Otherwise, the natural order of the bean properties is used which is dependent on the JVM vendor. 288 * 289 * <h5 class='section'>Example:</h5> 290 * <p class='bjava'> 291 * <jc>// Define our filter.</jc> 292 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 293 * <jk>public</jk> MyFilter() { 294 * sortProperties(); 295 * } 296 * } 297 * 298 * <jc>// Register it with a serializer.</jc> 299 * WriterSerializer <jv>serializer</jv> = JsonSerializer 300 * .<jsm>create</jsm>() 301 * .beanFilters(MyFilter.<jk>class</jk>) 302 * .build(); 303 * 304 * <jc>// Properties will be sorted alphabetically.</jc> 305 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean()); 306 * </p> 307 * 308 * <h5 class='section'>See Also:</h5><ul> 309 * <li class='ja'>{@link Bean#sort()} 310 * <li class='jf'>{@link BeanContext.Builder#sortProperties()} 311 * </ul> 312 * 313 * @param value 314 * The new value for this property. 315 * <br>The default is <jk>false</jk>. 316 * @return This object. 317 */ 318 public Builder sortProperties(boolean value) { 319 this.sortProperties = value; 320 return this; 321 } 322 323 /** 324 * Sort bean properties. 325 * 326 * <p> 327 * Shortcut for calling <code>sortProperties(<jk>true</jk>)</code>. 328 * 329 * <h5 class='section'>See Also:</h5><ul> 330 * <li class='ja'>{@link Bean#sort()} 331 * <li class='jf'>{@link BeanContext.Builder#sortProperties()} 332 * </ul> 333 * 334 * @return This object. 335 */ 336 public Builder sortProperties() { 337 this.sortProperties = true; 338 return this; 339 } 340 341 /** 342 * Find fluent setters. 343 * 344 * <p> 345 * When enabled, fluent setters are detected on beans. 346 * 347 * <p> 348 * Fluent setters must have the following attributes: 349 * <ul> 350 * <li>Public. 351 * <li>Not static. 352 * <li>Take in one parameter. 353 * <li>Return the bean itself. 354 * </ul> 355 * 356 * <h5 class='section'>Example:</h5> 357 * <p class='bjava'> 358 * <jc>// Define our filter.</jc> 359 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 360 * <jk>public</jk> MyFilter() { 361 * findFluentSetters(); 362 * } 363 * } 364 * </p> 365 * 366 * <h5 class='section'>See Also:</h5><ul> 367 * <li class='ja'>{@link Bean#findFluentSetters()} 368 * <li class='jm'>{@link BeanContext.Builder#findFluentSetters()} 369 * </ul> 370 * 371 * @return This object. 372 */ 373 public Builder findFluentSetters() { 374 this.fluentSetters = true; 375 return this; 376 } 377 378 /** 379 * Bean property namer 380 * 381 * <p> 382 * The class to use for calculating bean property names. 383 * 384 * <h5 class='section'>Example:</h5> 385 * <p class='bjava'> 386 * <jc>// Define our filter.</jc> 387 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 388 * <jk>public</jk> MyFilter() { 389 * <jc>// Use Dashed-Lower-Case property names.</jc> 390 * <jc>// (e.g. "foo-bar-url" instead of "fooBarURL")</jc> 391 * propertyNamer(PropertyNamerDLC.<jk>class</jk>); 392 * } 393 * } 394 * 395 * <jc>// Register it with a serializer or parser.</jc> 396 * WriterSerializer <jv>serializer</jv> = JsonSerializer 397 * .<jsm>create</jsm>() 398 * .beanFilters(MyFilter.<jk>class</jk>) 399 * .build(); 400 * 401 * <jc>// Properties names will be Dashed-Lower-Case.</jc> 402 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean()); 403 * </p> 404 * 405 * <h5 class='section'>See Also:</h5><ul> 406 * <li class='ja'>{@link Bean#propertyNamer()} 407 * <li class='jm'>{@link BeanContext.Builder#propertyNamer(Class)} 408 * <li class='jc'>{@link PropertyNamer} 409 * </ul> 410 * 411 * @param value 412 * The new value for this setting. 413 * <br>The default is {@link BasicPropertyNamer}. 414 * @return This object. 415 */ 416 public Builder propertyNamer(Class<? extends PropertyNamer> value) { 417 this.propertyNamer.type(value); 418 return this; 419 } 420 421 /** 422 * Bean property includes. 423 * 424 * <p> 425 * Specifies the set and order of names of properties associated with the bean class. 426 * 427 * <h5 class='section'>Example:</h5> 428 * <p class='bjava'> 429 * <jc>// Define our filter.</jc> 430 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 431 * <jk>public</jk> MyFilter() { 432 * properties(<js>"foo,bar,baz"</js>); 433 * } 434 * } 435 * 436 * <jc>// Register it with a serializer.</jc> 437 * WriterSerializer <jv>serializer</jv> = JsonSerializer 438 * .<jsm>create</jsm>() 439 * .beanFilters(MyFilter.<jk>class</jk>) 440 * .build(); 441 * 442 * <jc>// Only serializes the properties 'foo', 'bar', and 'baz'.</jc> 443 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean()); 444 * </p> 445 * 446 * <h5 class='section'>See Also:</h5><ul> 447 * <li class='ja'>{@link Bean#properties()} 448 * <li class='jm'>{@link BeanContext.Builder#beanProperties(Class, String)} 449 * <li class='jm'>{@link BeanContext.Builder#beanProperties(String, String)} 450 * <li class='jm'>{@link BeanContext.Builder#beanProperties(Map)} 451 * </ul> 452 * 453 * @param value 454 * The new value for this setting. 455 * <br>Values can contain comma-delimited list of property names. 456 * @return This object. 457 */ 458 public Builder properties(String...value) { 459 this.properties = set(); 460 for (String v : value) 461 Utils.split(v, x -> properties.add(x)); 462 return this; 463 } 464 465 /** 466 * Bean property excludes. 467 * 468 * <p> 469 * Specifies properties to exclude from the bean class. 470 * 471 * <h5 class='section'>Example:</h5> 472 * <p class='bjava'> 473 * <jc>// Define our filter.</jc> 474 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 475 * <jk>public</jk> MyFilter() { 476 * excludeProperties(<js>"foo,bar"</js>); 477 * } 478 * } 479 * 480 * <jc>// Register it with a serializer.</jc> 481 * WriterSerializer <jv>serializer</jv> = JsonSerializer 482 * .<jsm>create</jsm>() 483 * .beanFilters(MyFilter.<jk>class</jk>) 484 * .build(); 485 * 486 * <jc>// Serializes all properties except for 'foo' and 'bar'.</jc> 487 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean()); 488 * </p> 489 * 490 * <h5 class='section'>See Also:</h5><ul> 491 * <li class='ja'>{@link Bean#excludeProperties()} 492 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesExcludes(Class, String)} 493 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesExcludes(String, String)} 494 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesExcludes(Map)} 495 * </ul> 496 * 497 * @param value 498 * The new value for this setting. 499 * <br>Values can contain comma-delimited list of property names. 500 * @return This object. 501 */ 502 public Builder excludeProperties(String...value) { 503 this.excludeProperties = set(); 504 for (String v : value) 505 Utils.split(v, x -> excludeProperties.add(x)); 506 return this; 507 } 508 509 /** 510 * Read-only bean properties. 511 * 512 * <p> 513 * Specifies one or more properties on a bean that are read-only despite having valid getters. 514 * Serializers will serialize such properties as usual, but parsers will silently ignore them. 515 * 516 * <h5 class='section'>Example:</h5> 517 * <p class='bjava'> 518 * <jc>// Define our filter.</jc> 519 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 520 * <jk>public</jk> MyFilter() { 521 * readOnlyProperties(<js>"foo,bar"</js>); 522 * } 523 * } 524 * 525 * <jc>// Register it with a parser.</jc> 526 * ReaderParser <jv>parser</jv> = JsonParser 527 * .<jsm>create</jsm>() 528 * .beanFilters(MyFilter.<jk>class</jk>) 529 * .build(); 530 * 531 * <jc>// Parsers all properties except for 'foo' and 'bar'.</jc> 532 * MyBean <jv>bean</jv> = <jv>parser</jv>.parse(<js>"..."</js>, MyBean.<jk>class</jk>); 533 * </p> 534 * 535 * <h5 class='section'>See Also:</h5><ul> 536 * <li class='ja'>{@link Bean#readOnlyProperties()} 537 * <li class='ja'>{@link Beanp#ro()} 538 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesReadOnly(Class, String)} 539 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesReadOnly(String, String)} 540 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesReadOnly(Map)} 541 * </ul> 542 * 543 * @param value 544 * The new value for this setting. 545 * <br>Values can contain comma-delimited list of property names. 546 * @return This object. 547 */ 548 public Builder readOnlyProperties(String...value) { 549 this.readOnlyProperties = set(); 550 for (String v : value) 551 Utils.split(v, x -> readOnlyProperties.add(x)); 552 return this; 553 } 554 555 /** 556 * Write-only bean properties. 557 * 558 * <p> 559 * Specifies one or more properties on a bean that are write-only despite having valid setters. 560 * Parsers will parse such properties as usual, but serializers will silently ignore them. 561 * 562 * <h5 class='section'>Example:</h5> 563 * <p class='bjava'> 564 * <jc>// Define our filter.</jc> 565 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 566 * <jk>public</jk> MyFilter() { 567 * writeOnlyProperties(<js>"foo,bar"</js>); 568 * } 569 * } 570 * 571 * <jc>// Register it with a serializer.</jc> 572 * WriterSerializer <jv>serializer</jv> = JsonSerializer 573 * .<jsm>create</jsm>() 574 * .beanFilters(MyFilter.<jk>class</jk>) 575 * .build(); 576 * 577 * <jc>// Serializes all properties except for 'foo' and 'bar'.</jc> 578 * String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean()); 579 * </p> 580 * 581 * <h5 class='section'>See Also:</h5><ul> 582 * <li class='ja'>{@link Bean#writeOnlyProperties()} 583 * <li class='ja'>{@link Beanp#wo()} 584 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesWriteOnly(Class, String)} 585 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesWriteOnly(String, String)} 586 * <li class='jm'>{@link BeanContext.Builder#beanPropertiesWriteOnly(Map)} 587 * </ul> 588 * 589 * @param value 590 * The new value for this setting. 591 * <br>Values can contain comma-delimited list of property names. 592 * @return This object. 593 */ 594 public Builder writeOnlyProperties(String...value) { 595 this.writeOnlyProperties = set(); 596 for (String v : value) 597 Utils.split(v, x -> writeOnlyProperties.add(x)); 598 return this; 599 } 600 601 /** 602 * Bean dictionary. 603 * 604 * <p> 605 * Adds to the list of classes that make up the bean dictionary for this bean. 606 * 607 * <h5 class='section'>Example:</h5> 608 * <p class='bjava'> 609 * <jc>// Define our filter.</jc> 610 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 611 * <jk>public</jk> MyFilter() { 612 * <jc>// Our bean contains generic collections of Foo and Bar objects.</jc> 613 * beanDictionary(Foo.<jk>class</jk>, Bar.<jk>class</jk>); 614 * } 615 * } 616 * 617 * <jc>// Register it with a parser.</jc> 618 * ReaderParser <jv>parser</jv> = JsonParser 619 * .<jsm>create</jsm>() 620 * .beanFilters(MyFilter.<jk>class</jk>) 621 * .build(); 622 * 623 * <jc>// Instantiate our bean.</jc> 624 * MyBean <jv>bean</jv> = <jv>parser</jv>.parse(<jv>json</jv>); 625 * </p> 626 * 627 * <h5 class='section'>See Also:</h5><ul> 628 * <li class='ja'>{@link Bean#dictionary()} 629 * <li class='jm'>{@link BeanContext.Builder#beanDictionary(Class...)} 630 * </ul> 631 * 632 * @param values 633 * The values to add to this property. 634 * @return This object. 635 */ 636 public Builder dictionary(Class<?>...values) { 637 if (dictionary == null) 638 dictionary = list(values); 639 else for (Class<?> cc : values) 640 dictionary.add(cc); 641 return this; 642 } 643 644 /** 645 * Example. 646 * 647 * @param value 648 * The new value for this property. 649 * @return This object. 650 */ 651 public Builder example(String value) { 652 this.example = value; 653 return this; 654 } 655 656 /** 657 * Bean interceptor. 658 * 659 * <p> 660 * The interceptor to use for intercepting and altering getter and setter calls. 661 * 662 * <h5 class='section'>Example:</h5> 663 * <p class='bjava'> 664 * <jc>// Define our filter.</jc> 665 * <jk>public class</jk> MyFilter <jk>extends</jk> Builder<MyBean> { 666 * <jk>public</jk> MyFilter() { 667 * <jc>// Our bean contains generic collections of Foo and Bar objects.</jc> 668 * interceptor(AddressInterceptor.<jk>class</jk>); 669 * } 670 * } 671 * 672 * <jc>// Register it with a serializer or parser.</jc> 673 * WriterSerializer <jv>serializer</jv> = JsonSerializer 674 * .<jsm>create</jsm>() 675 * .beanFilters(MyFilter.<jk>class</jk>) 676 * .build(); 677 * </p> 678 * 679 * <h5 class='section'>See Also:</h5><ul> 680 * <li class='ja'>{@link Bean#interceptor()} 681 * <li class='jc'>{@link BeanInterceptor} 682 * </ul> 683 * 684 * @param value 685 * The new value for this setting. 686 * <br>The default value is {@link BeanInterceptor}. 687 * @return This object. 688 */ 689 public Builder interceptor(Class<?> value) { 690 this.interceptor.type(value); 691 return this; 692 } 693 694 /** 695 * Creates a {@link BeanFilter} with settings in this builder class. 696 * 697 * @return A new {@link BeanFilter} instance. 698 */ 699 public BeanFilter build() { 700 return new BeanFilter(this); 701 } 702 } 703 704 //----------------------------------------------------------------------------------------------------------------- 705 // Instance 706 //----------------------------------------------------------------------------------------------------------------- 707 708 private final Class<?> beanClass; 709 private final Set<String> properties, excludeProperties, readOnlyProperties, writeOnlyProperties; 710 private final PropertyNamer propertyNamer; 711 private final Class<?> implClass, interfaceClass, stopClass; 712 private final boolean sortProperties, fluentSetters; 713 private final String typeName, example; 714 private final Class<?>[] beanDictionary; 715 @SuppressWarnings("rawtypes") 716 private final BeanInterceptor interceptor; 717 718 /** 719 * Constructor. 720 */ 721 BeanFilter(Builder builder) { 722 this.beanClass = builder.beanClass; 723 this.typeName = builder.typeName; 724 this.properties = copyOf(builder.properties); 725 this.excludeProperties = copyOf(builder.excludeProperties); 726 this.readOnlyProperties = copyOf(builder.readOnlyProperties); 727 this.writeOnlyProperties = copyOf(builder.writeOnlyProperties); 728 this.example = builder.example; 729 this.implClass = builder.implClass; 730 this.interfaceClass = builder.interfaceClass; 731 this.stopClass = builder.stopClass; 732 this.sortProperties = builder.sortProperties; 733 this.fluentSetters = builder.fluentSetters; 734 this.propertyNamer = builder.propertyNamer.orElse(null); 735 this.beanDictionary = 736 builder.dictionary == null 737 ? null 738 : builder.dictionary.toArray(new Class<?>[builder.dictionary.size()]); 739 this.interceptor = builder.interceptor.orElse(BeanInterceptor.DEFAULT); 740 } 741 742 /** 743 * Returns the bean class that this filter applies to. 744 * 745 * @return The bean class that this filter applies to. 746 */ 747 public Class<?> getBeanClass() { 748 return beanClass; 749 } 750 751 /** 752 * Returns the dictionary name associated with this bean. 753 * 754 * @return The dictionary name associated with this bean, or <jk>null</jk> if no name is defined. 755 */ 756 public String getTypeName() { 757 return typeName; 758 } 759 760 /** 761 * Returns the bean dictionary defined on this bean. 762 * 763 * @return The bean dictionary defined on this bean, or <jk>null</jk> if no bean dictionary is defined. 764 */ 765 public Class<?>[] getBeanDictionary() { 766 return beanDictionary; 767 } 768 769 /** 770 * Returns the set and order of names of properties associated with a bean class. 771 * 772 * @return 773 * The names of the properties associated with a bean class, or and empty set if all bean properties should 774 * be used. 775 */ 776 public Set<String> getProperties() { 777 return properties; 778 } 779 780 /** 781 * Returns the list of properties to ignore on a bean. 782 * 783 * @return The names of the properties to ignore on a bean, or an empty set to not ignore any properties. 784 */ 785 public Set<String> getExcludeProperties() { 786 return excludeProperties; 787 } 788 789 /** 790 * Returns the list of read-only properties on a bean. 791 * 792 * @return The names of the read-only properties on a bean, or an empty set to not have any read-only properties. 793 */ 794 public Set<String> getReadOnlyProperties() { 795 return readOnlyProperties; 796 } 797 798 /** 799 * Returns the list of write-only properties on a bean. 800 * 801 * @return The names of the write-only properties on a bean, or an empty set to not have any write-only properties. 802 */ 803 public Set<String> getWriteOnlyProperties() { 804 return writeOnlyProperties; 805 } 806 807 /** 808 * Returns <jk>true</jk> if the properties defined on this bean class should be ordered alphabetically. 809 * 810 * <p> 811 * This method is only used when the {@link #getProperties()} method returns <jk>null</jk>. 812 * Otherwise, the ordering of the properties in the returned value is used. 813 * 814 * @return <jk>true</jk> if bean properties should be sorted. 815 */ 816 public boolean isSortProperties() { 817 return sortProperties; 818 } 819 820 /** 821 * Returns <jk>true</jk> if we should find fluent setters. 822 * 823 * @return <jk>true</jk> if fluent setters should be found. 824 */ 825 public boolean isFluentSetters() { 826 return fluentSetters; 827 } 828 829 /** 830 * Returns the {@link PropertyNamer} associated with the bean to tailor the names of bean properties. 831 * 832 * @return The property namer class, or <jk>null</jk> if no property namer is associated with this bean property. 833 */ 834 public PropertyNamer getPropertyNamer() { 835 return propertyNamer; 836 } 837 838 /** 839 * Returns the implementation class associated with this class. 840 * 841 * @return The implementation class associated with this class, or <jk>null</jk> if no implementation class is associated. 842 */ 843 public Class<?> getImplClass() { 844 return implClass; 845 } 846 847 /** 848 * Returns the interface class associated with this class. 849 * 850 * @return The interface class associated with this class, or <jk>null</jk> if no interface class is associated. 851 */ 852 public Class<?> getInterfaceClass() { 853 return interfaceClass; 854 } 855 856 /** 857 * Returns the stop class associated with this class. 858 * 859 * @return The stop class associated with this class, or <jk>null</jk> if no stop class is associated. 860 */ 861 public Class<?> getStopClass() { 862 return stopClass; 863 } 864 865 /** 866 * Returns the example associated with this class. 867 * 868 * @return The example associated with this class, or <jk>null</jk> if no example is associated. 869 */ 870 public String getExample() { 871 return example; 872 } 873 874 /** 875 * Calls the {@link BeanInterceptor#readProperty(Object, String, Object)} method on the registered property filters. 876 * 877 * @param bean The bean from which the property was read. 878 * @param name The property name. 879 * @param value The value just extracted from calling the bean getter. 880 * @return The value to serialize. Default is just to return the existing value. 881 */ 882 @SuppressWarnings("unchecked") 883 public Object readProperty(Object bean, String name, Object value) { 884 return interceptor.readProperty(bean, name, value); 885 } 886 887 /** 888 * Calls the {@link BeanInterceptor#writeProperty(Object, String, Object)} method on the registered property filters. 889 * 890 * @param bean The bean from which the property was read. 891 * @param name The property name. 892 * @param value The value just parsed. 893 * @return The value to serialize. Default is just to return the existing value. 894 */ 895 @SuppressWarnings("unchecked") 896 public Object writeProperty(Object bean, String name, Object value) { 897 return interceptor.writeProperty(bean, name, value); 898 } 899}