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 &quot;{@code get}&quot;, 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 &quot;{@code is}&quot; instead
582     *  of &quot;{@code get}&quot;.</p>
583     *  <p>The remaining part of the name after &quot;{@code get}&quot; or
584     *  &quot;{@code is}&quot; 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 &quot;{@code get}&quot;, 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 &quot;{@code is}&quot; instead
659     *  of &quot;{@code get}&quot;.</p>
660     *  <p>The remaining part of the name after &quot;{@code get}&quot; or
661     *  &quot;{@code is}&quot; 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     *  &quot;main&quot;, 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     *  (&quot;{@code _}&quot;), 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 &quot;{@code get}&quot;, followed
1284     *  by the name of the property that they return, with its first letter in
1285     *  uppercase (e.g. for the property &quot;{@code name}&quot;, get getter
1286     *  would be named &quot;{@code getName()}&quot;</li>
1287     *  <li>... may have a name that starts with &quot;{@code is}&quot; instead
1288     *  of &quot;{@code get}&quot; 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 */