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