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