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