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