0

Im making an application which (to sum it up in very basic terms) acts as an interface to another application, so rather than having to view things through the console I can see them in a nice Swing window. I had made an earlier version of this, which simply output everything into a JTextArea as plain text. It worked fine, and I had no issues with it. But feeling the need to expand out this very basic program a bit, I decided to try and color coat the messages so that someone other than myself could find their way around the output a bit more easily. First part of the program involves this:

class ServerIOStream extends Thread {

    private boolean _isRunning = false;
    private Process _serverProcess = null;
    private BufferedReader input = null;
    private PrintWriter writer = null;

    public void run() {
        try {
            _serverProcess = Runtime.getRuntime().exec(PropertiesReader.getLaunchString());
            input = new BufferedReader(new InputStreamReader(_serverProcess.getInputStream()));
            _isRunning = true;
            String line;
            while ((line = input.readLine()) != null)
                MainWindow.writeToOutput(line, 0);
            _isRunning = false;
            MainWindow.writeToOutput("Service has stopped...", 1);
        } catch (IOException ex) {
            MainWindow.writeToOutput(ex.getMessage(), 3);
            _isRunning = false;
        }
    }

    public boolean ServerStatus(){
        return _isRunning;
    }
}

My apologies if things seem a bit messy and some stuff does not make sense, as it is quite late and I have been tinkering with the code in strange ways to try and get this to work as desired.

NOW, as you can see, upon being run the thread will pick up a runtime, and then via a loop just spit out what it receives into method. That method (Code shown below) is responsible for delivering the content to JEditorPane. Here is how the MainWindow.writeToOutput() function works.

    private static String output;
public static void writeToOutput(String message, int inputType){
    String finalMessage = null;
    String type = null;
    switch(inputType){
        case 0: // For input from the server
            finalMessage = message;
            break;
        case 1: // General info from the GUI
            type = "INFO";
            break;
        case 2: // Warnings from the GUI
            type = "WARN";
            break;
        case 3: // Error messages from the GUI
            type = "ERROR";
            break;
        default: // Unknown messages
            type = "UNKNOWN";
            break;
    }
    if(inputType == 1 || inputType == 2 || inputType == 3){
        finalMessage = String.format("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + " GUI %s" + "]: %s", type, message);
        if(inputType == 1)
            finalMessage = "<font color=\"" + PropertiesReader.getInfoColor() + "\">" + finalMessage + "</font>";
        else if(inputType == 2)
            finalMessage = "<font color=\"" + PropertiesReader.getWarnColor() + "\">" + finalMessage + "</font>";
        else if(inputType == 3)
            finalMessage = "<font color=\"" + PropertiesReader.getErrColor() + "\">" + finalMessage + "</font>";
    }
    else if(inputType == 0){
        try{
            String s = finalMessage;
            s = s.substring(s.indexOf("[") + 1);
            s = s.substring(0, s.indexOf("]"));

            if(s.contains("INFO"))
                finalMessage = "<font color=\"" + PropertiesReader.getInfoColor() + "\">" + finalMessage + "</font>";
            else if(s.contains("WARN"))
                finalMessage = "<font color=\"" + PropertiesReader.getWarnColor() + "\">" + finalMessage + "</font>";
            else if(s.contains("ERROR"))
                finalMessage = "<font color=\"" + PropertiesReader.getErrColor() + "\">" + finalMessage + "</font>";
        }
        catch(StringIndexOutOfBoundsException ex){
            finalMessage = "<font color=\"red\">" + finalMessage + "</font>";
        }
    }

    if(output != null)
        output += finalMessage + "<br>";
    else
        output = finalMessage + "<br>";

    ServerOutputTextArea.setText(output);

    if(inputType == 0 || inputType == 1 || inputType == 2)
        System.out.println(finalMessage);
    else
        System.err.println(finalMessage);
    if(AutoScrollCheckbox.isSelected())
        ServerOutputTextArea.setCaretPosition(ServerOutputTextArea.getDocument().getLength());

    finalMessage = null;
    message = null;
    System.out.println("\nLength: " + output.length());
}

I apologize yet again if the code is hard to follow or nonsensical. As I said before, it is late and I was just tinkering with it in all sorts of ways in an attempt to get it to work.

Basically, input goes as follows. Argument 1 is the message, and argument 2 is the type of message represented as an int. 0 is used to represent messages coming from the runtime displayed before, 1 is for general info, 2 is for warnings, and 3 is for errors/exceptions. If argument 2 is equal to zero, then the first chunk of the message (Typically formatted as such: "[HH:MM:SS Type]") will be dissected to determine its type.

The code I have made is very makeshift and clunky and I understand that- however that is not the topic of discussion right now. The issue at hand is that first, each time something new is output to the JEditorPane the pane will briefly go blank (for a fraction of a second) before the text reappears with the new message at the bottom of it. Honestly when it happens from a single message its hard to notice, but when the process is spitting messages out left and right the program will flicker and flash too much, and cut and scroll down to random points in the application inexplicably as the autoscroll struggles to keep up.

The second issue is the memory issue. What happens is when the application first starts and before the process has really started to get going it sits calmly around 50MB of memory usage, and hogs little to none of my precious CPU cycles. But suddenly as the process gives messages faster and faster, memory usage seems to increase exponentially. Going from 50MB, to 100MB, to 500MB, and normally up to a max of around 1.5GB to 1.8GB of memory usage.

At first, I was not sure if this was being caused by the JEditorPane, but then I ran a small experiment. I went back to using JTextArea and left most everything the same and sure enough, even when the process was spitting out info like mad I only ever saw peaks of around 70MB of memory usage, and the flickering and autoscroll struggle that I described was non-existent.

Here is what I saw in testing:

  • JTextArea | Output contained 47553 characters, and memory usage was at 52MB
  • JEditorPane | Output contained 50531 characters, and memory usage was at 1550MB

ALSO: On one final note I would like to say that I am still not very experienced with Java, or when it comes to making applications with visual interfaces in general. Thank you very much to all who answer.

pmiccich
  • 49
  • 1
  • 2
  • 9
  • 1
    It's hard to be sure, but you appear to be violating the single thread rules of Swing, which isn't going to help. See [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/). The text components do tend to require a large amount of memory for what is considered a small about of text. It might be better to interact with the `Document` of the text components and insert text rather then using `setText` – MadProgrammer Dec 11 '15 at 10:56
  • Some alternatives [here](http://stackoverflow.com/q/25526833/230513); you can `exec()` from `doInBackground()`. – trashgod Dec 11 '15 at 13:55

2 Answers2

0

I decided to try and color coat the messages

I would use a JTextPane instead of a JEditor pane. I find working with attributes easier than working with HTML and don't know for sure but would guess the memory requirement would be smaller since you don't need to parse the HTML.

Using attributes is straight forward:

JTextPane textPane = new JTextPane();
textPane.setText( "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight" );
StyledDocument doc = textPane.getStyledDocument();

//  Define a keyword attribute

SimpleAttributeSet keyWord = new SimpleAttributeSet();
StyleConstants.setForeground(keyWord, Color.RED);
StyleConstants.setBackground(keyWord, Color.YELLOW);
StyleConstants.setUnderline(keyWord, Boolean.TRUE );
StyleConstants.setBold(keyWord, true);

//  Define green attribute

SimpleAttributeSet green = new SimpleAttributeSet();
StyleConstants.setFontFamily(green, "Courier New Italic");
StyleConstants.setForeground(green, Color.GREEN);


//  Change attributes on some text

doc.setCharacterAttributes(0, 5, keyWord, false);
doc.setCharacterAttributes(20, 4, green, true);

//  Add some text with attributes

try
{
    doc.insertString(3, "\ngreen text", green);
}
catch(Exception e) {}
camickr
  • 321,443
  • 19
  • 166
  • 288
0

I would use StringBuilder rather than String for the

private static String output

Also simple setText() replaced with setDocument()

Document doc=ServerOutputTextArea.getEditorKit().createDefaultDocument();
ServerOutputTextArea.getEditorKit().read(new StringReader(output),doc,0);
ServerOutputTextArea.setDocument(doc);

setText() calls remove(0, getLength()) for the document and could leave some cached data - positions, image cache etc.

StanislavL
  • 56,971
  • 9
  • 68
  • 98