001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau;
018
019import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.ClassUtils.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024
025import java.beans.*;
026import java.util.*;
027
028import org.apache.juneau.annotation.*;
029import org.apache.juneau.commons.reflect.*;
030import org.apache.juneau.cp.*;
031import org.apache.juneau.swap.*;
032
033/**
034 * Bean filter for customizing bean property handling during serialization and parsing.
035 *
036 * <p>
037 * Bean filters provide fine-grained control over how beans are processed, allowing you to:
038 * <ul>
039 *    <li>Specify which properties to include or exclude
040 *    <li>Define read-only and write-only properties
041 *    <li>Control property ordering and naming
042 *    <li>Configure bean dictionaries for polymorphic types
043 *    <li>Intercept property getter and setter calls
044 *    <li>Define interface classes and stop classes for property filtering
045 * </ul>
046 *
047 * <p>
048 * Bean filters are created using the {@link Builder} class, which is the programmatic equivalent to the
049 * {@link Bean @Bean} annotation. Filters can be registered with serializers and parsers to customize
050 * how specific bean classes are handled.
051 *
052 * <h5 class='section'>Example:</h5>
053 * <p class='bjava'>
054 *    <jc>// Define a custom filter for MyBean</jc>
055 *    <jk>public class</jk> MyBeanFilter <jk>extends</jk> BeanFilter.Builder&lt;MyBean&gt; {
056 *       <jk>public</jk> MyBeanFilter() {
057 *          properties(<js>"id,name,email"</js>);  <jc>// Only include these properties</jc>
058 *          excludeProperties(<js>"password"</js>);  <jc>// Exclude sensitive data</jc>
059 *          readOnlyProperties(<js>"id"</js>);  <jc>// ID is read-only</jc>
060 *          sortProperties();  <jc>// Sort properties alphabetically</jc>
061 *       }
062 *    }
063 *
064 *    <jc>// Register the filter with a serializer</jc>
065 *    WriterSerializer <jv>serializer</jv> = JsonSerializer
066 *       .<jsm>create</jsm>()
067 *       .beanFilters(MyBeanFilter.<jk>class</jk>)
068 *       .build();
069 * </p>
070 *
071 * <h5 class='section'>See Also:</h5>
072 * <ul>
073 *    <li class='ja'>{@link Bean @Bean}
074 *    <li class='jc'>{@link BeanInterceptor}
075 *    <li class='jc'>{@link PropertyNamer}
076 * </ul>
077 */
078@SuppressWarnings("rawtypes")
079public class BeanFilter {
080
081   /**
082    * Builder class.
083    */
084   public static class Builder {
085
086      private ClassInfoTyped<?> beanClass;
087      private String typeName, example;
088      private Set<String> properties = set(), excludeProperties = set(), readOnlyProperties = set(), writeOnlyProperties = set();
089      private ClassInfo implClass, interfaceClass, stopClass;
090      private boolean sortProperties, fluentSetters;
091      private BeanCreator<PropertyNamer> propertyNamer = BeanCreator.of(PropertyNamer.class);
092      private List<ClassInfo> dictionary;
093      private BeanCreator<BeanInterceptor> interceptor = BeanCreator.of(BeanInterceptor.class);
094
095      /**
096       * Constructor.
097       *
098       * @param beanClass The bean class that this filter applies to.
099       */
100      protected Builder(ClassInfoTyped<?> beanClass) {
101         this.beanClass = beanClass;
102      }
103
104      /**
105       * Applies the information in the specified list of {@link Bean @Bean} annotations to this filter.
106       *
107       * @param annotations The annotations to apply.
108       * @return This object.
109       */
110      public Builder applyAnnotations(List<Bean> annotations) {
111
112         annotations.forEach(x -> {
113            if (isAnyNotEmpty(x.properties(), x.p()))
114               properties(x.properties(), x.p());
115            if (x.sort())
116               sortProperties(true);
117            if (x.findFluentSetters())
118               findFluentSetters();
119            if (isAnyNotEmpty(x.excludeProperties(), x.xp()))
120               excludeProperties(x.excludeProperties(), x.xp());
121            if (isAnyNotEmpty(x.readOnlyProperties(), x.ro()))
122               readOnlyProperties(x.readOnlyProperties(), x.ro());
123            if (isAnyNotEmpty(x.writeOnlyProperties(), x.wo()))
124               writeOnlyProperties(x.writeOnlyProperties(), x.wo());
125            if (ne(x.typeName()))
126               typeName(x.typeName());
127            if (isNotVoid(x.propertyNamer()))
128               propertyNamer(x.propertyNamer());
129            if (isNotVoid(x.interfaceClass()))
130               interfaceClass(x.interfaceClass());
131            if (isNotVoid(x.stopClass()))
132               stopClass(x.stopClass());
133            if (isNotVoid(x.interceptor()))
134               interceptor(x.interceptor());
135            if (isNotVoid(x.implClass()))
136               implClass(x.implClass());
137            if (isNotEmptyArray(x.dictionary()))
138               dictionary(x.dictionary());
139            if (ne(x.example()))
140               example(x.example());
141         });
142         return this;
143      }
144
145      /**
146       * Creates a {@link BeanFilter} with settings in this builder class.
147       *
148       * @return A new {@link BeanFilter} instance.
149       */
150      public BeanFilter build() {
151         return new BeanFilter(this);
152      }
153
154      /**
155       * Bean dictionary.
156       *
157       * <p>
158       * Adds to the list of classes that make up the bean dictionary for this bean.
159       *
160       * <h5 class='section'>Example:</h5>
161       * <p class='bjava'>
162       *    <jc>// Define our filter.</jc>
163       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
164       *       <jk>public</jk> MyFilter() {
165       *          <jc>// Our bean contains generic collections of Foo and Bar objects.</jc>
166       *          beanDictionary(Foo.<jk>class</jk>, Bar.<jk>class</jk>);
167       *       }
168       *    }
169       *
170       *    <jc>// Register it with a parser.</jc>
171       *    ReaderParser <jv>parser</jv> = JsonParser
172       *       .<jsm>create</jsm>()
173       *       .beanFilters(MyFilter.<jk>class</jk>)
174       *       .build();
175       *
176       *    <jc>// Instantiate our bean.</jc>
177       *    MyBean <jv>bean</jv> = <jv>parser</jv>.parse(<jv>json</jv>);
178       * </p>
179       *
180       * <h5 class='section'>See Also:</h5><ul>
181       *    <li class='ja'>{@link Bean#dictionary()}
182       *    <li class='jm'>{@link BeanContext.Builder#beanDictionary(Class...)}
183       * </ul>
184       *
185       * @param values
186       *    The values to add to this property.
187       * @return This object.
188       */
189      public Builder dictionary(Class<?>...values) {
190         if (dictionary == null)
191            dictionary = list();
192         for (var cc : values)
193            dictionary.add(info(cc));
194         return this;
195      }
196
197      /**
198       * Bean dictionary.
199       *
200       * <p>
201       * Adds to the list of classes that make up the bean dictionary for this bean.
202       *
203       * <p>
204       * Same as the other dictionary method but accepts {@link ClassInfo} objects directly instead of {@link Class} objects.
205       *
206       * @param values The class info objects to add to this property.
207       * @return This object.
208       */
209      public Builder dictionary(ClassInfo...values) {
210         if (dictionary == null)
211            dictionary = list();
212         for (var ci : values)
213            dictionary.add(ci);
214         return this;
215      }
216
217      /**
218       * Example.
219       *
220       * @param value
221       *    The new value for this property.
222       * @return This object.
223       */
224      public Builder example(String value) {
225         example = value;
226         return this;
227      }
228
229      /**
230       * Bean property excludes.
231       *
232       * <p>
233       * Specifies properties to exclude from the bean class.
234       *
235       * <h5 class='section'>Example:</h5>
236       * <p class='bjava'>
237       *    <jc>// Define our filter.</jc>
238       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
239       *       <jk>public</jk> MyFilter() {
240       *          excludeProperties(<js>"foo,bar"</js>);
241       *       }
242       *    }
243       *
244       *    <jc>// Register it with a serializer.</jc>
245       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
246       *       .<jsm>create</jsm>()
247       *       .beanFilters(MyFilter.<jk>class</jk>)
248       *       .build();
249       *
250       *    <jc>// Serializes all properties except for 'foo' and 'bar'.</jc>
251       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean());
252       * </p>
253       *
254       * <h5 class='section'>See Also:</h5><ul>
255       *    <li class='ja'>{@link Bean#excludeProperties()}
256       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesExcludes(Class, String)}
257       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesExcludes(String, String)}
258       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesExcludes(Map)}
259       * </ul>
260       *
261       * @param value
262       *    The new value for this setting.
263       *    <br>Values can contain comma-delimited list of property names.
264       * @return This object.
265       */
266      public Builder excludeProperties(String...value) {
267         excludeProperties = set();
268         for (var v : value)
269            split(v, x -> excludeProperties.add(x));
270         return this;
271      }
272
273      /**
274       * Find fluent setters.
275       *
276       * <p>
277       * When enabled, fluent setters are detected on beans.
278       *
279       * <p>
280       * Fluent setters must have the following attributes:
281       * <ul>
282       *    <li>Public.
283       *    <li>Not static.
284       *    <li>Take in one parameter.
285       *    <li>Return the bean itself.
286       * </ul>
287       *
288       * <h5 class='section'>Example:</h5>
289       * <p class='bjava'>
290       *    <jc>// Define our filter.</jc>
291       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
292       *       <jk>public</jk> MyFilter() {
293       *          findFluentSetters();
294       *       }
295       *    }
296       * </p>
297       *
298       * <h5 class='section'>See Also:</h5><ul>
299       *    <li class='ja'>{@link Bean#findFluentSetters()}
300       *    <li class='jm'>{@link BeanContext.Builder#findFluentSetters()}
301       * </ul>
302       *
303       * @return This object.
304       */
305      public Builder findFluentSetters() {
306         fluentSetters = true;
307         return this;
308      }
309
310      /**
311       * Bean implementation class.
312       *
313       * @param value The new value for this setting.
314       * @return This object.
315       */
316      public Builder implClass(Class<?> value) {
317         implClass = value == null ? null : info(value);
318         return this;
319      }
320
321      /**
322       * Implementation class.
323       *
324       * <p>
325       * Same as the other implClass method but accepts a {@link ClassInfo} object directly instead of a {@link Class} object.
326       *
327       * @param value The new value for this setting.
328       * @return This object.
329       */
330      public Builder implClass(ClassInfo value) {
331         implClass = value;
332         return this;
333      }
334
335      /**
336       * Bean interceptor.
337       *
338       * <p>
339       * The interceptor to use for intercepting and altering getter and setter calls.
340       *
341       * <h5 class='section'>Example:</h5>
342       * <p class='bjava'>
343       *    <jc>// Define our filter.</jc>
344       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
345       *       <jk>public</jk> MyFilter() {
346       *          <jc>// Our bean contains generic collections of Foo and Bar objects.</jc>
347       *          interceptor(AddressInterceptor.<jk>class</jk>);
348       *       }
349       *    }
350       *
351       *    <jc>// Register it with a serializer or parser.</jc>
352       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
353       *       .<jsm>create</jsm>()
354       *       .beanFilters(MyFilter.<jk>class</jk>)
355       *       .build();
356       * </p>
357       *
358       * <h5 class='section'>See Also:</h5><ul>
359       *    <li class='ja'>{@link Bean#interceptor()}
360       *    <li class='jc'>{@link BeanInterceptor}
361       * </ul>
362       *
363       * @param value
364       *    The new value for this setting.
365       *    <br>The default value is {@link BeanInterceptor}.
366       * @return This object.
367       */
368      public Builder interceptor(Class<?> value) {
369         interceptor.type(value);
370         return this;
371      }
372
373      /**
374       * Bean interceptor.
375       *
376       * <p>
377       * Same as the other interceptor method but accepts a {@link ClassInfo} object directly instead of a {@link Class} object.
378       *
379       * <p>
380       * The interceptor to use for intercepting and altering getter and setter calls.
381       *
382       * <h5 class='section'>See Also:</h5><ul>
383       *    <li class='ja'>{@link Bean#interceptor()}
384       *    <li class='jc'>{@link BeanInterceptor}
385       * </ul>
386       *
387       * @param value The new value for this setting.
388       * @return This object.
389       */
390      public Builder interceptor(ClassInfo value) {
391         interceptor.type(value);
392         return this;
393      }
394
395      /**
396       * Bean interface class.
397       *
398       * Identifies a class to be used as the interface class for this and all subclasses.
399       *
400       * <p>
401       * When specified, only the list of properties defined on the interface class will be used during serialization.
402       * <br>Additional properties on subclasses will be ignored.
403       *
404       * <p class='bjava'>
405       *    <jc>// Parent class</jc>
406       *    <jk>public abstract class</jk> A {
407       *       <jk>public</jk> String <jf>f0</jf> = <js>"f0"</js>;
408       *    }
409       *
410       *    <jc>// Sub class</jc>
411       *    <jk>public class</jk> A1 <jk>extends</jk> A {
412       *       <jk>public</jk> String <jf>f1</jf> = <js>"f1"</js>;
413       *    }
414       *
415       *    <jc>// Define our filter.</jc>
416       *    <jk>public class</jk> AFilter <jk>extends</jk> Builder&lt;A&gt; {
417       *       <jk>public</jk> AFilter() {
418       *          interfaceClass(A.<jk>class</jk>);
419       *       }
420       *    }
421       *
422       *    <jc>// Register it with a serializer.</jc>
423       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
424       *       .<jsm>create</jsm>()
425       *       .beanFilters(AFilter.<jk>class</jk>)
426       *       .build();
427       *
428       *    <jc>// Use it.</jc>
429       *    A1 <jv>a1</jv> = <jk>new</jk> A1();
430       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jv>a1</jv>);
431       *    <jsm>assertEquals</jsm>(<js>"{f0:'f0'}"</js>, <jv>json</jv>);  <jc>// Note f1 is not serialized</jc>
432       * </p>
433       *
434       * <p>
435       * Note that this filter can be used on the parent class so that it filters to all child classes, or can be set
436       * individually on the child classes.
437       *
438       * <h5 class='section'>See Also:</h5><ul>
439       *    <li class='ja'>{@link Bean#interfaceClass()}
440       * </ul>
441       *
442       * @param value The new value for this setting.
443       * @return This object.
444       */
445      public Builder interfaceClass(Class<?> value) {
446         interfaceClass = value == null ? null : info(value);
447         return this;
448      }
449
450      /**
451       * Interface class.
452       *
453       * <p>
454       * Same as the other interfaceClass method but accepts a {@link ClassInfo} object directly instead of a {@link Class} object.
455       *
456       * @param value The new value for this setting.
457       * @return This object.
458       */
459      public Builder interfaceClass(ClassInfo value) {
460         interfaceClass = value;
461         return this;
462      }
463
464      /**
465       * Bean property includes.
466       *
467       * <p>
468       * Specifies the set and order of names of properties associated with the bean class.
469       *
470       * <h5 class='section'>Example:</h5>
471       * <p class='bjava'>
472       *    <jc>// Define our filter.</jc>
473       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
474       *       <jk>public</jk> MyFilter() {
475       *          properties(<js>"foo,bar,baz"</js>);
476       *       }
477       *    }
478       *
479       *    <jc>// Register it with a serializer.</jc>
480       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
481       *       .<jsm>create</jsm>()
482       *       .beanFilters(MyFilter.<jk>class</jk>)
483       *       .build();
484       *
485       *    <jc>// Only serializes the properties 'foo', 'bar', and 'baz'.</jc>
486       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean());
487       * </p>
488       *
489       * <h5 class='section'>See Also:</h5><ul>
490       *    <li class='ja'>{@link Bean#properties()}
491       *    <li class='jm'>{@link BeanContext.Builder#beanProperties(Class, String)}
492       *    <li class='jm'>{@link BeanContext.Builder#beanProperties(String, String)}
493       *    <li class='jm'>{@link BeanContext.Builder#beanProperties(Map)}
494       * </ul>
495       *
496       * @param value
497       *    The new value for this setting.
498       *    <br>Values can contain comma-delimited list of property names.
499       * @return This object.
500       */
501      public Builder properties(String...value) {
502         properties = set();
503         for (var v : value)
504            split(v, x -> properties.add(x));
505         return this;
506      }
507
508      /**
509       * Bean property namer
510       *
511       * <p>
512       * The class to use for calculating bean property names.
513       *
514       * <h5 class='section'>Example:</h5>
515       * <p class='bjava'>
516       *    <jc>// Define our filter.</jc>
517       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
518       *       <jk>public</jk> MyFilter() {
519       *          <jc>// Use Dashed-Lower-Case property names.</jc>
520       *          <jc>// (e.g. "foo-bar-url" instead of "fooBarURL")</jc>
521       *          propertyNamer(PropertyNamerDLC.<jk>class</jk>);
522       *       }
523       *    }
524       *
525       *    <jc>// Register it with a serializer or parser.</jc>
526       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
527       *       .<jsm>create</jsm>()
528       *       .beanFilters(MyFilter.<jk>class</jk>)
529       *       .build();
530       *
531       *    <jc>// Properties names will be Dashed-Lower-Case.</jc>
532       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean());
533       * </p>
534       *
535       * <h5 class='section'>See Also:</h5><ul>
536       *    <li class='ja'>{@link Bean#propertyNamer()}
537       *    <li class='jm'>{@link BeanContext.Builder#propertyNamer(Class)}
538       *    <li class='jc'>{@link PropertyNamer}
539       * </ul>
540       *
541       * @param value
542       *    The new value for this setting.
543       *    <br>The default is {@link BasicPropertyNamer}.
544       * @return This object.
545       */
546      public Builder propertyNamer(Class<? extends PropertyNamer> value) {
547         propertyNamer.type(value);
548         return this;
549      }
550
551      /**
552       * Bean property namer.
553       *
554       * <p>
555       * Same as the other propertyNamer method but accepts a {@link ClassInfoTyped} object directly instead of a {@link Class} object.
556       *
557       * <p>
558       * The class to use for calculating bean property names.
559       *
560       * <h5 class='section'>See Also:</h5><ul>
561       *    <li class='ja'>{@link Bean#propertyNamer()}
562       *    <li class='jm'>{@link BeanContext.Builder#propertyNamer(Class)}
563       *    <li class='jc'>{@link PropertyNamer}
564       * </ul>
565       *
566       * @param value The new value for this setting.
567       * @return This object.
568       */
569      public Builder propertyNamer(ClassInfoTyped<? extends PropertyNamer> value) {
570         propertyNamer.type(value);
571         return this;
572      }
573
574      /**
575       * Read-only bean properties.
576       *
577       * <p>
578       * Specifies one or more properties on a bean that are read-only despite having valid getters.
579       * Serializers will serialize such properties as usual, but parsers will silently ignore them.
580       *
581       * <h5 class='section'>Example:</h5>
582       * <p class='bjava'>
583       *    <jc>// Define our filter.</jc>
584       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
585       *       <jk>public</jk> MyFilter() {
586       *          readOnlyProperties(<js>"foo,bar"</js>);
587       *       }
588       *    }
589       *
590       *    <jc>// Register it with a parser.</jc>
591       *  ReaderParser <jv>parser</jv> = JsonParser
592       *       .<jsm>create</jsm>()
593       *       .beanFilters(MyFilter.<jk>class</jk>)
594       *       .build();
595       *
596       *    <jc>// Parsers all properties except for 'foo' and 'bar'.</jc>
597       *    MyBean <jv>bean</jv> = <jv>parser</jv>.parse(<js>"..."</js>, MyBean.<jk>class</jk>);
598       * </p>
599       *
600       * <h5 class='section'>See Also:</h5><ul>
601       *    <li class='ja'>{@link Bean#readOnlyProperties()}
602       *    <li class='ja'>{@link Beanp#ro()}
603       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesReadOnly(Class, String)}
604       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesReadOnly(String, String)}
605       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesReadOnly(Map)}
606       * </ul>
607       *
608       * @param value
609       *    The new value for this setting.
610       *    <br>Values can contain comma-delimited list of property names.
611       * @return This object.
612       */
613      public Builder readOnlyProperties(String...value) {
614         readOnlyProperties = set();
615         for (var v : value)
616            split(v, x -> readOnlyProperties.add(x));
617         return this;
618      }
619
620      /**
621       * Sort bean properties.
622       *
623       * <p>
624       * Shortcut for calling <code>sortProperties(<jk>true</jk>)</code>.
625       *
626       * <h5 class='section'>See Also:</h5><ul>
627       *    <li class='ja'>{@link Bean#sort()}
628       *    <li class='jf'>{@link BeanContext.Builder#sortProperties()}
629       * </ul>
630       *
631       * @return This object.
632       */
633      public Builder sortProperties() {
634         sortProperties = true;
635         return this;
636      }
637
638      /**
639       * Sort bean properties.
640       *
641       * <p>
642       * When <jk>true</jk>, all bean properties will be serialized and access in alphabetical order.
643       * <br>Otherwise, the natural order of the bean properties is used which is dependent on the JVM vendor.
644       *
645       * <h5 class='section'>Example:</h5>
646       * <p class='bjava'>
647       *    <jc>// Define our filter.</jc>
648       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
649       *       <jk>public</jk> MyFilter() {
650       *          sortProperties();
651       *       }
652       *    }
653       *
654       *    <jc>// Register it with a serializer.</jc>
655       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
656       *       .<jsm>create</jsm>()
657       *       .beanFilters(MyFilter.<jk>class</jk>)
658       *       .build();
659       *
660       *    <jc>// Properties will be sorted alphabetically.</jc>
661       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean());
662       * </p>
663       *
664       * <h5 class='section'>See Also:</h5><ul>
665       *    <li class='ja'>{@link Bean#sort()}
666       *    <li class='jf'>{@link BeanContext.Builder#sortProperties()}
667       * </ul>
668       *
669       * @param value
670       *    The new value for this property.
671       *    <br>The default is <jk>false</jk>.
672       * @return This object.
673       */
674      public Builder sortProperties(boolean value) {
675         sortProperties = value;
676         return this;
677      }
678
679      /**
680       * Bean stop class.
681       *
682       * <p>
683       * Identifies a stop class for this class and all subclasses.
684       *
685       * <p>
686       * Identical in purpose to the stop class specified by {@link Introspector#getBeanInfo(Class, Class)}.
687       * <br>Any properties in the stop class or in its base classes will be ignored during analysis.
688       *
689       * <p>
690       * For example, in the following class hierarchy, instances of <c>C3</c> will include property <c>p3</c>,
691       * but not <c>p1</c> or <c>p2</c>.
692       *
693       * <h5 class='section'>Example:</h5>
694       * <p class='bjava'>
695       *    <jk>public class</jk> C1 {
696       *       <jk>public int</jk> getP1();
697       *    }
698       *
699       *    <jk>public class</jk> C2 <jk>extends</jk> C1 {
700       *       <jk>public int</jk> getP2();
701       *    }
702       *
703       *    <jk>public class</jk> C3 <jk>extends</jk> C2 {
704       *       <jk>public int</jk> getP3();
705       *    }
706       *
707       *    <jc>// Define our filter.</jc>
708       *    <jk>public class</jk> C3Filter <jk>extends</jk> Builder&lt;C3&gt; {
709       *       <jk>public</jk> C3Filter() {
710       *          stopClass(C2.<jk>class</jk>);
711       *       }
712       *    }
713       *
714       *    <jc>// Register it with a serializer.</jc>
715       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
716       *       .<jsm>create</jsm>()
717       *       .beanFilters(C3Filter.<jk>class</jk>)
718       *       .build();
719       *
720       *    <jc>// Serializes property 'p3', but NOT 'p1' or 'p2'.</jc>
721       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> C3());
722       * </p>
723       *
724       * <h5 class='section'>See Also:</h5><ul>
725       *    <li class='ja'>{@link Bean#stopClass()}
726       * </ul>
727       *
728       * @param value The new value for this setting.
729       * @return This object.
730       */
731      public Builder stopClass(Class<?> value) {
732         stopClass = value == null ? null : info(value);
733         return this;
734      }
735
736      /**
737       * Stop class.
738       *
739       * <p>
740       * Same as the other stopClass method but accepts a {@link ClassInfo} object directly instead of a {@link Class} object.
741       *
742       * @param value The new value for this setting.
743       * @return This object.
744       */
745      public Builder stopClass(ClassInfo value) {
746         stopClass = value;
747         return this;
748      }
749
750      /**
751       * Bean dictionary type name.
752       *
753       * <p>
754       * Specifies the dictionary type name for this bean.
755       *
756       * <h5 class='section'>Example:</h5>
757       * <p class='bjava'>
758       *    <jc>// Define our filter.</jc>
759       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
760       *       <jk>public</jk> MyFilter() {
761       *          typeName(<js>"mybean"</js>);
762       *       }
763       *    }
764       *
765       *    <jc>// Register it with a serializer or parser.</jc>
766       *    WriterSerializer <jv>serializer<jv> = JsonSerializer
767       *       .<jsm>create</jsm>()
768       *       .beanFilters(MyFilter.<jk>class</jk>)
769       *       .build();
770       *
771       *    <jc>// Produces:  "{_type:'mybean', ...}"</jc>
772       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean());
773       * </p>
774       *
775       * <h5 class='section'>See Also:</h5><ul>
776       *    <li class='ja'>{@link Bean#typeName()}
777       * </ul>
778       *
779       * @param value The new value for this setting.
780       * @return This object.
781       */
782      public Builder typeName(String value) {
783         typeName = value;
784         return this;
785      }
786
787      /**
788       * Write-only bean properties.
789       *
790       * <p>
791       * Specifies one or more properties on a bean that are write-only despite having valid setters.
792       * Parsers will parse such properties as usual, but serializers will silently ignore them.
793       *
794       * <h5 class='section'>Example:</h5>
795       * <p class='bjava'>
796       *    <jc>// Define our filter.</jc>
797       *    <jk>public class</jk> MyFilter <jk>extends</jk> Builder&lt;MyBean&gt; {
798       *       <jk>public</jk> MyFilter() {
799       *          writeOnlyProperties(<js>"foo,bar"</js>);
800       *       }
801       *    }
802       *
803       *    <jc>// Register it with a serializer.</jc>
804       *  WriterSerializer <jv>serializer</jv> = JsonSerializer
805       *       .<jsm>create</jsm>()
806       *       .beanFilters(MyFilter.<jk>class</jk>)
807       *       .build();
808       *
809       *    <jc>// Serializes all properties except for 'foo' and 'bar'.</jc>
810       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean());
811       * </p>
812       *
813       * <h5 class='section'>See Also:</h5><ul>
814       *    <li class='ja'>{@link Bean#writeOnlyProperties()}
815       *    <li class='ja'>{@link Beanp#wo()}
816       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesWriteOnly(Class, String)}
817       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesWriteOnly(String, String)}
818       *    <li class='jm'>{@link BeanContext.Builder#beanPropertiesWriteOnly(Map)}
819       * </ul>
820       *
821       * @param value
822       *    The new value for this setting.
823       *    <br>Values can contain comma-delimited list of property names.
824       * @return This object.
825       */
826      public Builder writeOnlyProperties(String...value) {
827         writeOnlyProperties = set();
828         for (var v : value)
829            split(v, x -> writeOnlyProperties.add(x));
830         return this;
831      }
832   }
833
834   /**
835    * Create a new builder for this object.
836    *
837    * @param <T> The bean class being filtered.
838    * @param beanClass The bean class being filtered.
839    * @return A new builder.
840    */
841   public static <T> Builder create(ClassInfoTyped<T> beanClass) {
842      return new Builder(beanClass);
843   }
844
845   private final ClassInfoTyped<?> beanClass;
846   private final List<ClassInfo> beanDictionary;
847   private final String example;
848   private final Set<String> excludeProperties;
849   private final boolean fluentSetters;
850   private final ClassInfo implClass;
851   private final ClassInfo interfaceClass;
852   private final BeanInterceptor interceptor;
853   private final Set<String> properties;
854   private final PropertyNamer propertyNamer;
855   private final Set<String> readOnlyProperties;
856   private final boolean sortProperties;
857   private final ClassInfo stopClass;
858   private final String typeName;
859   private final Set<String> writeOnlyProperties;
860
861   /**
862    * Constructor.
863    */
864   BeanFilter(Builder builder) {
865      this.beanClass = builder.beanClass;
866      this.typeName = builder.typeName;
867      this.properties = copyOf(builder.properties);
868      this.excludeProperties = copyOf(builder.excludeProperties);
869      this.readOnlyProperties = copyOf(builder.readOnlyProperties);
870      this.writeOnlyProperties = copyOf(builder.writeOnlyProperties);
871      this.example = builder.example;
872      this.implClass = builder.implClass;
873      this.interfaceClass = builder.interfaceClass;
874      this.stopClass = builder.stopClass;
875      this.sortProperties = builder.sortProperties;
876      this.fluentSetters = builder.fluentSetters;
877      this.propertyNamer = builder.propertyNamer.orElse(null);
878      this.beanDictionary = builder.dictionary == null ? list() : u(copyOf(builder.dictionary));
879      this.interceptor = builder.interceptor.orElse(BeanInterceptor.DEFAULT);
880   }
881
882   /**
883    * Returns the bean class that this filter applies to.
884    *
885    * @return The bean class that this filter applies to.
886    */
887   public ClassInfoTyped<?> getBeanClass() { return beanClass; }
888
889   /**
890    * Returns the bean dictionary defined on this bean.
891    *
892    * @return An unmodifiable list of the bean dictionary defined on this bean, or an empty list if no bean dictionary is defined.
893    */
894   public List<ClassInfo> getBeanDictionary() { return beanDictionary; }
895
896   /**
897    * Returns the example associated with this class.
898    *
899    * @return The example associated with this class, or <jk>null</jk> if no example is associated.
900    */
901   public String getExample() { return example; }
902
903   /**
904    * Returns the list of properties to ignore on a bean.
905    *
906    * @return The names of the properties to ignore on a bean, or an empty set to not ignore any properties.
907    */
908   public Set<String> getExcludeProperties() { return excludeProperties; }
909
910   /**
911    * Returns the implementation class associated with this class.
912    *
913    * @return The implementation class associated with this class, or <jk>null</jk> if no implementation class is associated.
914    */
915   public ClassInfo getImplClass() { return implClass; }
916
917   /**
918    * Returns the interface class associated with this class.
919    *
920    * @return The interface class associated with this class, or <jk>null</jk> if no interface class is associated.
921    */
922   public ClassInfo getInterfaceClass() { return interfaceClass; }
923
924   /**
925    * Returns the set and order of names of properties associated with a bean class.
926    *
927    * @return
928    *    The names of the properties associated with a bean class, or and empty set if all bean properties should
929    *    be used.
930    */
931   public Set<String> getProperties() { return properties; }
932
933   /**
934    * Returns the {@link PropertyNamer} associated with the bean to tailor the names of bean properties.
935    *
936    * @return The property namer class, or <jk>null</jk> if no property namer is associated with this bean property.
937    */
938   public PropertyNamer getPropertyNamer() { return propertyNamer; }
939
940   /**
941    * Returns the list of read-only properties on a bean.
942    *
943    * @return The names of the read-only properties on a bean, or an empty set to not have any read-only properties.
944    */
945   public Set<String> getReadOnlyProperties() { return readOnlyProperties; }
946
947   /**
948    * Returns the stop class associated with this class.
949    *
950    * @return The stop class associated with this class, or <jk>null</jk> if no stop class is associated.
951    */
952   public ClassInfo getStopClass() { return stopClass; }
953
954   /**
955    * Returns the dictionary name associated with this bean.
956    *
957    * @return The dictionary name associated with this bean, or <jk>null</jk> if no name is defined.
958    */
959   public String getTypeName() { return typeName; }
960
961   /**
962    * Returns the list of write-only properties on a bean.
963    *
964    * @return The names of the write-only properties on a bean, or an empty set to not have any write-only properties.
965    */
966   public Set<String> getWriteOnlyProperties() { return writeOnlyProperties; }
967
968   /**
969    * Returns <jk>true</jk> if we should find fluent setters.
970    *
971    * @return <jk>true</jk> if fluent setters should be found.
972    */
973   public boolean isFluentSetters() { return fluentSetters; }
974
975   /**
976    * Returns <jk>true</jk> if the properties defined on this bean class should be ordered alphabetically.
977    *
978    * <p>
979    * This method is only used when the {@link #getProperties()} method returns <jk>null</jk>.
980    * Otherwise, the ordering of the properties in the returned value is used.
981    *
982    * @return <jk>true</jk> if bean properties should be sorted.
983    */
984   public boolean isSortProperties() { return sortProperties; }
985
986   /**
987    * Calls the {@link BeanInterceptor#readProperty(Object, String, Object)} method on the registered property filters.
988    *
989    * @param bean The bean from which the property was read.
990    * @param name The property name.
991    * @param value The value just extracted from calling the bean getter.
992    * @return The value to serialize.  Default is just to return the existing value.
993    */
994   @SuppressWarnings("unchecked")
995   public Object readProperty(Object bean, String name, Object value) {
996      return interceptor.readProperty(bean, name, value);
997   }
998
999   /**
1000    * Calls the {@link BeanInterceptor#writeProperty(Object, String, Object)} method on the registered property filters.
1001    *
1002    * @param bean The bean from which the property was read.
1003    * @param name The property name.
1004    * @param value The value just parsed.
1005    * @return The value to serialize.  Default is just to return the existing value.
1006    */
1007   @SuppressWarnings("unchecked")
1008   public Object writeProperty(Object bean, String name, Object value) {
1009      return interceptor.writeProperty(bean, name, value);
1010   }
1011}