5
\$\begingroup\$

This is basically a logger which I use/wrote. It has convenient features like including one-line stack traces with print statements, making the text easier to find, and piping all output to either standard out or standard error to make it so that no print statements will be out of order.

This is part of my LogAppTester. I am looking for any reviews or suggestions.

package Utilities;

import java.io.*
import java.text.*
import java.util.*

/**
 * Use this for various testing/debugging purposes including multi-threaded
 * print statements with built in stack trace, assertions that stop the entire
 * application rather than just the current thread, and application termination
 * with a full stack trace for terminal error conditions. Can also check and
 * respond to background events.
 *
 * @author johnmichaelreed2
 */
public class AppTester {

    // <editor-fold defaultstate="collapsed" desc="Enums">
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Vars, Getters, and Setters">

    /**
     * The system independent line separator, shortened to two letters for
     * convenience.
     */
    public static final String ls = System.getProperty("line.separator");
    static {
        AppTester.check(ls != null);
    }

    /**
     * The number of important rows in a stack trace.
     */
    private static int numberOfRowsIDisplayInStackTraces_ = 6;

    public static int getNumberOfRowsIDisplayInStackTraces_() {
        return numberOfRowsIDisplayInStackTraces_;
    }

    public static void setNumberOfRowsIDisplayInStackTraces_(int aNumberOfRowsIDisplayInStackTraces_) {
        AppTester.check(aNumberOfRowsIDisplayInStackTraces_ >= 0, "You can't display a negative number of rows in a stack trace.");
        numberOfRowsIDisplayInStackTraces_ = aNumberOfRowsIDisplayInStackTraces_;
    }

    /**
     * When this variable is true, the log file will be printed to. If it is false,
     * the log file will not be used even if it can be written to.
     */
    private static boolean printToLogFile_ = true;

    /**
     * When this variable is true, the terminal will be printed to. If it is false,
     * the terminal will not be printed to.
     */
    private static boolean printToTerminal_ = true;

    /**
     * All messages that are at this debug level or higher are printed. By
     * default set to {@link #NORMAL}
     */
    private static Rank myRank_ = NORMAL;

    /**
     * Specified where print stream messages should go to. By default set to
     * {@link #EITHER_STD_OUT_OR_STD_ERROR}
     */
    private static DefaultPrintStream myTargetPrintStream_ = DefaultPrintStream.ONLY_STANDARD_OUT;

    /**
     * This thread polls for a variety of background events, where each
     * background events is an materialization of the
     * {@link Utilities.BackgroundEvent_Interface} interface. Its name is
     * "Event_Checker". Its initialization is deferred until its first use.
     */
    private static ScheduledExecutorService myScheduler_
            = null; //Executors.newSingleThreadScheduledExecutor();

    /**
     * This represents the main thread. If this thread is dead,
     * {@link #myScheduler_} thread must die as well, otherwise, the application
     * won't exit at the end of the main method.
     */
    private static final Thread myMainThread_;

    static {
        myMainThread_ = Thread.currentThread();
    }

    public static Thread getMyMainThread() {
        return myMainThread_;
    }

    /**
     * @return the variable {@link #printToLogFile_}
     */
    public static boolean getGenerateLogFiles() {
        return printToLogFile_;
    }

    /**
     * Determines whether or not the log file should be printed to.
     * @param useLogFileOrNot true for printing to the log file and false
     * for no printing to the log file.
     */
    public static void setPrintToLogFile(boolean useLogFileOrNot) {
        printToLogFile_ = useLogFileOrNot;
    }

    /**
     * @return the variable {@link #printToTerminal_}
     */
    public static boolean getPrintToTerminal() {
        return printToTerminal_;
    }

    /**
     * Determines whether of not the terminal should be printed to.
     * @param toPrint true for printing to the terminal or false for no printing
     * to the terminal.
     */
    public static void setPrintToTerminal(boolean toPrint) {
        printToTerminal_ = toPrint;
    }

    /**
     * @return the currently in use {@link Utilities.AppTester.Rank}
     */
    public static Rank getMyDebugLevel() {
        return myRank_;
    }

    /**
     * @param level all messages that are at this debug level or higher are
     * printed. Messages that are below (less important than) this debug level
     * are not printed.
     */
    public static void setMyDebugLevel(Rank level) {
        myRank_ = level;
    }

    /**
     * @return the currently in use
     * {@link Utilities.AppTester.DefaultPrintStream}
     */
    public static DefaultPrintStream getMyDefaultPrintStream() {
        return myTargetPrintStream_;
    }

    /**
     * All terminal error messages will go through this
     * {@link Utilities.AppTester.DefaultPrintStream}
     *
     * @param streamType
     */
    public static void setMyDefaultPrintStream(DefaultPrintStream streamType) {
        myTargetPrintStream_ = streamType;
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Delayed Initialization Vars">
    /**
     * The name of the folder that holds all the log files, or null if there are
     * no log files.
     */
    private static final String myLogFolderNameOrNull_;// = "Log_Files";

    static {
        // initialize the above variable in the static block.
        final String intendedFolderName = "Log_Files";
        final File logFilesFolder = new File(FileFinder.WORKING_DIRECTORY
                + FileFinder.FILE_SEPARATOR + intendedFolderName);
        if (!logFilesFolder.exists()) {
            // No folder exists, so the folder needs to be created.
            boolean madeFolder = false;
            try {
                madeFolder = logFilesFolder.mkdir();
            } catch (SecurityException se) {
                // madeFolder remains false.
            }

            if (madeFolder) {
                //Tester.print("A log file folder was created.", NORMAL);
                myLogFolderNameOrNull_ = intendedFolderName;
            } else {
                //Tester.print("The log file folder could not be created.", NORMAL);
                myLogFolderNameOrNull_ = null;
            }
        } else {
            // The folder already exists.
            //Tester.print("The log file folder already exists.", NORMAL);
            myLogFolderNameOrNull_ = intendedFolderName;
        }
    }

    /**
     * The name of the log file if log file creation was successful, or null if
     * no log file was created. Perform null checks before use.
     */
    private static final String myLogFileNameOrNull_;

    /**
     * @return Either the name of the log file or empty string if the log file
     * was not successfully created.
     */
    public static String getLogFileNameOrEmptyString() {
        if(myLogFileNameOrNull_ != null) {
            return myLogFileNameOrNull_;
        } else {
            return "";
        }
    }
    /**
     * If initialization was successful, this variable will be non-null,
     * otherwise it will remain null. Perform null checks before use.
     */
    private static FileOutputStream myLogFileOutputStreamOrNull_ = null;
    /**
     * If initialization was successful, this variable will be non-null,
     * otherwise it will remain null. Perform null checks before use.
     */
    private static OutputStreamWriter myLogOutputStreamWriterOrNull_ = null;
    /**
     * If initialization was successful, this variable will be non-null,
     * otherwise it will by null. Perform null checks before use.
     */
    private static final BufferedWriter myLogBufferedWriterOrNull_; // this does the actual writing

    static {
        // initialize myLogFileNameOrNull_ and the writers.
        if (!(myLogFolderNameOrNull_ == null)) {
            final DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd___HH:mm:ss");
            final Calendar cal = Calendar.getInstance();
            final String dateTimeForLogFile = dateFormat.format(cal.getTime()); /* 2014_08_06___16:00:22 */

            final String expectedFileName = dateTimeForLogFile + ".txt";
            final String fullPath = FileFinder.WORKING_DIRECTORY
                    + FileFinder.FILE_SEPARATOR + myLogFolderNameOrNull_
                    + FileFinder.FILE_SEPARATOR + expectedFileName;

            final File logFile = new File(fullPath);

            boolean wasFileCreated = false;
            try {
                wasFileCreated = logFile.createNewFile();
            } catch (IOException ioe) {
                // wasFileCreated remains false.
            } catch (SecurityException se) {
                // wasFileCreated remains false.
            }

            boolean success = false;
            if (wasFileCreated == true) {
                final String wasFileFound = FileFinder.tryFindPathToFileWhoseNameIs(expectedFileName);
                AppTester.check(wasFileFound != null, "I made the file so I should be able to find it.");
                //Tester.printEx("Managed to create log file.", AppTester.IMPORTANT);
                try {
                    myLogFileOutputStreamOrNull_ = new FileOutputStream(logFile, true); // File not found exception???
                    myLogOutputStreamWriterOrNull_ = new OutputStreamWriter(myLogFileOutputStreamOrNull_, "utf-8"); // unsupported encoding exception
                } catch (FileNotFoundException fnfe) {
                    AppTester.killApplication("It's impossible for a newly created file to not be found.", fnfe);
                } catch (UnsupportedEncodingException uee) {
                    AppTester.killApplication("It's impossible for UTF-8 to be an unsupported text format.", uee);
                } catch (Exception e) {
                    //just ignore it, the things we are trying to initialize will be null.
                    myLogFileOutputStreamOrNull_ = null;
                    myLogOutputStreamWriterOrNull_ = null;
                }

                if (myLogOutputStreamWriterOrNull_ != null) {
                    myLogBufferedWriterOrNull_ = new BufferedWriter(myLogOutputStreamWriterOrNull_);
                    try {
                        myLogBufferedWriterOrNull_.write("Starting log file" + ls);
                        success = true;
                    } catch (IOException ioe) {
                        // Just ignore it - don't kill the thread.
                    }
                } else {
                    myLogBufferedWriterOrNull_ = null;
                }
            } else {
                //Tester.printEx("Failed to create log file.", AppTester.IMPORTANT);
                myLogFileOutputStreamOrNull_ = null;
                myLogOutputStreamWriterOrNull_ = null;
                myLogBufferedWriterOrNull_ = null;
            }

            if (success == true) {
                myLogFileNameOrNull_ = expectedFileName;
            } else {
                myLogFileNameOrNull_ = null;
            }
        } else {
            // No log folder, so definetely no log files.
            AppTester.printerr("Could not create log file because log folder does not exist.");
            myLogFileNameOrNull_ = null;
            myLogBufferedWriterOrNull_ = null;
        }
    }

    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Functions">
    /**
     * Closes the AppTester by killing any background threads it may be running
     * and closing its log file writer. Must be called at or before termination.
     */
    public static void close() {

        try {
            if (myScheduler_ != null) {
                myScheduler_.shutdownNow();
                printlnToReadout("\n" + "The scheduler has been shut down", ReadoutCondition.NON_ERROR, UNIMPORTANT);
            }
        } catch (Exception e) {
            // ignore it.
            //e.printStackTraceNoLeadingLineNumber();
        }
        try {
            if (myLogBufferedWriterOrNull_ != null) {
                printlnToReadout("\n" + "The log file is being shut down.", ReadoutCondition.NON_ERROR, NORMAL);
                myLogBufferedWriterOrNull_.close();
            } 
        } catch (Exception ioe) {
            // ioe.printStackTraceNoLeadingLineNumber();
            // ignore the error. myLogBufferedWriterOrNull_ is already closed.
        }
    }

    /**
     * Tries to write text to log file. Replaces all "\n" newline
     * characters that may have been sent to the terminal with OS specific
     * end of line characters before printing to the text file.
     *
     * @param text the text to be written to the log file.
     * @return false if no text is written or true if text is successfully
     * written.
     */
    private static boolean tryWritingSomethingToLogFileNoNewline(String text) {
        if (myLogBufferedWriterOrNull_ == null) {
            return false;
        } else {
            try {
                String updatedText = text.replaceAll("\n", ls);
                myLogBufferedWriterOrNull_.write(updatedText);
                myLogBufferedWriterOrNull_.flush();
                return true;
            } catch (IOException ioe) {
                // means the writer was closed
                return false;
            }
        }
    }

    public static void printlnToReadout(final String message, ReadoutCondition condition, Rank severity) {
        printToReadout((message + "\n"), condition, severity);
    }

    /**
     * Prints to log/terminal without newline.
     * @param message the message to be printed
     * @param condition whether the message is an error or non-error message
     */
    public static synchronized void printToReadout(final String message, ReadoutCondition condition, Rank severity) {

        // Logging happens regardless of severity.
        if (printToLogFile_) {
            // log stuff
            if (!(AppTester.myLogFileNameOrNull_ == null)) {
                final String logFilesPathString = FileFinder.tryFindPathToFileWhoseNameIs(myLogFileNameOrNull_);
                AppTester.check(logFilesPathString != null, "The log file name is non-null, so the log file must exist.");
                boolean success = AppTester.
                        tryWritingSomethingToLogFileNoNewline(message.replaceAll("\n", ls));
                // this success is being silently ignored if it doesn't write to log file,
                // I'm not doing anything about it.
            } else {
                // Don't bother, it won't work.
            }
        }
        if(! printToTerminal_) {
            // Do not print anything.
            return;
        } else if (myRank_.getImportance() > severity.getImportance()) {
            // This message is not important enough to be printed. 
            return; // return without printing to terminal.
        } else {
            // This message is important enough to be printed to terminal.
            if (myTargetPrintStream_ == ONLY_STANDARD_OUT) {
                System.out.print(message);
            } else if (myTargetPrintStream_ == ONLY_STANDARD_ERROR) {
                System.err.print(message);
            } else {
                // myTargetPrintStream_ == EITHER_STD_OUT_OR_STD_ERROR
                if (condition == ReadoutCondition.ERROR) {
                    System.err.print(message);
                } else if (condition == ReadoutCondition.NON_ERROR) {
                    System.out.print(message);
                } else {
                    AppTester.killApplication("This condition is logically impossible");
                }
            }
        }
    }

    /**
     * Kills the entire application and leaves a stack trace.
     *
     * @param message - message to print before the application terminates.
     */
    public static void killApplication(String message) {
        AppTester.check(false, message, 3);
    }

    /**
     * Kills the application, prints a message, and also print the throwable who
     * is responsible for the crash. Prints the entire throwable.
     *
     * @param message message to print before the application terminates.
     * @param t throwable whose stack trace is to be included in the
     * termination.
     */
    public static void killApplication(String message, Throwable t) {
        StackTraceElement[] ste = t.getStackTrace();
        String concatenation = message + "\n";
        concatenation += t.toString() + "\n";
        for (int i = 0; i < ste.length; ++i) {
            concatenation += (ste[i].toString() + "\n");
        }
        AppTester.killApplicationNoStackTrace(concatenation);
    }

    /**
     * Alternate form of {@link #killApplication(java.lang.String, java.lang.Throwable) }.
     * Prints the entire stack trace.
     */
    public static void killApplication(Throwable t, String messsage) {
        killApplication(messsage, t);
    }

    /**
     * Kills the entire application and prints out the stack trace elements of
     * the throwable responsible
     *
     * @param t the throwable responsible.
     */
    public static void killApplication(Throwable t) {
        killApplication("", t);
    }

    /**
     * Kills the entire application without leaving a stack trace.
     *
     * @param message - message to print before the application terminates.
     */
    public static void killApplicationNoStackTrace(String message) {
        printlnToReadout("\n" + message, ReadoutCondition.ERROR, Rank.IMPORTANT);
        close();
        System.exit(-1);
    }

    /**
     * Checks to see if an assertion is true. Prints stack trace and crashes the
     * program if not true. USE THIS INSTEAD OF REGULAR "assert" STATEMENTS. For
     * information on how to use an assert statement, see:
     * <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/language/assert.html">Programming
     * With Assertions</a>
     *
     * @param assertion - assertion to be checked
     */
    public static void check(boolean assertion) {
        check(assertion, "Empty_Assertion", 3); // nomally it would be 2 but the indirections bumps it up to 3.
    }

    /**
     * Checks to see if an assertion is true and prints an error message if
     * false. Also prints a stack trace and crashes the program if false. For
     * information on how to use an assert statement, see:
     * <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/language/assert.html">Programming
     * With Assertions</a>
     *
     * @param assertion - assertion to be checked
     * @param message - error message to print
     */
    public static void check(final boolean assertion, final String message) {
        check(assertion, message, 3); // nomally it would be 2 but the indirections bumps it up to 3.
    }

    private static void check(final boolean assertion, final String message, int firstRowOfStackTrace) {
        if (!assertion) {

            String toBePrinted = "\n" + "Assertion failed in Thread: \""
                    + Thread.currentThread().getName() + "\"" + "\n" + message;
            // length of stack trace is 5 (thread called by check called by check called by handle called by main)
            //printlnToReadout(toBePrinted, ReadoutCondition.ERROR, Rank.IMPORTANT); // print rows 3 & 4
            final StackTraceElement[] stackTraceArray = Thread.currentThread().getStackTrace();
            // This should print the stack trace from firstRowOfStackTrace down.
            printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline(toBePrinted, stackTraceArray, firstRowOfStackTrace);
            close();
            System.exit(-1);
        }
    }

    private static void print(String message, Rank severityLevel, int stackTraceStart) {
        final String thread_name = Thread.currentThread().getName();
        final String location_of_print_statement = Thread.currentThread().getStackTrace()[stackTraceStart].toString();
        printlnToReadout("\n" + "Thread \"" + thread_name + "\": "
                + location_of_print_statement + "\n" + message, ReadoutCondition.NON_ERROR, severityLevel);
    }

    /**
     * Prints the name of the current thread and where the print statement comes
     * from to standard output or to the stream specified by
     * {@link #myTargetPrintStream_}. The {@link #myTargetPrintStream_} variable
     * acts as a global override for the preferred output stream of the
     * application.
     *
     * @param message - message to be printed
     */
    private static void print(String message, Rank severityLevel) {
        print(message, severityLevel, 3);
    }

    /**
     * Prints an unimportant non-error message.
     * Short for "unimportant print".
     */
    public static void uPrint(String message) {
        print(message, UNIMPORTANT, 3);
    }

    /**
     * Same as {@link #print(java.lang.String, Utilities.AppTester.Rank)  },
     * but with the {@link #NORMAL} severity level specified by default.
     *
     * @param message the message to be printed as if it were a regular
     * non-error message.
     */
    public static void print(String message) {
        print(message, NORMAL, 3);
    }

    /**
     * Prints an important non-error message.
     * Short for "important print."
     */
    public static void iPrint(String message) {
        print(message, IMPORTANT, 3);
    }

    /**
     * Prints an error message to terminal and/or log file including
     * accompanying stack trace element.
     *
     * @param message The error message
     * @param severityLevel How important the message is
     * @param stackTraceStart The position of the current line on the stack
     * trace
     */
    private static void printerr(String message, Rank severityLevel, int stackTraceStart) {
        final String thread_name = Thread.currentThread().getName();
        final String location_of_print_statement = Thread.currentThread().getStackTrace()[stackTraceStart].toString();
        printlnToReadout("\n" + "Thread \"" + thread_name + "\": "
                + location_of_print_statement + "\n" + message, ReadoutCondition.ERROR, severityLevel);
    }

    /**
     * Prints the name of the current thread and where the print statement comes
     * from to the error output stream or to the stream specified by
     * {@link #myTargetPrintStream_}. The {@link #myTargetPrintStream_} variable
     * acts as a global override for the preferred output stream of the
     * application.
     *
     * @param message - message to be printed
     */
    private static void printerr(String message, Rank severityLevel) {
        printerr(message, severityLevel, 3);
    }

    /**
     * Short for print error not important. Prints an unimportant error.
     * Short for "unimportant print error".
     */
    public static void uPrinterr(String message) {
        printerr(message, UNIMPORTANT, 3);
    }

    /**
     * Same as {@link #printerr(java.lang.String, Utilities.AppTester.Rank) 
     * }, but with the {@link #NORMAL} severity level specified by default.
     *
     * @param message the message to be printed as if it were an error.
     */
    public static void printerr(String message) {
        printerr(message, NORMAL, 3);
    }

    /**
     * Short for print error important. Prints an important error.
     * Short for "important print error".
     */
    public static void iPrinterr(String message) {
        printerr(message, IMPORTANT, 3);
    }

    /**
     * Prints out a throwable as if it were and error and logs it appropriately.
     * Short for "print exception".
     *
     * @param t Exception or error to be printed as an error.
     */
    public static void printEx(Throwable t) {
        printThrowableNoLeadingLineNumber(t);
    }

    /**
     * Prints out a message and a stack trace caused by a throwable as an error.
     * Message cannot be an empty string or null.
     *
     * @param message message to be printed
     * @param t throwable that caused the message to be printed
     */
    public static void printEx(String message, Throwable t) {
        printThrowableNoLeadingLineNumber(message, t);
    }

    /**
     * Same as {@link #printEx(java.lang.String, java.lang.Throwable) }, but
     * with a leading message.
     */
    public static void printEx(Throwable t, String message) {
        printThrowableNoLeadingLineNumber(message, t);
    }

    /**
     * Prints a stack trace starting from firstRow preceded by a leading message and a newline.
     * @param message the message which appears just before the stack trace.
     * @param stackTrace The stack trace to be printed.
     * @param firstRow The first row of the stack trace, between zero and length.
     */
    private static void printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline(final String message, StackTraceElement[] stackTrace, int firstRow) {
        // Truncated for brevity. Basically prints a message and the stack trace, making the first n lines important and the rest uninimportant.
    } 

    private static void printThrowableNoLeadingLineNumber(String nonNullMessage, Throwable t) {
        AppTester.check(nonNullMessage != null, "The message is not supposed to be null");
        String toPrint = "\n" + nonNullMessage + "\n" + t.toString();
        // printlnToReadout(toPrint, ReadoutCondition.ERROR, IMPORTANT);
        printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline(toPrint, t.getStackTrace(), 0);
        printlnToReadout(Thread.currentThread().getStackTrace()[3].toString() + "\n", ReadoutCondition.ERROR, IMPORTANT);
    }

    private static void printThrowableNoLeadingLineNumber(Throwable t) {
        String toPrint = "\n" + t.toString();
        //printlnToReadout(toPrint, ReadoutCondition.ERROR, IMPORTANT);
        printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline(toPrint, t.getStackTrace(), 0);
        printlnToReadout(Thread.currentThread().getStackTrace()[3].toString() + "\n", ReadoutCondition.ERROR, IMPORTANT);
    }
    // </editor-fold>
}
\$\endgroup\$
0

2 Answers 2

5
\$\begingroup\$

It's very very long, so don't be surprised when I stop at some random point.

print statements with built in stack trace, assertions that stop the entire application..., Can also

Nearly every class described as "does this and that and also this" should usually be three classes.

However, I like the idea of print statements with stack trace. Actually, that's something I've been using since long and I find it much more efficient than normal debugging.

Nonetheless, logging is a different story and in case I want logging, there's logback and many other frameworks.


package Utilities;

The name is wrong, unless you own the top level domain "utilities". Even then, capitalized package names are OK, but not exactly preferred. So maybe johnreedlol.utilities or better, use a reversed name of a domain you own (or "pretend" to own).


import java.io.*

Don't use start imports. Let your IDE take care of it.


static {
    AppTester.check(ls != null);
}

It's usually no good idea to call methods which may use not yet initialized parts of the class.

Moreover, statics and static initializer blocks should be rather sparingly as they make the code hard to test. Even for a class which gets used only statically, I'd create an instance and delegate all calls to it.


/**
 * The number of important rows in a stack trace.
 */

private static int numberOfRowsIDisplayInStackTraces_ = 6;

IIRC there's a language using trailing underscores by convention, but it's not Java. The word "important" in the comment does not correspond with what the variable controls, does it?


public static void setNumberOfRowsIDisplayInStackTraces_(int aNumberOfRowsIDisplayInStackTraces_) {
     AppTester.check(aNumberOfRowsIDisplayInStackTraces_ >= 0, "You can't display a negative number of rows in a stack trace.");
    numberOfRowsIDisplayInStackTraces_ = aNumberOfRowsIDisplayInStackTraces_;
}

Why so cruel? If someone passes an illegal argument, then an IllegalArgumentException should be thrown. No reason to call the four horsemen to terminate the world.


 /**
  * All messages that are at this debug level or higher are printed. By
  * default set to {@link #NORMAL}
  */
 private static Rank myRank_ = NORMAL;

So "Level" or "Rank"? Decide which one is better and stick with it.


 * {@link #EITHER_STD_OUT_OR_STD_ERROR}

There's nothing like this there.


private static ScheduledExecutorService myScheduler_
        = null; //Executors.newSingleThreadScheduledExecutor();

You're using git, so there's no need to leave dead code lying around.


/**
 * This represents the main thread. If this thread is dead,
 * {@link #myScheduler_} thread must die as well, otherwise, the application
 * won't exit at the end of the main method.
 */
private static final Thread myMainThread_;

static {
    myMainThread_ = Thread.currentThread();
}

This could be written as

private static final Thread myMainThread_ = Thread.currentThread();

It's pretty fragile. Your "main thread" is defined as the thread which caused the loading of AppTester. So when I call AppTester.whatever in my main, open a JFrame, and let main exit normally, then your "main thread" is gone, although the relevant thread (AWT) is still running.

The proper solution would be to use deamon threads in the scheduler.


public static boolean getGenerateLogFiles() {
    return printToLogFile_;
}

Decide what's the better name and stick with it.


public static void setMyDebugLevel(Rank level) {
    myRank_ = level;
}

Again, "level", "debugLevel", or "myRank"?


... skip ... skip ...


public static void killApplication(Throwable t, String messsage) {

This might be a useful method... from time to time. There's surely no need to provide 4 overloads. If someone wants to terminate the application and has nothing to say, then let them provide the empty message themselves. Frequently used operations should be as comfortable as possible, rarely used one don't need to.

public static void killApplicationNoStackTrace(String message) {

Do you need it?

    System.exit(-1);

The exit code meaning is system dependent, but -1 usually mean something extraordinary terrible. I guess, +1 would be appropriate.


 * Checks to see if an assertion is true. Prints stack trace and crashes the
 * program if not true. USE THIS INSTEAD OF REGULAR "assert" STATEMENTS. For

That's completely backwards. There's no use in replacing assert by your check.

  • The regular assert is for program sanity checks and can be switched off (and is off by default), which is incredibly useful for performance.
  • Methods should check their inputs and (at least for public methods) this should never be switched off. That's what Guava Preconditions are good for.
  • Sometimes the problem is unsolvable and program termination is the proper outcome. Then your check is good, but note that this is a very exceptional event.

private static void printerr(String message, Rank severityLevel) {

This sounds like printer with a typo. I don't thing saving two letters is worth it. It should be called printError (with capital "E").

print, iPrint, uPrinterr, iPrinterr, ....

This sounds like logging, but I'm afraid that it's hard to find a system there. I'd prefer the standard names

trace, debug, info, warn, error


printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline

New line and what? As I read the end I've already forgot how it started. Sure, naming is hard, but what about something like printStackTraceInternal? It's private and nobody cares much what it exactly does. Describing less important details in Javadoc only is good enough.

Summary

As you can see, there are many things I dislike, but the problem is not the code, but rather that what you're trying to achieve. Some long time ago I wrote a similar class and later found myself using only a tiny part of it. I've dropped the logging part and improved the debugging part, so I can write

 Debug.out("x =", x, ", y =", y);

and get

maaartinus.some.package.SomeClass:123
x = 12 , y = 34

I added some formatting support and removed everything what I didn't use really often. In fact, I threw away vast majority of it and reworked everything several times. My lesson learned was that unlike a building, software can and should be build top-down. I'd suggest you check your code usage and rethink what you really need.

\$\endgroup\$
10
  • \$\begingroup\$ Could you explain what is the problem with star imports? I'm interested in your reasoning. \$\endgroup\$
    – juunas
    Jul 26, 2015 at 5:39
  • \$\begingroup\$ @juunas There's no big problem, but also no advantage in using them as the IDE does all the work and then folds them nicely. \$\endgroup\$
    – maaartinus
    Jul 26, 2015 at 6:00
  • 1
    \$\begingroup\$ (My actual code didn't use star imports, I just changed them to star imports to get the character count under 30k so that SE would accept it). The reason I hate when other people use star imports is that before I read code, I look at the imports to get a sense of what dependencies will be in the code and I often will glance at the documentation of those dependencies to get a sense of understanding before reading the actual code. \$\endgroup\$ Jul 26, 2015 at 17:10
  • \$\begingroup\$ @MichaelLafayette I've never seen "www" in a package name and haven't used it in a URL since years. And if you get a domain, there's no "www" in either. Just leave it out and use com.michael-lafayette as the root package and com.michael-lafayette.utilities (or better com.michael-lafayette.utils as this is what Java does) for utilities. +++ The domain name is just for the root package and this makes it unique. For example, I'm using com.google.common.collect and it's unique because of Google owning the domain "google.com" and because of them organizing everything below com.google. \$\endgroup\$
    – maaartinus
    Jul 26, 2015 at 17:32
  • 1
    \$\begingroup\$ @MichaelLafayette Concerning star imports, I agree with you. On CR, I usually leave all imports out and link to the source at github (but people miss them anyway). +++ "character count under 30k" - this may be a sign that your class is doing too much. \$\endgroup\$
    – maaartinus
    Jul 26, 2015 at 17:46
2
\$\begingroup\$
  • The name of:

    public static final String ls = ...

    isn't too descriptive. Also, Java convention is to use solely uppercase letters and the underscore in final names:

    public static final String LINE_SEPARATOR = ... or LINE_BREAK if you want it shorter.

  • Why do you use "\n" all over the place instead of ls,LINE_SEPARATORor LINE_BREAK you defined?

  • The line:

    String updatedText = text.replaceAll("\n", ls);
    

    is not platform independent since on Windows line.separator equals "\r\n" so all the existing "\r\n"s would be replaced by "\r\r\n".

    /** ... Replaces all "\n" newline characters that may have been sent to the terminal
     * with OS specific end of line characters 
    

    There is no terminal in Windows.

  • I'd use String.format() for clearness:

    String toBePrinted =
                String.format("%nAssertion failed in Thread: '%s'%n%s",
                Thread.currentThread().getName(), message);
    

    instead of string concatenation:

    String toBePrinted = "\n" + "Assertion failed in Thread: \""
                + Thread.currentThread().getName() + "\"" + "\n" + message;
    

    The same applies (2 times) to:

    printlnToReadout("\n" + "Thread \"" + thread_name + "\": "
            + location_of_print_statement + "\n" + message, ReadoutCondition.NON_ERROR, severityLevel);
    

    and to:

    String toPrint = "\n" + nonNullMessage + "\n" + t.toString();
    

    I always use String.format() if there would be more than one '+' in a concatenation.

  • Java convention for variable names is camelCase:

    thread_namethreadName, location_of_print_statementlocationOfPrintStatement

  • The line

         final String thread_name = Thread.currentThread().getName();
    

    in:

     private static void print(String message, Rank severityLevel, int stackTraceStart) {
         final String thread_name = Thread.currentThread().getName();
         final String location_of_print_statement = Thread.currentThread().getStackTrace()[stackTraceStart].toString();
         printlnToReadout("\n" + "Thread \"" + thread_name + "\": "
                 + location_of_print_statement + "\n" + message, ReadoutCondition.NON_ERROR, severityLevel);
         }
    

    is pointless if you use thread_name just once then. I'd rather use:

         final Thread thread = Thread.currentThread();
         ...
         ... thread.getStackTrace()[...] ... 
         ...
         ... thread.getName() ...
    
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.