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