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<<ja>BeanConfig</ja>,BeanContext.Builder> {
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<BeanConfig> <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 }