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