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