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 getLastAnnotation(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 getLastAnnotation(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().getLastAnnotation(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().getLastAnnotation(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 * <p> 178 * Results are in parent-to-child order. 179 * 180 * @param a 181 * The annotation to search for. 182 * @return 183 * A list of all matching annotations found or an empty list if none found. 184 */ 185 public <T extends Annotation> List<T> getAnnotations(Class<T> a) { 186 return appendAnnotations(new ArrayList<>(), a, true); 187 } 188 189 @SuppressWarnings("unchecked") 190 private <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a, boolean parentFirst) { 191 if (eInfo.isConstructor) { 192 ClassInfo ci = eInfo.getParamType(index).resolved(); 193 Annotation[] annotations = eInfo.getParameterAnnotations(index); 194 if (parentFirst) { 195 ci.appendAnnotations(l, a); 196 for (Annotation a2 : annotations) 197 if (a.isInstance(a2)) 198 l.add((T)a2); 199 } else { 200 for (Annotation a2 : annotations) 201 if (a.isInstance(a2)) 202 l.add((T)a2); 203 ci.appendAnnotations(l, a); 204 } 205 } else { 206 MethodInfo mi = (MethodInfo)eInfo; 207 ClassInfo ci = eInfo.getParamType(index).resolved(); 208 if (parentFirst) { 209 ci.appendAnnotations(l, a); 210 for (Method m2 : mi.getMatchingParentFirst()) 211 for (Annotation a2 : m2.getParameterAnnotations()[index]) 212 if (a.isInstance(a2)) 213 l.add((T)a2); 214 } else { 215 for (Method m2 : mi.getMatching()) 216 for (Annotation a2 : m2.getParameterAnnotations()[index]) 217 if (a.isInstance(a2)) 218 l.add((T)a2); 219 ci.appendAnnotations(l, a); 220 } 221 } 222 return l; 223 } 224 225 private synchronized Map<Class<?>,Optional<Annotation>> annotationMap() { 226 if (annotationMap == null) 227 annotationMap = new ConcurrentHashMap<>(); 228 return annotationMap; 229 } 230 231 //----------------------------------------------------------------------------------------------------------------- 232 // Other methods. 233 //----------------------------------------------------------------------------------------------------------------- 234 235 /** 236 * Returns <jk>true</jk> if the parameter has a name provided by the class file. 237 * 238 * @return <jk>true</jk> if the parameter has a name provided by the class file. 239 */ 240 public boolean hasName() { 241 return p.isNamePresent() || p.isAnnotationPresent(Name.class); 242 } 243 244 /** 245 * Returns the name of the parameter. 246 * 247 * <p> 248 * If the parameter's name is present, then this method returns the name provided by the class file. 249 * 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. 250 * 251 * @return The name of the parameter. 252 * @see Parameter#getName() 253 */ 254 public String getName() { 255 Name n = p.getAnnotation(Name.class); 256 if (n != null) 257 return n.value(); 258 if (p.isNamePresent()) 259 return p.getName(); 260 return null; 261 } 262 263 @Override 264 public String toString() { 265 return (eInfo.getSimpleName()) + "[" + index + "]"; 266 } 267}