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.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.ClassUtils.*;
021import static org.apache.juneau.commons.utils.StringUtils.*;
022import static org.apache.juneau.commons.utils.Utils.*;
023
024import java.lang.annotation.*;
025import java.nio.charset.*;
026import java.util.*;
027import java.util.stream.*;
028
029import org.apache.juneau.annotation.*;
030import org.apache.juneau.commons.reflect.*;
031import org.apache.juneau.commons.utils.*;
032import org.apache.juneau.svl.*;
033
034/**
035 * Class used to add properties to a context builder (e.g. {@link BeanContext.Builder}) from an annotation (e.g. {@link BeanConfig}).
036 *
037 * <p>
038 * Used by {@link Context.Builder#applyAnnotations(Class...)} and {@link Context.Builder#applyAnnotations(Object...)} to apply
039 * annotations to context beans.
040 *
041 * <p>
042 * The following code shows the general design pattern.
043 *
044 * <p class='bjava'>
045 *    <jc>// The annotation applied to classes and methods.</jc>
046 *    <ja>@Target</ja>({METHOD,TYPE})
047 *    <ja>@Retention</ja>(<jsf>RUNTIME</jsf>)
048 *    <ja>@ContextApply</ja>(BeanConfigAnnotationApplier.<jk>class</jk>)
049 *    <jk>public</jk> <jk>@interface </jk>BeanConfig {
050 *
051 *       String sortProperties() <jk>default</jk> <js>""</js>;
052 *
053 *    }
054 *
055 *    <jc>// The applier that applies the annotation to the bean context builder.</jc>
056 *    <jk>public class</jk> BeanConfigAnnotationApplier <jk>extends</jk> AnnotationApplier&lt;<ja>BeanConfig</ja>,BeanContext.Builder&gt; {
057 *
058 *    <jc>// Required constructor. </jc>
059 *       <jk>public</jk> Applier(VarResolverSession <jv>vr</jv>) {
060 *          <jk>super</jk>(BeanConfig.<jk>class</jk>, BeanContext.Builder.<jk>class</jk>, <jv>vr</jv>);
061 *       }
062 *
063 *       <ja>@Override</ja>
064 *       <jk>public void</jk> apply(AnnotationInfo&lt;BeanConfig&gt; <jv>annotationInfo</jv>, BeanContext.Builder <jv>builder</jv>) {
065 *          <ja>BeanConfig</ja> <jv>beanConfig</jv> = <jv>annotationInfo</jv>.getAnnotation();
066 *
067 *          String <jv>sortProperties</jv> = <jv>beanConfig</jv>.sortProperties();
068 *          <jk>if</jk> (! <jv>sortProperties</jv>.isEmpty())
069 *             <jv>builder</jv>.sortProperties(Boolean.<jsm>parseBoolean</jsm>(<jv>sortProperties</jv>));
070 *       }
071 *    }
072 *
073 * <jc>// An annotated class.</jc>
074 *    <ja>@BeanConfig</ja>(sortProperties=<js>"true"</js>)
075 *    <jk>public class</jk> AnnotatedClass {}
076 *
077 * <jc>// Putting it together.</jc>
078 *    <jk>public static void</jk> main(String[] <jv>args</jv>) {
079 *
080 *    <jc>// Create a JSON serializer with sorted properties.</jc>
081 *       Serializer <jv>serializer</jv> = JsonSerializer.<jsm>create</jsm>().applyAnnotations(AnnotatedClass.<jk>class</jk>).build();
082 *    }
083 * </p>
084 *
085 * @param <A> The annotation that this applier reads from.
086 * @param <B> The builder class to apply the annotation to.
087 */
088public abstract class AnnotationApplier<A extends Annotation,B> {
089
090   private final VarResolverSession vr;
091   private final Class<A> ca;
092   private final Class<B> cb;
093
094   /**
095    * Constructor.
096    *
097    * @param annotationClass The annotation class.
098    * @param builderClass The builder class.
099    * @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}