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.common.utils.Utils.*;
020import static org.apache.juneau.internal.ClassUtils.*;
021
022import java.lang.annotation.*;
023import java.nio.charset.*;
024import java.util.*;
025import java.util.stream.*;
026
027import org.apache.juneau.annotation.*;
028import org.apache.juneau.common.utils.*;
029import org.apache.juneau.reflect.*;
030import org.apache.juneau.svl.*;
031
032/**
033 * Class used to add properties to a context builder (e.g. {@link BeanContext.Builder}) from an annotation (e.g. {@link BeanConfig}).
034 *
035 * <p>
036 * Used by {@link Context.Builder#applyAnnotations(Class...)} and {@link Context.Builder#applyAnnotations(Object...)} to apply
037 * annotations to context beans.
038 *
039 * <p>
040 * The following code shows the general design pattern.
041 *
042 * <p class='bjava'>
043 *    <jc>// The annotation applied to classes and methods.</jc>
044 *    <ja>@Target</ja>({METHOD,TYPE})
045 *    <ja>@Retention</ja>(<jsf>RUNTIME</jsf>)
046 *    <ja>@ContextApply</ja>(BeanConfigAnnotationApplier.<jk>class</jk>)
047 *    <jk>public</jk> <jk>@interface </jk>BeanConfig {
048 *
049 *       String sortProperties() <jk>default</jk> <js>""</js>;
050 *
051 *    }
052 *
053 *    <jc>// The applier that applies the annotation to the bean context builder.</jc>
054 *    <jk>public class</jk> BeanConfigAnnotationApplier <jk>extends</jk> AnnotationApplier&lt;<ja>BeanConfig</ja>,BeanContext.Builder&gt; {
055 *
056 *    <jc>// Required constructor. </jc>
057 *       <jk>public</jk> Applier(VarResolverSession <jv>vr</jv>) {
058 *          <jk>super</jk>(BeanConfig.<jk>class</jk>, BeanContext.Builder.<jk>class</jk>, <jv>vr</jv>);
059 *       }
060 *
061 *       <ja>@Override</ja>
062 *       <jk>public void</jk> apply(AnnotationInfo&lt;BeanConfig&gt; <jv>annotationInfo</jv>, BeanContext.Builder <jv>builder</jv>) {
063 *          <ja>BeanConfig</ja> <jv>beanConfig</jv> = <jv>annotationInfo</jv>.getAnnotation();
064 *
065 *          String <jv>sortProperties</jv> = <jv>beanConfig</jv>.sortProperties();
066 *          <jk>if</jk> (! <jv>sortProperties</jv>.isEmpty())
067 *             <jv>builder</jv>.sortProperties(Boolean.<jsm>parseBoolean</jsm>(<jv>sortProperties</jv>));
068 *       }
069 *    }
070 *
071 * <jc>// An annotated class.</jc>
072 *    <ja>@BeanConfig</ja>(sortProperties=<js>"true"</js>)
073 *    <jk>public class</jk> AnnotatedClass {}
074 *
075 * <jc>// Putting it together.</jc>
076 *    <jk>public static void</jk> main(String[] <jv>args</jv>) {
077 *
078 *    <jc>// Create a JSON serializer with sorted properties.</jc>
079 *       Serializer <jv>serializer</jv> = JsonSerializer.<jsm>create</jsm>().applyAnnotations(AnnotatedClass.<jk>class</jk>).build();
080 *    }
081 * </p>
082 *
083 * <h5 class='section'>See Also:</h5><ul>
084 * </ul>
085 *
086 * @param <A> The annotation that this applier reads from.
087 * @param <B> The builder class to apply the annotation to.
088 */
089public abstract class AnnotationApplier<A extends Annotation, B> {
090
091   private final VarResolverSession vr;
092   private final Class<A> ca;
093   private final Class<B> cb;
094
095   /**
096    * Constructor.
097    *
098    * @param annotationClass The annotation class.
099    * @param builderClass The annotation class.
100    * @param vr The string resolver to use for resolving strings.
101    */
102   protected AnnotationApplier(Class<A> annotationClass, Class<B> builderClass, VarResolverSession vr) {
103      this.vr = vr == null ? VarResolver.DEFAULT.createSession() : vr;
104      this.ca = annotationClass;
105      this.cb = builderClass;
106   }
107
108   /**
109    * Apply the specified annotation to the specified property store builder.
110    *
111    * @param annotationInfo The annotation.
112    * @param builder The property store builder.
113    */
114   public abstract void apply(AnnotationInfo<A> annotationInfo, B builder);
115
116   /**
117    * Returns <jk>true</jk> if this apply can be appied to the specified builder.
118    *
119    * @param builder The builder to check.
120    * @return <jk>true</jk> if this apply can be appied to the specified builder.
121    */
122   public boolean canApply(Object builder) {
123      return cb.isInstance(builder);
124   }
125
126   /**
127    * Returns the builder class that this applier applies to.
128    *
129    * @return The builder class that this applier applies to.
130    */
131   public Class<?> getBuilderClass() {
132      return cb;
133   }
134
135   /**
136    * Returns the var resolver session for this apply.
137    *
138    * @return The var resolver session for this apply.
139    */
140   protected VarResolverSession vr() {
141      return vr;
142   }
143
144   /**
145    * Resolves the specified string.
146    *
147    * @param in The string containing variables to resolve.
148    * @return An optional containing the specified string if it exists, or {@link Optional#empty()} if it does not.
149    */
150   protected Optional<String> string(String in) {
151      in = vr.resolve(in);
152      return Utils.opt(Utils.isEmpty(in) ? null : in);
153   }
154
155   /**
156    * Returns the specified value if it's simple name is not <js>"void"</js>.
157    *
158    * @param <T> The value to return.
159    * @param in The value to return.
160    * @return An optional containing the specified value.
161    */
162   protected <T> Optional<Class<T>> type(Class<T> in) {
163      return Utils.opt(in).filter(NOT_VOID);
164   }
165
166   /**
167    * Returns the specified string array as an {@link Optional}.
168    *
169    * <p>
170    * If the array is empty, then returns {@link Optional#empty()}.
171    *
172    * @param in The string array.
173    * @return The array wrapped in an {@link Optional}.
174    */
175   protected Optional<String[]> strings(String[] in) {
176      return Utils.opt(in.length == 0 ? null : Arrays.stream(in).map(vr::resolve).filter(Utils::isNotEmpty).toArray(String[]::new));
177   }
178
179   /**
180    * Resolves the specified string as a comma-delimited list of strings.
181    *
182    * @param in The CDL string containing variables to resolve.
183    * @return An array with resolved strings.
184    */
185   protected Stream<String> stream(String[] in) {
186      return Arrays.stream(in).map(vr::resolve).filter(Utils::isNotEmpty);
187   }
188
189   /**
190    * Resolves the specified string as a comma-delimited list of strings.
191    *
192    * @param in The CDL string containing variables to resolve.
193    * @return An array with resolved strings.
194    */
195   protected Stream<String> cdl(String in) {
196      return Arrays.stream(splita(vr.resolve(in))).filter(Utils::isNotEmpty);
197   }
198
199   /**
200    * Resolves the specified string and converts it to a boolean.
201    *
202    * @param in The string containing variables to resolve.
203    * @return The resolved boolean.
204    */
205   public Optional<Boolean> bool(String in) {
206      return string(in).map(Boolean::parseBoolean);
207   }
208
209   /**
210    * Resolves the specified string and converts it to an int.
211    *
212    * @param in The string containing variables to resolve.
213    * @param loc The annotation field name.
214    * @return The resolved int.
215    */
216   protected Optional<Integer> integer(String in, String loc) {
217      try {
218         return string(in).map(Integer::parseInt);
219      } catch (NumberFormatException e) {
220         throw new ConfigException("Invalid syntax for integer on annotation @{0}({1}): {2}", ca.getSimpleName(), loc, in);
221      }
222   }
223
224   /**
225    * Resolves the specified string and converts it to a Charset.
226    *
227    * @param in The string containing variables to resolve.
228    * @return The resolved Charset.
229    */
230   protected Optional<Charset> charset(String in) {
231      return string(in).map(x -> "default".equalsIgnoreCase(x) ? Charset.defaultCharset() : Charset.forName(x));
232   }
233
234   /**
235    * Resolves the specified string and converts it to a Character.
236    *
237    * @param in The string containing variables to resolve.
238    * @param loc The annotation field name.
239    * @return The resolved Character.
240    */
241   protected Optional<Character> character(String in, String loc) {
242      return string(in).map(x -> toCharacter(x, loc));
243   }
244
245   private Character toCharacter(String in, String loc) {
246      if (in.length() != 1)
247         throw new ConfigException("Invalid syntax for character on annotation @{0}({1}): {2}", ca.getSimpleName(), loc, in);
248      return in.charAt(0);
249   }
250
251   /**
252    * Returns the specified class array as an {@link Optional}.
253    *
254    * <p>
255    * If the array is empty, then returns {@link Optional#empty()}.
256    *
257    * @param in The class array.
258    * @return The array wrapped in an {@link Optional}.
259    */
260   protected Optional<Class<?>[]> classes(Class<?>[] in) {
261      return Utils.opt(in.length == 0 ? null : in);
262   }
263
264   /**
265    * Represents a no-op configuration apply.
266    */
267   public static class NoOp extends AnnotationApplier<Annotation,Object> {
268
269      /**
270       * Constructor.
271       *
272       * @param r The string resolver to use for resolving strings.
273       */
274      public NoOp(VarResolverSession r) {
275         super(Annotation.class, Object.class, r);
276      }
277
278      @Override /* ConfigApply */
279      public void apply(AnnotationInfo<Annotation> ai, Object b) { /* no-op */ }
280   }
281}