1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
package Torello.JavaDoc.Messager;

import java.io.File;

import Torello.Java.StringParse;
import Torello.Java.UnreachableError;
import Torello.Java.C;
import Torello.Java.StorageWriter;

import Torello.JavaDoc.Declaration;

public class PrintRecord
{
    // ********************************************************************************************
    // ********************************************************************************************
    // Constructor & Immutable-Fields ==> Fields set in stone at time of construction
    // ********************************************************************************************
    // ********************************************************************************************


    // This Appendable allows the user to pick a place to store the log-contents to disk, or just
    // about any output-Appendable.  This java.lang.Appendable may be specified/configured with the
    // class Upgrade - using the method: setLogFile
    //
    // NOTE: There are two methods for setting this field.  One allows the user to provide a file
    //       name, and the other allows them to provide any free-formed java.lang.Appendable.
    // 
    // FINAL-IMMUTABLE & **PRIVATE** Instance-Field - Only Used inside this class

    private final Appendable logAppendable;


    // Here is the level of the Verbosity.  The actual printing is done inside class MsgPkgPrivate,
    // and in that method, messages are only printed if they are above or equal to the current
    // Verbosity-Level

    final int verbosityLevel;


    // This just tells whether the above field is null.
    // TRUE => NON-NULL
    // FALSE => NULL
    // NOTE: This one is null

    final boolean hasLogAppendable;


    // Some stupid little thing, which I totally don't completely remember right now.
    // It is used inside the PrintHeading class.
    // Certain Verbosity-Levels necessitate a newline character

    final String VERBOSITY_LEVEL_NEW_LINE;


    // The "StorageWriter" Prints to the Screen.  The String's that it saves inside of it's
    // "Storage", are then send to the 'logAppendable' - if-and-only-if the logAppendable is non
    // null!

    final StorageWriter screenWriterSW = new StorageWriter();


    // This is used OVER-AND-OVER through out the Print-Messager & Print-Heading classes
    // It is "reset" whenever it is finished, rather than destroyed / freed.
    // 
    // This is currently important because it allows the logic to avoid creating and destroying a
    // million little StringBuilder's.  The only other point to "know about" is that StringBuilder
    // wasn't used at all a few months back (except "implicity", since the JDK, internally, uses a 
    // StringBuilder when doing its concatenation routines).
    // 
    // Now that some of the Parse Code is being turned into an external thing, there is the 
    // potential to re-use the messager code.  If that happens, a StringBuilder is used to retrieve
    // the Error-Message Strings, and then shove them into an exception instead!  (Thereby 
    // revering the original premise for having a "Messager" at all, namely avoid exception throws)

    final StringBuilder sb = new StringBuilder();

    // The actual constructor
    PrintRecord(final int verbosityLevel, final Appendable logAppendable)
    {
        this.verbosityLevel             = verbosityLevel;
        this.VERBOSITY_LEVEL_NEW_LINE   = (verbosityLevel < 2) ? "\n" : "";
        this.logAppendable              = logAppendable;
        this.hasLogAppendable           = (logAppendable != null);
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Static-Constants
    // ********************************************************************************************
    // ********************************************************************************************


    private static final byte   mode_FILE_AND_PROC_ONLY         = 1;
    private static final byte   mode_FILE_PROC_AND_DECL         = 2;
    private static final int    indentation_FILE_AND_PROC_ONLY  = 8;
    private static final int    indentation_FILE_PROC_AND_DECL  = 12;

    // These are **ONLY** used in the getter below
    private static final String I8  = StringParse.nChars(' ', 8);
    private static final String I12 = StringParse.nChars(' ', 12);


    // There is a very detail explanation for why "19" and "23" have been chosen, below!
    // 
    // final String fodIndent = " Processing ".length() + fileOrDir.length() + 2;
    // 
    // ==> " Directory " is 11 (9 + 2), " File " is 6 (4 + 2)
    // ==> " Processing " is 12 spaces long
    // ==> 12 + (11 or 6)
    // ==> 23 or 18

    private static final String I18 = StringParse.nChars(' ', 18);
    private static final String I23 = StringParse.nChars(' ', 23);


    // ********************************************************************************************
    // ********************************************************************************************
    // PRIVATE & **MUTABLE** Instance-Fields
    // ********************************************************************************************
    // ********************************************************************************************


    private int errorCount   = 0;
    private int warningCount = 0;

    // The mode it's using
    private byte mode = mode_FILE_AND_PROC_ONLY;

    // This sets the indentation level - as a number of space ' ' characters
    private Integer indentation = indentation_FILE_AND_PROC_ONLY;


    // The "current" Java Doc HTML File being analyzed.  Whenever an error occurs, presuming this
    // field is non-null, it will be printed at the top of the error message.

    private String fileName = null;


    // In this class, there is a method which "sets" the file-name.  This is done many times insde
    // the JDU-Internal classes.  Whenever the file-name is set, there is a second parameter 
    // included in the "set" method that mandates / requires a brief-small description of what the
    // file is also be provided.

    private String fileNameKind = null;

    // This shall be set to ==>   ? " Directory " : " File ";
    private String fileOrDir = null;


    // There is a very detail explanation for why "19" and "23" have been chosen, ABOVE!
    // final String fodIndent = " Processing ".length() + fileOrDir.length() + 2;

    private String fodIndentation = null;


    // The first time that an error or warning is printed, the file-name being "analyzed" must be
    // printed at the top of the message.  All subsequent messges do not need this error
    // header message, AS LONG AS THE SAME FILE IS STILL BEING ANALYZED.
    // 
    // When the Program Control-Flow sends a message that a new File-Name is being analzyed, then
    // the boolean is reset to FALSE immediately!
    // 
    // Whenever this boolean is FALSE, the File-Name being anaylzed has to be printed to any output
    // error-message or warning-messages that are printed to the user's terminal.

    private boolean printedFileNameAlready = false;


    // The "current" declaration about which error messages will be printed (if there are any)
    private Declaration declaration = null;


    // The "EmbedTag" processor treats the "TopDescription" as one of the details section.  It
    // really isn't "a hack" - because the code is so long and the "Top Description" Embed Tag's
    // are the exact same as the "Details" EmbedTag's.  This is also true for the "HiLiteDividers"
    // 
    // This, sort of, "overrides" the "detailSection" boolean.  Since the "Top Description" part of
    // any Java-Doc Web-Page also needs to be analyzed-and-updated, it is altogether possible that
    // that an error-or-warning message for the Top-Description HTML might be generated.
    // 
    // This needs to be handled IN THE EXACT SAME MANNER as error-or-warning messages are handled
    // for the Detail-Sections.  It needes to be printed to the user's terminal before the actual
    // error-message itself!
    //
    // This boolean just says: JDU isn't currently processing **ANY** Detail-Sections, so don't 
    // print the Detail-Section's name, instead please print a friendly message saying
    // that the "Top-Description Segment" of the Java-Doc Web-Page is being processed and analyzed
    // at the moment.

    private boolean topDescriptionAsDecl = false;


    // The first time that an error or warning is printed, the detail-section location must be
    // printed at the top of the message.  All subsequent messges do not need this error
    // header message.

    private boolean printedDeclAlready = false;


    // The purpose of is to - sort of - "Prevent" reprinting the Processor-Name and / or the  
    // Sub-Processor Name (over and over) when the Messager Receives requests to print errors over 
    // and over that are from the exact same Messager-Processor Location.
    // 
    // It looks **REALLY UGLY** to see the exact same, long-winded, Processor Location over and 
    // over for the exact same type of error.

    private Where_Am_I prev_WHERE_AM_I = null;



    // ********************************************************************************************
    // ********************************************************************************************
    // PACKAGE-PRIVATE **GETTERS** - Used in "PrintHeading" 
    // ********************************************************************************************
    // ********************************************************************************************


    boolean     hadErrors()                 { return this.errorCount > 0;                   }
    boolean     hadWarnings()               { return this.warningCount > 0;                 }
    int         getErrorCount()             { return this.errorCount;                       }
    int         getWarningCount()           { return this.warningCount;                     }
    int         getIndentation()            { return this.indentation;                      }
    byte        getMode()                   { return this.mode;                             }
    String      getFileName()               { return this.fileName;                         }
    String      getFileNameKind()           { return this.fileNameKind;                     }
    boolean     alreadyPrintedFileName()    { return this.printedFileNameAlready;           }
    Declaration getDeclaration()            { return this.declaration;                      }
    boolean     topDescriptionAsDecl()      { return this.topDescriptionAsDecl;             }
    boolean     alreadyPrintedDecl()        { return this.printedDeclAlready;               }
    boolean     modeFileAndProc()           { return this.mode == mode_FILE_AND_PROC_ONLY;  }
    boolean     modeFileProcAndDecl()       { return this.mode == mode_FILE_PROC_AND_DECL;  }
    String      isFileOrDir()               { return this.fileOrDir;                        }
    String      fodIndentation()            { return this.fodIndentation;                   }

    boolean whereAmIChanged(final Where_Am_I WHERE_AM_I)
    { return this.prev_WHERE_AM_I == WHERE_AM_I; }

    String getIndentationStr()
    {
        if (this.indentation == 8)  return I8;
        if (this.indentation == 12) return I12;

        throw new UnreachableError();
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Some simple setters
    // ********************************************************************************************
    // ********************************************************************************************


    void printingFileNameRightNow()
    { this.printedFileNameAlready = true; }

    void printingFirstDeclRightNow()
    { this.printedDeclAlready = true; }

    int incErrorCountAndCheck()
    {
        if (++this.errorCount > 25) throw new ReachedMaxErrors();
        else                        return this.errorCount;
    }

    int incWarningCount()
    { return ++this.warningCount;   }

    void setPreviouseWhereAmI(final Where_Am_I WHERE_AM_I)
    { this.prev_WHERE_AM_I = WHERE_AM_I;    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Messager Control Setters
    // ********************************************************************************************
    // ********************************************************************************************


    void setCurrentFileName(final String fName, final String fNameKind)
    {
        final boolean b = fName.endsWith(File.separator);

        this.fileNameKind           = fNameKind;
        this.fileName               = fName;
        this.fileOrDir              = b ? " Directory " : " File ";
        this.fodIndentation         = b ? I18 : I23;
        this.printedFileNameAlready = false;
        this.indentation            = indentation_FILE_AND_PROC_ONLY;
        this.mode                   = mode_FILE_AND_PROC_ONLY;

        // Also clear the detail section, and the entity
        this.declaration        = null;
        this.printedDeclAlready = false;
    }

    void setTopDescriptionSection()
    {
        this.declaration            = null;
        this.printedDeclAlready     = false;
        this.topDescriptionAsDecl   = true;
        this.indentation            = indentation_FILE_PROC_AND_DECL;
        this.mode                   = mode_FILE_PROC_AND_DECL;
    }

    void setDeclaration(final Declaration declaration)
    {
        this.declaration            = declaration;
        this.printedDeclAlready     = false;
        this.topDescriptionAsDecl   = false;
        this.indentation            = indentation_FILE_PROC_AND_DECL;
        this.mode                   = mode_FILE_PROC_AND_DECL;
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Do Something
    // ********************************************************************************************
    // ********************************************************************************************


    void writeSBToScreen() 
    { 
        this.screenWriterSW.print(this.sb.toString());
        this.sb.setLength(0);
    }

    void checkPointLog()
    {
        final String textAlreadyWrittenToScreen = C
            .toHTML(this.screenWriterSW.getString(), true, true, true)
            .replace("\t", "        ");

        this.screenWriterSW.erase();

        try
            { this.logAppendable.append(textAlreadyWrittenToScreen); }

        catch (Exception e)
        {
            this.setCurrentFileName("User Provided Log", null);

            Messager.assertFail(
                e,
                "Exception thrown while attempting to write to User-Provided Appendable-Log\n" +
                "The Appendable which was provided to the class 'Torello.JavaDoc.Upgrade' has " +
                "thrown an Exception while attempting to write to it.",
                JDUMessager.PrintRecord
            );
        }
    }
}