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 java.lang.annotation.*; 016import java.lang.reflect.*; 017import java.util.*; 018import java.util.concurrent.*; 019 020import org.apache.juneau.annotation.*; 021 022/** 023 * Lightweight utility class for introspecting information about a method parameter. 024 */ 025@BeanIgnore 026public final class ParamInfo { 027 028 private final ExecutableInfo eInfo; 029 private final Parameter p; 030 private final int index; 031 private volatile Map<Class<?>,Optional<Annotation>> annotationMap; 032 033 //----------------------------------------------------------------------------------------------------------------- 034 // Instantiation. 035 //----------------------------------------------------------------------------------------------------------------- 036 037 /** 038 * Constructor. 039 * 040 * @param eInfo The constructor or method wrapper. 041 * @param p The parameter being wrapped. 042 * @param index The parameter index. 043 */ 044 protected ParamInfo(ExecutableInfo eInfo, Parameter p, int index) { 045 this.eInfo = eInfo; 046 this.p = p; 047 this.index = index; 048 } 049 050 /** 051 * Returns the index position of this parameter. 052 * 053 * @return The index position of this parameter. 054 */ 055 public int getIndex() { 056 return index; 057 } 058 059 /** 060 * Returns the method that this parameter belongs to. 061 * 062 * @return The method that this parameter belongs to, or <jk>null</jk> if it belongs to a constructor. 063 */ 064 public MethodInfo getMethod() { 065 return eInfo.isConstructor() ? null : (MethodInfo)eInfo; 066 } 067 068 /** 069 * Returns the constructor that this parameter belongs to. 070 * 071 * @return The constructor that this parameter belongs to, or <jk>null</jk> if it belongs to a method. 072 */ 073 public ConstructorInfo getConstructor() { 074 return eInfo.isConstructor() ? (ConstructorInfo)eInfo : null; 075 } 076 077 /** 078 * Returns the class type of this parameter. 079 * 080 * @return The class type of this parameter. 081 */ 082 public ClassInfo getParameterType() { 083 return eInfo.getParamType(index); 084 } 085 086 //----------------------------------------------------------------------------------------------------------------- 087 // Annotations. 088 //----------------------------------------------------------------------------------------------------------------- 089 090 /** 091 * Returns the parameter annotations declared on this parameter. 092 * 093 * @return The parameter annotations declared on this parameter, or an empty array if none found. 094 */ 095 public Annotation[] getDeclaredAnnotations() { 096 return eInfo.getParameterAnnotations(index); 097 } 098 099 /** 100 * Returns the specified parameter annotation declared on this parameter. 101 * 102 * @param a 103 * The annotation to search for. 104 * @param <T> 105 * The annotation type. 106 * @return The specified parameter annotation declared on this parameter, or <jk>null</jk> if not found. 107 */ 108 @SuppressWarnings("unchecked") 109 public <T extends Annotation> T getDeclaredAnnotation(Class<T> a) { 110 if (a != null) 111 for (Annotation aa : eInfo.getParameterAnnotations(index)) 112 if (a.isInstance(aa)) 113 return (T)aa; 114 return null; 115 } 116 117 /** 118 * Finds the annotation of the specified type defined on this method parameter. 119 * 120 * <p> 121 * If the annotation cannot be found on the immediate method, searches methods with the same 122 * signature on the parent classes or interfaces. 123 * <br>The search is performed in child-to-parent order. 124 * 125 * <p> 126 * If still not found, searches for the annotation on the return type of the method. 127 * 128 * @param a 129 * The annotation to search for. 130 * @return 131 * The annotation if found, or <jk>null</jk> if not. 132 */ 133 @SuppressWarnings("unchecked") 134 public <T extends Annotation> T getAnnotation(Class<T> a) { 135 Optional<Annotation> o = annotationMap().get(a); 136 if (o == null) { 137 o = Optional.ofNullable(findAnnotation(a)); 138 annotationMap().put(a, o); 139 } 140 return o.isPresent() ? (T)o.get() : null; 141 } 142 143 /** 144 * Returns <jk>true</jk> if this parameter has the specified annotation. 145 * 146 * @param a 147 * The annotation to search for. 148 * @return 149 * The <jk>true</jk> if annotation if found. 150 */ 151 public boolean hasAnnotation(Class<? extends Annotation> a) { 152 return getAnnotation(a) != null; 153 } 154 155 @SuppressWarnings("unchecked") 156 private <T extends Annotation> T findAnnotation(Class<T> a) { 157 if (eInfo.isConstructor()) { 158 for (Annotation a2 : eInfo.getParameterAnnotations(index)) 159 if (a.isInstance(a2)) 160 return (T)a2; 161 return eInfo.getParamType(index).resolved().getAnnotation(a); 162 } 163 MethodInfo mi = (MethodInfo)eInfo; 164 for (Method m2 : mi.getMatching()) 165 for (Annotation a2 : m2.getParameterAnnotations()[index]) 166 if (a.isInstance(a2)) 167 return (T)a2; 168 return eInfo.getParamType(index).resolved().getAnnotation(a); 169 } 170 171 /** 172 * Returns all annotations of the specified type defined on this method parameter. 173 * 174 * <p> 175 * Searches all methods with the same signature on the parent classes or interfaces 176 * and the return type on the method. 177 * 178 * @param a 179 * The annotation to search for. 180 * @return 181 * A list of all matching annotations found in child-to-parent order, or an empty list if none found. 182 */ 183 public <T extends Annotation> List<T> getAnnotations(Class<T> a) { 184 return appendAnnotations(new ArrayList<>(), a, false); 185 } 186 187 /** 188 * Identical to {@link #getAnnotations(Class)} but returns the list in reverse (parent-to-child) order. 189 * 190 * @param a 191 * The annotation to search for. 192 * @return 193 * A list of all matching annotations found or an empty list if none found. 194 */ 195 public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a) { 196 return appendAnnotations(new ArrayList<>(), a, true); 197 } 198 199 @SuppressWarnings("unchecked") 200 private <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a, boolean parentFirst) { 201 if (eInfo.isConstructor) { 202 ClassInfo ci = eInfo.getParamType(index).resolved(); 203 Annotation[] annotations = eInfo.getParameterAnnotations(index); 204 if (parentFirst) { 205 ci.appendAnnotationsParentFirst(l, a); 206 for (Annotation a2 : annotations) 207 if (a.isInstance(a2)) 208 l.add((T)a2); 209 } else { 210 for (Annotation a2 : annotations) 211 if (a.isInstance(a2)) 212 l.add((T)a2); 213 ci.appendAnnotations(l, a); 214 } 215 } else { 216 MethodInfo mi = (MethodInfo)eInfo; 217 ClassInfo ci = eInfo.getParamType(index).resolved(); 218 if (parentFirst) { 219 ci.appendAnnotationsParentFirst(l, a); 220 for (Method m2 : mi.getMatchingParentFirst()) 221 for (Annotation a2 : m2.getParameterAnnotations()[index]) 222 if (a.isInstance(a2)) 223 l.add((T)a2); 224 } else { 225 for (Method m2 : mi.getMatching()) 226 for (Annotation a2 : m2.getParameterAnnotations()[index]) 227 if (a.isInstance(a2)) 228 l.add((T)a2); 229 ci.appendAnnotations(l, a); 230 } 231 } 232 return l; 233 } 234 235 private synchronized Map<Class<?>,Optional<Annotation>> annotationMap() { 236 if (annotationMap == null) 237 annotationMap = new ConcurrentHashMap<>(); 238 return annotationMap; 239 } 240 241 //----------------------------------------------------------------------------------------------------------------- 242 // Other methods. 243 //----------------------------------------------------------------------------------------------------------------- 244 245 /** 246 * Returns <jk>true</jk> if the parameter has a name provided by the class file. 247 * 248 * @return <jk>true</jk> if the parameter has a name provided by the class file. 249 */ 250 public boolean hasName() { 251 return p.isNamePresent() || p.isAnnotationPresent(Name.class); 252 } 253 254 /** 255 * Returns the name of the parameter. 256 * 257 * <p> 258 * If the parameter's name is present, then this method returns the name provided by the class file. 259 * 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. 260 * 261 * @return The name of the parameter. 262 * @see Parameter#getName() 263 */ 264 public String getName() { 265 Name n = p.getAnnotation(Name.class); 266 if (n != null) 267 return n.value(); 268 if (p.isNamePresent()) 269 return p.getName(); 270 return null; 271 } 272 273 @Override 274 public String toString() { 275 return (eInfo.getSimpleName()) + "[" + index + "]"; 276 } 277}