View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau;
18  
19  import static org.apache.juneau.commons.utils.AssertionUtils.*;
20  import static org.apache.juneau.commons.utils.ClassUtils.*;
21  import static org.apache.juneau.commons.utils.StringUtils.*;
22  import static org.apache.juneau.commons.utils.Utils.*;
23  
24  import java.lang.annotation.*;
25  import java.nio.charset.*;
26  import java.util.*;
27  import java.util.stream.*;
28  
29  import org.apache.juneau.annotation.*;
30  import org.apache.juneau.commons.reflect.*;
31  import org.apache.juneau.commons.utils.*;
32  import org.apache.juneau.svl.*;
33  
34  /**
35   * Class used to add properties to a context builder (e.g. {@link BeanContext.Builder}) from an annotation (e.g. {@link BeanConfig}).
36   *
37   * <p>
38   * Used by {@link Context.Builder#applyAnnotations(Class...)} and {@link Context.Builder#applyAnnotations(Object...)} to apply
39   * annotations to context beans.
40   *
41   * <p>
42   * The following code shows the general design pattern.
43   *
44   * <p class='bjava'>
45   * 	<jc>// The annotation applied to classes and methods.</jc>
46   * 	<ja>@Target</ja>({METHOD,TYPE})
47   * 	<ja>@Retention</ja>(<jsf>RUNTIME</jsf>)
48   * 	<ja>@ContextApply</ja>(BeanConfigAnnotationApplier.<jk>class</jk>)
49   * 	<jk>public</jk> <jk>@interface </jk>BeanConfig {
50   *
51   * 		String sortProperties() <jk>default</jk> <js>""</js>;
52   *
53   * 	}
54   *
55   * 	<jc>// The applier that applies the annotation to the bean context builder.</jc>
56   * 	<jk>public class</jk> BeanConfigAnnotationApplier <jk>extends</jk> AnnotationApplier&lt;<ja>BeanConfig</ja>,BeanContext.Builder&gt; {
57   *
58   *		<jc>// Required constructor. </jc>
59   * 		<jk>public</jk> Applier(VarResolverSession <jv>vr</jv>) {
60   * 			<jk>super</jk>(BeanConfig.<jk>class</jk>, BeanContext.Builder.<jk>class</jk>, <jv>vr</jv>);
61   * 		}
62   *
63   * 		<ja>@Override</ja>
64   * 		<jk>public void</jk> apply(AnnotationInfo&lt;BeanConfig&gt; <jv>annotationInfo</jv>, BeanContext.Builder <jv>builder</jv>) {
65   * 			<ja>BeanConfig</ja> <jv>beanConfig</jv> = <jv>annotationInfo</jv>.getAnnotation();
66   *
67   * 			String <jv>sortProperties</jv> = <jv>beanConfig</jv>.sortProperties();
68   * 			<jk>if</jk> (! <jv>sortProperties</jv>.isEmpty())
69   * 				<jv>builder</jv>.sortProperties(Boolean.<jsm>parseBoolean</jsm>(<jv>sortProperties</jv>));
70   * 		}
71   * 	}
72   *
73   *	<jc>// An annotated class.</jc>
74   * 	<ja>@BeanConfig</ja>(sortProperties=<js>"true"</js>)
75   * 	<jk>public class</jk> AnnotatedClass {}
76   *
77   *	<jc>// Putting it together.</jc>
78   * 	<jk>public static void</jk> main(String[] <jv>args</jv>) {
79   *
80   *		<jc>// Create a JSON serializer with sorted properties.</jc>
81   * 		Serializer <jv>serializer</jv> = JsonSerializer.<jsm>create</jsm>().applyAnnotations(AnnotatedClass.<jk>class</jk>).build();
82   * 	}
83   * </p>
84   *
85   * @param <A> The annotation that this applier reads from.
86   * @param <B> The builder class to apply the annotation to.
87   */
88  public abstract class AnnotationApplier<A extends Annotation,B> {
89  
90  	private final VarResolverSession vr;
91  	private final Class<A> ca;
92  	private final Class<B> cb;
93  
94  	/**
95  	 * Constructor.
96  	 *
97  	 * @param annotationClass The annotation class.
98  	 * @param builderClass The builder class.
99  	 * @param varResolverSession The string resolver to use for resolving strings.
100 	 */
101 	protected AnnotationApplier(Class<A> annotationClass, Class<B> builderClass, VarResolverSession varResolverSession) {
102 		ca = assertArgNotNull("annotationClass", annotationClass);
103 		cb = assertArgNotNull("builderClass", builderClass);
104 		vr = assertArgNotNull("vr", varResolverSession);
105 	}
106 
107 	/**
108 	 * Apply the specified annotation to the specified property store builder.
109 	 *
110 	 * @param annotationInfo The annotation.
111 	 * @param builder The property store builder.
112 	 */
113 	public abstract void apply(AnnotationInfo<A> annotationInfo, B builder);
114 
115 	/**
116 	 * Resolves the specified string and converts it to a boolean.
117 	 *
118 	 * @param in The string containing variables to resolve.
119 	 * @return The resolved boolean.
120 	 */
121 	public Optional<Boolean> bool(String in) {
122 		return string(in).map(Boolean::parseBoolean);
123 	}
124 
125 	/**
126 	 * Returns <jk>true</jk> if this apply can be appied to the specified builder.
127 	 *
128 	 * @param builder The builder to check.
129 	 * @return <jk>true</jk> if this apply can be appied to the specified builder.
130 	 */
131 	public boolean canApply(Object builder) {
132 		return cb.isInstance(builder);
133 	}
134 
135 	private Character toCharacter(String in, String loc) {
136 		if (in.length() != 1)
137 			throw new ConfigException("Invalid syntax for character on annotation @{0}({1}): {2}", ca.getSimpleName(), loc, in);
138 		return in.charAt(0);
139 	}
140 
141 	/**
142 	 * Resolves the specified string as a comma-delimited list of strings.
143 	 *
144 	 * @param in The CDL string containing variables to resolve.
145 	 * @return An array with resolved strings.
146 	 */
147 	protected Stream<String> cdl(String in) {
148 		return Arrays.stream(splita(vr.resolve(in))).filter(Utils::ne);
149 	}
150 
151 	/**
152 	 * Resolves the specified string and converts it to a Character.
153 	 *
154 	 * @param in The string containing variables to resolve.
155 	 * @param loc The annotation field name.
156 	 * @return The resolved Character.
157 	 */
158 	protected Optional<Character> character(String in, String loc) {
159 		return string(in).map(x -> toCharacter(x, loc));
160 	}
161 
162 	/**
163 	 * Resolves the specified string and converts it to a Charset.
164 	 *
165 	 * @param in The string containing variables to resolve.
166 	 * @return The resolved Charset.
167 	 */
168 	protected Optional<Charset> charset(String in) {
169 		return string(in).map(x -> "default".equalsIgnoreCase(x) ? Charset.defaultCharset() : Charset.forName(x));
170 	}
171 
172 	/**
173 	 * Returns the specified class array as an {@link Optional}.
174 	 *
175 	 * <p>
176 	 * If the array is empty, then returns {@link Optional#empty()}.
177 	 *
178 	 * @param in The class array.
179 	 * @return The array wrapped in an {@link Optional}.
180 	 */
181 	protected Optional<Class<?>[]> classes(Class<?>[] in) {
182 		return opt(in.length == 0 ? null : in);
183 	}
184 
185 	/**
186 	 * Resolves the specified string and converts it to an int.
187 	 *
188 	 * @param in The string containing variables to resolve.
189 	 * @param loc The annotation field name.
190 	 * @return The resolved int.
191 	 */
192 	protected Optional<Integer> integer(String in, String loc) {
193 		try {
194 			return string(in).map(Integer::parseInt);
195 		} catch (NumberFormatException e) {
196 			throw new ConfigException(e, "Invalid syntax for integer on annotation @{0}({1}): {2}", ca.getSimpleName(), loc, in);
197 		}
198 	}
199 
200 	/**
201 	 * Resolves the specified string as a comma-delimited list of strings.
202 	 *
203 	 * @param in The CDL string containing variables to resolve.
204 	 * @return An array with resolved strings.
205 	 */
206 	protected Stream<String> stream(String[] in) {
207 		return Arrays.stream(in).map(vr::resolve).filter(Utils::ne);
208 	}
209 
210 	/**
211 	 * Resolves the specified string.
212 	 *
213 	 * @param in The string containing variables to resolve.
214 	 * @return An optional containing the specified string if it exists, or {@link Optional#empty()} if it does not.
215 	 */
216 	protected Optional<String> string(String in) {
217 		in = vr.resolve(in);
218 		return opt(isEmpty(in) ? null : in);
219 	}
220 
221 	/**
222 	 * Returns the specified string array as an {@link Optional}.
223 	 *
224 	 * <p>
225 	 * If the array is empty, then returns {@link Optional#empty()}.
226 	 *
227 	 * @param in The string array.
228 	 * @return The array wrapped in an {@link Optional}.
229 	 */
230 	protected Optional<String[]> strings(String[] in) {
231 		return opt(in.length == 0 ? null : Arrays.stream(in).map(vr::resolve).filter(Utils::ne).toArray(String[]::new));
232 	}
233 
234 	/**
235 	 * Returns the specified value if it's simple name is not <js>"void"</js>.
236 	 *
237 	 * @param <T> The value to return.
238 	 * @param in The value to return.
239 	 * @return An optional containing the specified value.
240 	 */
241 	protected <T> Optional<Class<T>> type(Class<T> in) {
242 		return opt(in).filter(NOT_VOID);
243 	}
244 
245 	/**
246 	 * Returns the var resolver session for this apply.
247 	 *
248 	 * @return The var resolver session for this apply.
249 	 */
250 	protected VarResolverSession vr() {
251 		return vr;
252 	}
253 }