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