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 * Returns all annotations of the specified type defined on the specified method. 155 * 156 * <p> 157 * Searches all methods with the same signature on the parent classes or interfaces 158 * and the return type on the method. 159 * 160 * @param a 161 * The annotation to search for. 162 * @return 163 * A list of all matching annotations found in child-to-parent order, or an empty list if none found. 164 */ 165 public <T extends Annotation> List<T> getAnnotations(Class<T> a) { 166 return appendAnnotations(new ArrayList<>(), a); 167 } 168 169 /** 170 * Identical to {@link #getAnnotations(Class)} but returns the list in reverse (parent-to-child) order. 171 * 172 * @param a 173 * The annotation to search for. 174 * @return 175 * A list of all matching annotations found or an empty list if none found. 176 */ 177 public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a) { 178 return appendAnnotationsParentFirst(new ArrayList<>(), a); 179 } 180 181 /** 182 * Finds and appends the specified annotation on the specified method and methods on superclasses/interfaces to the specified 183 * list. 184 * 185 * <p> 186 * Results are ordered in child-to-parent order. 187 * 188 * @param l The list of annotations. 189 * @param a The annotation. 190 * @return The same list. 191 */ 192 @SuppressWarnings("unchecked") 193 public <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a) { 194 for (Method m2 : getMatching()) 195 for (Annotation a2 : m2.getDeclaredAnnotations()) 196 if (a.isInstance(a2)) 197 l.add((T)a2); 198 getReturnType().resolved().appendAnnotations(l, a); 199 declaringClass.appendAnnotations(l, a); 200 return l; 201 } 202 203 /** 204 * Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified 205 * list. 206 * 207 * @param l The list of annotations. 208 * @param a The annotation. 209 * @return The same list. 210 */ 211 @SuppressWarnings("unchecked") 212 public <T extends Annotation> List<T> appendAnnotationsParentFirst(List<T> l, Class<T> a) { 213 declaringClass.appendAnnotationsParentFirst(l, a); 214 for (Method m2 : getMatchingParentFirst()) 215 for (Annotation a2 : m2.getDeclaredAnnotations()) 216 if (a.isInstance(a2)) 217 l.add((T)a2); 218 getReturnType().resolved().appendAnnotations(l, a); 219 return l; 220 } 221 222 /** 223 * Returns the first annotation in the specified list on this method. 224 * 225 * @param c The annotations that cannot be present on the method. 226 * @return <jk>true</jk> if this method does not have any of the specified annotations. 227 */ 228 @SafeVarargs 229 public final Annotation getAnnotation(Class<? extends Annotation>...c) { 230 for (Class<? extends Annotation> cc : c) { 231 Annotation a = getAnnotation(cc); 232 if (a != null) 233 return a; 234 } 235 return null; 236 } 237 238 /** 239 * Constructs an {@link AnnotationList} of all annotations found on this method. 240 * 241 * <p> 242 * Annotations are appended in the following orders: 243 * <ol> 244 * <li>On this method and matching methods ordered child-to-parent. 245 * <li>On this class. 246 * <li>On parent classes ordered child-to-parent. 247 * <li>On interfaces ordered child-to-parent. 248 * <li>On the package of this class. 249 * </ol> 250 * 251 * @param filter 252 * Optional filter to apply to limit which annotations are added to the list. 253 * <br>Can be <jk>null</jk> for no filtering. 254 * @return A new {@link AnnotationList} object on every call. 255 */ 256 public AnnotationList getAnnotationList(Predicate<AnnotationInfo<?>> filter) { 257 return appendAnnotationList(new AnnotationList(filter)); 258 } 259 260 /** 261 * Constructs an {@link AnnotationList} of all annotations found on this method. 262 * 263 * <p> 264 * Annotations are appended in the following orders: 265 * <ol> 266 * <li>On the package of this class. 267 * <li>On interfaces ordered parent-to-child. 268 * <li>On parent classes ordered parent-to-child. 269 * <li>On this class. 270 * <li>On this method and matching methods ordered parent-to-child. 271 * </ol> 272 * 273 * @param filter 274 * Optional filter to apply to limit which annotations are added to the list. 275 * <br>Can be <jk>null</jk> for no filtering. 276 * @return A new {@link AnnotationList} object on every call. 277 */ 278 public AnnotationList getAnnotationListParentFirst(Predicate<AnnotationInfo<?>> filter) { 279 return appendAnnotationListParentFirst(new AnnotationList(filter)); 280 } 281 282 /** 283 * Same as {@link #getAnnotationListParentFirst(Predicate)} except only returns annotations defined on methods. 284 * 285 * @param filter 286 * Optional filter to apply to limit which annotations are added to the list. 287 * <br>Can be <jk>null</jk> for no filtering. 288 * @return A new {@link AnnotationList} object on every call. 289 */ 290 public AnnotationList getAnnotationListMethodOnlyParentFirst(Predicate<AnnotationInfo<?>> filter) { 291 return appendAnnotationListMethodOnlyParentFirst(new AnnotationList(filter)); 292 } 293 294 /** 295 * Returns <jk>true</jk> if this method or parent methods have any annotations annotated with {@link PropertyStoreApply}. 296 * 297 * @return <jk>true</jk> if this method or parent methods have any annotations annotated with {@link PropertyStoreApply}. 298 */ 299 public boolean hasConfigAnnotations() { 300 for (Method m2 : getMatching()) 301 for (Annotation a2 : m2.getAnnotations()) 302 if (a2.annotationType().getAnnotation(PropertyStoreApply.class) != null) 303 return true; 304 return false; 305 } 306 307 @Override 308 @SuppressWarnings("unchecked") 309 protected <T extends Annotation> T findAnnotation(Class<T> a) { 310 for (Method m2 : getMatching()) 311 for (Annotation a2 : m2.getAnnotations()) 312 if (a.isInstance(a2)) 313 return (T)a2; 314 return null; 315 } 316 317 AnnotationList appendAnnotationList(AnnotationList al) { 318 ClassInfo c = this.declaringClass; 319 for (ClassInfo ci : c.getParents()) { 320 appendMethodAnnotations(al, ci); 321 appendAnnotations(al, ci); 322 } 323 for (ClassInfo ci : c.getInterfaces()) { 324 appendMethodAnnotations(al, ci); 325 appendAnnotations(al, ci); 326 } 327 appendAnnotations(al, c.getPackage()); 328 return al; 329 } 330 331 AnnotationList appendAnnotationListParentFirst(AnnotationList al) { 332 ClassInfo c = this.declaringClass; 333 appendAnnotations(al, c.getPackage()); 334 for (ClassInfo ci : c.getInterfacesParentFirst()) { 335 appendAnnotations(al, ci); 336 appendMethodAnnotations(al, ci); 337 } 338 for (ClassInfo ci : c.getParentsParentFirst()) { 339 appendAnnotations(al, ci); 340 appendMethodAnnotations(al, ci); 341 } 342 return al; 343 } 344 345 AnnotationList appendAnnotationListMethodOnlyParentFirst(AnnotationList al) { 346 ClassInfo c = this.declaringClass; 347 for (ClassInfo ci : c.getInterfacesParentFirst()) 348 appendMethodAnnotations(al, ci); 349 for (ClassInfo ci : c.getParentsParentFirst()) 350 appendMethodAnnotations(al, ci); 351 return al; 352 } 353 354 void appendAnnotations(AnnotationList al, Package p) { 355 if (p != null) 356 for (Annotation a : p.getDeclaredAnnotations()) 357 al.add(AnnotationInfo.of(p, a)); 358 } 359 360 void appendAnnotations(AnnotationList al, ClassInfo ci) { 361 if (ci != null) 362 for (Annotation a : ci.c.getDeclaredAnnotations()) 363 al.add(AnnotationInfo.of(ci, a)); 364 } 365 366 void appendMethodAnnotations(AnnotationList al, ClassInfo ci) { 367 Method m = findMatchingOnClass(ci); 368 if (m != null) 369 for (Annotation a : m.getDeclaredAnnotations()) 370 al.add(AnnotationInfo.of(MethodInfo.of(m), a)); 371 } 372 373 //----------------------------------------------------------------------------------------------------------------- 374 // Return type. 375 //----------------------------------------------------------------------------------------------------------------- 376 377 /** 378 * Returns the generic return type of this method as a {@link ClassInfo} object. 379 * 380 * @return The generic return type of this method. 381 */ 382 public ClassInfo getReturnType() { 383 if (returnType == null) 384 returnType = ClassInfo.of(m.getReturnType(), m.getGenericReturnType()); 385 return returnType; 386 } 387 388 /** 389 * Returns the generic return type of this method as a {@link ClassInfo} object. 390 * 391 * <p> 392 * Unwraps the type if it's a {@link Value}. 393 * 394 * @return The generic return type of this method. 395 */ 396 public ClassInfo getResolvedReturnType() { 397 if (returnType == null) 398 returnType = ClassInfo.of(m.getReturnType(), m.getGenericReturnType()); 399 return returnType.resolved(); 400 } 401 402 /** 403 * Returns <jk>true</jk> if this method has this return type. 404 * 405 * @param c The return type to test for. 406 * @return <jk>true</jk> if this method has this return type. 407 */ 408 public boolean hasReturnType(Class<?> c) { 409 return m.getReturnType() == c; 410 } 411 412 /** 413 * Returns <jk>true</jk> if this method has this return type. 414 * 415 * @param ci The return type to test for. 416 * @return <jk>true</jk> if this method has this return type. 417 */ 418 public boolean hasReturnType(ClassInfo ci) { 419 return hasReturnType(ci.inner()); 420 } 421 422 /** 423 * Returns <jk>true</jk> if this method has this parent return type. 424 * 425 * @param c The return type to test for. 426 * @return <jk>true</jk> if this method has this parent return type. 427 */ 428 public boolean hasReturnTypeParent(Class<?> c) { 429 return ClassInfo.of(c).isParentOf(m.getReturnType()); 430 } 431 432 /** 433 * Returns <jk>true</jk> if this method has this parent return type. 434 * 435 * @param ci The return type to test for. 436 * @return <jk>true</jk> if this method has this parent return type. 437 */ 438 public boolean hasReturnTypeParent(ClassInfo ci) { 439 return hasReturnTypeParent(ci.inner()); 440 } 441 442 //----------------------------------------------------------------------------------------------------------------- 443 // Other methods 444 //----------------------------------------------------------------------------------------------------------------- 445 446 /** 447 * Shortcut for calling the invoke method on the underlying method. 448 * 449 * @param obj the object the underlying method is invoked from. 450 * @param args the arguments used for the method call 451 * @return The object returned from the method. 452 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 453 */ 454 @SuppressWarnings("unchecked") 455 public <T> T invoke(Object obj, Object...args) throws ExecutableException { 456 try { 457 return (T)m.invoke(obj, args); 458 } catch (IllegalAccessException | InvocationTargetException e) { 459 throw new ExecutableException(e); 460 } 461 } 462 463 /** 464 * Invokes the specified method using fuzzy-arg matching. 465 * 466 * <p> 467 * Arguments will be matched to the parameters based on the parameter types. 468 * <br>Arguments can be in any order. 469 * <br>Extra arguments will be ignored. 470 * <br>Missing arguments will be left <jk>null</jk>. 471 * 472 * <p> 473 * Note that this only works for methods that have distinguishable argument types. 474 * <br>It's not going to work on methods with generic argument types like <c>Object</c> 475 * 476 * @param pojo 477 * The POJO the method is being called on. 478 * <br>Can be <jk>null</jk> for static methods. 479 * @param args 480 * The arguments to pass to the method. 481 * @return 482 * The results of the method invocation. 483 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 484 */ 485 public Object invokeFuzzy(Object pojo, Object...args) throws ExecutableException { 486 try { 487 return m.invoke(pojo, ClassUtils.getMatchingArgs(m.getParameterTypes(), args)); 488 } catch (IllegalAccessException | InvocationTargetException e) { 489 throw new ExecutableException(e); 490 } 491 } 492 493 /** 494 * Returns the signature of this method. 495 * 496 * <p> 497 * For no-arg methods, the signature will be a simple string such as <js>"toString"</js>. 498 * For methods with one or more args, the arguments will be fully-qualified class names (e.g. 499 * <js>"append(java.util.StringBuilder,boolean)"</js>) 500 * 501 * @return The methods signature. 502 */ 503 public String getSignature() { 504 StringBuilder sb = new StringBuilder(128); 505 sb.append(m.getName()); 506 Class<?>[] pt = rawParamTypes(); 507 if (pt.length > 0) { 508 sb.append('('); 509 List<ParamInfo> mpi = getParams(); 510 for (int i = 0; i < pt.length; i++) { 511 if (i > 0) 512 sb.append(','); 513 mpi.get(i).getParameterType().appendFullName(sb); 514 } 515 sb.append(')'); 516 } 517 return sb.toString(); 518 } 519 520 /** 521 * Returns the bean property name if this is a getter or setter. 522 * 523 * @return The bean property name, or <jk>null</jk> if this isn't a getter or setter. 524 */ 525 public String getPropertyName() { 526 String n = m.getName(); 527 if ((n.startsWith("get") || n.startsWith("set")) && n.length() > 3) 528 return Introspector.decapitalize(n.substring(3)); 529 if (n.startsWith("is") && n.length() > 2) 530 return Introspector.decapitalize(n.substring(2)); 531 return n; 532 } 533 534 /** 535 * Returns <jk>true</jk> if the parameters on the method only consist of the types specified in the list. 536 * 537 * <h5 class='figure'>Example:</h5> 538 * <p class='bpcode w800'> 539 * 540 * <jc>// Example method:</jc> 541 * <jk>public void</jk> foo(String bar, Integer baz); 542 * 543 * argsOnlyOfType(fooMethod, String.<jk>class</jk>, Integer.<jk>class</jk>); <jc>// True.</jc> 544 * argsOnlyOfType(fooMethod, String.<jk>class</jk>, Integer.<jk>class</jk>, Map.<jk>class</jk>); <jc>// True.</jc> 545 * argsOnlyOfType(fooMethod, String.<jk>class</jk>); <jc>// False.</jc> 546 * </p> 547 * 548 * @param args The valid class types (exact) for the arguments. 549 * @return <jk>true</jk> if the method parameters only consist of the types specified in the list. 550 */ 551 public boolean argsOnlyOfType(Class<?>...args) { 552 for (Class<?> c1 : rawParamTypes()) { 553 boolean foundMatch = false; 554 for (Class<?> c2 : args) 555 if (c1 == c2) 556 foundMatch = true; 557 if (! foundMatch) 558 return false; 559 } 560 return true; 561 } 562 563 /** 564 * Returns <jk>true</jk> if this method is a bridge method. 565 * 566 * @return <jk>true</jk> if this method is a bridge method. 567 */ 568 public boolean isBridge() { 569 return m.isBridge(); 570 } 571 572 @Override 573 public int compareTo(MethodInfo o) { 574 int i = getSimpleName().compareTo(o.getSimpleName()); 575 if (i == 0) { 576 i = rawParamTypes().length - o.rawParamTypes().length; 577 if (i == 0) { 578 for (int j = 0; j < rawParamTypes().length && i == 0; j++) { 579 i = rawParamTypes()[j].getName().compareTo(o.rawParamTypes()[j].getName()); 580 } 581 } 582 } 583 return i; 584 } 585}