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