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