blob: abbbc86bf671feb0beabf80ba2e4e0abed67b64d [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau;
import static org.apache.juneau.internal.StringUtils.*;
import java.beans.*;
import java.util.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.transform.*;
/**
* Builder class for {@link BeanFilter} objects.
*
* <p>
* This class is the programmatic equivalent to the aggregation of one or more {@link Bean @Bean} annotations.
*/
public class BeanFilterBuilder {
Class<?> beanClass;
String typeName, example;
ASet<String>
properties = ASet.of(),
excludeProperties = ASet.of(),
readOnlyProperties = ASet.of(),
writeOnlyProperties = ASet.of();
Class<?> implClass, interfaceClass, stopClass;
boolean sortProperties, fluentSetters;
Object propertyNamer;
List<Class<?>> dictionary;
Object interceptor;
/**
* Constructor.
*
* @param beanClass The bean class that this filter applies to.
*/
public BeanFilterBuilder(Class<?> beanClass) {
this.beanClass = beanClass;
}
/**
* Applies the information in the specified list of {@link Bean @Bean} annotations to this filter.
*
* @param annotations The annotations to apply.
* @return This object (for method chaining).
*/
public BeanFilterBuilder applyAnnotations(List<Bean> annotations) {
for (Bean b : annotations) {
if (! (b.properties().isEmpty() && b.p().isEmpty()))
properties(b.properties(), b.p());
if (! b.typeName().isEmpty())
typeName(b.typeName());
if (b.sort())
sortProperties(true);
if (b.findFluentSetters())
findFluentSetters();
if (! (b.excludeProperties().isEmpty() && b.xp().isEmpty()))
excludeProperties(b.excludeProperties(), b.xp());
if (! (b.readOnlyProperties().isEmpty() && b.ro().isEmpty()))
readOnlyProperties(b.readOnlyProperties(), b.ro());
if (! (b.writeOnlyProperties().isEmpty() && b.wo().isEmpty()))
writeOnlyProperties(b.writeOnlyProperties(), b.wo());
if (b.propertyNamer() != BasicPropertyNamer.class)
propertyNamer(b.propertyNamer());
if (b.interfaceClass() != Null.class)
interfaceClass(b.interfaceClass());
if (b.stopClass() != Null.class)
stopClass(b.stopClass());
if (b.dictionary().length > 0)
dictionary(b.dictionary());
if (b.interceptor() != BeanInterceptor.Default.class)
interceptor(b.interceptor());
if (b.implClass() != Null.class)
implClass(b.implClass());
if (! b.example().isEmpty())
example(b.example());
}
return this;
}
/**
* Configuration property: Bean dictionary type name.
*
* <p>
* Specifies the dictionary type name for this bean.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* typeName(<js>"mybean"</js>);
* }
* }
*
* <jc>// Register it with a serializer or parser.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
*
* <jc>// Produces: "{_type:'mybean', ...}"</jc>
* String json = s.serialize(<jk>new</jk> MyBean());
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#typeName()}
* </ul>
*
* @param value The new value for this setting.
* @return This object (for method chaining).
*/
public BeanFilterBuilder typeName(String value) {
this.typeName = value;
return this;
}
/**
* Bean implementation class.
*
* @param value The new value for this setting.
* @return This object (for method chaining).
*/
public BeanFilterBuilder implClass(Class<?> value) {
this.implClass = value;
return this;
}
/**
* Bean interface class.
*
* Identifies a class to be used as the interface class for this and all subclasses.
*
* <p>
* When specified, only the list of properties defined on the interface class will be used during serialization.
* <br>Additional properties on subclasses will be ignored.
*
* <p class='bcode w800'>
* <jc>// Parent class</jc>
* <jk>public abstract class</jk> A {
* <jk>public</jk> String <jf>f0</jf> = <js>"f0"</js>;
* }
*
* <jc>// Sub class</jc>
* <jk>public class</jk> A1 <jk>extends</jk> A {
* <jk>public</jk> String <jf>f1</jf> = <js>"f1"</js>;
* }
*
* <jc>// Define our filter.</jc>
* <jk>public class</jk> AFilter <jk>extends</jk> BeanFilterBuilder&lt;A&gt; {
* <jk>public</jk> AFilter() {
* interfaceClass(A.<jk>class</jk>);
* }
* }
*
* <jc>// Register it with a serializer.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(AFilter.<jk>class</jk>)
* .build();
*
* <jc>// Use it.</jc>
* A1 a1 = <jk>new</jk> A1();
* String r = s.serialize(a1);
* <jsm>assertEquals</jsm>(<js>"{f0:'f0'}"</js>, r); <jc>// Note f1 is not serialized</jc>
* </p>
*
* <p>
* Note that this filter can be used on the parent class so that it filters to all child classes, or can be set
* individually on the child classes.
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#interfaceClass()}
* </ul>
*
* @param value The new value for this setting.
* @return This object (for method chaining).
*/
public BeanFilterBuilder interfaceClass(Class<?> value) {
this.interfaceClass = value;
return this;
}
/**
* Configuration property: Bean stop class.
*
* <p>
* Identifies a stop class for this class and all subclasses.
*
* <p>
* Identical in purpose to the stop class specified by {@link Introspector#getBeanInfo(Class, Class)}.
* <br>Any properties in the stop class or in its base classes will be ignored during analysis.
*
* <p>
* For example, in the following class hierarchy, instances of <c>C3</c> will include property <c>p3</c>,
* but not <c>p1</c> or <c>p2</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jk>public class</jk> C1 {
* <jk>public int</jk> getP1();
* }
*
* <jk>public class</jk> C2 <jk>extends</jk> C1 {
* <jk>public int</jk> getP2();
* }
*
* <jk>public class</jk> C3 <jk>extends</jk> C2 {
* <jk>public int</jk> getP3();
* }
*
* <jc>// Define our filter.</jc>
* <jk>public class</jk> C3Filter <jk>extends</jk> BeanFilterBuilder&lt;C3&gt; {
* <jk>public</jk> C3Filter() {
* stopClass(C2.<jk>class</jk>);
* }
* }
*
* <jc>// Register it with a serializer.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(C3Filter.<jk>class</jk>)
* .build();
*
* <jc>// Serializes property 'p3', but NOT 'p1' or 'p2'.</jc>
* String json = s.serialize(<jk>new</jk> C3());
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#stopClass()}
* </ul>
*
* @param value The new value for this setting.
* @return This object (for method chaining).
*/
public BeanFilterBuilder stopClass(Class<?> value) {
this.stopClass = value;
return this;
}
/**
* Configuration property: Sort bean properties.
*
* <p>
* When <jk>true</jk>, all bean properties will be serialized and access in alphabetical order.
* <br>Otherwise, the natural order of the bean properties is used which is dependent on the JVM vendor.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* sortProperties();
* }
* }
*
* <jc>// Register it with a serializer.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
*
* <jc>// Properties will be sorted alphabetically.</jc>
* String json = s.serialize(<jk>new</jk> MyBean());
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#sort()}
* <li class='jf'>{@link BeanContext#BEAN_sortProperties}
* </ul>
*
* @param value
* The new value for this property.
* <br>The default is <jk>false</jk>.
* @return This object (for method chaining).
*/
public BeanFilterBuilder sortProperties(boolean value) {
this.sortProperties = value;
return this;
}
/**
* Configuration property: Sort bean properties.
*
* <p>
* Shortcut for calling <code>sortProperties(<jk>true</jk>)</code>.
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#sort()}
* <li class='jf'>{@link BeanContext#BEAN_sortProperties}
* </ul>
*
* @return This object (for method chaining).
*/
public BeanFilterBuilder sortProperties() {
this.sortProperties = true;
return this;
}
/**
* Configuration property: Find fluent setters.
*
* <p>
* When enabled, fluent setters are detected on beans.
*
* <p>
* Fluent setters must have the following attributes:
* <ul>
* <li>Public.
* <li>Not static.
* <li>Take in one parameter.
* <li>Return the bean itself.
* </ul>
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* findFluentSetters();
* }
* }
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#findFluentSetters()}
* <li class='jf'>{@link BeanContext#BEAN_findFluentSetters}
* </ul>
*
* @return This object (for method chaining).
*/
public BeanFilterBuilder findFluentSetters() {
this.fluentSetters = true;
return this;
}
/**
* Configuration property: Bean property namer
*
* <p>
* The class to use for calculating bean property names.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* <jc>// Use Dashed-Lower-Case property names.</jc>
* <jc>// (e.g. "foo-bar-url" instead of "fooBarURL")</jc>
* propertyNamer(PropertyNamerDLC.<jk>class</jk>);
* }
* }
*
* <jc>// Register it with a serializer or parser.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
*
* <jc>// Properties names will be Dashed-Lower-Case.</jc>
* String json = s.serialize(<jk>new</jk> MyBean());
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#propertyNamer()}
* <li class='jf'>{@link BeanContext#BEAN_propertyNamer}
* <li class='jc'>{@link PropertyNamer}
* </ul>
*
* @param value
* The new value for this setting.
* <br>The default is {@link BasicPropertyNamer}.
* @return This object (for method chaining).
*/
public BeanFilterBuilder propertyNamer(Class<? extends PropertyNamer> value) {
this.propertyNamer = value;
return this;
}
/**
* Configuration property: Bean property includes.
*
* <p>
* Specifies the set and order of names of properties associated with the bean class.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* bpi(<js>"foo,bar,baz"</js>);
* }
* }
*
* <jc>// Register it with a serializer.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
*
* <jc>// Only serializes the properties 'foo', 'bar', and 'baz'.</jc>
* String json = s.serialize(<jk>new</jk> MyBean());
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#properties()}
* <li class='jm'>{@link BeanContextBuilder#beanProperties(Class, String)}
* <li class='jm'>{@link BeanContextBuilder#beanProperties(String, String)}
* <li class='jm'>{@link BeanContextBuilder#beanProperties(Map)}
* </ul>
*
* @param value
* The new value for this setting.
* <br>Values can contain comma-delimited list of property names.
* @return This object (for method chaining).
*/
public BeanFilterBuilder properties(String...value) {
this.properties = ASet.of();
for (String v : value)
if (!v.isEmpty())
properties.a(split(v));
return this;
}
/**
* Configuration property: Bean property excludes.
*
* <p>
* Specifies properties to exclude from the bean class.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* bpx(<js>"foo,bar"</js>);
* }
* }
*
* <jc>// Register it with a serializer.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
*
* <jc>// Serializes all properties except for 'foo' and 'bar'.</jc>
* String json = s.serialize(<jk>new</jk> MyBean());
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#excludeProperties()}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesExcludes(Class, String)}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesExcludes(String, String)}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesExcludes(Map)}
* </ul>
*
* @param value
* The new value for this setting.
* <br>Values can contain comma-delimited list of property names.
* @return This object (for method chaining).
*/
public BeanFilterBuilder excludeProperties(String...value) {
this.excludeProperties = ASet.of();
for (String v : value)
if (! v.isEmpty())
excludeProperties.a(split(v));
return this;
}
/**
* Configuration property: Read-only bean properties.
*
* <p>
* Specifies one or more properties on a bean that are read-only despite having valid getters.
* Serializers will serialize such properties as usual, but parsers will silently ignore them.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* bpro(<js>"foo,bar"</js>);
* }
* }
*
* <jc>// Register it with a parser.</jc>
* ReaderParser p = JsonParser
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
*
* <jc>// Parsers all properties except for 'foo' and 'bar'.</jc>
* MyBean b = p.parse(<js>"..."</js>, MyBean.<jk>class</jk>);
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#readOnlyProperties()}
* <li class='ja'>{@link Beanp#ro()}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesReadOnly(Class, String)}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesReadOnly(String, String)}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesReadOnly(Map)}
* </ul>
*
* @param value
* The new value for this setting.
* <br>Values can contain comma-delimited list of property names.
* @return This object (for method chaining).
*/
public BeanFilterBuilder readOnlyProperties(String...value) {
this.readOnlyProperties = ASet.of();
for (String v : value)
if (! v.isEmpty())
readOnlyProperties.a(split(v));
return this;
}
/**
* Configuration property: Write-only bean properties.
*
* <p>
* Specifies one or more properties on a bean that are write-only despite having valid setters.
* Parsers will parse such properties as usual, but serializers will silently ignore them.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* bpwo(<js>"foo,bar"</js>);
* }
* }
*
* <jc>// Register it with a serializer.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
*
* <jc>// Serializes all properties except for 'foo' and 'bar'.</jc>
* String json = s.serialize(<jk>new</jk> MyBean());
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#writeOnlyProperties()}
* <li class='ja'>{@link Beanp#wo()}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesWriteOnly(Class, String)}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesWriteOnly(String, String)}
* <li class='jm'>{@link BeanContextBuilder#beanPropertiesWriteOnly(Map)}
* </ul>
*
* @param value
* The new value for this setting.
* <br>Values can contain comma-delimited list of property names.
* @return This object (for method chaining).
*/
public BeanFilterBuilder writeOnlyProperties(String...value) {
this.writeOnlyProperties = ASet.of();
for (String v : value)
if (! v.isEmpty())
writeOnlyProperties.a(split(v));
return this;
}
/**
* Configuration property: Bean dictionary.
*
* <p>
* Adds to the list of classes that make up the bean dictionary for this bean.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* <jc>// Our bean contains generic collections of Foo and Bar objects.</jc>
* beanDictionary(Foo.<jk>class</jk>, Bar.<jk>class</jk>);
* }
* }
*
* <jc>// Register it with a parser.</jc>
* ReaderParser p = JsonParser
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
*
* <jc>// Instantiate our bean.</jc>
* MyBean myBean = p.parse(json);
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#dictionary()}
* <li class='jf'>{@link BeanContext#BEAN_beanDictionary}
* </ul>
*
* @param values
* The values to add to this property.
* @return This object (for method chaining).
*/
public BeanFilterBuilder dictionary(Class<?>...values) {
if (dictionary == null)
dictionary = Arrays.asList(values);
else for (Class<?> cc : values)
dictionary.add(cc);
return this;
}
/**
* Example.
*
* @param value
* The new value for this property.
* @return This object (for method chaining).
*/
public BeanFilterBuilder example(String value) {
this.example = value;
return this;
}
/**
* Configuration property: Bean interceptor.
*
* <p>
* The interceptor to use for intercepting and altering getter and setter calls.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Define our filter.</jc>
* <jk>public class</jk> MyFilter <jk>extends</jk> BeanFilterBuilder&lt;MyBean&gt; {
* <jk>public</jk> MyFilter() {
* <jc>// Our bean contains generic collections of Foo and Bar objects.</jc>
* interceptor(AddressInterceptor.<jk>class</jk>);
* }
* }
*
* <jc>// Register it with a serializer or parser.</jc>
* WriterSerializer s = JsonSerializer
* .<jsm>create</jsm>()
* .beanFilters(MyFilter.<jk>class</jk>)
* .build();
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Bean#interceptor()}
* <li class='jc'>{@link BeanInterceptor}
* </ul>
*
* @param value
* The new value for this setting.
* <br>The default value is {@link BeanInterceptor}.
* @return This object (for method chaining).
*/
public BeanFilterBuilder interceptor(Class<?> value) {
this.interceptor = value;
return this;
}
/**
* Creates a {@link BeanFilter} with settings in this builder class.
*
* @return A new {@link BeanFilter} instance.
*/
public BeanFilter build() {
return new BeanFilter(this);
}
}