001package Torello.JavaDoc.Messager;
002
003import Torello.Java.StrIndent;
004import Torello.JavaDoc.Declaration;
005import Torello.JavaDoc.ReflHTML;
006import Torello.Java.C;
007
008import static Torello.Java.C.RESET;
009import static Torello.Java.C.BWHITE;
010import static Torello.Java.C.RED_BKGND;
011
012
013import java.util.Vector;
014
015// A replacement for <CODE>System&#46;out, System&#46;err</CODE> and even the StorageWriter
016// class used to send error messages to the terminal about upgrade progress.
017
018/**
019 * The Messager class is the top-level interface for emitting structured messages, warnings, and
020 * assertion failures to the user terminal. It exposes a static API (e.g.,
021 * {@code Messager.userError(...)}) that can be called from anywhere in the application without
022 * requiring an explicit instance.
023 * 
024 * <EMBED CLASS='external-html' DATA-FILE-ID=MSGR_THREAD_LOCAL>
025 * <EMBED CLASS='external-html' DATA-FILE-ID=MSGR_PARAM_TABLE>
026 */
027public class Messager
028{
029    /**
030     * When {@code TRUE}, requests that the stack trace be printed to terminl output for any
031     * Assertion-Failure Error-Message which contains an Exception-Throw
032     */
033    public static boolean DEFAULT_SHOW_STACK_TRACE_ON_ASSERT_FAIL = true;
034
035
036    // ********************************************************************************************
037    // ********************************************************************************************
038    // Scoped-Values / ThreadLocal-Instance & Constructor
039    // ********************************************************************************************
040    // ********************************************************************************************
041
042
043    private static final java.lang.ThreadLocal<Messager> tlInstance =
044        new java.lang.ThreadLocal<>();
045
046    /**
047     * Initializes the {@code ThreadLocal<Messager>} instance for the current thread.
048     * 
049     * <BR /><BR />
050     * This method must be called exactly once per thread, before any of the static
051     * {@link Messager} printing methods (such as {@code userError},
052     * {@code assertionFailure}, etc.) are invoked. It constructs and wires together the internal
053     * helper classes {@link PrintRecord}, {@link PrintHeading}, and {@link PrintMessage}, and
054     * binds the resulting {@code Messager} object to the current thread using
055     * {@code ThreadLocal.set()}.
056     *
057     * <BR /><BR />
058     * This design avoids the need to explicitly pass around {@code Messager} instances
059     * across the application and enables safe, isolated message formatting per thread.
060     * This is particularly useful for tools that may one day process multiple source files
061     * or build artifacts in parallel (e.g., future parallelization of the JDU tool).
062     *
063     * <BR /><BR />
064     * This method must not be called more than once on the same thread. Doing so will result
065     * in an {@code InternalError}, as re-initialization would overwrite the existing
066     * {@code Messager} instance and compromise internal state.
067     * 
068     * @param verbosityLevel The verbosity level for message output (must be between 0 and 3
069     * inclusive).  Higher levels enable more verbose diagnostic output.
070     *
071     * @param logAppendable The {@code Appendable} to which log output should be written.  May be
072     * {@code null} if no log file output is needed.  If so, Output-Text is not saved to disk.
073     *
074     * @throws InternalError if this method is called more than once per thread and different
075     * assigned values to {@code 'verbosityLevel'} or {@code 'logAppendable'} are provided on the
076     * separate invocations.
077     * 
078     * <BR /><BR />This {@code Error} will also throw if the {@code verbosityLevel} is out of range
079     */
080    public static void initializeThreadLocalInstance
081        (final int verbosityLevel, final Appendable logAppendable)
082    {
083        Messager m = tlInstance.get();
084
085
086        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
087        // Newer Addition: This method may be invoked multiple times
088        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
089        // 
090        // In order to facilitate calling some of the JDU's Features before the Upgrader has 
091        // started running - which in the partiular case that forced this new addition was 
092        // handling an Error by the File-HiLiter inside the "MoreHiliting" Class - the Messager 
093        // has to be initialized before the Upgrader even starts it's primary "upgrade()" Method.
094        // 
095        // The default solution is to allow this method to be called as many times as are needed,
096        // with the minor caveat that if the input values change in any way shape or form, then
097        // an InternalError shall throw.
098
099        if (m != null)
100        {
101            final boolean eqV = (m.printRecord.verbosityLevel == verbosityLevel);
102            final boolean eqL = m.printRecord.checkEqualsLogAppendable(logAppendable);
103
104            if (eqV && eqL) return;
105
106            final String vStr = eqV
107                ? ""
108                :   "The input Verbosity-Level [" + verbosityLevel + "], does not equal the " +
109                    "previously assigned Verbosity-Level [" + m.printRecord.verbosityLevel + "]\n";
110
111            final String lStr = eqL
112                ? ""
113                :   "The input Log-Appendable is a different instance / reference Object than " +
114                    "the previously assigned Log-Appendable.\n";
115
116            throw new InternalError(
117                "Messager.initializeThreadLocalInstance(int, Appendable) has been called " +
118                "multiple times.  On this invocation:\n" + vStr + lStr
119            );
120        }
121
122        if ((verbosityLevel < 0) || (verbosityLevel > 3)) throw new InternalError(
123            "verbosityLevel=" + verbosityLevel + ", " +
124            "but it must be between 0 and 3."
125        );
126
127        final PrintRecord   printRecord     = new PrintRecord(verbosityLevel, logAppendable);
128        final PrintHeading  printHeading    = new PrintHeading(printRecord);
129        final PrintMessage  printMessage    = new PrintMessage(printRecord, printHeading);
130        final Messager      messager        = new Messager(printRecord, printMessage);
131
132        tlInstance.set(messager);
133    }
134
135
136    // ********************************************************************************************
137    // ********************************************************************************************
138    // Constructor & Fields
139    // ********************************************************************************************
140    // ********************************************************************************************
141
142
143    private final PrintRecord   printRecord;
144    private final PrintMessage  printMessage;
145
146
147    private Messager(final PrintRecord printRecord, final PrintMessage printMessage)
148    {
149        this.printRecord    = printRecord;
150        this.printMessage   = printMessage;
151    }
152
153
154    // ********************************************************************************************
155    // ********************************************************************************************
156    // Simple print / println ... and one "warning" Printer-Method
157    // ********************************************************************************************
158    // ********************************************************************************************
159
160
161    public static void print(final String s)
162    {
163        final Messager m = tlInstance.get();
164        if (m.printRecord.verbosityLevel >= 2) m.printRecord.screenWriterSW.print(s);
165    }
166
167    public static void println(final String s)
168    {
169        final Messager m = tlInstance.get();
170        if (m.printRecord.verbosityLevel >= 2) m.printRecord.screenWriterSW.println(s);
171    }
172
173    public static void println()
174    {
175        final Messager m = tlInstance.get();
176        if (m.printRecord.verbosityLevel >= 2) m.printRecord.screenWriterSW.println();
177    }
178
179    public static void printQuiet(final String s)
180    {
181        final Messager m = tlInstance.get();
182        if (m.printRecord.verbosityLevel == 1) m.printRecord.screenWriterSW.print(s);
183    }
184
185    public static void printSilent(final boolean dotOrNewLine)
186    {
187        final Messager m = tlInstance.get();
188
189        if (m.printRecord.verbosityLevel == 0)
190            m.printRecord.screenWriterSW.print(dotOrNewLine ? '.' : '\n');
191    }
192
193    public static void ifVerboseAppendVerbose()
194    {
195        final Messager m = tlInstance.get();
196
197        if (m.printRecord.verbosityLevel == 3)
198            m.printRecord.screenWriterSW.print(MsgVerbose.getAndClear());
199    }
200
201    public static void warning(final String message, final Where_Am_I WHERE_AM_I)
202    {
203        final Messager m = tlInstance.get();
204
205        m.printMessage.WARNING(message, WHERE_AM_I);
206        m.printRecord.writeSBToScreen();
207    }
208
209
210    // ********************************************************************************************
211    // ********************************************************************************************
212    // Fatal-Assert: Throws to Stop Program-Execution Immediatley
213    // ********************************************************************************************
214    // ********************************************************************************************
215
216
217    public static Error assertFail(final String message, final Where_Am_I WHERE_AM_I)
218    {
219        final Messager m = tlInstance.get();
220
221        m.printMessage.ERROR(
222            message,
223            null,   // signature
224            true,   // FATAL
225            WHERE_AM_I
226        );
227
228        m.printRecord.writeSBToScreen();
229        throw new AssertFail();
230    }
231
232    public static Error assertFail
233        (final String message, final String signature, final Where_Am_I WHERE_AM_I)
234    {
235        final Messager m = tlInstance.get();
236
237        m.printMessage.ERROR(
238            message,
239            signature,
240            true, // FATAL
241            WHERE_AM_I
242        );
243
244        m.printRecord.writeSBToScreen();
245        throw new AssertFail();
246    }
247
248    public static Error assertFail
249        (final Throwable t, final String message, final Where_Am_I WHERE_AM_I)
250    {
251        final Messager m = tlInstance.get();
252
253        m.printMessage.ERROR(
254            t,
255            message,
256            null,   // signature
257            true,   // FATAL
258            DEFAULT_SHOW_STACK_TRACE_ON_ASSERT_FAIL,
259            WHERE_AM_I
260        );
261
262        m.printRecord.writeSBToScreen();
263        throw new AssertFail();
264    }
265
266    public static Error assertFail(
267            final Throwable     t,
268            final String        message,
269            final String        signature,
270            final Where_Am_I    WHERE_AM_I
271        )
272    {
273        final Messager m = tlInstance.get();
274
275        m.printMessage.ERROR(
276            t,
277            message,
278            signature,
279            true,   // FATAL
280            DEFAULT_SHOW_STACK_TRACE_ON_ASSERT_FAIL,
281            WHERE_AM_I
282        );
283
284        m.printRecord.writeSBToScreen();
285        throw new AssertFail();
286    }
287
288
289    // ********************************************************************************************
290    // ********************************************************************************************
291    // Non-Fatal Assert: Error, but it doesn't need to stop immediately
292    // ********************************************************************************************
293    // ********************************************************************************************
294
295
296    public static void assertFailContinue(final String message, final Where_Am_I WHERE_AM_I)
297    {
298        final Messager m = tlInstance.get();
299
300        m.printMessage.ERROR(
301            message,
302            null,   // signature
303            false,  // NON-FATAL
304            WHERE_AM_I
305        );
306
307        m.printRecord.writeSBToScreen();
308    }
309
310    public static void assertFailContinue
311        (final Throwable t, final String message, final Where_Am_I WHERE_AM_I)
312    {
313        final Messager m = tlInstance.get();
314
315        m.printMessage.ERROR(
316            t,
317            message,
318            null,   // signature
319            false,  // NON-FATAL
320            false,  // Don't Show Stack-Trace
321            WHERE_AM_I
322        );
323
324        m.printRecord.writeSBToScreen();
325    }
326
327
328    // ********************************************************************************************
329    // ********************************************************************************************
330    // Fatal-User-Error: Throws to Stop Program-Execution Immediatley
331    // ********************************************************************************************
332    // ********************************************************************************************
333
334
335    public static Error userErrorHalt(final String message, final Where_Am_I WHERE_AM_I)
336    {
337        final Messager m = tlInstance.get();
338
339        m.printMessage.ERROR(
340            message,
341            null,       // signature    
342            true,       // FATAL
343            WHERE_AM_I
344        );
345
346        m.printRecord.writeSBToScreen();
347        throw new MessagerGeneratedError();
348    }
349
350    public static Error userErrorHalt
351        (final Throwable t, final String message, final Where_Am_I WHERE_AM_I)
352    {
353        final Messager m = tlInstance.get();
354
355        m.printMessage.ERROR(
356            t,          // Exception that was thrown
357            message,    // THE MESSAGE
358            null,       // signature    
359            true,       // FATAL
360            true,       // Show Stack-Trace
361            WHERE_AM_I
362        );
363
364        m.printRecord.writeSBToScreen();
365        throw new MessagerGeneratedError();
366    }
367
368
369    // ********************************************************************************************
370    // ********************************************************************************************
371    // User-Error: Program-Flow Execution Continues
372    // ********************************************************************************************
373    // ********************************************************************************************
374
375
376    public static void userErrorContinue(final String message, final Where_Am_I WHERE_AM_I)
377    {
378        final Messager m = tlInstance.get();
379
380        m.printMessage.ERROR(
381            message,
382            null,   // signature
383            false,  // NON-FATAL
384            WHERE_AM_I
385        );
386
387        m.printRecord.writeSBToScreen();
388    }
389
390    public static void userErrorContinue
391        (final String message, final String signature, final Where_Am_I WHERE_AM_I)
392    {
393        final Messager m = tlInstance.get();
394
395        m.printMessage.ERROR(
396            message,
397            signature,
398            false, // NON-FATAL
399            WHERE_AM_I
400        );
401
402        m.printRecord.writeSBToScreen();
403    }
404
405    public static void userErrorContinue
406        (final Throwable t, final String message, final Where_Am_I WHERE_AM_I)
407    {
408        final Messager m = tlInstance.get();
409
410        m.printMessage.ERROR(
411            t,
412            message,
413            null,   // signature
414            false,  // NON-FATAL
415            false,  // Don't Show Stack-Trace
416            WHERE_AM_I
417        );
418
419        m.printRecord.writeSBToScreen();
420    }
421
422    public static void userErrorContinueST
423        (final Throwable t, final String message, final Where_Am_I WHERE_AM_I)
424    {
425        final Messager m = tlInstance.get();
426
427        m.printMessage.ERROR(
428            t,
429            message,
430            null,   // signature
431            false,  // NON-FATAL
432            true,   // SHOW STACK TRACE
433            WHERE_AM_I
434        );
435
436        m.printRecord.writeSBToScreen();
437    }
438
439
440    // ********************************************************************************************
441    // ********************************************************************************************
442    // The Old Class "MsgControl"
443    // ********************************************************************************************
444    // ********************************************************************************************
445
446
447    public static void setCurrentFileName(final String fName, final String fNameKind)
448    {
449        final Messager m = tlInstance.get();
450        m.printRecord.setCurrentFileName(fName, fNameKind);
451    }
452
453    public static void setTopDescriptionSection()
454    {
455        final Messager m = tlInstance.get();
456        m.printRecord.setTopDescriptionSection();
457    }
458
459    public static void setDeclaration(final Declaration declaration)
460    {
461        final Messager m = tlInstance.get();
462        m.printRecord.setDeclaration(declaration);
463    }
464
465    public static boolean hadErrors()
466    {
467        final Messager m = tlInstance.get();
468        return m.printRecord.hadErrors();
469    }
470
471    public static void ifLogCheckPointLog()
472    {
473        final Messager m = tlInstance.get();
474        if (! m.printRecord.hasLogAppendable) return;
475        m.printRecord.checkPointLog();
476    }
477
478}