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<<ja>BeanConfig</ja>,BeanContext.Builder> { 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<BeanConfig> <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}