001/* 002 * ============================================================================ 003 * Copyright © 2002-2023 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.util; 019 020import static java.lang.ClassLoader.getPlatformClassLoader; 021import static java.lang.Thread.currentThread; 022import static java.lang.Thread.getAllStackTraces; 023import static java.lang.reflect.Modifier.isAbstract; 024import static java.lang.reflect.Modifier.isFinal; 025import static java.lang.reflect.Modifier.isNative; 026import static java.lang.reflect.Modifier.isPrivate; 027import static java.lang.reflect.Modifier.isProtected; 028import static java.lang.reflect.Modifier.isPublic; 029import static java.lang.reflect.Modifier.isStatic; 030import static java.lang.reflect.Modifier.isStrict; 031import static java.lang.reflect.Modifier.isSynchronized; 032import static java.lang.reflect.Modifier.isTransient; 033import static java.lang.reflect.Modifier.isVolatile; 034import static java.util.Arrays.stream; 035import static javax.lang.model.element.Modifier.ABSTRACT; 036import static javax.lang.model.element.Modifier.DEFAULT; 037import static javax.lang.model.element.Modifier.FINAL; 038import static javax.lang.model.element.Modifier.NATIVE; 039import static javax.lang.model.element.Modifier.PRIVATE; 040import static javax.lang.model.element.Modifier.PROTECTED; 041import static javax.lang.model.element.Modifier.PUBLIC; 042import static javax.lang.model.element.Modifier.STATIC; 043import static javax.lang.model.element.Modifier.STRICTFP; 044import static javax.lang.model.element.Modifier.SYNCHRONIZED; 045import static javax.lang.model.element.Modifier.TRANSIENT; 046import static javax.lang.model.element.Modifier.VOLATILE; 047import static org.apiguardian.api.API.Status.STABLE; 048import static org.tquadrat.foundation.lang.DebugOutput.ifDebug; 049import static org.tquadrat.foundation.lang.Objects.isNull; 050import static org.tquadrat.foundation.lang.Objects.nonNull; 051import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 052import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 053import static org.tquadrat.foundation.lang.Objects.requireValidArgument; 054import static org.tquadrat.foundation.util.StringUtils.capitalize; 055import static org.tquadrat.foundation.util.StringUtils.decapitalize; 056import static org.tquadrat.foundation.util.StringUtils.isNotEmpty; 057 058import javax.lang.model.SourceVersion; 059import javax.lang.model.element.Element; 060import javax.lang.model.element.ElementKind; 061import javax.lang.model.element.ExecutableElement; 062import javax.lang.model.element.Modifier; 063import javax.lang.model.type.NoType; 064import javax.lang.model.type.TypeKind; 065import java.lang.reflect.Method; 066import java.net.URL; 067import java.util.EnumSet; 068import java.util.Map; 069import java.util.NoSuchElementException; 070import java.util.Optional; 071import java.util.Set; 072 073import org.apiguardian.api.API; 074import org.tquadrat.foundation.annotation.ClassVersion; 075import org.tquadrat.foundation.annotation.PropertyName; 076import org.tquadrat.foundation.annotation.UtilityClass; 077import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError; 078import org.tquadrat.foundation.exception.UnexpectedExceptionError; 079import org.tquadrat.foundation.exception.ValidationException; 080import org.tquadrat.foundation.lang.DebugOutput; 081 082/** 083 * <p>{@summary This class provides a bunch of helper methods that deal with the Java 084 * language itself and some related areas.} In general, they are wrapping 085 * somehow the introspection and the reflection frameworks.</p> 086 * <p>All methods of this class are static, so no instance of this class is 087 * allowed.</p> 088 * 089 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 090 * @version $Id: JavaUtils.java 1060 2023-09-24 19:21:40Z tquadrat $ 091 * @since 0.0.5 092 * 093 * @UMLGraph.link 094 */ 095@SuppressWarnings( {"ClassWithTooManyMethods", "OverlyComplexClass"} ) 096@ClassVersion( sourceVersion = "$Id: JavaUtils.java 1060 2023-09-24 19:21:40Z tquadrat $" ) 097@UtilityClass 098public final class JavaUtils 099{ 100 /*-----------*\ 101 ====** Constants **======================================================== 102 \*-----------*/ 103 /** 104 * The prefix for the name of an 'add' method: {@value} 105 */ 106 @API( status = STABLE, since = "0.1.0" ) 107 public static final String PREFIX_ADD = "add"; 108 109 /** 110 * The prefix for the name of a getter method: {@value}. 111 */ 112 @API( status = STABLE, since = "0.0.5" ) 113 public static final String PREFIX_GET = "get"; 114 115 /** 116 * The prefix for the name of a getter method that returns a 117 * {@code boolean} value: {@value}. 118 */ 119 @API( status = STABLE, since = "0.0.5" ) 120 public static final String PREFIX_IS = "is"; 121 122 /** 123 * The prefix for the name of a setter method: {@value} 124 */ 125 @API( status = STABLE, since = "0.0.5" ) 126 public static final String PREFIX_SET = "set"; 127 128 /*------------------------*\ 129 ====** Static Initialisations **=========================================== 130 \*------------------------*/ 131 /** 132 * The flag that tracks the assertion on/off status for this package. 133 */ 134 private static boolean m_AssertionOn; 135 136 /** 137 * Unfortunately, 138 * {@link Class#forName(String, boolean, ClassLoader)} 139 * will not work for the classes of the primitive types. To enable 140 * {@link #loadClass(ClassLoader, String)} 141 * to return those classes, we use this table. 142 */ 143 @SuppressWarnings( "StaticCollection" ) 144 private static final Map<String,Class<?>> m_PrimitiveClasses; 145 146 static 147 { 148 //---* Determine the assertion status *-------------------------------- 149 m_AssertionOn = false; 150 /* 151 * As the JUnit tests will always be executed with the flag 152 * "-ea" (assertions enabled), this code sequence is not tested in all 153 * branches. 154 */ 155 //noinspection AssertWithSideEffects,PointlessBooleanExpression,NestedAssignment 156 assert (m_AssertionOn = true) == true : "Assertion is switched off"; 157 158 //---* Create the table with the primitive type class objects *-------- 159 m_PrimitiveClasses = Map.of( 160 "boolean", boolean.class, 161 "byte", byte.class, 162 "char", char.class, 163 "double", double.class, 164 "float", float.class, 165 "int", int.class, 166 "long", long.class, 167 "short", short.class, 168 "void", Void.class ); 169 } 170 171 /*--------------*\ 172 ====** Constructors **===================================================== 173 \*--------------*/ 174 /** 175 * No instance allowed for this class. 176 */ 177 private JavaUtils() { throw new PrivateConstructorForStaticClassCalledError( JavaUtils.class ); } 178 179 /*---------*\ 180 ====** Methods **========================================================== 181 \*---------*/ 182 /** 183 * Creates the name for the getter method for the property with the given 184 * name. It will always be generated with '{@code get}', names for 185 * {@code boolean} properties (that usually do start with '{@code is}') 186 * are not generated. 187 * 188 * @param propertyName The name of the property. 189 * @return The name for the getter of the respective property. 190 */ 191 @API( status = STABLE, since = "0.0.5" ) 192 public static final String composeGetterName( final String propertyName ) 193 { 194 final var retValue = PREFIX_GET + capitalize( requireNotEmptyArgument( propertyName, "propertyName" ) ); 195 196 //---* Done *---------------------------------------------------------- 197 return retValue; 198 } // composeGetterName() 199 200 /** 201 * Creates the name for the setter method for the property with the given 202 * name. 203 * 204 * @param propertyName The name of the property. 205 * @return The name for the setter of the respective property. 206 */ 207 @API( status = STABLE, since = "0.0.5" ) 208 public static final String composeSetterName( final String propertyName ) 209 { 210 final var retValue = PREFIX_SET + capitalize( requireNotEmptyArgument( propertyName, "propertyName" ) ); 211 212 //---* Done *---------------------------------------------------------- 213 return retValue; 214 } // composeSetterName() 215 216 /** 217 * <p>{@summary This method will find the caller for the method that calls 218 * this one and returns the appropriate stack trace element.}</p> 219 * <p>The {@code offset} determines which caller's stack trace element 220 * will be returned:</p> 221 * <ol start="0"> 222 * <li>this method</li> 223 * <li>the caller of this method</li> 224 * <li>the caller's caller</li> 225 * <li>… and so on</li> 226 * </ol> 227 * 228 * @param offset The offset on the stack for the correct entry. 229 * @return An instance of 230 * {@link Optional} 231 * that holds the stack trace element for the caller; will be empty if 232 * the offset is too high (higher than the number of call levels). 233 */ 234 @API( status = STABLE, since = "0.0.5" ) 235 public static final Optional<StackTraceElement> findCaller( final int offset ) 236 { 237 if( offset < 0 ) throw new ValidationException( "offset is negative" ); 238 239 //---* Retrieve the stack *-------------------------------------------- 240 final var stackTraceElements = currentThread().getStackTrace(); 241 final var len = stackTraceElements.length; 242 243 //---* Search the stack *---------------------------------------------- 244 Optional<StackTraceElement> retValue = Optional.empty(); 245 if( offset <= len ) 246 { 247 String className; 248 String methodName; 249 FindLoop: for( var i = 0; i < len; ++i ) 250 { 251 /* 252 * This loop searches the stack until it will find the entry 253 * for this method on it. It assumes then that the next entry 254 * on the stack will belong to the caller for this method, and 255 * that one after it will be the caller's caller - and so on. 256 * The stack trace element for this method is not necessarily 257 * the first on the stack, as getStackTrace() is on it as well, 258 * and the exact index for this method depends on the 259 * implementation of the VM and/or the Java Runtime Library. 260 */ 261 className = stackTraceElements [i].getClassName(); 262 methodName = stackTraceElements [i].getMethodName(); 263 if( className.equals( JavaUtils.class.getName() ) && "findCaller".equals( methodName ) ) 264 { 265 if( (i + offset) < len ) 266 { 267 retValue = Optional.of( stackTraceElements [i + offset] ); 268 } 269 break FindLoop; 270 } 271 } // FindLoop: 272 } 273 274 //---* Done *---------------------------------------------------------- 275 return retValue; 276 } // findCaller() 277 278 /** 279 * <p>{@summary This method will find the caller for the method that is 280 * identified by its name and class, and returns the appropriate stack 281 * trace element.}</p> 282 * <p>The return value is 283 * {@linkplain Optional#empty() empty} 284 * when the provided method is not on the stack trace.</p> 285 * 286 * @param methodName The name of the method that we need the caller for. 287 * @param owningClass The class for the called method. 288 * @return An instance of 289 * {@link Optional} 290 * that holds the stack trace element for the caller. 291 */ 292 @API( status = STABLE, since = "0.1.0" ) 293 public static final Optional<StackTraceElement> findCaller( final String methodName, final Class<?> owningClass ) 294 { 295 return DebugOutput.findCaller( methodName, owningClass ); 296 } // findCaller() 297 298 /** 299 * <p>{@summary Tries to identify the class with the {@code main()} method 300 * that started the application.}</p> 301 * <p>There are several reasons why this could fail:</p> 302 * <ul> 303 * <li>There is a 304 * {@link java.lang.SecurityManager} 305 * in place that forbids to access the necessary information.</li> 306 * <li>The {@code main} thread is already dead. This could happen 307 * with applications having a graphical user interface, or where the 308 * main class is mere starter for the real application threads.</li> 309 * <li>The code was not started as a program at all; this could be the 310 * fact for applets, or when it is started as a script.</li> 311 * </ul> 312 * 313 * @return An instance of 314 * {@link Optional} 315 * that holds the name of the main class. 316 */ 317 @SuppressWarnings( "removal" ) 318 @API( status = STABLE, since = "0.0.5" ) 319 public static final Optional<String> findMainClass() 320 { 321 String mainClassName = null; 322 try 323 { 324 //---* First, we will see if we are the main thread *-------------- 325 final var currentThread = currentThread(); 326 StackTraceElement [] stackTrace; 327 if( "main".equals( currentThread.getName() ) ) 328 { 329 //---* Search for the method main() *-------------------------- 330 stackTrace = currentThread.getStackTrace(); 331 mainClassName = searchStackTrace( stackTrace, "main" ); 332 333 if( isNull( mainClassName ) ) 334 { 335 /* 336 * There is no main() method in the main thread; this can 337 * happen if we were triggered from a static block. So 338 * let's search for <clinit>. 339 */ 340 mainClassName = searchStackTrace( stackTrace, "<clinit>" ); 341 } 342 } 343 344 //---* Not found yet, so we will search for the main thread *------ 345 if( isNull( mainClassName ) ) 346 { 347 final var stackTraces = getAllStackTraces(); 348 ThreadSearchLoop: for( final var thread : stackTraces.keySet() ) 349 { 350 if( "main".equals( thread.getName() ) ) 351 { 352 //---* Search for the method main() *------------------ 353 stackTrace = stackTraces.get( thread ); 354 mainClassName = searchStackTrace( stackTrace, "main" ); 355 if( isNull( mainClassName ) ) 356 { 357 /* 358 * There is no main() method in the main thread; 359 * this can happen if we were triggered from a 360 * static block. So let's search for <clinit>. 361 */ 362 mainClassName = searchStackTrace( stackTrace, "<clinit>" ); 363 } 364 if( nonNull( mainClassName ) ) break ThreadSearchLoop; 365 } 366 } // ThreadSearchLoop: 367 } 368 } 369 catch( final SecurityException ignored ) { /* Deliberately ignored */ } 370 371 //---* Compose the return value *-------------------------------------- 372 final var retValue = Optional.ofNullable( mainClassName ); 373 374 //---* Done *---------------------------------------------------------- 375 return retValue; 376 } // findMainClass() 377 378 /** 379 * <p>{@summary Retrieves the 380 * {@link ClassLoader} 381 * that loaded the class of the method that called the method that called 382 * this method.}</p> 383 * <p>If the class of the caller's caller was loaded by the Bootstrap 384 * classloader, the return value would be {@code null}, but this method 385 * will return the 386 * {@linkplain ClassLoader#getPlatformClassLoader() Platform classloader} 387 * instead.</p> 388 * 389 * @return The caller's {@code ClassLoader}. 390 * 391 * @see #findCaller(int) 392 * 393 * @since 0.0.6 394 */ 395 @API( status = STABLE, since = "0.0.6" ) 396 public static final ClassLoader getCallersClassLoader() 397 { 398 ClassLoader retValue = null; 399 try 400 { 401 final var callersCaller = findCaller( 3 ); 402 @SuppressWarnings( "OptionalGetWithoutIsPresent" ) 403 final var callersClass = Class.forName( callersCaller.get().getClassName() ); 404 retValue = callersClass.getClassLoader(); 405 if( isNull( retValue ) ) retValue = getPlatformClassLoader(); 406 } 407 catch( final NoSuchElementException e ) 408 { 409 throw new UnexpectedExceptionError( "Caller's caller must be on the stack", e ); 410 } 411 catch( final ClassNotFoundException e ) 412 { 413 throw new UnexpectedExceptionError( "Caller's class must exist", e ); 414 } 415 416 //---* Done *---------------------------------------------------------- 417 return retValue; 418 } // getCallersClassLoader() 419 420 /** 421 * <p>{@summary Retrieves the location from where the code for the given 422 * class was loaded.}</p> 423 * <p>No location will be provided for classes from the Java run-time 424 * library (like 425 * {@link java.lang.String}) 426 * or if there is a 427 * {@link java.lang.SecurityManager} 428 * in place that forbids this operation.</p> 429 * <p>Additionally, there are implementations of 430 * {@link java.lang.ClassLoader} 431 * that do not initialise the respective data structures 432 * appropriately.</p> 433 * 434 * @param candidateClass The class to inspect. 435 * @return An instance of 436 * {@link Optional} 437 * that holds the URL for the code source. 438 */ 439 @SuppressWarnings( "removal" ) 440 @API( status = STABLE, since = "0.0.5" ) 441 public static final Optional<URL> getCodeSource( final Class<?> candidateClass ) 442 { 443 Optional<URL> retValue = Optional.empty(); 444 try 445 { 446 final var protectionDomain = requireNonNullArgument( candidateClass, "candidateClass" ).getProtectionDomain(); 447 if( nonNull( protectionDomain ) ) 448 { 449 final var codeSource = protectionDomain.getCodeSource(); 450 if( nonNull( codeSource ) ) 451 { 452 retValue = Optional.ofNullable( codeSource.getLocation() ); 453 } 454 } 455 } 456 catch( final SecurityException ignored ) { /* Deliberately ignored */ } 457 458 //---* Done *---------------------------------------------------------- 459 return retValue; 460 } // getCodeSource() 461 462 /** 463 * <p>{@summary Checks if the given element is an '<i>add</i>' method or 464 * not.} Such a method is a bit like a 465 * {@linkplain #isSetter(Element) setter method}, 466 * but for 467 * {@link java.util.Collection Collection} 468 * instances or alike.</p> 469 * <p>Such an 'add' method is characterised by being public, not being 470 * default and not being static; it has a name starting with '{@code add}', 471 * it takes exactly one parameter, and it does not return any value.</p> 472 * <p>The remaining part of the name after '{@code add}' is taken as the 473 * name of the property.</p> 474 * 475 * @param element The element to check. 476 * @return {@code true} if the method is an 'add' method, {@code false} 477 * otherwise. 478 * 479 * @see #isGetter(Element) 480 * @see #isSetter(Element) 481 * 482 * @since 0.1.0 483 */ 484 @SuppressWarnings( "NestedAssignment" ) 485 @API( status = STABLE, since = "0.1.0" ) 486 public static final boolean isAddMethod( final Element element ) 487 { 488 //---* Check whether the element is a method at all *------------------ 489 var retValue = requireNonNullArgument( element, "element" ).getKind() == ElementKind.METHOD; 490 if( retValue ) 491 { 492 final var methodElement = (ExecutableElement) element; 493 494 //---* Check if the method is public and not static *-------------- 495 final var modifiers = methodElement.getModifiers(); 496 if( (retValue = modifiers.contains( PUBLIC ) && !modifiers.contains( STATIC ) && !modifiers.contains( DEFAULT )) == true ) 497 { 498 //---* Check the name *---------------------------------------- 499 final var name = methodElement.getSimpleName().toString(); 500 if( (retValue = name.startsWith( PREFIX_ADD )) == true ) 501 { 502 //---* Check if there is a property name *----------------- 503 final var pos = PREFIX_ADD.length(); 504 retValue = (name.length() > pos) && Character.isUpperCase( name.charAt( pos ) ); 505 506 //---* Check the number of parameters *-------------------- 507 retValue = retValue && (methodElement.getParameters().size() == 1); 508 509 //---* Check the return value *---------------------------- 510 retValue = retValue && (methodElement.getReturnType() instanceof NoType); 511 } 512 } 513 } 514 515 //---* Done *---------------------------------------------------------- 516 return retValue; 517 } // isAddMethod() 518 519 /** 520 * Checks whether JDK assertion is currently activated, meaning that the 521 * program was started with the command line flags {@code -ea} or 522 * {@code -enableassertions}. If assertions are activated for some 523 * selected packages only and {@code org.tquadrat.foundation.util} is not 524 * amongst these, or {@code org.tquadrat.foundation.util} is explicitly 525 * disabled with {@code -da} or {@code -disableassertions}, this method 526 * will return {@code false}. But even when it returns {@code true}, it is 527 * possible that assertions are still not activated for some packages. 528 * 529 * @return {@code true} if assertions are activated for the 530 * package {@code org.tquadrat.util} and hopefully also for any other 531 * package, {@code false} otherwise. 532 */ 533 @API( status = STABLE, since = "0.0.5" ) 534 public static final boolean isAssertionOn() { return m_AssertionOn; } 535 536 /** 537 * Checks if the given method is the method 538 * {@link Object#equals(Object) equals()} 539 * as defined by the class {@code Object}. To be this method, it has to be 540 * public, it has to have the name 'equals', it has to take one argument 541 * of type {@code Object} and it will return a result of type 542 * {@code boolean}. 543 * 544 * @param method The method to check. 545 * @return {@code true} if the method is the {@code equals()} method, 546 * {@code false} otherwise. 547 */ 548 @API( status = STABLE, since = "0.0.5" ) 549 public static final boolean isEquals( final Method method ) 550 { 551 //---* Check if the method is public and not static *------------------ 552 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 553 var retValue = isPublic( modifier ) && !isStatic( modifier ); 554 555 //---* Check the name *------------------------------------------------ 556 retValue = retValue && "equals".equals( method.getName() ); 557 558 //---* Check the return value *---------------------------------------- 559 retValue = retValue && method.getReturnType().equals( boolean.class ); 560 561 //---* Check the parameters *------------------------------------------ 562 if( retValue ) 563 { 564 final var parameterTypes = method.getParameterTypes(); 565 retValue = (parameterTypes.length == 1) && Object.class.equals( parameterTypes [0] ); 566 } 567 568 //---* Done *---------------------------------------------------------- 569 return retValue; 570 } // isEquals() 571 572 /** 573 * <p>{@summary Checks whether the given element is a <i>getter</i> method 574 * or not.}</p> 575 * <p>A getter method is public and not static, it has a name starting 576 * with "{@code get}", it does not take any arguments, and it 577 * will return a value. In case the return value is of type 578 * {@code boolean}, the name may start with "{@code is}" instead 579 * of "{@code get}".</p> 580 * <p>The remaining part of the name after "{@code get}" or 581 * "{@code is}" has to start with an uppercase letter; this is 582 * usually taken as the attribute's or property's name.</p> 583 * <p>For the method 584 * {@link Object#getClass()} 585 * (inherited by all classes from 586 * {@link Object}), 587 * this method will return {@code false}, as this is not a getter in the 588 * sense of the definition.</p> 589 * 590 * @param element The element to check. 591 * @return {@code true} if the element is a getter method, {@code false} 592 * otherwise. 593 */ 594 @SuppressWarnings( {"OverlyComplexMethod", "NestedAssignment"} ) 595 @API( status = STABLE, since = "0.0.5" ) 596 public static final boolean isGetter( final Element element ) 597 { 598 //---* Check whether the element is a method at all *------------------ 599 var retValue = requireNonNullArgument( element, "element" ).getKind() == ElementKind.METHOD; 600 if( retValue ) 601 { 602 final var methodElement = (ExecutableElement) element; 603 604 //---* Check if the method is public and not static *-------------- 605 final var modifiers = methodElement.getModifiers(); 606 if( (retValue = modifiers.contains( PUBLIC ) && !modifiers.contains( STATIC )) == true ) 607 { 608 //---* Check the name *---------------------------------------- 609 final var name = methodElement.getSimpleName().toString(); 610 if( (retValue = !"getClass".equals( name )) == true ) 611 { 612 var pos = 0; 613 if( name.startsWith( PREFIX_IS ) ) 614 { 615 //---* Check the return value *------------------------ 616 final var returnMirror = methodElement.getReturnType(); 617 retValue = !(returnMirror instanceof NoType) && (returnMirror.getKind() == TypeKind.BOOLEAN); 618 pos = PREFIX_IS.length(); 619 } 620 else if( name.startsWith( PREFIX_GET ) ) 621 { 622 //---* Check the return value *------------------------ 623 retValue = !(methodElement.getReturnType() instanceof NoType); 624 pos = PREFIX_GET.length(); 625 } 626 else 627 { 628 retValue = false; 629 } 630 631 if( retValue ) 632 { 633 //---* Check if there is a property name *------------- 634 retValue = (name.length() > pos) && Character.isUpperCase( name.charAt( pos ) ); 635 636 //---* Check the number of parameters *---------------- 637 retValue = retValue && methodElement.getParameters().isEmpty(); 638 } 639 } 640 } 641 } 642 643 //---* Done *---------------------------------------------------------- 644 return retValue; 645 } // isGetter() 646 647 /** 648 * <p>{@summary Checks whether the given method is a <i>getter</i> method 649 * or not.}</p> 650 * <p>A getter method is public and not static, it has a name starting 651 * with "{@code get}", it does not take any arguments, and it 652 * will return a value. In case the return value is of type 653 * {@code boolean}, the name may start with "{@code is}" instead 654 * of "{@code get}".</p> 655 * <p>The remaining part of the name after "{@code get}" or 656 * "{@code is}" has to start with an uppercase letter; this is 657 * usually taken as the attribute's or property's name.</p> 658 * <p>For the method 659 * {@link Object#getClass()} 660 * (inherited by all classes from 661 * {@link Object}), 662 * this method will return {@code false}, as this is not a getter in the 663 * sense of the definition.</p> 664 * 665 * @param method The method to check. 666 * @return {@code true} if the method is a getter, {@code false} 667 * otherwise. 668 */ 669 @API( status = STABLE, since = "0.0.5" ) 670 public static final boolean isGetter( final Method method ) 671 { 672 //---* Check if the method is public and not static *------------------ 673 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 674 var retValue = isPublic( modifier ) && !isStatic( modifier ); 675 676 //---* Check the number of parameters *-------------------------------- 677 retValue = retValue && (method.getParameterTypes().length == 0); 678 679 //---* Check the name *------------------------------------------------ 680 if( retValue ) 681 { 682 final var name = method.getName(); 683 retValue = !"getClass".equals( name ); 684 685 if( retValue ) 686 { 687 var pos = Integer.MAX_VALUE; 688 if( name.startsWith( PREFIX_IS ) ) 689 { 690 retValue = method.getReturnType().equals( boolean.class ); 691 pos = PREFIX_IS.length(); 692 } 693 else if( name.startsWith( PREFIX_GET ) ) 694 { 695 //---* Check the return value *---------------------------- 696 retValue = !method.getReturnType().equals( void.class ); 697 pos = PREFIX_GET.length(); 698 } 699 else 700 { 701 retValue = false; 702 } 703 704 //---* Check if there is a property name *--------------------- 705 retValue = retValue && (name.length() > pos) && Character.isUpperCase( name.charAt( pos ) ); 706 } 707 } 708 709 //---* Done *---------------------------------------------------------- 710 return retValue; 711 } // isGetter() 712 713 /** 714 * Checks if the given method is the method 715 * {@link Object#hashCode() hashCode()} 716 * as defined by the class {@code Object}. To be this method, it has to be 717 * public, it has to have the name 'hashCode', it does not take any 718 * argument, and it will return a result of type {@code integer}. 719 * 720 * @param method The method to check. 721 * @return {@code true} if the method is the {@code hashCode()} 722 * method, {@code false} otherwise. 723 */ 724 @API( status = STABLE, since = "0.0.5" ) 725 public static final boolean isHashCode( final Method method ) 726 { 727 //---* Check if the method is public and not static *------------------ 728 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 729 var retValue = isPublic( modifier ) && !isStatic( modifier ); 730 731 //---* Check the name *------------------------------------------------ 732 retValue = retValue && "hashCode".equals( method.getName() ); 733 734 //---* Check the return value *---------------------------------------- 735 retValue = retValue && method.getReturnType().equals( int.class ); 736 737 //---* Check the number of parameters *-------------------------------- 738 retValue = retValue && (method.getParameterTypes().length == 0); 739 740 //---* Done *---------------------------------------------------------- 741 return retValue; 742 } // isHashCode() 743 744 /** 745 * Checks if the given method is a {@code main()} method or not. A 746 * {@code main()} method is public and static, it has the name 747 * "main", it takes exactly one parameter of type 748 * {@code String []}, and it does not return any value. 749 * 750 * @param method The method to check. 751 * @return {@code true} if the method is a {@code main()} method, 752 * {@code false} otherwise. 753 */ 754 @API( status = STABLE, since = "0.0.5" ) 755 public static final boolean isMain( final Method method ) 756 { 757 //---* Check if the method is public and static *---------------------- 758 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 759 var retValue = isPublic( modifier ) && isStatic( modifier ); 760 761 //---* Check the name *------------------------------------------------ 762 retValue = retValue && "main".equals( method.getName() ); 763 764 //---* Check the return value *---------------------------------------- 765 retValue = retValue && method.getReturnType().equals( void.class ); 766 767 //---* Check the number of parameters *-------------------------------- 768 if( retValue ) 769 { 770 final var parameterTypes = method.getParameterTypes(); 771 retValue = (parameterTypes.length == 1) && String [].class.equals( parameterTypes [0] ); 772 } 773 774 //---* Done *---------------------------------------------------------- 775 return retValue; 776 } // isMain() 777 778 /** 779 * <p>{@summary Checks if the given element is a setter method or not.} A 780 * setter method is public, it has a name starting with 'set', it takes 781 * exactly one parameter, and it does not return any value.</p> 782 * <p>The remaining part of the name after 'set' is taken as the 783 * attribute's name.</p> 784 * 785 * @param element The element to check. 786 * @return {@code true} if the method is a setter, {@code false} 787 * otherwise. 788 */ 789 @SuppressWarnings( "NestedAssignment" ) 790 @API( status = STABLE, since = "0.0.5" ) 791 public static final boolean isSetter( final Element element ) 792 { 793 //---* Check whether the element is a method at all *------------------ 794 var retValue = requireNonNullArgument( element, "element" ).getKind() == ElementKind.METHOD; 795 if( retValue ) 796 { 797 final var methodElement = (ExecutableElement) element; 798 799 //---* Check if the method is public and not static *-------------- 800 final var modifiers = methodElement.getModifiers(); 801 if( (retValue = modifiers.contains( PUBLIC ) && !modifiers.contains( STATIC )) == true ) 802 { 803 //---* Check the name *---------------------------------------- 804 final var name = methodElement.getSimpleName().toString(); 805 if( (retValue = name.startsWith( PREFIX_SET )) == true ) 806 { 807 //---* Check if there is a property name *----------------- 808 final var pos = PREFIX_SET.length(); 809 retValue = (name.length() > pos) && Character.isUpperCase( name.charAt( pos ) ); 810 811 //---* Check the number of parameters *-------------------- 812 retValue = retValue && (methodElement.getParameters().size() == 1); 813 814 //---* Check the return value *---------------------------- 815 retValue = retValue && (methodElement.getReturnType() instanceof NoType); 816 } 817 } 818 } 819 820 //---* Done *---------------------------------------------------------- 821 return retValue; 822 } // isSetter() 823 824 /** 825 * Checks if the given method is a setter method or not. A setter method 826 * is public, it has a name starting with 'set', it takes exactly one 827 * parameter, and it does not return any value.<br> 828 * <br>The remaining part of the name after 'set' is taken as the 829 * attribute's name. 830 * 831 * @param method The method to check. 832 * @return {@code true} if the method is a setter, {@code false} 833 * otherwise. 834 */ 835 @API( status = STABLE, since = "0.0.5" ) 836 public static final boolean isSetter( final Method method ) 837 { 838 //---* Check if the method is public and not static *------------------ 839 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 840 var retValue = isPublic( modifier ) && !isStatic( modifier ); 841 842 //---* Check the name *------------------------------------------------ 843 if( retValue ) 844 { 845 final var name = method.getName(); 846 retValue = name.startsWith( PREFIX_SET ); 847 848 //---* Check if there is a property name *------------------------- 849 final var pos = PREFIX_SET.length(); 850 retValue = retValue && ((name.length() > pos) && Character.isUpperCase( name.charAt( pos ) )); 851 } 852 853 //---* Check the number of parameters *-------------------------------- 854 retValue = retValue && (method.getParameterTypes().length == 1); 855 856 //---* Check the return value *---------------------------------------- 857 retValue = retValue && method.getReturnType().equals( void.class ); 858 859 //---* Done *---------------------------------------------------------- 860 return retValue; 861 } // isSetter() 862 863 /** 864 * Checks if the given method is the method 865 * {@link Object#toString() toString()} 866 * as defined by the class {@code Object}. To be this method, it has to be 867 * public, it has to have the name 'toString', it does not take any 868 * argument, and it returns a result of type 869 * {@link String}. 870 * 871 * @param method The method to check. 872 * @return {@code true} if the method is the {@code toString()} 873 * method, {@code false} otherwise. 874 */ 875 @API( status = STABLE, since = "0.0.5" ) 876 public static final boolean isToString( final Method method ) 877 { 878 //---* Check if the method is public and not static *------------------ 879 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 880 var retValue = isPublic( modifier ) && !isStatic( modifier ); 881 882 //---* Check the name *------------------------------------------------ 883 retValue = retValue && "toString".equals( method.getName() ); 884 885 //---* Check the return value *---------------------------------------- 886 retValue = retValue && method.getReturnType().equals( String.class ); 887 888 //---* Check the number of parameters *-------------------------------- 889 retValue = retValue && (method.getParameterTypes().length == 0); 890 891 //---* Done *---------------------------------------------------------- 892 return retValue; 893 } // isToString() 894 895 /** 896 * Checks whether the given String is a valid Java name.<br> 897 * <br>This method will return {@code true} for <i>restricted 898 * keywords</i>, but not for {@code var}. For a single underscore 899 * ("{@code _}"), it will return {@code false}.<br> 900 * <br>The restricted keywords are 901 * <ul> 902 * <li>{@code exports}</li> 903 * <li>{@code module}</li> 904 * <li>{@code open}</li> 905 * <li>{@code opens}</li> 906 * <li>{@code provides}</li> 907 * <li>{@code requires}</li> 908 * <li>{@code to}</li> 909 * <li>{@code transitive}</li> 910 * <li>{@code uses}</li> 911 * <li>{@code with}</li> 912 * </ul> 913 * All these are used in a {@code module-info.java} file. 914 * 915 * @param name The String to check. 916 * @return {@code true} if the given String is a valid name for the Java 917 * language, {@code false} otherwise. 918 * 919 * @see javax.lang.model.SourceVersion#isName(CharSequence, SourceVersion) 920 * @see javax.lang.model.SourceVersion#isIdentifier(CharSequence) 921 * @see javax.lang.model.SourceVersion#isKeyword(CharSequence, SourceVersion) 922 * @see javax.lang.model.SourceVersion#latest() 923 */ 924 @API( status = STABLE, since = "0.0.5" ) 925 public static final boolean isValidName( final CharSequence name ) 926 { 927 final var retValue = SourceVersion.isName( requireNotEmptyArgument( name, "name" ) ) 928 && !name.equals( "var" ); 929 930 //---* Done *---------------------------------------------------------- 931 return retValue; 932 } // isValidName() 933 934 /** 935 * Loads the class with the given name, using the instance of 936 * {@link ClassLoader} 937 * that loaded the caller's class, and returns that class. If no class 938 * with that name could be found by that {@code ClassLoader}, no exception 939 * will be thrown; instead this method will return an empty 940 * {@link Optional} 941 * instance.<br> 942 * <br>If not loaded and initialised before, the loaded class is not yet 943 * initialised. That means that {@code static} code blocks have not been 944 * executed yet and class variables (static variables) are not 945 * initialised.<br> 946 * <br>Different from 947 * {@link Class#forName(String, boolean, ClassLoader)}, 948 * this method is able to load the class objects for the primitive types, 949 * too. 950 * 951 * @param classname The name of the class to load; may <i>not</i> be 952 * empty or {@code null}. 953 * @return The class wrapped in an 954 * {@link Optional} 955 * instance. 956 * 957 * @see Class#forName(String) 958 * @see Class#forName(String, boolean, ClassLoader) 959 * @see Optional#isPresent() 960 * @see #getCallersClassLoader() 961 */ 962 @API( status = STABLE, since = "0.0.5" ) 963 public static final Optional<Class<?>> loadClass( final String classname ) 964 { 965 final var classLoader = getCallersClassLoader(); 966 final var retValue = loadClass( classLoader, classname ); 967 968 //---* Done *---------------------------------------------------------- 969 return retValue; 970 } // loadClass() 971 972 /** 973 * <p>{@summary Loads the class with the given name, using the given 974 * {@link ClassLoader} 975 * instance, and returns that class.} If no class with that name could be 976 * found by that {@code ClassLoader}, no exception will be thrown; instead 977 * this method will return an empty 978 * {@link Optional} 979 * instance.</p> 980 * <p>If not loaded and initialised before, the loaded class is not yet 981 * initialised. That means that {@code static} code blocks have not been 982 * executed yet and class variables (static variables) are not 983 * initialised.</p> 984 * <p>Different from 985 * {@link Class#forName(String, boolean, ClassLoader)}, 986 * this method is able to load the class objects for the primitive types, 987 * too.</p> 988 * 989 * @param classLoader The class loader to use. 990 * @param classname The name of the class to load; may <i>not</i> be 991 * empty or {@code null}. 992 * @return The class wrapped in an 993 * {@link Optional} 994 * instance. 995 * 996 * @see Class#forName(String) 997 * @see Class#forName(String, boolean, ClassLoader) 998 * @see Optional#isPresent() 999 */ 1000 @API( status = STABLE, since = "0.0.5" ) 1001 public static final Optional<Class<?>> loadClass( final ClassLoader classLoader, final String classname ) 1002 { 1003 var resultClass = m_PrimitiveClasses.get( requireNotEmptyArgument( classname, "classname" ) ); 1004 if( isNull( resultClass ) ) 1005 { 1006 try 1007 { 1008 resultClass = Class.forName( classname, false, requireNonNullArgument( classLoader, "classLoader" ) ); 1009 } 1010 catch( final ClassNotFoundException e ) 1011 { 1012 //---* Deliberately ignored *---------------------------------- 1013 ifDebug( e ); 1014 } 1015 } 1016 1017 //---* Create the return value *--------------------------------------- 1018 final Optional<Class<?>> retValue = Optional.ofNullable( resultClass ); 1019 1020 //---* Done *---------------------------------------------------------- 1021 return retValue; 1022 } // loadClass() 1023 1024 /** 1025 * Loads the class with the given name, using the given 1026 * {@link ClassLoader} 1027 * instance, and returns that class, wrapped in an instance of 1028 * {@link Optional}. 1029 * If no class with that name could be found by that instance of 1030 * {@code ClassLoader}, or if it does not implement the given 1031 * interface/extend the given class, no exception will be thrown; instead 1032 * this method will return an empty 1033 * {@link Optional} 1034 * instance.<br> 1035 * <br>If not loaded and initialised before, the loaded class is not yet 1036 * initialised. That means that {@code static} code blocks have not been 1037 * executed yet and class variables (static variables) are not 1038 * initialised. 1039 * 1040 * @param <T> The type of the interface/class that the returned class 1041 * will implement/extend. 1042 * @param classLoader The class loader to use. 1043 * @param classname The name of the class to load; may <i>not</i> be 1044 * empty or {@code null}. 1045 * @param implementing The interface/class that the returned class 1046 * has to implement/extend. 1047 * @return The class wrapped in an 1048 * {@link Optional} 1049 * instance. 1050 * 1051 * @see Class#forName(String) 1052 * @see Class#forName(String, boolean, ClassLoader) 1053 * @see Optional#isPresent() 1054 */ 1055 @API( status = STABLE, since = "0.0.5" ) 1056 public static final <T> Optional<Class<? extends T>> loadClass( final ClassLoader classLoader, final String classname, final Class<? extends T> implementing ) 1057 { 1058 Class<? extends T> resultClass = null; 1059 try 1060 { 1061 final var candidateClass = Class.forName( requireNotEmptyArgument( classname, "classname" ), false, requireNonNullArgument( classLoader, "classLoader" ) ); 1062 if( requireNonNullArgument( implementing, "implementing" ).isAssignableFrom( candidateClass ) ) 1063 { 1064 resultClass = candidateClass.asSubclass( implementing ); 1065 } 1066 } 1067 catch( final ClassNotFoundException ignored ) { /* Deliberately ignored */ } 1068 1069 //---* Create the return value *--------------------------------------- 1070 final Optional<Class<? extends T>> retValue = Optional.ofNullable( resultClass ); 1071 1072 //---* Done *---------------------------------------------------------- 1073 return retValue; 1074 } // loadClass() 1075 1076 /** 1077 * If no class with that name could be found by that instance of 1078 * {@code ClassLoader}, or if it does not implement the given 1079 * interface/extend the given class, no exception will be thrown; instead 1080 * this method will return an empty 1081 * {@link Optional}.<br> 1082 * Loads the class with the given name, using the instance of 1083 * {@link ClassLoader} 1084 * that loaded the caller's class, and returns that class, wrapped in an 1085 * instance of 1086 * {@link Optional}. 1087 * If no class with that name could be found by that instance of 1088 * {@code ClassLoader}, or if it does not implement the given 1089 * interface/extend the given class, no exception will be thrown; instead 1090 * this method will return an empty 1091 * {@link Optional} 1092 * instance.<br> 1093 * <br>If not loaded and initialised before, the loaded class is not yet 1094 * initialised. That means that {@code static} code blocks have not been 1095 * executed yet and class variables (static variables) are not 1096 * initialised. 1097 * 1098 * @param <T> The type of the interface/class that the returned class 1099 * will implement/extend. 1100 * @param classname The name of the class to load; may <i>not</i> be 1101 * empty or {@code null}. 1102 * @param implementing The interface/class that the returned class 1103 * has to implement/extend. 1104 * @return The class wrapped in an 1105 * {@link Optional} 1106 * instance. 1107 * 1108 * @see Class#forName(String) 1109 * @see Class#forName(String, boolean, ClassLoader) 1110 * @see Optional#isPresent() 1111 * @see #getCallersClassLoader() 1112 */ 1113 @API( status = STABLE, since = "0.0.5" ) 1114 public static final <T> Optional<Class<? extends T>> loadClass( final String classname, final Class<T> implementing ) 1115 { 1116 final var classLoader = getCallersClassLoader(); 1117 final var retValue = loadClass( classLoader, classname, implementing ); 1118 1119 //---* Done *---------------------------------------------------------- 1120 return retValue; 1121 } // loadClass() 1122 1123 /** 1124 * Retrieves the public getter for the property with the given name. If 1125 * not {@code null}, the returned value will cause 1126 * {@link #isGetter(Method)} 1127 * to return {@code true}. 1128 * 1129 * @param beanClass The class for the getter. 1130 * @param propertyName The name of the property. 1131 * @return An instance of 1132 * {@link Optional} 1133 * that holds the getter method; will be empty if there is no public 1134 * getter for the given property on the provided class. 1135 * 1136 * @see #isGetter(Method) 1137 * @see #retrieveGetter(Class, String, boolean) 1138 */ 1139 public static final Optional<Method> retrieveGetter( final Class<?> beanClass, final String propertyName ) 1140 { 1141 final var retValue = retrieveGetter( beanClass, propertyName, true ); 1142 1143 //---* Done *---------------------------------------------------------- 1144 return retValue; 1145 } // retrieveGetter() 1146 1147 /** 1148 * <p>{@summary Retrieves the getter for the property with the given name.}</p> 1149 * <p>Usually, a getter method has to be public, but for some purposes, 1150 * it may be package local, protected or even private. A method returned 1151 * by a call to this method will not cause 1152 * {@link #isGetter(Method)} 1153 * to return {@code true} in all cases.</p> 1154 * 1155 * @param beanClass The class for the getter. 1156 * @param propertyName The name of the property. 1157 * @param isPublic {@code true} if the getter is required to be 1158 * public, {@code false} otherwise. 1159 * @return An instance of 1160 * {@link Optional} 1161 * that holds the getter method; will be empty if there is no getter 1162 * for the given property on the provided class. 1163 */ 1164 @SuppressWarnings( {"AssignmentToNull", "OverlyComplexMethod"} ) 1165 @API( status = STABLE, since = "0.0.5" ) 1166 public static final Optional<Method> retrieveGetter( final Class<?> beanClass, final String propertyName, final boolean isPublic ) 1167 { 1168 requireNonNullArgument( beanClass, "beanClass" ); 1169 1170 Method method = null; 1171 1172 if( !"class".equals( requireNotEmptyArgument( propertyName, "propertyName" ) ) ) 1173 { 1174 var getterName = composeGetterName( propertyName ); 1175 1176 //---* Retrieve a common getter *---------------------------------- 1177 if( !isPublic ) 1178 { 1179 try 1180 { 1181 method = beanClass.getDeclaredMethod( getterName ); 1182 } 1183 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1184 } 1185 1186 if( isNull( method ) ) 1187 { 1188 try 1189 { 1190 method = beanClass.getMethod( getterName ); 1191 } 1192 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1193 } 1194 1195 if( isNull( method ) ) 1196 { 1197 //---* Assume it is a boolean property ... *------------------- 1198 getterName = PREFIX_IS + capitalize( propertyName ); 1199 if( !isPublic ) 1200 { 1201 try 1202 { 1203 method = beanClass.getDeclaredMethod( getterName ); 1204 } 1205 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1206 } 1207 1208 if( isNull( method ) ) 1209 { 1210 try 1211 { 1212 method = beanClass.getMethod( getterName ); 1213 } 1214 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1215 } 1216 1217 //---* Check the return type *--------------------------------- 1218 if( nonNull( method ) ) 1219 { 1220 final var returnType = method.getReturnType(); 1221 if( !(returnType.equals( Boolean.class ) || returnType.equals( boolean.class )) ) 1222 { 1223 method = null; 1224 } 1225 } 1226 } 1227 else 1228 { 1229 //---* Check the return type *--------------------------------- 1230 if( method.getReturnType().equals( void.class ) ) 1231 { 1232 method = null; 1233 } 1234 } 1235 1236 if( nonNull( method ) ) 1237 { 1238 //---* Check if the method is public and not static *---------- 1239 final var modifier = method.getModifiers(); 1240 if( isStatic( modifier ) ) 1241 { 1242 method = null; 1243 } 1244 else if( !isPublic( modifier ) ) 1245 { 1246 if( isPublic ) 1247 { 1248 method = null; 1249 } 1250 else 1251 { 1252 /* 1253 * Ensure that the non-public method can be accessed. 1254 */ 1255 method.setAccessible( true ); 1256 } 1257 } 1258 } 1259 } 1260 1261 //---* Compose the return value *-------------------------------------- 1262 final var retValue = Optional.ofNullable( method ); 1263 1264 //---* Done *---------------------------------------------------------- 1265 return retValue; 1266 } // retrieveGetter() 1267 1268 /** 1269 * Returns all the getter methods from the given object.<br> 1270 * <br><i>getter</i> methods ... 1271 * <ul> 1272 * <li>... are public</li> 1273 * <li>... are <i>not</i> static</li> 1274 * <li>... will return a value (are not {@code void}</li> 1275 * <li>... do not take an argument</li> 1276 * <li>... have a name that starts with "{@code get}", followed 1277 * by the name of the property that they return, with its first letter in 1278 * uppercase (e.g. for the property "{@code name}", get getter 1279 * would be named "{@code getName()}"</li> 1280 * <li>... may have a name that starts with "{@code is}" instead 1281 * of "{@code get}" in case they return a {@code boolean} 1282 * value.</li> 1283 * </ul> 1284 * <br>The method will ignore the method 1285 * {@link Object#getClass()} 1286 * that is present for each object instance. 1287 * 1288 * @param o The object to inspect. 1289 * @return The list of getters; it may be empty, but will never be 1290 * {@code null}. 1291 * 1292 * @see #isGetter(Method) 1293 */ 1294 @API( status = STABLE, since = "0.0.5" ) 1295 public static final Method [] retrieveGetters( final Object o ) 1296 { 1297 final var retValue = 1298 stream( requireNonNullArgument( o, "o" ).getClass().getMethods() ) 1299 .filter( JavaUtils::isGetter ) 1300 .toArray( Method []::new ); 1301 1302 //---* Done *---------------------------------------------------------- 1303 return retValue; 1304 } // retrieveGetters() 1305 1306 /** 1307 * <p>{@summary Retrieves the public method with the given signature from 1308 * the given class.} The method will not throw an exception in case the 1309 * method does not exist. 1310 * 1311 * @param sourceClass The class. 1312 * @param methodName The name of the method. 1313 * @param args The types of the method arguments. 1314 * @return An instance of 1315 * {@link Optional} 1316 * that holds the found instance of 1317 * {@link Method}. 1318 * 1319 * @see Class#getMethod(String, Class[]) 1320 * 1321 * @since 0.1.0 1322 */ 1323 @API( status = STABLE, since = "0.1.0" ) 1324 public static final Optional<Method> retrieveMethod( final Class<?> sourceClass, final String methodName, final Class<?>... args ) 1325 { 1326 Optional<Method> retValue = Optional.empty(); 1327 try 1328 { 1329 final var method = sourceClass.getMethod( methodName, args ); 1330 retValue = Optional.of( method ); 1331 } 1332 catch( final NoSuchMethodException ignored ) { /* Deliberately ignored */ } 1333 1334 //---* Done *---------------------------------------------------------- 1335 return retValue; 1336 } // retrieveMethod() 1337 1338 /** 1339 * Retrieves the name of the property from the name of the given 1340 * executable element for a method that is either a 1341 * {@linkplain #isGetter(Element) getter}, 1342 * a 1343 * {@linkplain #isSetter(Element) setter}, 1344 * or an 1345 * {@linkplain #isAddMethod(Element) 'add'} 1346 * method. Alternatively the method has an annotation that provides the 1347 * name of the property. 1348 * 1349 * @param method The method. 1350 * @return The name of the property. 1351 */ 1352 public static final String retrievePropertyName( final ExecutableElement method ) 1353 { 1354 final String retValue; 1355 final var propertyNameAnnotation = requireNonNullArgument( method, "method" ).getAnnotation( PropertyName.class ); 1356 if( nonNull( propertyNameAnnotation ) ) 1357 { 1358 retValue = propertyNameAnnotation.value(); 1359 } 1360 else 1361 { 1362 final var methodName = requireValidArgument( method, "method", v -> isGetter( v ) || isSetter( v ) || isAddMethod( v ), $ -> "'%s()' is not a valid type of method".formatted( method.getSimpleName() ) ).getSimpleName().toString(); 1363 1364 /* 1365 * We know that the method is either a getter, a setter or an 'add' 1366 * method. Therefore, we know also that the name starts either with 1367 * "get", "set", "add" or "is". 1368 */ 1369 final var pos = methodName.startsWith( PREFIX_IS ) ? PREFIX_IS.length() : PREFIX_GET.length(); 1370 retValue = decapitalize( methodName.substring( pos ) ); 1371 } 1372 1373 //---* Done *---------------------------------------------------------- 1374 return retValue; 1375 } // retrievePropertyName() 1376 1377 /** 1378 * Retrieves the public setter for the property with the given name. If 1379 * not {@code null}, the returned value will cause 1380 * {@link #isSetter(Method)} 1381 * to return {@code true}. 1382 * 1383 * @param beanClass The class for the getter. 1384 * @param propertyName The name of the property. 1385 * @param propertyType The type of the property. 1386 * @return An instance of 1387 * {@link Optional} 1388 * that holds the setter method; will be empty if there is no public 1389 * setter for the given property on the provided class. 1390 * 1391 * @see #retrieveSetter(Class,String,Class,boolean) 1392 */ 1393 @API( status = STABLE, since = "0.0.5" ) 1394 public static final Optional<Method> retrieveSetter( final Class<?> beanClass, final String propertyName, final Class<?> propertyType ) 1395 { 1396 final var retValue = retrieveSetter( beanClass, propertyName, propertyType, true ); 1397 1398 //---* Done *---------------------------------------------------------- 1399 return retValue; 1400 } // retrieveSetter() 1401 1402 /** 1403 * Retrieves the setter for the property with the given name.<br> 1404 * <br>For some purposes, non-public setters are quite useful; when 1405 * {@code isPublic} is provided as {@code false}, this method will also 1406 * return those setters. 1407 * 1408 * @param beanClass The class for the getter. 1409 * @param propertyName The name of the property. 1410 * @param propertyType The type of the property. 1411 * @param isPublic {@code true} if the setter is required to be 1412 * public, {@code false} otherwise. 1413 * @return An instance of 1414 * {@link Optional} 1415 * that holds the setter method; will be empty if there is no setter 1416 * for the given property on the provided class. 1417 * 1418 * @see #isSetter(Method) 1419 */ 1420 @SuppressWarnings( "AssignmentToNull" ) 1421 @API( status = STABLE, since = "0.0.5" ) 1422 public static final Optional<Method> retrieveSetter( final Class<?> beanClass, final String propertyName, final Class<?> propertyType, final boolean isPublic ) 1423 { 1424 requireNonNullArgument( beanClass, "beanClass" ); 1425 requireNonNullArgument( propertyType, "propertyType" ); 1426 1427 //---* Retrieve a common setter *-------------------------------------- 1428 Method method = null; 1429 if( !isPublic ) 1430 { 1431 try 1432 { 1433 /* 1434 * Class.getDeclaredMethod() will return any method with the 1435 * given name that is declared on the given class, no matter 1436 * whether it is public or private. 1437 */ 1438 method = beanClass.getDeclaredMethod( composeSetterName( propertyName ), propertyType ); 1439 } 1440 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1441 } 1442 1443 /* 1444 * method is null here, either because isPublic() is true, or the 1445 * public (or protected) method was not declared on the given class, 1446 * but inherited from the parent class (or it does not exist at all 1447 * ...) 1448 */ 1449 if( isNull( method ) ) 1450 { 1451 try 1452 { 1453 method = beanClass.getMethod( composeSetterName( propertyName ), propertyType ); 1454 } 1455 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1456 } 1457 1458 /* 1459 * We found a method with the right name, now have to perform some 1460 * checks on it. 1461 */ 1462 if( nonNull( method ) ) 1463 { 1464 //---* Check the return value *------------------------------------ 1465 if( !method.getReturnType().equals( void.class ) ) 1466 { 1467 method = null; // Wrong return type ... 1468 } 1469 else 1470 { 1471 //---* Check if the method is public and not static *---------- 1472 final var modifier = method.getModifiers(); 1473 if( isStatic( modifier ) ) 1474 { 1475 method = null; // Setters are never static 1476 } 1477 else if( !isPublic( modifier ) ) 1478 { 1479 if( isPublic ) 1480 { 1481 method = null; // It has to be public, but it isn't 1482 } 1483 else 1484 { 1485 /* 1486 * Ensure that the non-public method can be accessed. 1487 */ 1488 method.setAccessible( true ); 1489 } 1490 } 1491 } 1492 } 1493 1494 //---* Compose the return value *-------------------------------------- 1495 final var retValue = Optional.ofNullable( method ); 1496 1497 //---* Done *---------------------------------------------------------- 1498 return retValue; 1499 } // retrieveSetter() 1500 1501 /** 1502 * <p>{@summary Searches the given stack trace for references to the 1503 * method with the given name and returns the name for the respective 1504 * class.}</p> 1505 * <p>This is a helper method for 1506 * {@link #findMainClass()}.</p> 1507 * 1508 * @param stackTrace The stack trace. 1509 * @param methodName The name of the method to look for. 1510 * @return The class name, or {@code null} if there is no reference to 1511 * the given method in the stack trace. 1512 */ 1513 @API( status = STABLE, since = "0.0.5" ) 1514 private static final String searchStackTrace( final StackTraceElement [] stackTrace, final String methodName ) 1515 { 1516 assert nonNull( stackTrace ) : "stackTrace is null"; 1517 assert isNotEmpty( methodName ) : "methodName is empty or null"; 1518 1519 String retValue = null; 1520 String foundMethodName; 1521 for( var i = stackTrace.length; (i > 0) && isNull( retValue ); --i ) 1522 { 1523 foundMethodName = stackTrace [i-1].getMethodName(); 1524 if( foundMethodName.equals( methodName ) ) 1525 { 1526 retValue = stackTrace [i-1].getClassName(); 1527 } 1528 } 1529 1530 //---* Done *---------------------------------------------------------- 1531 return retValue; 1532 } // searchStackTrace() 1533 1534 /** 1535 * <p>{@summary Translates the integer value for the modifiers for a 1536 * class, method or field as it is used by reflection to the {@code enum} 1537 * values from 1538 * {@link Modifier}.}</p> 1539 * <p>The modifier 1540 * {@link javax.lang.model.element.Modifier#DEFAULT} 1541 * will not be in the return set as this cannot be retrieved at runtime, 1542 * and the value 1543 * {@link java.lang.reflect.Modifier#INTERFACE} 1544 * does not exist as an {@code enum} value in 1545 * {@link javax.lang.model.element.Modifier}.</p> 1546 * <p>The modifiers {@code sealed} and {@code non-sealed}, belonging to 1547 * the preview feature 'Sealed Classes' are defined in 1548 * {@code javax.lang.model.element.Modifier}, but 1549 * {@link Class#getModifiers()} 1550 * will not return them, and they are not (yet) defined in 1551 * {@link java.lang.reflect.Modifier}. Therefore they will not appear in 1552 * the return set, too.</p> 1553 * 1554 * @param modifiers The integer value for the modifiers. 1555 * @return The modifier values. 1556 * 1557 * @see javax.lang.model.element.Modifier#NON_SEALED 1558 * @see javax.lang.model.element.Modifier#SEALED 1559 */ 1560 @SuppressWarnings( "OverlyComplexMethod" ) 1561 @API( status = STABLE, since = "0.0.5" ) 1562 public static final Set<Modifier> translateModifiers( final int modifiers ) 1563 { 1564 final Set<Modifier> retValue = EnumSet.noneOf( Modifier.class ); 1565 if( isAbstract( modifiers ) ) retValue.add( ABSTRACT ); 1566 if( isFinal( modifiers ) ) retValue.add( FINAL ); 1567 if( isNative( modifiers ) ) retValue.add( NATIVE ); 1568 if( isPrivate( modifiers ) ) retValue.add( PRIVATE ); 1569 if( isProtected( modifiers ) ) retValue.add( PROTECTED ); 1570 if( isPublic( modifiers ) ) retValue.add( PUBLIC ); 1571 if( isStatic( modifiers ) ) retValue.add( STATIC ); 1572 if( isStrict( modifiers ) ) retValue.add( STRICTFP ); 1573 if( isSynchronized( modifiers ) ) retValue.add( SYNCHRONIZED ); 1574 if( isTransient( modifiers ) ) retValue.add( TRANSIENT ); 1575 if( isVolatile( modifiers ) ) retValue.add( VOLATILE ); 1576 1577 //---* Done *---------------------------------------------------------- 1578 return retValue; 1579 } // translateModifiers() 1580} 1581// class JavaUtils 1582 1583/* 1584 * End of File 1585 */