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