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.*; 016 017import java.beans.*; 018import java.lang.annotation.*; 019import java.lang.reflect.*; 020import java.util.*; 021import java.util.function.*; 022 023import org.apache.juneau.*; 024import org.apache.juneau.annotation.*; 025import org.apache.juneau.internal.*; 026 027/** 028 * Lightweight utility class for introspecting information about a method. 029 */ 030@BeanIgnore 031public final class MethodInfo extends ExecutableInfo implements Comparable<MethodInfo> { 032 033 private ClassInfo returnType; 034 private final Method m; 035 private List<Method> matching; 036 037 //----------------------------------------------------------------------------------------------------------------- 038 // Instantiation. 039 //----------------------------------------------------------------------------------------------------------------- 040 041 /** 042 * Constructor. 043 * 044 * @param declaringClass The class that declares this method. 045 * @param m The method being wrapped. 046 */ 047 protected MethodInfo(ClassInfo declaringClass, Method m) { 048 super(declaringClass, m); 049 this.m = m; 050 } 051 052 /** 053 * Convenience method for instantiating a {@link MethodInfo}; 054 * 055 * @param declaringClass The class that declares this method. 056 * @param m The method being wrapped. 057 * @return A new {@link MethodInfo} object, or <jk>null</jk> if the method was null; 058 */ 059 public static MethodInfo of(ClassInfo declaringClass, Method m) { 060 if (m == null) 061 return null; 062 return new MethodInfo(declaringClass, m); 063 } 064 065 /** 066 * Convenience method for instantiating a {@link MethodInfo}; 067 * 068 * @param m The method being wrapped. 069 * @return A new {@link MethodInfo} object, or <jk>null</jk> if the method was null; 070 */ 071 public static MethodInfo of(Method m) { 072 if (m == null) 073 return null; 074 return new MethodInfo(ClassInfo.of(m.getDeclaringClass()), m); 075 } 076 077 /** 078 * Returns the wrapped method. 079 * 080 * @return The wrapped method. 081 */ 082 public Method inner() { 083 return m; 084 } 085 086 //----------------------------------------------------------------------------------------------------------------- 087 // Matching methods. 088 //----------------------------------------------------------------------------------------------------------------- 089 090 /** 091 * Finds all declared methods with the same name and arguments on all superclasses and interfaces. 092 * 093 * @return 094 * All matching methods including this method itself. 095 * <br>Methods are ordered from child-to-parent order. 096 */ 097 public List<Method> getMatching() { 098 if (matching == null) 099 matching = Collections.unmodifiableList(findMatching(new ArrayList<>(), m, m.getDeclaringClass())); 100 return matching; 101 } 102 103 /** 104 * Convenience method for retrieving values in {@link #getMatching()} in parent-to-child order. 105 * 106 * @return 107 * All matching methods including this method itself. 108 * <br>Methods are ordered from parent-to-child order. 109 */ 110 public Iterable<Method> getMatchingParentFirst() { 111 return iterable(getMatching(), true); 112 } 113 114 private static List<Method> findMatching(List<Method> l, Method m, Class<?> c) { 115 for (Method m2 : c.getDeclaredMethods()) 116 if (m.getName().equals(m2.getName()) && Arrays.equals(m.getParameterTypes(), m2.getParameterTypes())) 117 l.add(m2); 118 Class<?> pc = c.getSuperclass(); 119 if (pc != null) 120 findMatching(l, m, pc); 121 for (Class<?> ic : c.getInterfaces()) 122 findMatching(l, m, ic); 123 return l; 124 } 125 126 private Method findMatchingOnClass(ClassInfo c) { 127 for (Method m2 : c.inner().getDeclaredMethods()) 128 if (m.getName().equals(m2.getName()) && Arrays.equals(m.getParameterTypes(), m2.getParameterTypes())) 129 return m2; 130 return null; 131 } 132 133 //----------------------------------------------------------------------------------------------------------------- 134 // Annotations 135 //----------------------------------------------------------------------------------------------------------------- 136 137 /** 138 * Returns all annotations of the specified type defined on the specified method. 139 * 140 * <p> 141 * Searches all methods with the same signature on the parent classes or interfaces 142 * and the return type on the method. 143 * 144 * @param a 145 * The annotation to search for. 146 * @return 147 * A list of all matching annotations found in child-to-parent order, or an empty list if none found. 148 */ 149 public <T extends Annotation> List<T> getAnnotations(Class<T> a) { 150 return appendAnnotations(new ArrayList<>(), a); 151 } 152 153 /** 154 * Identical to {@link #getAnnotations(Class)} but returns the list in reverse (parent-to-child) order. 155 * 156 * @param a 157 * The annotation to search for. 158 * @return 159 * A list of all matching annotations found or an empty list if none found. 160 */ 161 public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a) { 162 return appendAnnotationsParentFirst(new ArrayList<>(), a); 163 } 164 165 /** 166 * Finds and appends the specified annotation on the specified method and methods on superclasses/interfaces to the specified 167 * list. 168 * 169 * <p> 170 * Results are ordered in child-to-parent order. 171 * 172 * @param l The list of annotations. 173 * @param a The annotation. 174 * @return The same list. 175 */ 176 @SuppressWarnings("unchecked") 177 public <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a) { 178 for (Method m2 : getMatching()) 179 for (Annotation a2 : m2.getDeclaredAnnotations()) 180 if (a.isInstance(a2)) 181 l.add((T)a2); 182 getReturnType().resolved().appendAnnotations(l, a); 183 declaringClass.appendAnnotations(l, a); 184 return l; 185 } 186 187 /** 188 * Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified 189 * list. 190 * 191 * @param l The list of annotations. 192 * @param a The annotation. 193 * @return The same list. 194 */ 195 @SuppressWarnings("unchecked") 196 public <T extends Annotation> List<T> appendAnnotationsParentFirst(List<T> l, Class<T> a) { 197 declaringClass.appendAnnotationsParentFirst(l, a); 198 for (Method m2 : getMatchingParentFirst()) 199 for (Annotation a2 : m2.getDeclaredAnnotations()) 200 if (a.isInstance(a2)) 201 l.add((T)a2); 202 getReturnType().resolved().appendAnnotations(l, a); 203 return l; 204 } 205 206 /** 207 * Returns the first annotation in the specified list on this method. 208 * 209 * @param c The annotations that cannot be present on the method. 210 * @return <jk>true</jk> if this method does not have any of the specified annotations. 211 */ 212 @SafeVarargs 213 public final Annotation getAnnotation(Class<? extends Annotation>...c) { 214 for (Class<? extends Annotation> cc : c) { 215 Annotation a = getAnnotation(cc); 216 if (a != null) 217 return a; 218 } 219 return null; 220 } 221 222 /** 223 * Constructs an {@link AnnotationList} of all annotations found on this method. 224 * 225 * <p> 226 * Annotations are appended in the following orders: 227 * <ol> 228 * <li>On this method and matching methods ordered child-to-parent. 229 * <li>On this class. 230 * <li>On parent classes ordered child-to-parent. 231 * <li>On interfaces ordered child-to-parent. 232 * <li>On the package of this class. 233 * </ol> 234 * 235 * @param filter 236 * Optional filter to apply to limit which annotations are added to the list. 237 * <br>Can be <jk>null</jk> for no filtering. 238 * @return A new {@link AnnotationList} object on every call. 239 */ 240 public AnnotationList getAnnotationList(Predicate<AnnotationInfo<?>> filter) { 241 return appendAnnotationList(new AnnotationList(filter)); 242 } 243 244 /** 245 * Constructs an {@link AnnotationList} of all annotations found on this method. 246 * 247 * <p> 248 * Annotations are appended in the following orders: 249 * <ol> 250 * <li>On the package of this class. 251 * <li>On interfaces ordered parent-to-child. 252 * <li>On parent classes ordered parent-to-child. 253 * <li>On this class. 254 * <li>On this method and matching methods ordered parent-to-child. 255 * </ol> 256 * 257 * @param filter 258 * Optional filter to apply to limit which annotations are added to the list. 259 * <br>Can be <jk>null</jk> for no filtering. 260 * @return A new {@link AnnotationList} object on every call. 261 */ 262 public AnnotationList getAnnotationListParentFirst(Predicate<AnnotationInfo<?>> filter) { 263 return appendAnnotationListParentFirst(new AnnotationList(filter)); 264 } 265 266 /** 267 * Same as {@link #getAnnotationListParentFirst(Predicate)} except only returns annotations defined on methods. 268 * 269 * @param filter 270 * Optional filter to apply to limit which annotations are added to the list. 271 * <br>Can be <jk>null</jk> for no filtering. 272 * @return A new {@link AnnotationList} object on every call. 273 */ 274 public AnnotationList getAnnotationListMethodOnlyParentFirst(Predicate<AnnotationInfo<?>> filter) { 275 return appendAnnotationListMethodOnlyParentFirst(new AnnotationList(filter)); 276 } 277 278 /** 279 * Returns <jk>true</jk> if this method or parent methods have any annotations annotated with {@link PropertyStoreApply}. 280 * 281 * @return <jk>true</jk> if this method or parent methods have any annotations annotated with {@link PropertyStoreApply}. 282 */ 283 public boolean hasConfigAnnotations() { 284 for (Method m2 : getMatching()) 285 for (Annotation a2 : m2.getAnnotations()) 286 if (a2.annotationType().getAnnotation(PropertyStoreApply.class) != null) 287 return true; 288 return false; 289 } 290 291 @Override 292 @SuppressWarnings("unchecked") 293 protected <T extends Annotation> T findAnnotation(Class<T> a) { 294 for (Method m2 : getMatching()) 295 for (Annotation a2 : m2.getAnnotations()) 296 if (a.isInstance(a2)) 297 return (T)a2; 298 return getReturnType().resolved().getAnnotation(a); 299 } 300 301 AnnotationList appendAnnotationList(AnnotationList al) { 302 ClassInfo c = this.declaringClass; 303 for (ClassInfo ci : c.getParents()) { 304 appendMethodAnnotations(al, ci); 305 appendAnnotations(al, ci); 306 } 307 for (ClassInfo ci : c.getInterfaces()) { 308 appendMethodAnnotations(al, ci); 309 appendAnnotations(al, ci); 310 } 311 appendAnnotations(al, c.getPackage()); 312 return al; 313 } 314 315 AnnotationList appendAnnotationListParentFirst(AnnotationList al) { 316 ClassInfo c = this.declaringClass; 317 appendAnnotations(al, c.getPackage()); 318 for (ClassInfo ci : c.getInterfacesParentFirst()) { 319 appendAnnotations(al, ci); 320 appendMethodAnnotations(al, ci); 321 } 322 for (ClassInfo ci : c.getParentsParentFirst()) { 323 appendAnnotations(al, ci); 324 appendMethodAnnotations(al, ci); 325 } 326 return al; 327 } 328 329 AnnotationList appendAnnotationListMethodOnlyParentFirst(AnnotationList al) { 330 ClassInfo c = this.declaringClass; 331 for (ClassInfo ci : c.getInterfacesParentFirst()) 332 appendMethodAnnotations(al, ci); 333 for (ClassInfo ci : c.getParentsParentFirst()) 334 appendMethodAnnotations(al, ci); 335 return al; 336 } 337 338 void appendAnnotations(AnnotationList al, Package p) { 339 if (p != null) 340 for (Annotation a : p.getDeclaredAnnotations()) 341 al.add(AnnotationInfo.of(p, a)); 342 } 343 344 void appendAnnotations(AnnotationList al, ClassInfo ci) { 345 if (ci != null) 346 for (Annotation a : ci.c.getDeclaredAnnotations()) 347 al.add(AnnotationInfo.of(ci, a)); 348 } 349 350 void appendMethodAnnotations(AnnotationList al, ClassInfo ci) { 351 Method m = findMatchingOnClass(ci); 352 if (m != null) 353 for (Annotation a : m.getDeclaredAnnotations()) 354 al.add(AnnotationInfo.of(MethodInfo.of(m), a)); 355 } 356 357 //----------------------------------------------------------------------------------------------------------------- 358 // Return type. 359 //----------------------------------------------------------------------------------------------------------------- 360 361 /** 362 * Returns the generic return type of this method as a {@link ClassInfo} object. 363 * 364 * @return The generic return type of this method. 365 */ 366 public ClassInfo getReturnType() { 367 if (returnType == null) 368 returnType = ClassInfo.of(m.getReturnType(), m.getGenericReturnType()); 369 return returnType; 370 } 371 372 /** 373 * Returns <jk>true</jk> if this method has this return type. 374 * 375 * @param c The return type to test for. 376 * @return <jk>true</jk> if this method has this return type. 377 */ 378 public boolean hasReturnType(Class<?> c) { 379 return m.getReturnType() == c; 380 } 381 382 /** 383 * Returns <jk>true</jk> if this method has this return type. 384 * 385 * @param ci The return type to test for. 386 * @return <jk>true</jk> if this method has this return type. 387 */ 388 public boolean hasReturnType(ClassInfo ci) { 389 return hasReturnType(ci.inner()); 390 } 391 392 /** 393 * Returns <jk>true</jk> if this method has this parent return type. 394 * 395 * @param c The return type to test for. 396 * @return <jk>true</jk> if this method has this parent return type. 397 */ 398 public boolean hasReturnTypeParent(Class<?> c) { 399 return ClassInfo.of(c).isParentOf(m.getReturnType()); 400 } 401 402 /** 403 * Returns <jk>true</jk> if this method has this parent return type. 404 * 405 * @param ci The return type to test for. 406 * @return <jk>true</jk> if this method has this parent return type. 407 */ 408 public boolean hasReturnTypeParent(ClassInfo ci) { 409 return hasReturnTypeParent(ci.inner()); 410 } 411 412 //----------------------------------------------------------------------------------------------------------------- 413 // Other methods 414 //----------------------------------------------------------------------------------------------------------------- 415 416 /** 417 * Shortcut for calling the invoke method on the underlying method. 418 * 419 * @param obj the object the underlying method is invoked from. 420 * @param args the arguments used for the method call 421 * @return The object returned from the method. 422 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 423 */ 424 @SuppressWarnings("unchecked") 425 public <T> T invoke(Object obj, Object...args) throws ExecutableException { 426 try { 427 return (T)m.invoke(obj, args); 428 } catch (IllegalAccessException | InvocationTargetException e) { 429 throw new ExecutableException(e); 430 } 431 } 432 433 /** 434 * Invokes the specified method using fuzzy-arg matching. 435 * 436 * <p> 437 * Arguments will be matched to the parameters based on the parameter types. 438 * <br>Arguments can be in any order. 439 * <br>Extra arguments will be ignored. 440 * <br>Missing arguments will be left <jk>null</jk>. 441 * 442 * <p> 443 * Note that this only works for methods that have distinguishable argument types. 444 * <br>It's not going to work on methods with generic argument types like <c>Object</c> 445 * 446 * @param pojo 447 * The POJO the method is being called on. 448 * <br>Can be <jk>null</jk> for static methods. 449 * @param args 450 * The arguments to pass to the method. 451 * @return 452 * The results of the method invocation. 453 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 454 */ 455 public Object invokeFuzzy(Object pojo, Object...args) throws ExecutableException { 456 try { 457 return m.invoke(pojo, ClassUtils.getMatchingArgs(m.getParameterTypes(), args)); 458 } catch (IllegalAccessException | InvocationTargetException e) { 459 throw new ExecutableException(e); 460 } 461 } 462 463 /** 464 * Returns the signature of this method. 465 * 466 * <p> 467 * For no-arg methods, the signature will be a simple string such as <js>"toString"</js>. 468 * For methods with one or more args, the arguments will be fully-qualified class names (e.g. 469 * <js>"append(java.util.StringBuilder,boolean)"</js>) 470 * 471 * @return The methods signature. 472 */ 473 public String getSignature() { 474 StringBuilder sb = new StringBuilder(128); 475 sb.append(m.getName()); 476 Class<?>[] pt = rawParamTypes(); 477 if (pt.length > 0) { 478 sb.append('('); 479 List<ParamInfo> mpi = getParams(); 480 for (int i = 0; i < pt.length; i++) { 481 if (i > 0) 482 sb.append(','); 483 mpi.get(i).getParameterType().appendFullName(sb); 484 } 485 sb.append(')'); 486 } 487 return sb.toString(); 488 } 489 490 /** 491 * Returns the bean property name if this is a getter or setter. 492 * 493 * @return The bean property name, or <jk>null</jk> if this isn't a getter or setter. 494 */ 495 public String getPropertyName() { 496 String n = m.getName(); 497 if ((n.startsWith("get") || n.startsWith("set")) && n.length() > 3) 498 return Introspector.decapitalize(n.substring(3)); 499 if (n.startsWith("is") && n.length() > 2) 500 return Introspector.decapitalize(n.substring(2)); 501 return n; 502 } 503 504 /** 505 * Returns <jk>true</jk> if the parameters on the method only consist of the types specified in the list. 506 * 507 * <h5 class='figure'>Example:</h5> 508 * <p class='bpcode w800'> 509 * 510 * <jc>// Example method:</jc> 511 * <jk>public void</jk> foo(String bar, Integer baz); 512 * 513 * argsOnlyOfType(fooMethod, String.<jk>class</jk>, Integer.<jk>class</jk>); <jc>// True.</jc> 514 * argsOnlyOfType(fooMethod, String.<jk>class</jk>, Integer.<jk>class</jk>, Map.<jk>class</jk>); <jc>// True.</jc> 515 * argsOnlyOfType(fooMethod, String.<jk>class</jk>); <jc>// False.</jc> 516 * </p> 517 * 518 * @param args The valid class types (exact) for the arguments. 519 * @return <jk>true</jk> if the method parameters only consist of the types specified in the list. 520 */ 521 public boolean argsOnlyOfType(Class<?>...args) { 522 for (Class<?> c1 : rawParamTypes()) { 523 boolean foundMatch = false; 524 for (Class<?> c2 : args) 525 if (c1 == c2) 526 foundMatch = true; 527 if (! foundMatch) 528 return false; 529 } 530 return true; 531 } 532 533 /** 534 * Returns <jk>true</jk> if this method is a bridge method. 535 * 536 * @return <jk>true</jk> if this method is a bridge method. 537 */ 538 public boolean isBridge() { 539 return m.isBridge(); 540 } 541 542 @Override 543 public int compareTo(MethodInfo o) { 544 int i = getSimpleName().compareTo(o.getSimpleName()); 545 if (i == 0) { 546 i = rawParamTypes().length - o.rawParamTypes().length; 547 if (i == 0) { 548 for (int j = 0; j < rawParamTypes().length && i == 0; j++) { 549 i = rawParamTypes()[j].getName().compareTo(o.rawParamTypes()[j].getName()); 550 } 551 } 552 } 553 return i; 554 } 555}