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&lt;MyBean&gt; {
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&lt;MyBean&gt; {
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&lt;MyBean&gt; {
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&lt;MyBean&gt; {
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&lt;A&gt; {
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&lt;C3&gt; {
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&lt;MyBean&gt; {
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&lt;MyBean&gt; {
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&lt;MyBean&gt; {
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&lt;MyBean&gt; {
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&lt;MyBean&gt; {
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}