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.marshall;
018
019import static org.apache.juneau.commons.utils.ClassUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.commons.utils.StringUtils.*;
022
023import java.lang.annotation.*;
024import java.nio.charset.*;
025import java.util.*;
026import java.util.stream.*;
027
028import org.apache.juneau.commons.reflect.*;
029import org.apache.juneau.commons.svl.*;
030import org.apache.juneau.commons.utils.*;
031
032/**
033 * Class used to add properties to a context builder (e.g. {@link MarshallingContext.Builder}) from an annotation (e.g. {@link MarshalledConfig}).
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>(MarshalledConfigAnnotationApplier.<jk>class</jk>)
047 *    <jk>public</jk> <jk>@interface </jk>MarshalledConfig {
048 *
049 *       String unsortedProperties() <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> MarshalledConfigAnnotationApplier <jk>extends</jk> AnnotationApplier&lt;<ja>MarshalledConfig</ja>,MarshallingContext.Builder&lt;?&gt;&gt; {
055 *
056 *    <jc>// Required constructor. </jc>
057 *       <jk>public</jk> Applier(VarResolverSession <jv>vr</jv>) {
058 *          <jk>super</jk>(MarshalledConfig.<jk>class</jk>, MarshallingContext.Builder.<jk>class</jk>, <jv>vr</jv>);
059 *       }
060 *
061 *       <ja>@Override</ja>
062 *       <jk>public void</jk> apply(AnnotationInfo&lt;MarshalledConfig&gt; <jv>annotationInfo</jv>, MarshallingContext.Builder&lt;?&gt; <jv>builder</jv>) {
063 *          <ja>MarshalledConfig</ja> <jv>beanConfig</jv> = <jv>annotationInfo</jv>.getAnnotation();
064 *
065 *          String <jv>unsortedProperties</jv> = <jv>beanConfig</jv>.unsortedProperties();
066 *          <jk>if</jk> (! <jv>unsortedProperties</jv>.isEmpty())
067 *             <jv>builder</jv>.unsortedProperties(Boolean.<jsm>parseBoolean</jsm>(<jv>unsortedProperties</jv>));
068 *       }
069 *    }
070 *
071 * <jc>// An annotated class opting out of the default sorted behavior.</jc>
072 *    <ja>@MarshalledConfig</ja>(unsortedProperties=<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 where AnnotatedClass uses natural JVM order.</jc>
079 *       Serializer <jv>serializer</jv> = JsonSerializer.<jsm>create</jsm>().applyAnnotations(AnnotatedClass.<jk>class</jk>).build();
080 *    }
081 * </p>
082 *
083 * @param <A> The annotation that this applier reads from.
084 * @param <B> The builder class to apply the annotation to.
085 */
086@SuppressWarnings({
087   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
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. Must not be <jk>null</jk>.
099    * @param builderClass The builder class. Must not be <jk>null</jk>.
100    * @param varResolverSession The string resolver to use for resolving strings. Must not be <jk>null</jk>.
101    */
102   protected AnnotationApplier(Class<A> annotationClass, Class<B> builderClass, VarResolverSession varResolverSession) {
103      ca = reqnn("annotationClass", annotationClass);
104      cb = reqnn("builderClass", builderClass);
105      vr = reqnn("vr", varResolverSession);
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    * Resolves the specified string and converts it to a boolean.
118    *
119    * @param in The string containing variables to resolve.
120    * @return The resolved boolean.
121    */
122   public Optional<Boolean> bool(String in) {
123      return string(in).map(Boolean::parseBoolean);
124   }
125
126   /**
127    * Returns <jk>true</jk> if this apply can be appied to the specified builder.
128    *
129    * @param builder The builder to check.
130    * @return <jk>true</jk> if this apply can be appied to the specified builder.
131    */
132   public boolean canApply(Object builder) {
133      return cb.isInstance(builder);
134   }
135
136   private Character toCharacter(String in, String loc) {
137      if (in.length() != 1)
138         throw new ConfigException("Invalid syntax for character on annotation @%s(%s): %s", ca.getSimpleName(), loc, in);
139      return in.charAt(0);
140   }
141
142   /**
143    * Resolves the specified string as a comma-delimited list of strings.
144    *
145    * @param in The CDL string containing variables to resolve.
146    * @return A stream of resolved strings.
147    */
148   protected Stream<String> cdl(String in) {
149      return Arrays.stream(splita(vr.resolve(in))).filter(Shorts::ine);
150   }
151
152   /**
153    * Resolves the specified string and converts it to a Character.
154    *
155    * @param in The string containing variables to resolve.
156    * @param loc The annotation field name.
157    * @return The resolved Character.
158    */
159   protected Optional<Character> character(String in, String loc) {
160      return string(in).map(x -> toCharacter(x, loc));
161   }
162
163   /**
164    * Resolves the specified string and converts it to a Charset.
165    *
166    * @param in The string containing variables to resolve.
167    * @return The resolved Charset.
168    */
169   protected Optional<Charset> charset(String in) {
170      return string(in).map(x -> eqic("default", x) ? Charset.defaultCharset() : Charset.forName(x));
171   }
172
173   /**
174    * Returns the specified class array as an {@link Optional}.
175    *
176    * <p>
177    * If the array is empty, then returns {@link Optional#empty()}.
178    *
179    * @param in The class array.
180    * @return The array wrapped in an {@link Optional}.
181    */
182   protected Optional<Class<?>[]> classes(Class<?>[] in) {
183      return o(in.length == 0 ? null : in);
184   }
185
186   /**
187    * Resolves the specified string and converts it to an int.
188    *
189    * @param in The string containing variables to resolve.
190    * @param loc The annotation field name.
191    * @return The resolved int.
192    */
193   protected Optional<Integer> integer(String in, String loc) {
194      try {
195         return string(in).map(Integer::parseInt);
196      } catch (NumberFormatException e) {
197         throw new ConfigException(e, "Invalid syntax for integer on annotation @%s(%s): %s", ca.getSimpleName(), loc, in);
198      }
199   }
200
201   /**
202    * Resolves each string in the specified array.
203    *
204    * @param in The array of strings containing variables to resolve.
205    * @return A stream of resolved strings.
206    */
207   protected Stream<String> stream(String[] in) {
208      return Arrays.stream(in).map(vr::resolve).filter(Shorts::ine);
209   }
210
211   /**
212    * Resolves the specified string.
213    *
214    * @param in The string containing variables to resolve.
215    * @return An optional containing the specified string if it exists, or {@link Optional#empty()} if it does not.
216    */
217   protected Optional<String> string(String in) {
218      in = vr.resolve(in);
219      return o(isEmpty(in) ? null : in);
220   }
221
222   /**
223    * Returns the specified string array as an {@link Optional}.
224    *
225    * <p>
226    * If the array is empty, then returns {@link Optional#empty()}.
227    *
228    * @param in The string array.
229    * @return The array wrapped in an {@link Optional}.
230    */
231   protected Optional<String[]> strings(String[] in) {
232      return o(in.length == 0 ? null : Arrays.stream(in).map(vr::resolve).filter(Shorts::ine).toArray(String[]::new));
233   }
234
235   /**
236    * Returns the specified value if it's simple name is not <js>"void"</js>.
237    *
238    * @param <T> The value to return.
239    * @param in The value to return.
240    * @return An optional containing the specified value.
241    */
242   protected <T> Optional<Class<T>> type(Class<T> in) {
243      return o(in).filter(NOT_VOID);
244   }
245
246   /**
247    * Returns the var resolver session for this apply.
248    *
249    * @return The var resolver session for this apply.
250    */
251   protected VarResolverSession vr() {
252      return vr;
253   }
254}