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.reflect; 014 015import static org.apache.juneau.internal.CollectionUtils.*; 016import static org.apache.juneau.internal.ConsumerUtils.*; 017 018import java.lang.annotation.*; 019import java.lang.reflect.*; 020import java.util.*; 021import java.util.concurrent.*; 022import java.util.function.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.annotation.*; 026 027/** 028 * Lightweight utility class for introspecting information about a method parameter. 029 * 030 * <h5 class='section'>See Also:</h5><ul> 031 * </ul> 032 */ 033public final class ParamInfo { 034 035 private final ExecutableInfo eInfo; 036 private final Parameter p; 037 private final int index; 038 private volatile Map<Class<?>,Optional<Annotation>> annotationMap; 039 040 //----------------------------------------------------------------------------------------------------------------- 041 // Instantiation 042 //----------------------------------------------------------------------------------------------------------------- 043 044 /** 045 * Constructor. 046 * 047 * @param eInfo The constructor or method wrapper. 048 * @param p The parameter being wrapped. 049 * @param index The parameter index. 050 */ 051 protected ParamInfo(ExecutableInfo eInfo, Parameter p, int index) { 052 this.eInfo = eInfo; 053 this.p = p; 054 this.index = index; 055 } 056 057 /** 058 * Returns the index position of this parameter. 059 * 060 * @return The index position of this parameter. 061 */ 062 public int getIndex() { 063 return index; 064 } 065 066 /** 067 * Returns the method that this parameter belongs to. 068 * 069 * @return The method that this parameter belongs to, or <jk>null</jk> if it belongs to a constructor. 070 */ 071 public MethodInfo getMethod() { 072 return eInfo.isConstructor() ? null : (MethodInfo)eInfo; 073 } 074 075 /** 076 * Returns the constructor that this parameter belongs to. 077 * 078 * @return The constructor that this parameter belongs to, or <jk>null</jk> if it belongs to a method. 079 */ 080 public ConstructorInfo getConstructor() { 081 return eInfo.isConstructor() ? (ConstructorInfo)eInfo : null; 082 } 083 084 /** 085 * Returns the class type of this parameter. 086 * 087 * @return The class type of this parameter. 088 */ 089 public ClassInfo getParameterType() { 090 return eInfo.getParamType(index); 091 } 092 093 //----------------------------------------------------------------------------------------------------------------- 094 // Annotations 095 //----------------------------------------------------------------------------------------------------------------- 096 097 /** 098 * Performs an action on all matching annotations declared on this parameter. 099 * 100 * @param <A> The annotation type to look for. 101 * @param type The annotation type to look for. 102 * @param filter A predicate to apply to the entries to determine if action should be performed. Can be <jk>null</jk>. 103 * @param action An action to perform on the entry. 104 * @return This object. 105 */ 106 public <A extends Annotation> ParamInfo forEachDeclaredAnnotation(Class<A> type, Predicate<A> filter, Consumer<A> action) { 107 for (Annotation a : eInfo._getParameterAnnotations(index)) 108 consume(type, filter, action, a); 109 return this; 110 } 111 112 /** 113 * Returns the specified parameter annotation declared on this parameter. 114 * 115 * @param <A> The annotation type to look for. 116 * @param type The annotation type to look for. 117 * @return The specified parameter annotation declared on this parameter, or <jk>null</jk> if not found. 118 */ 119 public <A extends Annotation> A getDeclaredAnnotation(Class<A> type) { 120 if (type != null) 121 for (Annotation a : eInfo._getParameterAnnotations(index)) 122 if (type.isInstance(a)) 123 return type.cast(a); 124 return null; 125 } 126 127 /** 128 * Finds the annotation of the specified type defined on this method parameter. 129 * 130 * <p> 131 * If the annotation cannot be found on the immediate method, searches methods with the same 132 * signature on the parent classes or interfaces. 133 * <br>The search is performed in child-to-parent order. 134 * 135 * <p> 136 * If still not found, searches for the annotation on the return type of the method. 137 * 138 * @param <A> The annotation type to look for. 139 * @param type The annotation type to look for. 140 * @return 141 * The annotation if found, or <jk>null</jk> if not. 142 */ 143 @SuppressWarnings("unchecked") 144 public <A extends Annotation> A getAnnotation(Class<A> type) { 145 Optional<Annotation> o = annotationMap().get(type); 146 if (o == null) { 147 o = optional(findAnnotation(type)); 148 annotationMap().put(type, o); 149 } 150 return o.isPresent() ? (A)o.get() : null; 151 } 152 153 /** 154 * Returns <jk>true</jk> if this parameter has the specified annotation. 155 * 156 * @param <A> The annotation type to look for. 157 * @param type The annotation type to look for. 158 * @return 159 * The <jk>true</jk> if annotation if found. 160 */ 161 public <A extends Annotation> boolean hasAnnotation(Class<A> type) { 162 return getAnnotation(type) != null; 163 } 164 165 /** 166 * Returns <jk>true</jk> if this parameter doesn't have the specified annotation. 167 * 168 * @param <A> The annotation type to look for. 169 * @param type The annotation type to look for. 170 * @return 171 * The <jk>true</jk> if annotation if not found. 172 */ 173 public <A extends Annotation> boolean hasNoAnnotation(Class<A> type) { 174 return ! hasAnnotation(type); 175 } 176 177 private <A extends Annotation> A findAnnotation(Class<A> type) { 178 if (eInfo.isConstructor()) { 179 for (Annotation a2 : eInfo._getParameterAnnotations(index)) 180 if (type.isInstance(a2)) 181 return type.cast(a2); 182 return eInfo.getParamType(index).unwrap(Value.class,Optional.class).getAnnotation(type); 183 } 184 MethodInfo mi = (MethodInfo)eInfo; 185 Value<A> v = Value.empty(); 186 mi.forEachMatchingParentFirst(x -> true, x -> x.forEachParameterAnnotation(index, type, y -> true, y -> v.set(y))); 187 return v.orElseGet(() -> eInfo.getParamType(index).unwrap(Value.class,Optional.class).getAnnotation(type)); 188 } 189 190 /** 191 * Performs an action on all matching annotations on this parameter. 192 * 193 * <p> 194 * Searches all methods with the same signature on the parent classes or interfaces 195 * and the return type on the method. 196 * <p> 197 * Results are in parent-to-child order. 198 * 199 * @param <A> The annotation type to look for. 200 * @param type The annotation type to look for. 201 * @param filter A predicate to apply to the entries to determine if action should be performed. Can be <jk>null</jk>. 202 * @param action An action to perform on the entry. 203 * @return This object. 204 */ 205 public <A extends Annotation> ParamInfo forEachAnnotation(Class<A> type, Predicate<A> filter, Consumer<A> action) { 206 return forEachAnnotation(AnnotationProvider.DEFAULT, type, filter, action); 207 } 208 209 /** 210 * Returns the first matching annotation on this method parameter. 211 * 212 * <p> 213 * Searches all methods with the same signature on the parent classes or interfaces 214 * and the return type on the method. 215 * <p> 216 * Results are in parent-to-child order. 217 * 218 * @param <A> The annotation type to look for. 219 * @param type The annotation type to look for. 220 * @param filter A predicate to apply to the entries to determine if value should be used. Can be <jk>null</jk>. 221 * @return A list of all matching annotations found or an empty list if none found. 222 */ 223 @SuppressWarnings("unchecked") 224 public <A extends Annotation> A getAnnotation(Class<A> type, Predicate<A> filter) { 225 if (eInfo.isConstructor) { 226 ClassInfo ci = eInfo.getParamType(index).unwrap(Value.class,Optional.class); 227 A o = ci.getAnnotation(type, filter); 228 if (o != null) 229 return o; 230 for (Annotation a2 : eInfo._getParameterAnnotations(index)) 231 if (test(type, filter, a2)) 232 return (A)a2; 233 } else { 234 MethodInfo mi = (MethodInfo)eInfo; 235 ClassInfo ci = eInfo.getParamType(index).unwrap(Value.class,Optional.class); 236 A o = ci.getAnnotation(type, filter); 237 if (o != null) 238 return o; 239 Value<A> v = Value.empty(); 240 mi.forEachMatchingParentFirst(x -> true, x -> x.forEachParameterAnnotation(index, type, filter, y -> v.set(y))); 241 return v.orElse(null); 242 } 243 return null; 244 } 245 246 private <A extends Annotation> ParamInfo forEachAnnotation(AnnotationProvider ap, Class<A> a, Predicate<A> filter, Consumer<A> action) { 247 if (eInfo.isConstructor) { 248 ClassInfo ci = eInfo.getParamType(index).unwrap(Value.class,Optional.class); 249 Annotation[] annotations = eInfo._getParameterAnnotations(index); 250 ci.forEachAnnotation(ap, a, filter, action); 251 for (Annotation a2 : annotations) 252 consume(a, filter, action, a2); 253 } else { 254 MethodInfo mi = (MethodInfo)eInfo; 255 ClassInfo ci = eInfo.getParamType(index).unwrap(Value.class,Optional.class); 256 ci.forEachAnnotation(ap, a, filter, action); 257 mi.forEachMatchingParentFirst(x -> true, x -> x.forEachParameterAnnotation(index, a, filter, action)); 258 } 259 return this; 260 } 261 262 private Map<Class<?>,Optional<Annotation>> annotationMap() { 263 if (annotationMap == null) { 264 synchronized(this) { 265 annotationMap = new ConcurrentHashMap<>(); 266 } 267 } 268 return annotationMap; 269 } 270 271 //----------------------------------------------------------------------------------------------------------------- 272 // Other methods 273 //----------------------------------------------------------------------------------------------------------------- 274 275 /** 276 * Returns <jk>true</jk> if this object passes the specified predicate test. 277 * 278 * @param test The test to perform. 279 * @return <jk>true</jk> if this object passes the specified predicate test. 280 */ 281 public boolean matches(Predicate<ParamInfo> test) { 282 return test(test, this); 283 } 284 285 /** 286 * Performs an action on this object if the specified predicate test passes. 287 * 288 * @param test A test to apply to determine if action should be executed. Can be <jk>null</jk>. 289 * @param action An action to perform on this object. 290 * @return This object. 291 */ 292 public ParamInfo accept(Predicate<ParamInfo> test, Consumer<ParamInfo> action) { 293 if (matches(test)) 294 action.accept(this); 295 return this; 296 } 297 298 /** 299 * Returns <jk>true</jk> if the parameter type is an exact match for the specified class. 300 * 301 * @param c The type to check. 302 * @return <jk>true</jk> if the parameter type is an exact match for the specified class. 303 */ 304 public boolean isType(Class<?> c) { 305 return getParameterType().is(c); 306 } 307 308 /** 309 * Returns <jk>true</jk> if the parameter has a name provided by the class file. 310 * 311 * @return <jk>true</jk> if the parameter has a name provided by the class file. 312 */ 313 public boolean hasName() { 314 return p.isNamePresent() || p.isAnnotationPresent(Name.class); 315 } 316 317 /** 318 * Returns the name of the parameter. 319 * 320 * <p> 321 * If the parameter's name is present, then this method returns the name provided by the class file. 322 * Otherwise, this method synthesizes a name of the form argN, where N is the index of the parameter in the descriptor of the method which declares the parameter. 323 * 324 * @return The name of the parameter. 325 * @see Parameter#getName() 326 */ 327 public String getName() { 328 Name n = p.getAnnotation(Name.class); 329 if (n != null) 330 return n.value(); 331 if (p.isNamePresent()) 332 return p.getName(); 333 return null; 334 } 335 336 /** 337 * Returns <jk>true</jk> if this parameter can accept the specified value. 338 * 339 * @param value The value to check. 340 * @return <jk>true</jk> if this parameter can accept the specified value. 341 */ 342 public boolean canAccept(Object value) { 343 return getParameterType().isInstance(value); 344 } 345 346 @Override 347 public String toString() { 348 return (eInfo.getSimpleName()) + "[" + index + "]"; 349 } 350}