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