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