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<<ja>MarshalledConfig</ja>,MarshallingContext.Builder<?>> { 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<MarshalledConfig> <jv>annotationInfo</jv>, MarshallingContext.Builder<?> <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}