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