001/*
002 * ============================================================================
003 *  Copyright © 2002-2024 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.lang;
019
020import static java.lang.Boolean.getBoolean;
021import static java.lang.String.format;
022import static java.lang.System.out;
023import static java.lang.Thread.currentThread;
024import static java.nio.charset.Charset.defaultCharset;
025import static org.apiguardian.api.API.Status.STABLE;
026import static org.tquadrat.foundation.lang.CommonConstants.PROPERTY_IS_DEBUG;
027import static org.tquadrat.foundation.lang.CommonConstants.PROPERTY_IS_TEST;
028import static org.tquadrat.foundation.lang.Objects.nonNull;
029import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
030import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
031
032import java.io.ByteArrayOutputStream;
033import java.io.PrintStream;
034import java.util.Optional;
035import java.util.function.BooleanSupplier;
036import java.util.function.Function;
037import java.util.function.Supplier;
038
039import org.apiguardian.api.API;
040import org.tquadrat.foundation.annotation.ClassVersion;
041import org.tquadrat.foundation.annotation.UtilityClass;
042import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError;
043
044/**
045 *  <p>{@summary Some functions for DEBUG and TEST output to the console.}</p>
046 *
047 *  @version $Id: DebugOutput.java 1084 2024-01-03 15:31:20Z tquadrat $
048 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
049 *  @UMLGraph.link
050 *  @since 0.1.0
051 */
052@UtilityClass
053@ClassVersion( sourceVersion = "$Id: DebugOutput.java 1084 2024-01-03 15:31:20Z tquadrat $" )
054@API( status = STABLE, since = "0.1.0" )
055public final class DebugOutput
056{
057        /*------------*\
058    ====** Attributes **=======================================================
059        \*------------*/
060    /**
061     *  The printer.
062     */
063    @SuppressWarnings( "UseOfSystemOutOrSystemErr" )
064    private static Printer m_Printer = out::printf;
065
066        /*------------------------*\
067    ====** Static Initialisations **===========================================
068        \*------------------------*/
069    /**
070     *  The DEBUG flag.
071     *
072     *  @see org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_DEBUG
073     */
074    private static final boolean m_IsDebug;
075
076    /**
077     *  The TEST flag.
078     *
079     *  @see org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_TEST
080     */
081    private static final boolean m_IsTest;
082
083    static
084    {
085        m_IsDebug = getBoolean( PROPERTY_IS_DEBUG );
086        m_IsTest = getBoolean( PROPERTY_IS_TEST );
087    }
088
089        /*--------------*\
090    ====** Constructors **=====================================================
091        \*--------------*/
092    /**
093     *  No instance is allowed for this class!
094     */
095    private DebugOutput() { throw new PrivateConstructorForStaticClassCalledError( DebugOutput.class ); }
096
097        /*---------*\
098    ====** Methods **==========================================================
099        \*---------*/
100    /**
101     *  <p>{@summary This method will find the caller for the method that is
102     *  identified by its name and class, and returns the appropriate stack
103     *  trace element.}</p>
104     *  <p>The return value is
105     *  {@linkplain Optional#empty() empty}
106     *  when the provided method is not on the stack trace.</p>
107     *
108     *  @param  methodName  The name of the method that we need the caller for.
109     *  @param  owningClass The class for the called method.
110     *  @return An instance of
111     *      {@link Optional}
112     *      that holds the stack trace element for the caller.
113     */
114    @API( status = STABLE, since = "0.1.0" )
115    public static final Optional<StackTraceElement> findCaller( final String methodName, final Class<?> owningClass )
116    {
117        requireNotEmptyArgument( methodName, "methodName" );
118        final var className = requireNonNullArgument( owningClass, "owningClass" ).getName();
119
120        //---* Retrieve the stack *--------------------------------------------
121        final var stackTraceElements = currentThread().getStackTrace();
122        final var len = stackTraceElements.length;
123
124        //---* Search the stack *----------------------------------------------
125        Optional<StackTraceElement> retValue = Optional.empty();
126        FindLoop: for( var i = 0; (i < len) && retValue.isEmpty(); ++i )
127        {
128            /*
129             * This loop searches the stack until it will find the entry for
130             * that matches that for the provided arguments on it. It assumes
131             * then that the next entry on the stack will belong to the caller
132             * for this method.
133             */
134            if( className.equals( stackTraceElements [i].getClassName() ) && methodName.equals( stackTraceElements [i].getMethodName() ) )
135            {
136                if( i + 1 < len ) retValue = Optional.of( stackTraceElements [i + 1] );
137            }
138        }   //  FindLoop:
139
140        //---* Done *----------------------------------------------------------
141        return retValue;
142    }   //  findCaller()
143
144    /**
145     *  If the
146     *  {@linkplain System#getProperty(String) System property}
147     *  {@value org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_DEBUG}
148     *  is set, call the specified
149     *  {@link Printer}
150     *  to write the given message.
151     *
152     *  @param  message The message; it is a format as defined for
153     *      {@link java.util.Formatter}.
154     *  @param  args    Optional argument for the {@code supplier}.
155     *
156     *  @see #setPrinter(Printer)
157     */
158    @API( status = STABLE, since = "0.1.0" )
159    public static final void ifDebug( final String message, final Object... args )
160    {
161        if( m_IsDebug && nonNull( message ) && !message.isBlank() )
162        {
163            findCaller( "ifDebug", DebugOutput.class )
164                .ifPresentOrElse( stackTraceElement -> m_Printer.printf( "DEBUG - %2$s: %1$s%n", format( message, args ), stackTraceElement.toString() ),
165                    () -> m_Printer.printf( "DEBUG: %s%n", format( message, args ) ) );
166        }
167    }   //  ifDebug()
168
169    /**
170     *  If the
171     *  {@linkplain System#getProperty(String) System property}
172     *  {@value org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_DEBUG}
173     *  is set, call the specified
174     *  {@link Printer}
175     *  to write the given message.
176     *
177     *  @param  supplier    The {@code Function} for the output.
178     *  @param  args    Optional argument for the {@code supplier}.
179     *
180     *  @see #setPrinter(Printer)
181     */
182    @API( status = STABLE, since = "0.1.0" )
183    public static final void ifDebug( final Function<Object [],String> supplier, final Object... args )
184    {
185        if( m_IsDebug )
186        {
187            final var message = requireNonNullArgument( supplier, "supplier" )
188                .apply( requireNonNullArgument( args, "args" ) );
189            if( nonNull( message ) && !message.isBlank() )
190            {
191                findCaller( "ifDebug", DebugOutput.class )
192                    .ifPresentOrElse( stackTraceElement -> m_Printer.printf( "DEBUG - %2$s: %1$s%n", message, stackTraceElement.toString() ),
193                        () -> m_Printer.printf( "DEBUG: %s%n", message ) );
194            }
195        }
196    }   //  ifDebug()
197
198    /**
199     *  If the
200     *  {@linkplain System#getProperty(String) System property}
201     *  {@value org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_DEBUG}
202     *  is set and the given
203     *  {@link BooleanSupplier condition}
204     *  resolves to {@code true}, execute the given
205     *  {@link Function}
206     *  and call the specified
207     *  {@link Printer}
208     *  to write result.
209     *
210     *  @param  condition   Only if {@code true}, there will be an output.
211     *  @param  supplier    The {@code Supplier} for the output.
212     *  @param  args    Optional argument for the {@code supplier}.
213     *
214     *  @see #setPrinter(Printer)
215     */
216    @API( status = STABLE, since = "0.1.0" )
217    public static final void ifDebug( final BooleanSupplier condition, final Function<Object [],String> supplier, final Object... args )
218    {
219        if( m_IsDebug && requireNonNullArgument( condition, "condition" ).getAsBoolean() )
220        {
221            final var message = requireNonNullArgument( supplier, "supplier" ).apply( requireNonNullArgument( args, "args" ) );
222            if( nonNull( message ) && !message.isBlank() )
223            {
224                findCaller( "ifDebug", DebugOutput.class )
225                    .ifPresentOrElse( stackTraceElement -> m_Printer.printf( "DEBUG - %2$s: %1$s%n", message, stackTraceElement.toString() ),
226                        () -> m_Printer.printf( "DEBUG: %s%n", message ) );
227            }
228        }
229    }   //  debugOutput()
230
231    /**
232     *  If the
233     *  {@linkplain System#getProperty(String) System property}
234     *  {@value org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_DEBUG}
235     *  is set and the given condition resolves to {@code true}, execute the
236     *  given
237     *  {@link Function}
238     *  and call the specified
239     *  {@link Printer}
240     *  to write the result.
241     *
242     *  @param  condition   Only if {@code true}, there will be an output.
243     *  @param  supplier    The {@code Supplier} for the output.
244     *  @param  args    Optional argument for the {@code supplier}.
245     *
246     *  @see #setPrinter(Printer)
247     */
248    @API( status = STABLE, since = "0.1.0" )
249    public static final void ifDebug( final boolean condition, final Function<Object [],String> supplier, final Object... args )
250    {
251        if( m_IsDebug && condition )
252        {
253            final var message = requireNonNullArgument( supplier, "supplier" ).apply( requireNonNullArgument( args, "args" ) );
254            if( nonNull( message ) && !message.isBlank() )
255            {
256                findCaller( "ifDebug", DebugOutput.class )
257                    .ifPresentOrElse( stackTraceElement -> m_Printer.printf( "DEBUG - %2$s: %1$s%n", message, stackTraceElement.toString() ),
258                        () -> m_Printer.printf( "DEBUG: %s%n", message ) );
259            }
260        }
261    }   //  ifDebug()
262
263    /**
264     *  <p>{@summary If the
265     *  {@linkplain System#getProperty(String) System property}
266     *  {@value CommonConstants#PROPERTY_IS_DEBUG}
267     *  is set, a call to
268     *  {@link Throwable#printStackTrace(PrintStream) e.printStackTrace()}
269     *  is made.}</p>
270     *  <p>Use this to provide a view to an otherwise ignored exception.</p>
271     *
272     *  @param  e   The exception.
273     *
274     *  @see #setPrinter(Printer)
275     */
276    @API( status = STABLE, since = "0.1.0" )
277    public static final void ifDebug( final Throwable e )
278    {
279        if( m_IsDebug && nonNull( e ) )
280        {
281            m_Printer.print( "DEBUG: " );
282            final var bos = new ByteArrayOutputStream();
283            e.printStackTrace( new PrintStream( bos, true, defaultCharset() ) );
284            m_Printer.println( bos.toString() );
285        }
286    }   //  ifDebug()
287
288    /**
289     *  If the
290     *  {@linkplain System#getProperty(String) System property}
291     *  {@value org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_TEST}
292     *  is set, call the specified
293     *  {@link Printer}
294     *  to write the given message.
295     *
296     *  @param  message The message; it is a format as defined for
297     *      {@link java.util.Formatter}.
298     *  @param  args    Optional argument for the {@code supplier}.
299     *
300     *  @see #setPrinter(Printer)
301     */
302    @API( status = STABLE, since = "0.1.0" )
303    public static final void ifTest( final String message, final Object... args )
304    {
305        if( m_IsTest && nonNull( message ) && !message.isBlank() )
306        {
307            findCaller( "ifTest", DebugOutput.class )
308                .ifPresentOrElse( stackTraceElement -> m_Printer.printf( "TEST - %2$s: %1$s%n", format( message, args ), stackTraceElement ),
309                    () -> m_Printer.printf( "TEST: %s%n", format( message, args ) ) );
310        }
311    }   //  ifDebug()
312
313    /**
314     *  If the
315     *  {@linkplain System#getProperty(String) System property}
316     *  {@value org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_TEST}
317     *  is set, execute the given
318     *  {@link Function}
319     *  and call the specified
320     *  {@link Printer}
321     *  to write the result.
322     *
323     *  @param  supplier    The {@code Supplier} for the output.
324     *  @param  args    Optional argument for the {@code supplier}.
325     *
326     *  @see #setPrinter(Printer)
327     */
328    @API( status = STABLE, since = "0.1.0" )
329    public static final void ifTest( final Function<Object [],String> supplier, final Object... args )
330    {
331        if( m_IsTest )
332        {
333            final var message = requireNonNullArgument( supplier, "supplier" ).apply( requireNonNullArgument( args, "args" ) );
334            if( nonNull( message ) && !message.isBlank() )
335            {
336                findCaller( "ifTest", DebugOutput.class )
337                    .ifPresentOrElse( stackTraceElement -> m_Printer.printf( "TEST - %2$s: %1$s%n", message, stackTraceElement.toString() ),
338                        () -> m_Printer.printf( "TEST: %s%n", message ) );
339            }
340        }
341    }   //  ifTest()
342
343    /**
344     *  If the
345     *  {@linkplain System#getProperty(String) System property}
346     *  {@value org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_TEST}
347     *  is set and the given
348     *  {@link BooleanSupplier condition}
349     *  resolves to {@code true}, execute the given
350     *  {@link Function}
351     *  and call the specified
352     *  {@link Printer}
353     *  to write the result.
354     *
355     *  @param  condition   Only if {@code true}, there will be an output.
356     *  @param  supplier    The {@code Supplier} for the output.
357     *  @param  args    Optional argument for the {@code supplier}.
358     *
359     *  @see #setPrinter(Printer)
360     */
361    @API( status = STABLE, since = "0.1.0" )
362    public static final void ifTest( final BooleanSupplier condition, final Function<Object [],String> supplier, final Object... args )
363    {
364        if( m_IsDebug && requireNonNullArgument( condition, "condition" ).getAsBoolean() )
365        {
366            final var message = requireNonNullArgument( supplier, "supplier" ).apply( requireNonNullArgument( args, "args" ) );
367            if( nonNull( message ) && !message.isBlank() )
368            {
369                findCaller( "ifTest", DebugOutput.class )
370                    .ifPresentOrElse( stackTraceElement -> m_Printer.printf( "TEST - %2$s: %1$s%n", message, stackTraceElement.toString() ),
371                        () -> m_Printer.printf( "TEST: %s%n", message ) );
372            }
373        }
374    }   //  ifTest()
375
376    /**
377     *  If the
378     *  {@linkplain System#getProperty(String) System property}
379     *  {@value org.tquadrat.foundation.lang.CommonConstants#PROPERTY_IS_TEST}
380     *  is set and the given condition resolves to {@code true}, execute the
381     *  given
382     *  {@link Supplier}
383     *  and call the specified
384     *  {@link Printer}
385     *  to write the result.
386     *
387     *  @param  condition   Only if {@code true}, there will be an output.
388     *  @param  supplier    The {@code Supplier} for the output.
389     *  @param  args    Optional argument for the {@code supplier}.
390     *
391     *  @see #setPrinter(Printer)
392     */
393    @API( status = STABLE, since = "0.1.0" )
394    public static final void ifTest( final boolean condition, final Function<Object [],String> supplier, final Object... args )
395    {
396        if( m_IsDebug && condition )
397        {
398            final var message = requireNonNullArgument( supplier, "supplier" ).apply( requireNonNullArgument( args, "args" ) );
399            if( nonNull( message ) && !message.isBlank() )
400            {
401                findCaller( "ifTest", DebugOutput.class )
402                    .ifPresentOrElse( stackTraceElement -> m_Printer.printf( "TEST - %2$s: %1$s%n", message, stackTraceElement.toString() ),
403                        () -> m_Printer.printf( "TEST: %s%n", message ) );
404            }
405        }
406    }   //  ifTest()
407
408    /**
409     *  <p>{@summary If the
410     *  {@linkplain System#getProperty(String) System property}
411     *  {@value CommonConstants#PROPERTY_IS_TEST}
412     *  is set, a call to
413     *  {@link Throwable#printStackTrace(PrintStream) e.printStackTrace()}
414     *  is made.}</p>
415     *  <p>Use this to get a view to an otherwise ignored exception.</p>
416     *
417     *  @param  e   The exception.
418     *
419     *  @see #setPrinter(Printer)
420     */
421    @API( status = STABLE, since = "0.1.0" )
422    public static final void ifTest( final Throwable e )
423    {
424        if( m_IsDebug && nonNull( e ) )
425        {
426            m_Printer.print( "TEST: " );
427            final var bos = new ByteArrayOutputStream();
428            e.printStackTrace( new PrintStream( bos, true, defaultCharset() ) );
429            m_Printer.println( bos.toString() );
430        }
431    }   //  ifTest()
432
433    /**
434     *  Returns the DEBUG flag.
435     *
436     *  @return {@code true} if the DEBUG flag is set, {@code false} otherwise.
437     *
438     *  @see CommonConstants#PROPERTY_IS_DEBUG
439     */
440    public static final boolean isDebug() { return m_IsDebug; }
441
442    /**
443     *  Returns the TEST flag.
444     *
445     *  @return {@code true} if the TEST flag is set, {@code false} otherwise.
446     *
447     *  @see CommonConstants#PROPERTY_IS_TEST
448     */
449    public static final boolean isTest() { return m_IsTest; }
450
451    /**
452     *  <p>{@summary Assigns the
453     *  {@link Printer}
454     *  for the DEBUG/TEST output.}</p>
455     *  <p>The default implementation writes to
456     *  {@link System#out}.</p>
457     *
458     *  @param  printer The printer
459     */
460    public static final void setPrinter( final Printer printer )
461    {
462        m_Printer = requireNonNullArgument( printer, "printer" );
463    }   //  setPrinter()
464}
465//  class DebugOutput
466
467/*
468 *  End of File
469 */