> getType() {
return optional(type);
}
/**
* Specifies a pre-instantiated bean for the {@link #build()} method to return.
*
* @param value The value for this setting.
* @return This object.
*/
@FluentSetter
public Builder impl(Context value) {
impl = value;
return this;
}
/**
* Returns true if any of the annotations/appliers can be applied to this builder.
*
* @param work The work to check.
* @return true if any of the annotations/appliers can be applied to this builder.
*/
public boolean canApply(AnnotationWorkList work) {
Flag f = Flag.create();
work.forEach(x -> builders.forEach(b -> f.setIf(x.canApply(b))));
return f.isSet();
}
/**
* Applies a set of applied to this builder.
*
*
* An {@link AnnotationWork} consists of a single pair of {@link AnnotationInfo} that represents an annotation instance,
* and {@link AnnotationApplier} which represents the code used to apply the values in that annotation to a specific builder.
*
*
Example:
*
* // A class annotated with a config annotation.
* @BeanConfig(sortProperties="$S{sortProperties,false}")
* public class MyClass {...}
*
* // Find all annotations that themselves are annotated with @ContextPropertiesApply.
* AnnotationList annotations = ClassInfo.of(MyClass.class).getAnnotationList(CONTEXT_APPLY_FILTER);
* VarResolverSession vrs = VarResolver.DEFAULT.createSession();
* AnnotationWorkList work = AnnotationWorkList.of(vrs, annotations);
*
* // Apply any settings found on the annotations.
* WriterSerializer serializer = JsonSerializer
* .create()
* .apply(work)
* .build();
*
*
* @param work The list of annotations and appliers to apply to this builder.
* @return This object.
*/
@FluentSetter
public Builder apply(AnnotationWorkList work) {
applied.addAll(work);
work.forEach(x -> builders.forEach(y -> x.apply(y)));
return this;
}
/**
* Returns all the annotations that have been applied to this builder.
*
* @return All the annotations that have been applied to this builder.
*/
public AnnotationWorkList getApplied() {
return applied;
}
/**
* Registers the specified secondary builders with this context builder.
*
*
* When {@link #apply(AnnotationWorkList)} is called, it gets called on all registered builders.
*
* @param builders The builders to add to the list of builders.
*/
protected void registerBuilders(Object...builders) {
for (Object b : builders) {
if (b == this)
this.builders.add(b);
else if (b instanceof Builder)
this.builders.addAll(((Builder)b).builders);
else
this.builders.add(b);
}
}
/**
* Applies any of the various @XConfig annotations on the specified class to this context.
*
*
* Any annotations found that themselves are annotated with {@link ContextApply} will be resolved and
* applied as properties to this builder. These annotations include:
*
* - {@link BeanConfig}
*
- {@link CsvConfig}
*
- {@link HtmlConfig}
*
- {@link HtmlDocConfig}
*
- {@link JsonConfig}
*
- {@link JsonSchemaConfig}
*
- {@link MsgPackConfig}
*
- {@link OpenApiConfig}
*
- {@link ParserConfig}
*
- {@link PlainTextConfig}
*
- {@link SerializerConfig}
*
- {@link SoapXmlConfig}
*
- {@link UonConfig}
*
- {@link UrlEncodingConfig}
*
- {@link XmlConfig}
*
- RdfConfig
*
*
*
* Annotations on classes are appended in the following order:
*
* - On the package of this class.
*
- On interfaces ordered parent-to-child.
*
- On parent classes ordered parent-to-child.
*
- On this class.
*
*
*
* The default var resolver {@link VarResolver#DEFAULT} is used to resolve any variables in annotation field values.
*
*
Example:
*
* // A class annotated with a config annotation.
* @BeanConfig(sortProperties="$S{sortProperties,false}")
* public class MyClass {...}
*
* // Apply any settings found on the annotations.
* WriterSerializer serializer = JsonSerializer
* .create()
* .applyAnnotations(MyClass.class)
* .build();
*
*
* @param fromClasses The classes on which the annotations are defined.
* @return This object.
*/
@FluentSetter
public Builder applyAnnotations(Class>...fromClasses) {
AnnotationWorkList work = AnnotationWorkList.create();
for (Class> c : fromClasses)
work.add(ClassInfo.of(c).getAnnotationList(CONTEXT_APPLY_FILTER));
return apply(work);
}
/**
* Applies any of the various @XConfig annotations on the specified method to this context.
*
*
* Any annotations found that themselves are annotated with {@link ContextApply} will be resolved and
* applied as properties to this builder. These annotations include:
*
* - {@link BeanConfig}
*
- {@link CsvConfig}
*
- {@link HtmlConfig}
*
- {@link HtmlDocConfig}
*
- {@link JsonConfig}
*
- {@link JsonSchemaConfig}
*
- {@link MsgPackConfig}
*
- {@link OpenApiConfig}
*
- {@link ParserConfig}
*
- {@link PlainTextConfig}
*
- {@link SerializerConfig}
*
- {@link SoapXmlConfig}
*
- {@link UonConfig}
*
- {@link UrlEncodingConfig}
*
- {@link XmlConfig}
*
- RdfConfig
*
*
*
* Annotations on methods are appended in the following order:
*
* - On the package of the method class.
*
- On interfaces ordered parent-to-child.
*
- On parent classes ordered parent-to-child.
*
- On the method class.
*
- On this method and matching methods ordered parent-to-child.
*
*
*
* The default var resolver {@link VarResolver#DEFAULT} is used to resolve any variables in annotation field values.
*
*
Example:
*
* // A method annotated with a config annotation.
* public class MyClass {
* @BeanConfig(sortProperties="$S{sortProperties,false}")
* public void myMethod() {...}
* }
*
* // Apply any settings found on the annotations.
* WriterSerializer serializer = JsonSerializer
* .create()
* .applyAnnotations(MyClass.class.getMethod("myMethod"))
* .build();
*
*
* @param fromMethods The methods on which the annotations are defined.
* @return This object.
*/
@FluentSetter
public Builder applyAnnotations(Method...fromMethods) {
AnnotationWorkList work = AnnotationWorkList.create();
for (Method m : fromMethods)
work.add(MethodInfo.of(m).getAnnotationList(CONTEXT_APPLY_FILTER));
return apply(work);
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Defines annotations to apply to specific classes and methods.
*
*
* Allows you to dynamically apply Juneau annotations typically applied directly to classes and methods.
* Useful in cases where you want to use the functionality of the annotation on beans and bean properties but
* do not have access to the code to do so.
*
*
* As a rule, any Juneau annotation with an on() method can be used with this setting.
*
*
* The following example shows the equivalent methods for applying the {@link Bean @Bean} annotation:
*
* // Class with explicit annotation.
* @Bean(properties="street,city,state")
* public class A {...}
*
* // Class with annotation applied via @BeanConfig
* public class B {...}
*
* // Java REST method with @BeanConfig annotation.
* @RestGet(...)
* @Bean(on="B", properties="street,city,state")
* public void doFoo() {...}
*
*
*
* In general, the underlying framework uses this method when it finds dynamically applied annotations on
* config annotations. However, concrete implementations of annotations are also provided that can be passed
* directly into builder classes like so:
*
* // Create a concrete @Bean annotation.
* Bean annotation = BeanAnnotation.create(B.class).properties("street,city,state").build();
*
* // Apply it to a serializer.
* WriterSerializer serializer = JsonSerializer.create().annotations(annotation).build();
*
* // Serialize a bean with the dynamically applied annotation.
* String json = serializer.serialize(new B());
*
*
*
* The following is the list of annotations builders provided that can be constructed
* and passed into the builder class:
*
* - {@link org.apache.juneau.annotation.BeanAnnotation}
*
- {@link org.apache.juneau.annotation.BeancAnnotation}
*
- {@link org.apache.juneau.annotation.BeanIgnoreAnnotation}
*
- {@link org.apache.juneau.annotation.BeanpAnnotation}
*
- {@link org.apache.juneau.annotation.ExampleAnnotation}
*
- {@link org.apache.juneau.annotation.NamePropertyAnnotation}
*
- {@link org.apache.juneau.annotation.ParentPropertyAnnotation}
*
- {@link org.apache.juneau.annotation.SwapAnnotation}
*
- {@link org.apache.juneau.annotation.UriAnnotation}
*
- {@link org.apache.juneau.csv.annotation.CsvAnnotation}
*
- {@link org.apache.juneau.html.annotation.HtmlAnnotation}
*
- {@link org.apache.juneau.json.annotation.JsonAnnotation}
*
- {@link org.apache.juneau.annotation.SchemaAnnotation}
*
- {@link org.apache.juneau.msgpack.annotation.MsgPackAnnotation}
*
- {@link org.apache.juneau.oapi.annotation.OpenApiAnnotation}
*
- {@link org.apache.juneau.plaintext.annotation.PlainTextAnnotation}
*
- {@link org.apache.juneau.soap.annotation.SoapXmlAnnotation}
*
- {@link org.apache.juneau.uon.annotation.UonAnnotation}
*
- {@link org.apache.juneau.urlencoding.annotation.UrlEncodingAnnotation}
*
- {@link org.apache.juneau.xml.annotation.XmlAnnotation}
*
*
*
* The syntax for the on() pattern match parameter depends on whether it applies to a class, method, field, or constructor.
* The valid pattern matches are:
*
* - Classes:
*
* - Fully qualified:
*
*
- Fully qualified inner class:
*
* - "com.foo.MyClass$Inner1$Inner2"
*
* - Simple:
*
*
- Simple inner:
*
* - "MyClass$Inner1$Inner2"
*
- "Inner1$Inner2"
*
- "Inner2"
*
*
* - Methods:
*
* - Fully qualified with args:
*
* - "com.foo.MyClass.myMethod(String,int)"
*
- "com.foo.MyClass.myMethod(java.lang.String,int)"
*
- "com.foo.MyClass.myMethod()"
*
* - Fully qualified:
*
* - "com.foo.MyClass.myMethod"
*
* - Simple with args:
*
* - "MyClass.myMethod(String,int)"
*
- "MyClass.myMethod(java.lang.String,int)"
*
- "MyClass.myMethod()"
*
* - Simple:
*
*
- Simple inner class:
*
* - "MyClass$Inner1$Inner2.myMethod"
*
- "Inner1$Inner2.myMethod"
*
- "Inner2.myMethod"
*
*
* - Fields:
*
* - Fully qualified:
*
* - "com.foo.MyClass.myField"
*
* - Simple:
*
*
- Simple inner class:
*
* - "MyClass$Inner1$Inner2.myField"
*
- "Inner1$Inner2.myField"
*
- "Inner2.myField"
*
*
* - Constructors:
*
* - Fully qualified with args:
*
* - "com.foo.MyClass(String,int)"
*
- "com.foo.MyClass(java.lang.String,int)"
*
- "com.foo.MyClass()"
*
* - Simple with args:
*
* - "MyClass(String,int)"
*
- "MyClass(java.lang.String,int)"
*
- "MyClass()"
*
* - Simple inner class:
*
* - "MyClass$Inner1$Inner2()"
*
- "Inner1$Inner2()"
*
- "Inner2()"
*
*
* - A comma-delimited list of anything on this list.
*
*
* See Also:
*
* @param values
* The annotations to register with the context.
* @return This object.
*/
@FluentSetter
public Builder annotations(Annotation...values) {
annotations = addAll(annotations, values);
return this;
}
/**
* Context configuration property: Debug mode.
*
*
* Enables the following additional information during serialization:
*
* -
* When bean getters throws exceptions, the exception includes the object stack information
* in order to determine how that method was invoked.
*
-
* Enables {@link BeanTraverseContext.Builder#detectRecursions()}.
*
*
*
* Enables the following additional information during parsing:
*
* -
* When bean setters throws exceptions, the exception includes the object stack information
* in order to determine how that method was invoked.
*
*
* Example:
*
* // Create a serializer with debug enabled.
* WriterSerializer serializer = JsonSerializer
* .create()
* .debug()
* .build();
*
* // Create a POJO model with a recursive loop.
* public class MyBean {
* public Object f;
* }
* MyBean bean = new MyBean();
* bean.f = bean;
*
* // Throws a SerializeException and not a StackOverflowError
* String json = serializer.serialize(bean);
*
*
* See Also:
* - {@link org.apache.juneau.annotation.BeanConfig#debug()}
*
- {@link org.apache.juneau.ContextSession.Builder#debug(Boolean)}
*
*
* @return This object.
*/
@FluentSetter
public Builder debug() {
return debug(true);
}
/**
* Same as {@link #debug()} but allows you to explicitly specify the value.
*
* @param value The value for this setting.
* @return This object.
*/
@FluentSetter
public Builder debug(boolean value) {
debug = value;
return this;
}
/**
* Returns true if debug is enabled.
*
* @return true if debug is enabled.
*/
public boolean isDebug() {
return debug;
}
/**
* Looks up a system property or environment variable.
*
*
* First looks in system properties. Then converts the name to env-safe and looks in the system environment.
* Then returns the default if it can't be found.
*
* @param The type to convert to.
* @param name The property name.
* @param def The default value if not found.
* @return The default value.
*/
protected T env(String name, T def) {
return SystemEnv.env(name, def);
}
/**
* Looks up a system property or environment variable.
*
*
* First looks in system properties. Then converts the name to env-safe and looks in the system environment.
* Then returns the default if it can't be found.
*
* @param name The property name.
* @return The value if found.
*/
protected Optional env(String name) {
return SystemEnv.env(name);
}
//
//
}
//-----------------------------------------------------------------------------------------------------------------
// Instance
//-----------------------------------------------------------------------------------------------------------------
final List annotations;
final boolean debug;
private final ReflectionMap annotationMap;
private final TwoKeyConcurrentCache,Class extends Annotation>,Annotation[]> classAnnotationCache;
private final TwoKeyConcurrentCache,Class extends Annotation>,Annotation[]> declaredClassAnnotationCache;
private final TwoKeyConcurrentCache,Annotation[]> methodAnnotationCache;
private final TwoKeyConcurrentCache,Annotation[]> fieldAnnotationCache;
private final TwoKeyConcurrentCache,Class extends Annotation>,Annotation[]> constructorAnnotationCache;
/**
* Copy constructor.
*
* @param copyFrom The context to copy from.
*/
protected Context(Context copyFrom) {
annotationMap = copyFrom.annotationMap;
annotations = copyFrom.annotations;
debug = copyFrom.debug;
classAnnotationCache = copyFrom.classAnnotationCache;
declaredClassAnnotationCache = copyFrom.declaredClassAnnotationCache;
methodAnnotationCache = copyFrom.methodAnnotationCache;
fieldAnnotationCache = copyFrom.fieldAnnotationCache;
constructorAnnotationCache = copyFrom.constructorAnnotationCache;
}
/**
* Constructor for this class.
*
* @param builder The builder for this class.
*/
protected Context(Builder builder) {
init(builder);
debug = builder.debug;
annotations = optional(builder.annotations).orElseGet(CollectionUtils::emptyList);
ReflectionMap.Builder rmb = ReflectionMap.create(Annotation.class);
annotations.forEach(a -> {
try {
ClassInfo ci = ClassInfo.of(a.getClass());
MethodInfo mi = ci.getPublicMethod(x -> x.hasName("onClass"));
if (mi != null) {
if (! mi.getReturnType().is(Class[].class))
throw new ConfigException("Invalid annotation @{0} used in BEAN_annotations property. Annotation must define an onClass() method that returns a Class array.", a.getClass().getSimpleName());
for (Class> c : (Class>[])mi.accessible().invoke(a))
rmb.append(c.getName(), a);
}
mi = ci.getPublicMethod(x -> x.hasName("on"));
if (mi != null) {
if (! mi.getReturnType().is(String[].class))
throw new ConfigException("Invalid annotation @{0} used in BEAN_annotations property. Annotation must define an on() method that returns a String array.", a.getClass().getSimpleName());
for (String s : (String[])mi.accessible().invoke(a))
rmb.append(s, a);
}
} catch (Exception e) {
throw new ConfigException(e, "Invalid annotation @{0} used in BEAN_annotations property.", className(a));
}
});
this.annotationMap = rmb.build();
boolean disabled = Boolean.getBoolean("juneau.disableAnnotationCaching");
classAnnotationCache = new TwoKeyConcurrentCache<>(disabled, (k1,k2) -> annotationMap.appendAll(k1, k2, k1.getAnnotationsByType(k2)));
declaredClassAnnotationCache = new TwoKeyConcurrentCache<>(disabled, (k1,k2) -> annotationMap.appendAll(k1, k2, k1.getDeclaredAnnotationsByType(k2)));
methodAnnotationCache = new TwoKeyConcurrentCache<>(disabled, (k1,k2) -> annotationMap.appendAll(k1, k2, k1.getAnnotationsByType(k2)));
fieldAnnotationCache = new TwoKeyConcurrentCache<>(disabled, (k1,k2) -> annotationMap.appendAll(k1, k2, k1.getAnnotationsByType(k2)));
constructorAnnotationCache = new TwoKeyConcurrentCache<>(disabled, (k1,k2) -> annotationMap.appendAll(k1, k2, k1.getAnnotationsByType(k2)));
}
/**
* Perform optional initialization on builder before it is used.
*
*
* Default behavior is a no-op.
*
* @param builder The builder to initialize.
*/
protected void init(Builder builder) {}
/**
* Creates a builder from this context object.
*
*
* Builders are used to define new contexts (e.g. serializers, parsers) based on existing configurations.
*
* @return A new Builder object.
*/
public Builder copy() {
throw new UnsupportedOperationException("Not implemented.");
}
/**
* Create a session builder based on the properties defined on this context.
*
*
* Use this method for creating sessions where you want to override basic settings.
* Otherwise, use {@link #getSession()} directly.
*
* @return A new session builder.
*/
public ContextSession.Builder createSession() {
throw new UnsupportedOperationException("Not implemented.");
}
/**
* Returns a session to use for this context.
*
*
* Note that subclasses may opt to return a reusable non-modifiable session.
*
* @return A new session object.
*/
public ContextSession getSession() {
return createSession().build();
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Debug mode.
*
* @see Context.Builder#debug()
* @return
* true if debug mode is enabled.
*/
public boolean isDebug() {
return debug;
}
//-----------------------------------------------------------------------------------------------------------------
// MetaProvider methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* MetaProvider */
public void forEachAnnotation(Class type, Class> onClass, Predicate filter, Consumer action) {
if (type != null && onClass != null)
for (A a : annotations(type, onClass))
consume(filter, action, a);
}
@Override /* MetaProvider */
public A firstAnnotation(Class type, Class> onClass, Predicate filter) {
if (type != null && onClass != null)
for (A a : annotations(type, onClass))
if (test(filter, a))
return a;
return null;
}
@Override /* MetaProvider */
public A lastAnnotation(Class type, Class> onClass, Predicate filter) {
A x = null;
if (type != null && onClass != null)
for (A a : annotations(type, onClass))
if (test(filter, a))
x = a;
return x;
}
@Override /* MetaProvider */
public void forEachDeclaredAnnotation(Class type, Class> onClass, Predicate filter, Consumer action) {
if (type != null && onClass != null)
for (A a : declaredAnnotations(type, onClass))
consume(filter, action, a);
}
@Override /* MetaProvider */
public A firstDeclaredAnnotation(Class type, Class> onClass, Predicate filter) {
if (type != null && onClass != null)
for (A a : declaredAnnotations(type, onClass))
if (test(filter, a))
return a;
return null;
}
@Override /* MetaProvider */
public A lastDeclaredAnnotation(Class type, Class> onClass, Predicate filter) {
A x = null;
if (type != null && onClass != null)
for (A a : declaredAnnotations(type, onClass))
if (test(filter, a))
x = a;
return x;
}
@Override /* MetaProvider */
public void forEachAnnotation(Class type, Method onMethod, Predicate filter, Consumer action) {
if (type != null && onMethod != null)
for (A a : annotations(type, onMethod))
consume(filter, action, a);
}
@Override /* MetaProvider */
public A firstAnnotation(Class type, Method onMethod, Predicate filter) {
if (type != null && onMethod != null)
for (A a : annotations(type, onMethod))
if (test(filter, a))
return a;
return null;
}
@Override /* MetaProvider */
public A lastAnnotation(Class type, Method onMethod, Predicate filter) {
A x = null;
if (type != null && onMethod != null)
for (A a : annotations(type, onMethod))
if (test(filter, a))
x = a;
return x;
}
@Override /* MetaProvider */
public void forEachAnnotation(Class type, Field onField, Predicate filter, Consumer action) {
if (type != null && onField != null)
for (A a : annotations(type, onField))
consume(filter, action, a);
}
@Override /* MetaProvider */
public A firstAnnotation(Class type, Field onField, Predicate filter) {
if (type != null && onField != null)
for (A a : annotations(type, onField))
if (test(filter, a))
return a;
return null;
}
@Override /* MetaProvider */
public A lastAnnotation(Class type, Field onField, Predicate filter) {
A x = null;
if (type != null && onField != null)
for (A a : annotations(type, onField))
if (test(filter, a))
x = a;
return x;
}
@Override /* MetaProvider */
public void forEachAnnotation(Class type, Constructor> onConstructor, Predicate filter, Consumer action) {
if (type != null && onConstructor != null)
for (A a : annotations(type, onConstructor))
consume(filter, action, a);
}
@Override /* MetaProvider */
public A firstAnnotation(Class type, Constructor> onConstructor, Predicate filter) {
if (type != null && onConstructor != null)
for (A a : annotations(type, onConstructor))
if (test(filter, a))
return a;
return null;
}
@Override /* MetaProvider */
public A lastAnnotation(Class type, Constructor> onConstructor, Predicate filter) {
A x = null;
if (type != null && onConstructor != null)
for (A a : annotations(type, onConstructor))
if (test(filter, a))
x = a;
return x;
}
/**
* Returns true if getAnnotation(a,c) returns a non-null value.
*
* @param The annotation being checked for.
* @param type The annotation being checked for.
* @param onClass The class being checked on.
* @return true if the annotation exists on the specified class.
*/
public boolean hasAnnotation(Class type, Class> onClass) {
return annotations(type, onClass).length > 0;
}
/**
* Returns true if getAnnotation(a,m) returns a non-null value.
*
* @param The annotation being checked for.
* @param type The annotation being checked for.
* @param onMethod The method being checked on.
* @return true if the annotation exists on the specified method.
*/
public boolean hasAnnotation(Class type, Method onMethod) {
return annotations(type, onMethod).length > 0;
}
/**
* Returns true if getAnnotation(a,f) returns a non-null value.
*
* @param The annotation being checked for.
* @param type The annotation being checked for.
* @param onField The field being checked on.
* @return true if the annotation exists on the specified field.
*/
public boolean hasAnnotation(Class type, Field onField) {
return annotations(type, onField).length > 0;
}
/**
* Returns true if getAnnotation(a,c) returns a non-null value.
*
* @param The annotation being checked for.
* @param type The annotation being checked for.
* @param onConstructor The constructor being checked on.
* @return true if the annotation exists on the specified field.
*/
public boolean hasAnnotation(Class type, Constructor> onConstructor) {
return annotations(type, onConstructor).length > 0;
}
@SuppressWarnings("unchecked")
private A[] annotations(Class type, Class> onClass) {
return (A[])classAnnotationCache.get(onClass, type);
}
@SuppressWarnings("unchecked")
private A[] declaredAnnotations(Class type, Class> onClass) {
return (A[])declaredClassAnnotationCache.get(onClass, type);
}
@SuppressWarnings("unchecked")
private A[] annotations(Class type, Method onMethod) {
return (A[])methodAnnotationCache.get(onMethod, type);
}
@SuppressWarnings("unchecked")
private A[] annotations(Class type, Field onField) {
return (A[])fieldAnnotationCache.get(onField, type);
}
@SuppressWarnings("unchecked")
private A[] annotations(Class type, Constructor> onConstructor) {
return (A[])constructorAnnotationCache.get(onConstructor, type);
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the properties on this bean as a map for debugging.
*
* @return The properties on this bean as a map for debugging.
*/
protected JsonMap properties() {
return filteredMap("annotations", annotations, "debug", debug);
}
@Override /* Object */
public String toString() {
return ObjectUtils.toPropertyMap(this).asReadableString();
}
}