001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.transform; 014 015import static org.apache.juneau.internal.ClassUtils.*; 016 017import java.beans.*; 018import java.util.*; 019 020import org.apache.juneau.*; 021import org.apache.juneau.annotation.*; 022 023/** 024 * Builder class for {@link BeanFilter} objects. 025 * 026 * <p> 027 * This class is the programmatic equivalent to the {@link Bean @Bean} annotation. 028 * 029 * <p> 030 * The general approach to defining bean filters is to create subclasses from this class and call methods to 031 * set various attributes 032 * <p class='bcode w800'> 033 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 034 * 035 * <jc>// Must provide a no-arg constructor!</jc> 036 * <jk>public</jk> MyFilter() { 037 * 038 * <jc>// Call one or more configuration methods.</jc> 039 * includeProperties(<js>"foo,bar,baz"</js>); 040 * sortProperties(); 041 * propertyNamer(PropertyNamerULC.<jk>class</jk>); 042 * } 043 * } 044 * 045 * <jc>// Register it with a serializer or parser.</jc> 046 * WriterSerializer s = JsonSerializer 047 * .<jsm>create</jsm>() 048 * .beanFilters(MyFilter.<jk>class</jk>) 049 * .build(); 050 * </p> 051 * 052 * <h5 class='section'>See Also:</h5> 053 * <ul> 054 * <li class='link'>{@doc juneau-marshall.Transforms.BeanFilters} 055 * </ul> 056 * 057 * @param <T> The bean type that this filter applies to. 058 */ 059public class BeanFilterBuilder<T> { 060 061 Class<?> beanClass; 062 String typeName; 063 String[] includeProperties, excludeProperties; 064 Class<?> interfaceClass, stopClass; 065 boolean sortProperties, fluentSetters; 066 Object propertyNamer; 067 List<Class<?>> beanDictionary; 068 Object propertyFilter; 069 070 /** 071 * Constructor. 072 * 073 * <p> 074 * Bean class is determined through reflection of the parameter type. 075 */ 076 protected BeanFilterBuilder() { 077 beanClass = resolveParameterType(BeanFilterBuilder.class, 0, this.getClass()); 078 } 079 080 /** 081 * Constructor. 082 * 083 * @param beanClass The bean class that this filter applies to. 084 */ 085 protected BeanFilterBuilder(Class<?> beanClass) { 086 this.beanClass = beanClass; 087 } 088 089 /** 090 * Configuration property: Bean dictionary type name. 091 * 092 * <p> 093 * Specifies the dictionary type name for this bean. 094 * 095 * <h5 class='section'>Example:</h5> 096 * <p class='bcode w800'> 097 * <jc>// Define our filter.</jc> 098 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 099 * <jk>public</jk> MyFilter() { 100 * typeName(<js>"mybean"</js>); 101 * } 102 * } 103 * 104 * <jc>// Register it with a serializer or parser.</jc> 105 * WriterSerializer s = JsonSerializer 106 * .<jsm>create</jsm>() 107 * .beanFilters(MyFilter.<jk>class</jk>) 108 * .build(); 109 * 110 * <jc>// Produces: "{_type:'mybean', ...}"</jc> 111 * String json = s.serialize(<jk>new</jk> MyBean()); 112 * </p> 113 * 114 * <h5 class='section'>See Also:</h5> 115 * <ul> 116 * <li class='ja'>{@link Bean#typeName()} 117 * </ul> 118 * 119 * @param value The new value for this setting. 120 * @return This object (for method chaining). 121 */ 122 public BeanFilterBuilder<T> typeName(String value) { 123 this.typeName = value; 124 return this; 125 } 126 127 /** 128 * Configuration property: Bean property includes. 129 * 130 * <p> 131 * Specifies the set and order of names of properties associated with the bean class. 132 * 133 * <h5 class='section'>Example:</h5> 134 * <p class='bcode w800'> 135 * <jc>// Define our filter.</jc> 136 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 137 * <jk>public</jk> MyFilter() { 138 * includeProperties(<js>"foo,bar,baz"</js>); 139 * } 140 * } 141 * 142 * <jc>// Register it with a serializer.</jc> 143 * WriterSerializer s = JsonSerializer 144 * .<jsm>create</jsm>() 145 * .beanFilters(MyFilter.<jk>class</jk>) 146 * .build(); 147 * 148 * <jc>// Only serializes the properties 'foo', 'bar', and 'baz'.</jc> 149 * String json = s.serialize(<jk>new</jk> MyBean()); 150 * </p> 151 * 152 * <h5 class='section'>See Also:</h5> 153 * <ul> 154 * <li class='ja'>{@link Bean#properties()} 155 * <li class='jf'>{@link BeanContext#BEAN_includeProperties} 156 * </ul> 157 * 158 * @param value 159 * The new value for this setting. 160 * <br>Values can contain comma-delimited list of property names. 161 * @return This object (for method chaining). 162 */ 163 public BeanFilterBuilder<T> properties(String...value) { 164 this.includeProperties = value; 165 return this; 166 } 167 168 /** 169 * Configuration property: Bean property excludes. 170 * 171 * <p> 172 * Specifies properties to exclude from the bean class. 173 * 174 * <h5 class='section'>Example:</h5> 175 * <p class='bcode w800'> 176 * <jc>// Define our filter.</jc> 177 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 178 * <jk>public</jk> MyFilter() { 179 * excludeProperties(<js>"foo,bar"</js>); 180 * } 181 * } 182 * 183 * <jc>// Register it with a serializer.</jc> 184 * WriterSerializer s = JsonSerializer 185 * .<jsm>create</jsm>() 186 * .beanFilters(MyFilter.<jk>class</jk>) 187 * .build(); 188 * 189 * <jc>// Serializes all properties except for 'foo' and 'bar'.</jc> 190 * String json = s.serialize(<jk>new</jk> MyBean()); 191 * </p> 192 * 193 * <h5 class='section'>See Also:</h5> 194 * <ul> 195 * <li class='ja'>{@link Bean#excludeProperties()} 196 * <li class='jf'>{@link BeanContext#BEAN_excludeProperties} 197 * </ul> 198 * 199 * @param value 200 * The new value for this setting. 201 * <br>Values can contain comma-delimited list of property names. 202 * @return This object (for method chaining). 203 */ 204 public BeanFilterBuilder<T> excludeProperties(String...value) { 205 this.excludeProperties = value; 206 return this; 207 } 208 209 /** 210 * Configuration property: Bean interface class. 211 * 212 * Identifies a class to be used as the interface class for this and all subclasses. 213 * 214 * <p> 215 * When specified, only the list of properties defined on the interface class will be used during serialization. 216 * <br>Additional properties on subclasses will be ignored. 217 * 218 * <p class='bcode w800'> 219 * <jc>// Parent class</jc> 220 * <jk>public abstract class</jk> A { 221 * <jk>public</jk> String <jf>f0</jf> = <js>"f0"</js>; 222 * } 223 * 224 * <jc>// Sub class</jc> 225 * <jk>public class</jk> A1 <jk>extends</jk> A { 226 * <jk>public</jk> String <jf>f1</jf> = <js>"f1"</js>; 227 * } 228 * 229 * <jc>// Define our filter.</jc> 230 * <jk>public class</jk> AFilter <jk>extends</jk> BeanFilterBuilder<A> { 231 * <jk>public</jk> AFilter() { 232 * interfaceClass(A.<jk>class</jk>); 233 * } 234 * } 235 * 236 * <jc>// Register it with a serializer.</jc> 237 * WriterSerializer s = JsonSerializer 238 * .<jsm>create</jsm>() 239 * .beanFilters(AFilter.<jk>class</jk>) 240 * .build(); 241 * 242 * <jc>// Use it.</jc> 243 * A1 a1 = <jk>new</jk> A1(); 244 * String r = s.serialize(a1); 245 * <jsm>assertEquals</jsm>(<js>"{f0:'f0'}"</js>, r); <jc>// Note f1 is not serialized</jc> 246 * </p> 247 * 248 * <p> 249 * Note that this filter can be used on the parent class so that it filters to all child classes, or can be set 250 * individually on the child classes. 251 * 252 * <h5 class='section'>See Also:</h5> 253 * <ul> 254 * <li class='ja'>{@link Bean#interfaceClass()} 255 * <li class='jf'>{@link BeanContext#BEAN_beanFilters} 256 * </ul> 257 * 258 * @param value The new value for this setting. 259 * @return This object (for method chaining). 260 */ 261 public BeanFilterBuilder<T> interfaceClass(Class<?> value) { 262 this.interfaceClass = value; 263 return this; 264 } 265 266 /** 267 * Configuration property: Bean stop class. 268 * 269 * <p> 270 * Identifies a stop class for this class and all subclasses. 271 * 272 * <p> 273 * Identical in purpose to the stop class specified by {@link Introspector#getBeanInfo(Class, Class)}. 274 * <br>Any properties in the stop class or in its base classes will be ignored during analysis. 275 * 276 * <p> 277 * For example, in the following class hierarchy, instances of <code>C3</code> will include property <code>p3</code>, 278 * but not <code>p1</code> or <code>p2</code>. 279 * 280 * <h5 class='section'>Example:</h5> 281 * <p class='bcode w800'> 282 * <jk>public class</jk> C1 { 283 * <jk>public int</jk> getP1(); 284 * } 285 * 286 * <jk>public class</jk> C2 <jk>extends</jk> C1 { 287 * <jk>public int</jk> getP2(); 288 * } 289 * 290 * <jk>public class</jk> C3 <jk>extends</jk> C2 { 291 * <jk>public int</jk> getP3(); 292 * } 293 * 294 * <jc>// Define our filter.</jc> 295 * <jk>public class</jk> C3Filter <jk>extends</jk> BeanFilterBuilder<C3> { 296 * <jk>public</jk> C3Filter() { 297 * stopClass(C2.<jk>class</jk>); 298 * } 299 * } 300 * 301 * <jc>// Register it with a serializer.</jc> 302 * WriterSerializer s = JsonSerializer 303 * .<jsm>create</jsm>() 304 * .beanFilters(C3Filter.<jk>class</jk>) 305 * .build(); 306 * 307 * <jc>// Serializes property 'p3', but NOT 'p1' or 'p2'.</jc> 308 * String json = s.serialize(<jk>new</jk> C3()); 309 * </p> 310 * 311 * <h5 class='section'>See Also:</h5> 312 * <ul> 313 * <li class='ja'>{@link Bean#stopClass()} 314 * </ul> 315 * 316 * @param value The new value for this setting. 317 * @return This object (for method chaining). 318 */ 319 public BeanFilterBuilder<T> stopClass(Class<?> value) { 320 this.stopClass = value; 321 return this; 322 } 323 324 /** 325 * Configuration property: Sort bean properties. 326 * 327 * <p> 328 * When <jk>true</jk>, all bean properties will be serialized and access in alphabetical order. 329 * <br>Otherwise, the natural order of the bean properties is used which is dependent on the JVM vendor. 330 * 331 * <h5 class='section'>Example:</h5> 332 * <p class='bcode w800'> 333 * <jc>// Define our filter.</jc> 334 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 335 * <jk>public</jk> MyFilter() { 336 * sortProperties(); 337 * } 338 * } 339 * 340 * <jc>// Register it with a serializer.</jc> 341 * WriterSerializer s = JsonSerializer 342 * .<jsm>create</jsm>() 343 * .beanFilters(MyFilter.<jk>class</jk>) 344 * .build(); 345 * 346 * <jc>// Properties will be sorted alphabetically.</jc> 347 * String json = s.serialize(<jk>new</jk> MyBean()); 348 * </p> 349 * 350 * <h5 class='section'>See Also:</h5> 351 * <ul> 352 * <li class='ja'>{@link Bean#sort()} 353 * <li class='jf'>{@link BeanContext#BEAN_sortProperties} 354 * </ul> 355 * 356 * @param value 357 * The new value for this property. 358 * <br>The default is <jk>false</jk>. 359 * @return This object (for method chaining). 360 */ 361 public BeanFilterBuilder<T> sortProperties(boolean value) { 362 this.sortProperties = value; 363 return this; 364 } 365 366 /** 367 * Configuration property: Sort bean properties. 368 * 369 * <p> 370 * Shortcut for calling <code>sortProperties(<jk>true</jk>)</code>. 371 * 372 * <h5 class='section'>See Also:</h5> 373 * <ul> 374 * <li class='ja'>{@link Bean#sort()} 375 * <li class='jf'>{@link BeanContext#BEAN_sortProperties} 376 * </ul> 377 * 378 * @return This object (for method chaining). 379 */ 380 public BeanFilterBuilder<T> sortProperties() { 381 this.sortProperties = true; 382 return this; 383 } 384 385 /** 386 * Configuration property: Find fluent setters. 387 * 388 * <p> 389 * When enabled, fluent setters are detected on beans. 390 * 391 * <p> 392 * Fluent setters must have the following attributes: 393 * <ul> 394 * <li>Public. 395 * <li>Not static. 396 * <li>Take in one parameter. 397 * <li>Return the bean itself. 398 * </ul> 399 * 400 * <h5 class='section'>Example:</h5> 401 * <p class='bcode w800'> 402 * <jc>// Define our filter.</jc> 403 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 404 * <jk>public</jk> MyFilter() { 405 * fluentSetters(); 406 * } 407 * } 408 * </p> 409 * 410 * <h5 class='section'>See Also:</h5> 411 * <ul> 412 * <li class='ja'>{@link Bean#fluentSetters()} 413 * <li class='jf'>{@link BeanContext#BEAN_fluentSetters} 414 * </ul> 415 * 416 * @param value 417 * The new value for this property. 418 * <br>The default is <jk>false</jk>. 419 * @return This object (for method chaining). 420 */ 421 public BeanFilterBuilder<T> fluentSetters(boolean value) { 422 this.fluentSetters = value; 423 return this; 424 } 425 426 /** 427 * Configuration property: Find fluent setters. 428 * 429 * <p> 430 * Shortcut for calling <code>fluentSetters(<jk>true</jk>)</code>. 431 * 432 * <h5 class='section'>See Also:</h5> 433 * <ul> 434 * <li class='ja'>{@link Bean#fluentSetters()} 435 * <li class='jf'>{@link BeanContext#BEAN_fluentSetters} 436 * </ul> 437 * 438 * @return This object (for method chaining). 439 */ 440 public BeanFilterBuilder<T> fluentSetters() { 441 this.fluentSetters = true; 442 return this; 443 } 444 445 /** 446 * Configuration property: Bean property namer 447 * 448 * <p> 449 * The class to use for calculating bean property names. 450 * 451 * <h5 class='section'>Example:</h5> 452 * <p class='bcode w800'> 453 * <jc>// Define our filter.</jc> 454 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 455 * <jk>public</jk> MyFilter() { 456 * <jc>// Use Dashed-Lower-Case property names.</jc> 457 * <jc>// (e.g. "foo-bar-url" instead of "fooBarURL")</jc> 458 * propertyNamer(PropertyNamerDLC.<jk>class</jk>); 459 * } 460 * } 461 * 462 * <jc>// Register it with a serializer or parser.</jc> 463 * WriterSerializer s = JsonSerializer 464 * .<jsm>create</jsm>() 465 * .beanFilters(MyFilter.<jk>class</jk>) 466 * .build(); 467 * 468 * <jc>// Properties names will be Dashed-Lower-Case.</jc> 469 * String json = s.serialize(<jk>new</jk> MyBean()); 470 * </p> 471 * 472 * <h5 class='section'>See Also:</h5> 473 * <ul> 474 * <li class='ja'>{@link Bean#propertyNamer()} 475 * <li class='jf'>{@link BeanContext#BEAN_propertyNamer} 476 * <li class='jc'>{@link PropertyNamer} 477 * </ul> 478 * 479 * @param value 480 * The new value for this setting. 481 * <br>The default is {@link PropertyNamerDefault}. 482 * @return This object (for method chaining). 483 */ 484 public BeanFilterBuilder<T> propertyNamer(Class<? extends PropertyNamer> value) { 485 this.propertyNamer = value; 486 return this; 487 } 488 489 /** 490 * Configuration property: Bean dictionary. 491 * 492 * <p> 493 * Adds to the list of classes that make up the bean dictionary for this bean. 494 * 495 * <h5 class='section'>Example:</h5> 496 * <p class='bcode w800'> 497 * <jc>// Define our filter.</jc> 498 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 499 * <jk>public</jk> MyFilter() { 500 * <jc>// Our bean contains generic collections of Foo and Bar objects.</jc> 501 * beanDictionary(Foo.<jk>class</jk>, Bar.<jk>class</jk>); 502 * } 503 * } 504 * 505 * <jc>// Register it with a parser.</jc> 506 * ReaderParser p = JsonParser 507 * .<jsm>create</jsm>() 508 * .beanFilters(MyFilter.<jk>class</jk>) 509 * .build(); 510 * 511 * <jc>// Instantiate our bean.</jc> 512 * MyBean myBean = p.parse(json); 513 * </p> 514 * 515 * <h5 class='section'>See Also:</h5> 516 * <ul> 517 * <li class='ja'>{@link Bean#beanDictionary()} 518 * <li class='jf'>{@link BeanContext#BEAN_beanDictionary} 519 * </ul> 520 * 521 * @param values 522 * The values to add to this property. 523 * @return This object (for method chaining). 524 */ 525 public BeanFilterBuilder<T> beanDictionary(Class<?>...values) { 526 if (beanDictionary == null) 527 beanDictionary = new ArrayList<>(Arrays.asList(values)); 528 else for (Class<?> cc : values) 529 beanDictionary.add(cc); 530 return this; 531 } 532 533 /** 534 * Configuration property: Property filter. 535 * 536 * <p> 537 * The property filter to use for intercepting and altering getter and setter calls. 538 * 539 * <h5 class='section'>Example:</h5> 540 * <p class='bcode w800'> 541 * <jc>// Define our filter.</jc> 542 * <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder<MyBean> { 543 * <jk>public</jk> MyFilter() { 544 * <jc>// Our bean contains generic collections of Foo and Bar objects.</jc> 545 * propertyFilter(AddressPropertyFilter.<jk>class</jk>); 546 * } 547 * } 548 * 549 * <jc>// Register it with a serializer or parser.</jc> 550 * WriterSerializer s = JsonSerializer 551 * .<jsm>create</jsm>() 552 * .beanFilters(MyFilter.<jk>class</jk>) 553 * .build(); 554 * </p> 555 * 556 * <h5 class='section'>See Also:</h5> 557 * <ul> 558 * <li class='ja'>{@link Bean#propertyFilter()} 559 * <li class='jc'>{@link PropertyFilter} 560 * </ul> 561 * 562 * @param value 563 * The new value for this setting. 564 * <br>The default value is {@link PropertyFilter}. 565 * @return This object (for method chaining). 566 */ 567 public BeanFilterBuilder<T> propertyFilter(Class<? extends PropertyFilter> value) { 568 this.propertyFilter = value; 569 return this; 570 } 571 572 /** 573 * Creates a {@link BeanFilter} with settings in this builder class. 574 * 575 * @return A new {@link BeanFilter} instance. 576 */ 577 public BeanFilter build() { 578 return new BeanFilter(this); 579 } 580}