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.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 1258 2026-06-04 18:33:06Z tquadrat $ 091 * @since 0.0.5 092 * 093 * @UMLGraph.link 094 */ 095@SuppressWarnings( {"ClassWithTooManyMethods", "OverlyComplexClass"} ) 096@ClassVersion( sourceVersion = "$Id: JavaUtils.java 1258 2026-06-04 18:33:06Z 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 {@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; 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", "unused"} ) 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 {@true} if the method is an 'add' method, {@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 //noinspection PointlessBooleanExpression 497 if( (retValue = modifiers.contains( PUBLIC ) && !modifiers.contains( STATIC ) && !modifiers.contains( DEFAULT )) == true ) 498 { 499 //---* Check the name *---------------------------------------- 500 final var name = methodElement.getSimpleName().toString(); 501 //noinspection PointlessBooleanExpression 502 if( (retValue = name.startsWith( PREFIX_ADD )) == true ) 503 { 504 //---* Check if there is a property name *----------------- 505 final var pos = PREFIX_ADD.length(); 506 retValue = (name.length() > pos) && Character.isUpperCase( name.charAt( pos ) ); 507 508 //---* Check the number of parameters *-------------------- 509 retValue = retValue && (methodElement.getParameters().size() == 1); 510 511 //---* Check the return value *---------------------------- 512 retValue = retValue && (methodElement.getReturnType() instanceof NoType); 513 } 514 } 515 } 516 517 //---* Done *---------------------------------------------------------- 518 return retValue; 519 } // isAddMethod() 520 521 /** 522 * Checks whether JDK assertion is currently activated, meaning that the 523 * program was started with the command line flags {@code -ea} or 524 * {@code -enableassertions}. If assertions are activated for some 525 * selected packages only and {@code org.tquadrat.foundation.util} is not 526 * amongst these, or {@code org.tquadrat.foundation.util} is explicitly 527 * disabled with {@code -da} or {@code -disableassertions}, this method 528 * will return {@false}. But even when it returns {@true}, it is 529 * possible that assertions are still not activated for some packages. 530 * 531 * @return {@true} if assertions are activated for the 532 * package {@code org.tquadrat.util} and hopefully also for any other 533 * package, {@false} otherwise. 534 */ 535 @SuppressWarnings( "unused" ) 536 @API( status = STABLE, since = "0.0.5" ) 537 public static final boolean isAssertionOn() { return m_AssertionOn; } 538 539 /** 540 * Checks if the given method is the method 541 * {@link Object#equals(Object) equals()} 542 * as defined by the class {@code Object}. To be this method, it has to be 543 * public, it has to have the name 'equals', it has to take one argument 544 * of type {@code Object} and it will return a result of type 545 * {@code boolean}. 546 * 547 * @param method The method to check. 548 * @return {@true} if the method is the {@code equals()} method, 549 * {@false} otherwise. 550 */ 551 @API( status = STABLE, since = "0.0.5" ) 552 public static final boolean isEquals( final Method method ) 553 { 554 //---* Check if the method is public and not static *------------------ 555 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 556 var retValue = isPublic( modifier ) && !isStatic( modifier ); 557 558 //---* Check the name *------------------------------------------------ 559 retValue = retValue && "equals".equals( method.getName() ); 560 561 //---* Check the return value *---------------------------------------- 562 retValue = retValue && method.getReturnType().equals( boolean.class ); 563 564 //---* Check the parameters *------------------------------------------ 565 if( retValue ) 566 { 567 final var parameterTypes = method.getParameterTypes(); 568 retValue = (parameterTypes.length == 1) && Object.class.equals( parameterTypes [0] ); 569 } 570 571 //---* Done *---------------------------------------------------------- 572 return retValue; 573 } // isEquals() 574 575 /** 576 * <p>{@summary Checks whether the given element is a <i>getter</i> method 577 * or not.}</p> 578 * <p>A getter method is public and not static, it has a name starting 579 * with "{@code get}", it does not take any arguments, and it 580 * will return a value. In case the return value is of type 581 * {@code boolean}, the name may start with "{@code is}" instead 582 * of "{@code get}".</p> 583 * <p>The remaining part of the name after "{@code get}" or 584 * "{@code is}" has to start with an uppercase letter; this is 585 * usually taken as the attribute's or property's name.</p> 586 * <p>For the method 587 * {@link Object#getClass()} 588 * (inherited by all classes from 589 * {@link Object}), 590 * this method will return {@false}, as this is not a getter in the 591 * sense of the definition.</p> 592 * 593 * @param element The element to check. 594 * @return {@true} if the element is a getter method, {@false} 595 * otherwise. 596 */ 597 @SuppressWarnings( {"OverlyComplexMethod", "NestedAssignment"} ) 598 @API( status = STABLE, since = "0.0.5" ) 599 public static final boolean isGetter( final Element element ) 600 { 601 //---* Check whether the element is a method at all *------------------ 602 var retValue = requireNonNullArgument( element, "element" ).getKind() == ElementKind.METHOD; 603 if( retValue ) 604 { 605 final var methodElement = (ExecutableElement) element; 606 607 //---* Check if the method is public and not static *-------------- 608 final var modifiers = methodElement.getModifiers(); 609 //noinspection PointlessBooleanExpression 610 if( (retValue = modifiers.contains( PUBLIC ) && !modifiers.contains( STATIC )) == true ) 611 { 612 //---* Check the name *---------------------------------------- 613 final var name = methodElement.getSimpleName().toString(); 614 //noinspection PointlessBooleanExpression 615 if( (retValue = !"getClass".equals( name )) == true ) 616 { 617 var pos = 0; 618 if( name.startsWith( PREFIX_IS ) ) 619 { 620 //---* Check the return value *------------------------ 621 final var returnMirror = methodElement.getReturnType(); 622 retValue = !(returnMirror instanceof NoType) && (returnMirror.getKind() == TypeKind.BOOLEAN); 623 pos = PREFIX_IS.length(); 624 } 625 else if( name.startsWith( PREFIX_GET ) ) 626 { 627 //---* Check the return value *------------------------ 628 retValue = !(methodElement.getReturnType() instanceof NoType); 629 pos = PREFIX_GET.length(); 630 } 631 else 632 { 633 retValue = false; 634 } 635 636 if( retValue ) 637 { 638 //---* Check if there is a property name *------------- 639 retValue = (name.length() > pos) && Character.isUpperCase( name.charAt( pos ) ); 640 641 //---* Check the number of parameters *---------------- 642 retValue = retValue && methodElement.getParameters().isEmpty(); 643 } 644 } 645 } 646 } 647 648 //---* Done *---------------------------------------------------------- 649 return retValue; 650 } // isGetter() 651 652 /** 653 * <p>{@summary Checks whether the given method is a <i>getter</i> method 654 * or not.}</p> 655 * <p>A getter method is public and not static, it has a name starting 656 * with "{@code get}", it does not take any arguments, and it 657 * will return a value. In case the return value is of type 658 * {@code boolean}, the name may start with "{@code is}" instead 659 * of "{@code get}".</p> 660 * <p>The remaining part of the name after "{@code get}" or 661 * "{@code is}" has to start with an uppercase letter; this is 662 * usually taken as the attribute's or property's name.</p> 663 * <p>For the method 664 * {@link Object#getClass()} 665 * (inherited by all classes from 666 * {@link Object}), 667 * this method will return {@false}, as this is not a getter in the 668 * sense of the definition.</p> 669 * 670 * @param method The method to check. 671 * @return {@true} if the method is a getter, {@false} 672 * otherwise. 673 */ 674 @API( status = STABLE, since = "0.0.5" ) 675 public static final boolean isGetter( final Method method ) 676 { 677 //---* Check if the method is public and not static *------------------ 678 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 679 var retValue = isPublic( modifier ) && !isStatic( modifier ); 680 681 //---* Check the number of parameters *-------------------------------- 682 retValue = retValue && (method.getParameterTypes().length == 0); 683 684 //---* Check the name *------------------------------------------------ 685 if( retValue ) 686 { 687 final var name = method.getName(); 688 retValue = !"getClass".equals( name ); 689 690 if( retValue ) 691 { 692 var pos = Integer.MAX_VALUE; 693 if( name.startsWith( PREFIX_IS ) ) 694 { 695 retValue = method.getReturnType().equals( boolean.class ); 696 pos = PREFIX_IS.length(); 697 } 698 else if( name.startsWith( PREFIX_GET ) ) 699 { 700 //---* Check the return value *---------------------------- 701 retValue = !method.getReturnType().equals( void.class ); 702 pos = PREFIX_GET.length(); 703 } 704 else 705 { 706 retValue = false; 707 } 708 709 //---* Check if there is a property name *--------------------- 710 retValue = retValue && (name.length() > pos) && Character.isUpperCase( name.charAt( pos ) ); 711 } 712 } 713 714 //---* Done *---------------------------------------------------------- 715 return retValue; 716 } // isGetter() 717 718 /** 719 * Checks if the given method is the method 720 * {@link Object#hashCode() hashCode()} 721 * as defined by the class {@code Object}. To be this method, it has to be 722 * public, it has to have the name 'hashCode', it does not take any 723 * argument, and it will return a result of type {@code integer}. 724 * 725 * @param method The method to check. 726 * @return {@true} if the method is the {@code hashCode()} 727 * method, {@false} otherwise. 728 */ 729 @API( status = STABLE, since = "0.0.5" ) 730 public static final boolean isHashCode( final Method method ) 731 { 732 //---* Check if the method is public and not static *------------------ 733 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 734 var retValue = isPublic( modifier ) && !isStatic( modifier ); 735 736 //---* Check the name *------------------------------------------------ 737 retValue = retValue && "hashCode".equals( method.getName() ); 738 739 //---* Check the return value *---------------------------------------- 740 retValue = retValue && method.getReturnType().equals( int.class ); 741 742 //---* Check the number of parameters *-------------------------------- 743 retValue = retValue && (method.getParameterTypes().length == 0); 744 745 //---* Done *---------------------------------------------------------- 746 return retValue; 747 } // isHashCode() 748 749 /** 750 * Checks if the given method is a {@code main()} method or not. A 751 * {@code main()} method is public and static, it has the name 752 * "main", it takes exactly one parameter of type 753 * {@code String []}, and it does not return any value. 754 * 755 * @param method The method to check. 756 * @return {@true} if the method is a {@code main()} method, 757 * {@false} otherwise. 758 */ 759 @API( status = STABLE, since = "0.0.5" ) 760 public static final boolean isMain( final Method method ) 761 { 762 //---* Check if the method is public and static *---------------------- 763 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 764 var retValue = isPublic( modifier ) && isStatic( modifier ); 765 766 //---* Check the name *------------------------------------------------ 767 retValue = retValue && "main".equals( method.getName() ); 768 769 //---* Check the return value *---------------------------------------- 770 retValue = retValue && method.getReturnType().equals( void.class ); 771 772 //---* Check the number of parameters *-------------------------------- 773 if( retValue ) 774 { 775 final var parameterTypes = method.getParameterTypes(); 776 retValue = (parameterTypes.length == 1) && String [].class.equals( parameterTypes [0] ); 777 } 778 779 //---* Done *---------------------------------------------------------- 780 return retValue; 781 } // isMain() 782 783 /** 784 * <p>{@summary Checks if the given element is a setter method or not.} A 785 * setter method is public, it has a name starting with 'set', it takes 786 * exactly one parameter, and it does not return any value.</p> 787 * <p>The remaining part of the name after 'set' is taken as the 788 * attribute's name.</p> 789 * 790 * @param element The element to check. 791 * @return {@true} if the method is a setter, {@false} 792 * otherwise. 793 */ 794 @SuppressWarnings( "NestedAssignment" ) 795 @API( status = STABLE, since = "0.0.5" ) 796 public static final boolean isSetter( final Element element ) 797 { 798 //---* Check whether the element is a method at all *------------------ 799 var retValue = requireNonNullArgument( element, "element" ).getKind() == ElementKind.METHOD; 800 if( retValue ) 801 { 802 final var methodElement = (ExecutableElement) element; 803 804 //---* Check if the method is public and not static *-------------- 805 final var modifiers = methodElement.getModifiers(); 806 //noinspection PointlessBooleanExpression 807 if( (retValue = modifiers.contains( PUBLIC ) && !modifiers.contains( STATIC )) == true ) 808 { 809 //---* Check the name *---------------------------------------- 810 final var name = methodElement.getSimpleName().toString(); 811 //noinspection PointlessBooleanExpression 812 if( (retValue = name.startsWith( PREFIX_SET )) == true ) 813 { 814 //---* Check if there is a property name *----------------- 815 final var pos = PREFIX_SET.length(); 816 retValue = (name.length() > pos) && Character.isUpperCase( name.charAt( pos ) ); 817 818 //---* Check the number of parameters *-------------------- 819 retValue = retValue && (methodElement.getParameters().size() == 1); 820 821 //---* Check the return value *---------------------------- 822 retValue = retValue && (methodElement.getReturnType() instanceof NoType); 823 } 824 } 825 } 826 827 //---* Done *---------------------------------------------------------- 828 return retValue; 829 } // isSetter() 830 831 /** 832 * Checks if the given method is a setter method or not. A setter method 833 * is public, it has a name starting with 'set', it takes exactly one 834 * parameter, and it does not return any value.<br> 835 * <br>The remaining part of the name after 'set' is taken as the 836 * attribute's name. 837 * 838 * @param method The method to check. 839 * @return {@true} if the method is a setter, {@false} 840 * otherwise. 841 */ 842 @API( status = STABLE, since = "0.0.5" ) 843 public static final boolean isSetter( final Method method ) 844 { 845 //---* Check if the method is public and not static *------------------ 846 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 847 var retValue = isPublic( modifier ) && !isStatic( modifier ); 848 849 //---* Check the name *------------------------------------------------ 850 if( retValue ) 851 { 852 final var name = method.getName(); 853 retValue = name.startsWith( PREFIX_SET ); 854 855 //---* Check if there is a property name *------------------------- 856 final var pos = PREFIX_SET.length(); 857 retValue = retValue && ((name.length() > pos) && Character.isUpperCase( name.charAt( pos ) )); 858 } 859 860 //---* Check the number of parameters *-------------------------------- 861 retValue = retValue && (method.getParameterTypes().length == 1); 862 863 //---* Check the return value *---------------------------------------- 864 retValue = retValue && method.getReturnType().equals( void.class ); 865 866 //---* Done *---------------------------------------------------------- 867 return retValue; 868 } // isSetter() 869 870 /** 871 * Checks if the given method is the method 872 * {@link Object#toString() toString()} 873 * as defined by the class {@code Object}. To be this method, it has to be 874 * public, it has to have the name 'toString', it does not take any 875 * argument, and it returns a result of type 876 * {@link String}. 877 * 878 * @param method The method to check. 879 * @return {@true} if the method is the {@code toString()} 880 * method, {@false} otherwise. 881 */ 882 @API( status = STABLE, since = "0.0.5" ) 883 public static final boolean isToString( final Method method ) 884 { 885 //---* Check if the method is public and not static *------------------ 886 final var modifier = requireNonNullArgument( method, "method" ).getModifiers(); 887 var retValue = isPublic( modifier ) && !isStatic( modifier ); 888 889 //---* Check the name *------------------------------------------------ 890 retValue = retValue && "toString".equals( method.getName() ); 891 892 //---* Check the return value *---------------------------------------- 893 retValue = retValue && method.getReturnType().equals( String.class ); 894 895 //---* Check the number of parameters *-------------------------------- 896 retValue = retValue && (method.getParameterTypes().length == 0); 897 898 //---* Done *---------------------------------------------------------- 899 return retValue; 900 } // isToString() 901 902 /** 903 * Checks whether the given String is a valid Java name.<br> 904 * <br>This method will return {@true} for <i>restricted 905 * keywords</i>, but not for {@code var}. For a single underscore 906 * ("{@code _}"), it will return {@false}.<br> 907 * <br>The restricted keywords are 908 * <ul> 909 * <li>{@code exports}</li> 910 * <li>{@code module}</li> 911 * <li>{@code open}</li> 912 * <li>{@code opens}</li> 913 * <li>{@code provides}</li> 914 * <li>{@code requires}</li> 915 * <li>{@code to}</li> 916 * <li>{@code transitive}</li> 917 * <li>{@code uses}</li> 918 * <li>{@code with}</li> 919 * </ul> 920 * All these are used in a {@code module-info.java} file. 921 * 922 * @param name The String to check. 923 * @return {@true} if the given String is a valid name for the Java 924 * language, {@false} otherwise. 925 * 926 * @see javax.lang.model.SourceVersion#isName(CharSequence, SourceVersion) 927 * @see javax.lang.model.SourceVersion#isIdentifier(CharSequence) 928 * @see javax.lang.model.SourceVersion#isKeyword(CharSequence, SourceVersion) 929 * @see javax.lang.model.SourceVersion#latest() 930 */ 931 @API( status = STABLE, since = "0.0.5" ) 932 public static final boolean isValidName( final CharSequence name ) 933 { 934 final var retValue = SourceVersion.isName( requireNotEmptyArgument( name, "name" ) ) 935 && !name.equals( "var" ); 936 937 //---* Done *---------------------------------------------------------- 938 return retValue; 939 } // isValidName() 940 941 /** 942 * Loads the class with the given name, using the instance of 943 * {@link ClassLoader} 944 * that loaded the caller's class, and returns that class. If no class 945 * with that name could be found by that {@code ClassLoader}, no exception 946 * will be thrown; instead this method will return an empty 947 * {@link Optional} 948 * instance.<br> 949 * <br>If not loaded and initialised before, the loaded class is not yet 950 * initialised. That means that {@code static} code blocks have not been 951 * executed yet and class variables (static variables) are not 952 * initialised.<br> 953 * <br>Different from 954 * {@link Class#forName(String, boolean, ClassLoader)}, 955 * this method is able to load the class objects for the primitive types, 956 * too. 957 * 958 * @param classname The name of the class to load; may <i>not</i> be 959 * empty or {@null}. 960 * @return The class wrapped in an 961 * {@link Optional} 962 * instance. 963 * 964 * @see Class#forName(String) 965 * @see Class#forName(String, boolean, ClassLoader) 966 * @see Optional#isPresent() 967 * @see #getCallersClassLoader() 968 */ 969 @API( status = STABLE, since = "0.0.5" ) 970 public static final Optional<Class<?>> loadClass( final String classname ) 971 { 972 final var classLoader = getCallersClassLoader(); 973 final var retValue = loadClass( classLoader, classname ); 974 975 //---* Done *---------------------------------------------------------- 976 return retValue; 977 } // loadClass() 978 979 /** 980 * <p>{@summary Loads the class with the given name, using the given 981 * {@link ClassLoader} 982 * instance, and returns that class.} If no class with that name could be 983 * found by that {@code ClassLoader}, no exception will be thrown; instead 984 * this method will return an empty 985 * {@link Optional} 986 * instance.</p> 987 * <p>If not loaded and initialised before, the loaded class is not yet 988 * initialised. That means that {@code static} code blocks have not been 989 * executed yet and class variables (static variables) are not 990 * initialised.</p> 991 * <p>Different from 992 * {@link Class#forName(String, boolean, ClassLoader)}, 993 * this method is able to load the class objects for the primitive types, 994 * too.</p> 995 * 996 * @param classLoader The class loader to use. 997 * @param classname The name of the class to load; may <i>not</i> be 998 * empty or {@null}. 999 * @return The class wrapped in an 1000 * {@link Optional} 1001 * instance. 1002 * 1003 * @see Class#forName(String) 1004 * @see Class#forName(String, boolean, ClassLoader) 1005 * @see Optional#isPresent() 1006 */ 1007 @API( status = STABLE, since = "0.0.5" ) 1008 public static final Optional<Class<?>> loadClass( final ClassLoader classLoader, final String classname ) 1009 { 1010 var resultClass = m_PrimitiveClasses.get( requireNotEmptyArgument( classname, "classname" ) ); 1011 if( isNull( resultClass ) ) 1012 { 1013 try 1014 { 1015 resultClass = Class.forName( classname, false, requireNonNullArgument( classLoader, "classLoader" ) ); 1016 } 1017 catch( final ClassNotFoundException e ) 1018 { 1019 //---* Deliberately ignored *---------------------------------- 1020 ifDebug( e ); 1021 } 1022 } 1023 1024 //---* Create the return value *--------------------------------------- 1025 final Optional<Class<?>> retValue = Optional.ofNullable( resultClass ); 1026 1027 //---* Done *---------------------------------------------------------- 1028 return retValue; 1029 } // loadClass() 1030 1031 /** 1032 * Loads the class with the given name, using the given 1033 * {@link ClassLoader} 1034 * instance, and returns that class, wrapped in an instance of 1035 * {@link Optional}. 1036 * If no class with that name could be found by that instance of 1037 * {@code ClassLoader}, or if it does not implement the given 1038 * interface/extend the given class, no exception will be thrown; instead 1039 * this method will return an empty 1040 * {@link Optional} 1041 * instance.<br> 1042 * <br>If not loaded and initialised before, the loaded class is not yet 1043 * initialised. That means that {@code static} code blocks have not been 1044 * executed yet and class variables (static variables) are not 1045 * initialised. 1046 * 1047 * @param <T> The type of the interface/class that the returned class 1048 * will implement/extend. 1049 * @param classLoader The class loader to use. 1050 * @param classname The name of the class to load; may <i>not</i> be 1051 * empty or {@null}. 1052 * @param implementing The interface/class that the returned class 1053 * has to implement/extend. 1054 * @return The class wrapped in an 1055 * {@link Optional} 1056 * instance. 1057 * 1058 * @see Class#forName(String) 1059 * @see Class#forName(String, boolean, ClassLoader) 1060 * @see Optional#isPresent() 1061 */ 1062 @API( status = STABLE, since = "0.0.5" ) 1063 public static final <T> Optional<Class<? extends T>> loadClass( final ClassLoader classLoader, final String classname, final Class<? extends T> implementing ) 1064 { 1065 Class<? extends T> resultClass = null; 1066 try 1067 { 1068 final var candidateClass = Class.forName( requireNotEmptyArgument( classname, "classname" ), false, requireNonNullArgument( classLoader, "classLoader" ) ); 1069 if( requireNonNullArgument( implementing, "implementing" ).isAssignableFrom( candidateClass ) ) 1070 { 1071 resultClass = candidateClass.asSubclass( implementing ); 1072 } 1073 } 1074 catch( final ClassNotFoundException ignored ) { /* Deliberately ignored */ } 1075 1076 //---* Create the return value *--------------------------------------- 1077 final Optional<Class<? extends T>> retValue = Optional.ofNullable( resultClass ); 1078 1079 //---* Done *---------------------------------------------------------- 1080 return retValue; 1081 } // loadClass() 1082 1083 /** 1084 * If no class with that name could be found by that instance of 1085 * {@code ClassLoader}, or if it does not implement the given 1086 * interface/extend the given class, no exception will be thrown; instead 1087 * this method will return an empty 1088 * {@link Optional}.<br> 1089 * Loads the class with the given name, using the instance of 1090 * {@link ClassLoader} 1091 * that loaded the caller's class, and returns that class, wrapped in an 1092 * instance of 1093 * {@link Optional}. 1094 * If no class with that name could be found by that instance of 1095 * {@code ClassLoader}, or if it does not implement the given 1096 * interface/extend the given class, no exception will be thrown; instead 1097 * this method will return an empty 1098 * {@link Optional} 1099 * instance.<br> 1100 * <br>If not loaded and initialised before, the loaded class is not yet 1101 * initialised. That means that {@code static} code blocks have not been 1102 * executed yet and class variables (static variables) are not 1103 * initialised. 1104 * 1105 * @param <T> The type of the interface/class that the returned class 1106 * will implement/extend. 1107 * @param classname The name of the class to load; may <i>not</i> be 1108 * empty or {@null}. 1109 * @param implementing The interface/class that the returned class 1110 * has to implement/extend. 1111 * @return The class wrapped in an 1112 * {@link Optional} 1113 * instance. 1114 * 1115 * @see Class#forName(String) 1116 * @see Class#forName(String, boolean, ClassLoader) 1117 * @see Optional#isPresent() 1118 * @see #getCallersClassLoader() 1119 */ 1120 @API( status = STABLE, since = "0.0.5" ) 1121 public static final <T> Optional<Class<? extends T>> loadClass( final String classname, final Class<T> implementing ) 1122 { 1123 final var classLoader = getCallersClassLoader(); 1124 final var retValue = loadClass( classLoader, classname, implementing ); 1125 1126 //---* Done *---------------------------------------------------------- 1127 return retValue; 1128 } // loadClass() 1129 1130 /** 1131 * Retrieves the public getter for the property with the given name. If 1132 * not {@null}, the returned value will cause 1133 * {@link #isGetter(Method)} 1134 * to return {@true}. 1135 * 1136 * @param beanClass The class for the getter. 1137 * @param propertyName The name of the property. 1138 * @return An instance of 1139 * {@link Optional} 1140 * that holds the getter method; will be empty if there is no public 1141 * getter for the given property on the provided class. 1142 * 1143 * @see #isGetter(Method) 1144 * @see #retrieveGetter(Class, String, boolean) 1145 */ 1146 public static final Optional<Method> retrieveGetter( final Class<?> beanClass, final String propertyName ) 1147 { 1148 final var retValue = retrieveGetter( beanClass, propertyName, true ); 1149 1150 //---* Done *---------------------------------------------------------- 1151 return retValue; 1152 } // retrieveGetter() 1153 1154 /** 1155 * <p>{@summary Retrieves the getter for the property with the given name.}</p> 1156 * <p>Usually, a getter method has to be public, but for some purposes, 1157 * it may be package local, protected or even private. A method returned 1158 * by a call to this method will not cause 1159 * {@link #isGetter(Method)} 1160 * to return {@true} in all cases.</p> 1161 * 1162 * @param beanClass The class for the getter. 1163 * @param propertyName The name of the property. 1164 * @param isPublic {@true} if the getter is required to be 1165 * public, {@false} otherwise. 1166 * @return An instance of 1167 * {@link Optional} 1168 * that holds the getter method; will be empty if there is no getter 1169 * for the given property on the provided class. 1170 */ 1171 @SuppressWarnings( {"AssignmentToNull", "OverlyComplexMethod"} ) 1172 @API( status = STABLE, since = "0.0.5" ) 1173 public static final Optional<Method> retrieveGetter( final Class<?> beanClass, final String propertyName, final boolean isPublic ) 1174 { 1175 requireNonNullArgument( beanClass, "beanClass" ); 1176 1177 Method method = null; 1178 1179 if( !"class".equals( requireNotEmptyArgument( propertyName, "propertyName" ) ) ) 1180 { 1181 var getterName = composeGetterName( propertyName ); 1182 1183 //---* Retrieve a common getter *---------------------------------- 1184 if( !isPublic ) 1185 { 1186 try 1187 { 1188 method = beanClass.getDeclaredMethod( getterName ); 1189 } 1190 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1191 } 1192 1193 if( isNull( method ) ) 1194 { 1195 try 1196 { 1197 method = beanClass.getMethod( getterName ); 1198 } 1199 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1200 } 1201 1202 if( isNull( method ) ) 1203 { 1204 //---* Assume it is a boolean property ... *------------------- 1205 getterName = PREFIX_IS + capitalize( propertyName ); 1206 if( !isPublic ) 1207 { 1208 try 1209 { 1210 method = beanClass.getDeclaredMethod( getterName ); 1211 } 1212 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1213 } 1214 1215 if( isNull( method ) ) 1216 { 1217 try 1218 { 1219 method = beanClass.getMethod( getterName ); 1220 } 1221 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1222 } 1223 1224 //---* Check the return type *--------------------------------- 1225 if( nonNull( method ) ) 1226 { 1227 final var returnType = method.getReturnType(); 1228 if( !(returnType.equals( Boolean.class ) || returnType.equals( boolean.class )) ) 1229 { 1230 method = null; 1231 } 1232 } 1233 } 1234 else 1235 { 1236 //---* Check the return type *--------------------------------- 1237 if( method.getReturnType().equals( void.class ) ) 1238 { 1239 method = null; 1240 } 1241 } 1242 1243 if( nonNull( method ) ) 1244 { 1245 //---* Check if the method is public and not static *---------- 1246 final var modifier = method.getModifiers(); 1247 if( isStatic( modifier ) ) 1248 { 1249 method = null; 1250 } 1251 else if( !isPublic( modifier ) ) 1252 { 1253 if( isPublic ) 1254 { 1255 method = null; 1256 } 1257 else 1258 { 1259 /* 1260 * Ensure that the non-public method can be accessed. 1261 */ 1262 method.setAccessible( true ); 1263 } 1264 } 1265 } 1266 } 1267 1268 //---* Compose the return value *-------------------------------------- 1269 final var retValue = Optional.ofNullable( method ); 1270 1271 //---* Done *---------------------------------------------------------- 1272 return retValue; 1273 } // retrieveGetter() 1274 1275 /** 1276 * Returns all the getter methods from the given object.<br> 1277 * <br><i>getter</i> methods ... 1278 * <ul> 1279 * <li>... are public</li> 1280 * <li>... are <i>not</i> static</li> 1281 * <li>... will return a value (are not {@code void}</li> 1282 * <li>... do not take an argument</li> 1283 * <li>... have a name that starts with "{@code get}", followed 1284 * by the name of the property that they return, with its first letter in 1285 * uppercase (e.g. for the property "{@code name}", get getter 1286 * would be named "{@code getName()}"</li> 1287 * <li>... may have a name that starts with "{@code is}" instead 1288 * of "{@code get}" in case they return a {@code boolean} 1289 * value.</li> 1290 * </ul> 1291 * <br>The method will ignore the method 1292 * {@link Object#getClass()} 1293 * that is present for each object instance. 1294 * 1295 * @param o The object to inspect. 1296 * @return The list of getters; it may be empty, but will never be 1297 * {@null}. 1298 * 1299 * @see #isGetter(Method) 1300 */ 1301 @API( status = STABLE, since = "0.0.5" ) 1302 public static final Method [] retrieveGetters( final Object o ) 1303 { 1304 final var retValue = 1305 stream( requireNonNullArgument( o, "o" ).getClass().getMethods() ) 1306 .filter( JavaUtils::isGetter ) 1307 .toArray( Method []::new ); 1308 1309 //---* Done *---------------------------------------------------------- 1310 return retValue; 1311 } // retrieveGetters() 1312 1313 /** 1314 * <p>{@summary Retrieves the public method with the given signature from 1315 * the given class.} The method will not throw an exception in case the 1316 * method does not exist. 1317 * 1318 * @param sourceClass The class. 1319 * @param methodName The name of the method. 1320 * @param args The types of the method arguments. 1321 * @return An instance of 1322 * {@link Optional} 1323 * that holds the found instance of 1324 * {@link Method}. 1325 * 1326 * @see Class#getMethod(String, Class[]) 1327 * 1328 * @since 0.1.0 1329 */ 1330 @API( status = STABLE, since = "0.1.0" ) 1331 public static final Optional<Method> retrieveMethod( final Class<?> sourceClass, final String methodName, final Class<?>... args ) 1332 { 1333 Optional<Method> retValue = Optional.empty(); 1334 try 1335 { 1336 final var method = sourceClass.getMethod( methodName, args ); 1337 retValue = Optional.of( method ); 1338 } 1339 catch( final NoSuchMethodException ignored ) { /* Deliberately ignored */ } 1340 1341 //---* Done *---------------------------------------------------------- 1342 return retValue; 1343 } // retrieveMethod() 1344 1345 /** 1346 * Retrieves the name of the property from the name of the given 1347 * executable element for a method that is either a 1348 * {@linkplain #isGetter(Element) getter}, 1349 * a 1350 * {@linkplain #isSetter(Element) setter}, 1351 * or an 1352 * {@linkplain #isAddMethod(Element) 'add'} 1353 * method. Alternatively the method has an annotation that provides the 1354 * name of the property. 1355 * 1356 * @param method The method. 1357 * @return The name of the property. 1358 */ 1359 public static final String retrievePropertyName( final ExecutableElement method ) 1360 { 1361 final String retValue; 1362 @SuppressWarnings( "LocalVariableNamingConvention" ) 1363 final var propertyNameAnnotation = requireNonNullArgument( method, "method" ).getAnnotation( PropertyName.class ); 1364 if( nonNull( propertyNameAnnotation ) ) 1365 { 1366 retValue = propertyNameAnnotation.value(); 1367 } 1368 else 1369 { 1370 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(); 1371 1372 /* 1373 * We know that the method is either a getter, a setter or an 'add' 1374 * method. Therefore, we know also that the name starts either with 1375 * "get", "set", "add" or "is". 1376 */ 1377 final var pos = methodName.startsWith( PREFIX_IS ) ? PREFIX_IS.length() : PREFIX_GET.length(); 1378 retValue = decapitalize( methodName.substring( pos ) ); 1379 } 1380 1381 //---* Done *---------------------------------------------------------- 1382 return retValue; 1383 } // retrievePropertyName() 1384 1385 /** 1386 * Retrieves the public setter for the property with the given name. If 1387 * not {@null}, the returned value will cause 1388 * {@link #isSetter(Method)} 1389 * to return {@true}. 1390 * 1391 * @param beanClass The class for the getter. 1392 * @param propertyName The name of the property. 1393 * @param propertyType The type of the property. 1394 * @return An instance of 1395 * {@link Optional} 1396 * that holds the setter method; will be empty if there is no public 1397 * setter for the given property on the provided class. 1398 * 1399 * @see #retrieveSetter(Class,String,Class,boolean) 1400 */ 1401 @API( status = STABLE, since = "0.0.5" ) 1402 public static final Optional<Method> retrieveSetter( final Class<?> beanClass, final String propertyName, final Class<?> propertyType ) 1403 { 1404 final var retValue = retrieveSetter( beanClass, propertyName, propertyType, true ); 1405 1406 //---* Done *---------------------------------------------------------- 1407 return retValue; 1408 } // retrieveSetter() 1409 1410 /** 1411 * Retrieves the setter for the property with the given name.<br> 1412 * <br>For some purposes, non-public setters are quite useful; when 1413 * {@code isPublic} is provided as {@false}, this method will also 1414 * return those setters. 1415 * 1416 * @param beanClass The class for the getter. 1417 * @param propertyName The name of the property. 1418 * @param propertyType The type of the property. 1419 * @param isPublic {@true} if the setter is required to be 1420 * public, {@false} otherwise. 1421 * @return An instance of 1422 * {@link Optional} 1423 * that holds the setter method; will be empty if there is no setter 1424 * for the given property on the provided class. 1425 * 1426 * @see #isSetter(Method) 1427 */ 1428 @SuppressWarnings( "AssignmentToNull" ) 1429 @API( status = STABLE, since = "0.0.5" ) 1430 public static final Optional<Method> retrieveSetter( final Class<?> beanClass, final String propertyName, final Class<?> propertyType, final boolean isPublic ) 1431 { 1432 requireNonNullArgument( beanClass, "beanClass" ); 1433 requireNonNullArgument( propertyType, "propertyType" ); 1434 1435 //---* Retrieve a common setter *-------------------------------------- 1436 Method method = null; 1437 if( !isPublic ) 1438 { 1439 try 1440 { 1441 /* 1442 * Class.getDeclaredMethod() will return any method with the 1443 * given name that is declared on the given class, no matter 1444 * whether it is public or private. 1445 */ 1446 method = beanClass.getDeclaredMethod( composeSetterName( propertyName ), propertyType ); 1447 } 1448 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1449 } 1450 1451 /* 1452 * method is null here, either because isPublic() is true, or the 1453 * public (or protected) method was not declared on the given class, 1454 * but inherited from the parent class (or it does not exist at all 1455 * ...) 1456 */ 1457 if( isNull( method ) ) 1458 { 1459 try 1460 { 1461 method = beanClass.getMethod( composeSetterName( propertyName ), propertyType ); 1462 } 1463 catch( final NoSuchMethodException ignored ) { /* Will be deliberately ignored */ } 1464 } 1465 1466 /* 1467 * We found a method with the right name, now have to perform some 1468 * checks on it. 1469 */ 1470 if( nonNull( method ) ) 1471 { 1472 //---* Check the return value *------------------------------------ 1473 if( !method.getReturnType().equals( void.class ) ) 1474 { 1475 method = null; // Wrong return type ... 1476 } 1477 else 1478 { 1479 //---* Check if the method is public and not static *---------- 1480 final var modifier = method.getModifiers(); 1481 if( isStatic( modifier ) ) 1482 { 1483 method = null; // Setters are never static 1484 } 1485 else if( !isPublic( modifier ) ) 1486 { 1487 if( isPublic ) 1488 { 1489 method = null; // It has to be public, but it isn't 1490 } 1491 else 1492 { 1493 /* 1494 * Ensure that the non-public method can be accessed. 1495 */ 1496 method.setAccessible( true ); 1497 } 1498 } 1499 } 1500 } 1501 1502 //---* Compose the return value *-------------------------------------- 1503 final var retValue = Optional.ofNullable( method ); 1504 1505 //---* Done *---------------------------------------------------------- 1506 return retValue; 1507 } // retrieveSetter() 1508 1509 /** 1510 * <p>{@summary Searches the given stack trace for references to the 1511 * method with the given name and returns the name for the respective 1512 * class.}</p> 1513 * <p>This is a helper method for 1514 * {@link #findMainClass()}.</p> 1515 * 1516 * @param stackTrace The stack trace. 1517 * @param methodName The name of the method to look for. 1518 * @return The class name, or {@null} if there is no reference to 1519 * the given method in the stack trace. 1520 */ 1521 @API( status = STABLE, since = "0.0.5" ) 1522 private static final String searchStackTrace( final StackTraceElement [] stackTrace, final String methodName ) 1523 { 1524 assert nonNull( stackTrace ) : "stackTrace is null"; 1525 assert isNotEmpty( methodName ) : "methodName is empty or null"; 1526 1527 String retValue = null; 1528 String foundMethodName; 1529 for( var i = stackTrace.length; (i > 0) && isNull( retValue ); --i ) 1530 { 1531 foundMethodName = stackTrace [i-1].getMethodName(); 1532 if( foundMethodName.equals( methodName ) ) 1533 { 1534 retValue = stackTrace [i-1].getClassName(); 1535 } 1536 } 1537 1538 //---* Done *---------------------------------------------------------- 1539 return retValue; 1540 } // searchStackTrace() 1541 1542 /** 1543 * <p>{@summary Translates the integer value for the modifiers for a 1544 * class, method or field as it is used by reflection to the {@code enum} 1545 * values from 1546 * {@link Modifier}.}</p> 1547 * <p>The modifier 1548 * {@link javax.lang.model.element.Modifier#DEFAULT} 1549 * will not be in the return set as this cannot be retrieved at runtime, 1550 * and the value 1551 * {@link java.lang.reflect.Modifier#INTERFACE} 1552 * does not exist as an {@code enum} value in 1553 * {@link javax.lang.model.element.Modifier}.</p> 1554 * <p>The modifiers {@code sealed} and {@code non-sealed}, belonging to 1555 * the preview feature 'Sealed Classes' are defined in 1556 * {@code javax.lang.model.element.Modifier}, but 1557 * {@link Class#getModifiers()} 1558 * will not return them, and they are not (yet) defined in 1559 * {@link java.lang.reflect.Modifier}. Therefore, they will not appear in 1560 * the return set, too.</p> 1561 * 1562 * @param modifiers The integer value for the modifiers. 1563 * @return The modifier values. 1564 * 1565 * @see javax.lang.model.element.Modifier#NON_SEALED 1566 * @see javax.lang.model.element.Modifier#SEALED 1567 */ 1568 @SuppressWarnings( "OverlyComplexMethod" ) 1569 @API( status = STABLE, since = "0.0.5" ) 1570 public static final Set<Modifier> translateModifiers( final int modifiers ) 1571 { 1572 final Set<Modifier> retValue = EnumSet.noneOf( Modifier.class ); 1573 if( isAbstract( modifiers ) ) retValue.add( ABSTRACT ); 1574 if( isFinal( modifiers ) ) retValue.add( FINAL ); 1575 if( isNative( modifiers ) ) retValue.add( NATIVE ); 1576 if( isPrivate( modifiers ) ) retValue.add( PRIVATE ); 1577 if( isProtected( modifiers ) ) retValue.add( PROTECTED ); 1578 if( isPublic( modifiers ) ) retValue.add( PUBLIC ); 1579 if( isStatic( modifiers ) ) retValue.add( STATIC ); 1580 if( isStrict( modifiers ) ) retValue.add( STRICTFP ); 1581 if( isSynchronized( modifiers ) ) retValue.add( SYNCHRONIZED ); 1582 if( isTransient( modifiers ) ) retValue.add( TRANSIENT ); 1583 if( isVolatile( modifiers ) ) retValue.add( VOLATILE ); 1584 1585 //---* Done *---------------------------------------------------------- 1586 return retValue; 1587 } // translateModifiers() 1588} 1589// class JavaUtils 1590 1591/* 1592 * End of File 1593 */