My application freezes randomly with no error output. I've been beating my head over it's code and don't want to continue working on it until this problem is resolved. I'm thinking it's a synchronization problem. I've attached the source code. Any help would be greatly appreciated.

This is as small as I could get it. It happens when large amounts of text are managed.

public class JavaEdit extends JFrame {

    public static void main(String[] args) {
        new JavaEdit();
    }
    private JTextPane editText;
    private StyledDocument editTextDoc;
    private Style styleNormal;
    private Style styleRED;

    // Default constructor
    private JavaEdit() {
        new JavaEdit("one two three one two three one two three");
    }

    // Actual constructor
    public JavaEdit(String string) {
        this.setSize(800, 600);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);

        editText = new JTextPane();
        editText.setText(string);

        editTextDoc = (StyledDocument) editText.getDocument();
        styleNormal = editTextDoc.addStyle("normal", null);
        StyleConstants.setBackground(styleNormal, Color.black);
        StyleConstants.setForeground(styleNormal, Color.white);
        styleRED = editTextDoc.addStyle("normal", null);
        StyleConstants.setBackground(styleRED, Color.black);
        StyleConstants.setForeground(styleRED, Color.RED);

        JScrollPane scroll = new JScrollPane(editText);
        this.setLayout(new BorderLayout());
        this.add(scroll, BorderLayout.CENTER);

        new Thread(new Matcher()).start();

        this.setVisible(true);
    }

    private class Matcher implements Runnable {

        private Highlighter ohl;
        private int ipos;
        private int istart, stop;
        private char cmatch;
        private String Strcurrent;

        class Painter extends DefaultHighlighter.DefaultHighlightPainter {

            Painter(Color color) {
                super(color);
            }
        }
        Highlighter.HighlightPainter paint = new Painter(Color.MAGENTA);

        public void run() {
            while (true) {
                try {
                    Thread.sleep(100);
                    search();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private void search() throws Exception {

            synchronized (editTextDoc) {
                synchronized (editText) {
                    // clear old highlights and get cursor pos
                    ohl = editText.getHighlighter();
                    ohl.removeAllHighlights(); // todo: be specific
                    ipos = editText.getCaret().getDot();
                }
                // if on space move pos
                if (editTextDoc.getText(ipos, 1).contains(" ")) {
                    ipos--;
                }
                // no action needed
                if (editTextDoc.getLength() == 0) {
                    return;
                }
                // get pos of side ends of word
                istart = stop = ipos;
                cmatch = editTextDoc.getText(istart, 1).toCharArray()[0];
                while (!isMatch(cmatch) && istart > 0) {
                    istart--;
                    cmatch = editTextDoc.getText(istart, 1).toCharArray()[0];
                }
                cmatch = editTextDoc.getText(stop, 1).toCharArray()[0];
                while (!isMatch(cmatch) && stop < editTextDoc.getLength()) {
                    stop++;
                    cmatch = editTextDoc.getText(stop, 1).toCharArray()[0];
                }
                // get word
                String word = editTextDoc.getText(istart, stop - istart).trim();
                // no work done on whitespace
                if (word.length() == 0) {
                    return;
                }
                // highlight matches
                for (int i = 0; i < editTextDoc.getLength() - word.length() + 1; i++) {
                    Strcurrent = editTextDoc.getText(i, word.length());
                    if (word.contains(Strcurrent)) {
                        ohl.addHighlight(i, i + word.length(), paint);
                    }
                }
            }
        }

        boolean isMatch(char c) {
            if (c == ' ') {
                return true;
            } else {
                return false;
            }
        }
    }
}

I've never worked on Swing/AWT but there are a couple of universal things you should know about single threaded UI toolkit (and Swing is one of them); all UI updates should be done on the rendering thread (EDT - Event dispatcher thread in case of Swing).

The problem with your code is that even though you have the motivation for running a background task, that task is very heavily involved with mutating the UI (getting the text from text area, finding highlights and highlighting them). And since you end up adding highlights in a background thread, it violates the principle of "UI updates only in the EDT. This is the reason you are getting a freeze or technically speaking a "deadlock". I'll also show you how to find out such deadlocks.

Assuming you are running JDK 6, it already comes bundled with a profiling tool called Visual VM in the bin directory. If not, just download it from the official site (google visual vm, it shouldn't be hard). Now, fire up your application and wait for it to freeze. After it has frozen/stopped responding, start Visual VM. After it has started, in the left panel of Visual VM under the "Local" category, you should see the classname of your application. Right click on it and click "Thread Dump". This should give you the thread dump of the currently executing threads. This feature also has the capability of detecting deadlocks. Just navigate to the bottom of that thread dump and search for the word "Found one Java-level deadlock:". Below that, you'll find the stack trace of two threads along with the explanation of why they have deadlocked. For me it gives (note I have renamed your class to Del):

Java stack information for the threads listed above:
===================================================
"Thread-2":
	at javax.swing.text.DefaultHighlighter$SafeDamager.damageRange(DefaultHighlighter.java:591)
	- waiting to lock <0x22e6e920> (a javax.swing.text.DefaultHighlighter$SafeDamager)
	at javax.swing.text.DefaultHighlighter.safeDamageRange(DefaultHighlighter.java:287)
	at javax.swing.text.DefaultHighlighter.safeDamageRange(DefaultHighlighter.java:296)
	at javax.swing.text.DefaultHighlighter.addHighlight(DefaultHighlighter.java:107)
	at home.projects.config.Del$Matcher.search(Del.java:131)
	- locked <0x22e9e218> (a javax.swing.text.DefaultStyledDocument)
	at home.projects.config.Del$Matcher.run(Del.java:86)
	at java.lang.Thread.run(Thread.java:619)
"AWT-EventQueue-0":
	at javax.swing.text.AbstractDocument.readLock(AbstractDocument.java:1366)
	- waiting to lock <0x22e9e218> (a javax.swing.text.DefaultStyledDocument)
	at javax.swing.plaf.basic.BasicTextUI.damageRange(BasicTextUI.java:1154)
	at javax.swing.plaf.basic.BasicTextUI.damageRange(BasicTextUI.java:1137)
	at javax.swing.text.DefaultHighlighter$SafeDamager.run(DefaultHighlighter.java:567)
	- locked <0x22e6e920> (a javax.swing.text.DefaultHighlighter$SafeDamager)
	at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

Found 1 deadlock.

As you can clearly see from the explanation, for resources A and B, thread 1 has acquired A and is waiting for B whereas thread 2 has acquired B and is waiting for A resulting in a deadlock.

The solution? Re-write your code by reading some online tutorials which show you how you can actually run a UI mutating task in the background. A few tips:

  • Instead of spawning off your UI application in the "main" thread (the code which you write in main()) consider using SwingUtilities.invokeLater() method
  • Instead of busy waiting and peforming a task repeatedly, consider using event notifications i.e. run the "search" only if the "text" has changed
  • I've heard that SwingWorker class is ideal for such tasks where updates of processing need to be reflected in the UI/interface
  • Some mandatory reading: Doing swing right and How do I use SwingWorker

I'm sure others on this forum who have worked extensively on Swing might be able to add help you out further if you post an updated code based on the recommendations I've posted.

You've been a great help. I've resolved that issue; however there is a new issue arising from the instantiation of a new window thread. I'm afraid virtually every module so far is required to replicate this issue. I'll post it if someone asks. I've read both of the required readings to no avail. Here is my VisualVM Thread dump of all Threads not in a TIMED_WAIT or RUNNABLE state. Invalidation of my new window in a new thread is BLOCKED! I'm thinking something is static, but every threaded window locks up. Again, no exceptions are being thrown.

"Window #5" prio=10 tid=0x08b9e800 nid=0x3887 in Object.wait() [0xb4dd7000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x708b2870> (a javax.swing.text.DefaultStyledDocument)
	at java.lang.Object.wait(Object.java:502)
	at javax.swing.text.AbstractDocument.readLock(AbstractDocument.java:1389)
	- locked <0x708b2870> (a javax.swing.text.DefaultStyledDocument)
	at javax.swing.plaf.basic.BasicTextUI.getPreferredSize(BasicTextUI.java:913)
	at javax.swing.JComponent.getPreferredSize(JComponent.java:1634)
	at javax.swing.JEditorPane.getPreferredSize(JEditorPane.java:1389)
	at javax.swing.ScrollPaneLayout.layoutContainer(ScrollPaneLayout.java:788)
	at java.awt.Container.layout(Container.java:1481)
	at java.awt.Container.doLayout(Container.java:1470)
	at java.awt.Container.validateTree(Container.java:1568)
	at java.awt.Container.validateTree(Container.java:1575)
	at java.awt.Container.validateTree(Container.java:1575)
	at java.awt.Container.validateTree(Container.java:1575)
	at java.awt.Container.validateTree(Container.java:1575)
	at java.awt.Container.validate(Container.java:1540)
	- locked <0x7abe3cb8> (a java.awt.Component$AWTTreeLock)
	at java.awt.Window.show(Window.java:861)
	at java.awt.Component.show(Component.java:1468)
	at java.awt.Component.setVisible(Component.java:1420)
	at java.awt.Window.setVisible(Window.java:842)
	at javaedit.JavaEdit.run(JavaEdit.java:153)
	at java.lang.Thread.run(Thread.java:636)

   Locked ownable synchronizers:
	- None
"AWT-EventQueue-0" prio=10 tid=0x08c1e800 nid=0x3880 waiting for monitor entry [0xb4e2f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at java.awt.Component.invalidate(Component.java:2672)
	- waiting to lock <0x7abe3cb8> (a java.awt.Component$AWTTreeLock)
	at java.awt.Container.invalidate(Container.java:1506)
	at javax.swing.JComponent.revalidate(JComponent.java:4791)
	at javax.swing.plaf.basic.BasicTextUI$RootView.preferenceChanged(BasicTextUI.java:1411)
	at javax.swing.text.View.preferenceChanged(View.java:289)
	at javax.swing.text.BoxView.preferenceChanged(BoxView.java:286)
	at javax.swing.text.View.preferenceChanged(View.java:289)
	at javax.swing.text.BoxView.preferenceChanged(BoxView.java:286)
	at javax.swing.text.View.preferenceChanged(View.java:289)
	at javax.swing.text.View.preferenceChanged(View.java:289)
	at javax.swing.text.GlyphView.changedUpdate(GlyphView.java:947)
	at javax.swing.text.LabelView.changedUpdate(LabelView.java:303)
	at javax.swing.text.View.forwardUpdateToView(View.java:1207)
	at javax.swing.text.FlowView$LogicalView.forwardUpdateToView(FlowView.java:787)
	at javax.swing.text.View.forwardUpdate(View.java:1178)
	at javax.swing.text.View.changedUpdate(View.java:784)
	at javax.swing.text.FlowView.changedUpdate(FlowView.java:283)
	at javax.swing.text.ParagraphView.changedUpdate(ParagraphView.java:735)
	at javax.swing.text.View.forwardUpdateToView(View.java:1207)
	at javax.swing.text.View.forwardUpdate(View.java:1178)
	at javax.swing.text.BoxView.forwardUpdate(BoxView.java:240)
	at javax.swing.text.View.changedUpdate(View.java:784)
	at javax.swing.plaf.basic.BasicTextUI$RootView.changedUpdate(BasicTextUI.java:1635)
	at javax.swing.plaf.basic.BasicTextUI$UpdateHandler.changedUpdate(BasicTextUI.java:1896)
	at javax.swing.text.AbstractDocument.fireChangedUpdate(AbstractDocument.java:231)
	at javax.swing.text.DefaultStyledDocument$ChangeUpdateRunnable.run(DefaultStyledDocument.java:2595)
	at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:226)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:602)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:138)

   Locked ownable synchronizers:
	- None
"AWT-Shutdown" prio=10 tid=0x08c1e000 nid=0x387f in Object.wait() [0xb4e80000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x7abd1b10> (a java.lang.Object)
	at java.lang.Object.wait(Object.java:502)
	at sun.awt.AWTAutoShutdown.run(AWTAutoShutdown.java:281)
	- locked <0x7abd1b10> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:636)

   Locked ownable synchronizers:
	- None

I think you should post the latest updated code. That way, maybe I or someone else will give it a look and if it seems like a known issue, will let you know.

Here is my project so far. Work to be done. Also things are pretty nested. Feel free to do whatever you want with the provided files. I would also appreciate hints to better coding style if anyone has any advice. To replicate the error I type anything, start the server, then download a few times from localhost in the same app run, then boom!

Edited 5 Years Ago by seanbp: how to replicate error

Attachments
package javaedit;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

class Window {
    // keep track of windows for closeWindow()
    // todo: change to typed linked list
    static Integer windowCount = 0;
}

public class JavaEdit extends JFrame implements Runnable {

    public static void main(String[] args) {
        new JavaEdit();
    }
    private String initialContent;
    private Color initialColor;
    private JTextPane editText;
    private StyledDocument doc;
    private JTextField address;
    private JCheckBoxMenuItem serverCheckBox;
    private JMenuItem connectOption;
    private Thread serverThread = null;
    private int port = 9101;
    private JTextField portField;
    private JFrame clientFrame;

    // Default constructor
    private JavaEdit() {
        new JavaEdit("", Color.BLACK);
    }

    // Actual constructor
    public JavaEdit(String StrinitContent, Color fontColor) {
        initialContent = StrinitContent;
        initialColor = fontColor;
        synchronized (Window.windowCount) {
            Window.windowCount += 1;
            new Thread(this, "Window #"+Window.windowCount).start();
        }
    }

    // every main window has one
    public void run() {

        this.setSize(800, 600);
        this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        JMenuBar menuBar = new JMenuBar();
        JMenu connections = new JMenu("Connections");

        serverCheckBox = new JCheckBoxMenuItem("Serve Current Content");
        serverCheckBox.addActionListener(
                new ActionListener() {

                    public void actionPerformed(ActionEvent e) {
                        invokeServer();
                    }
                });
        connectOption = new JMenuItem("Connect To...");
        connectOption.addActionListener(
                new ActionListener() {

                    public void actionPerformed(ActionEvent e) {
                        connectDialog();
                    }
                });
        ///
        connections.add(serverCheckBox);
        connections.add(connectOption);
        menuBar.add(connections);

        JMenu app = new JMenu("Application");
        JMenuItem newOption = new JMenuItem("New Window");
        newOption.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent ae) {
                new JavaEdit();
            }
        });
        app.add(newOption);
        JMenuItem closeOption = new JMenuItem("Close This Window");
        closeOption.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent ae) {
                closeWindow();
            }
        });
        app.add(closeOption);
        JMenuItem closeall = new JMenuItem("Close All Windows");
        closeall.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent ae) {
                System.exit(0);
            }
        });
        app.add(closeall);
        menuBar.add(app);

        this.setJMenuBar(menuBar);

        editText = new JTextPane();
        editText.setEditable(true);
        editText.setForeground(initialColor);
        editText.setFont(Font.getFont("Monospaced"));
        editText.setText(initialContent);
        editText.addKeyListener(new Highlights());
        editText.addCaretListener(new Highlights());

        doc = (StyledDocument) editText.getDocument();

        JScrollPane scroll = new JScrollPane(editText);
        this.setLayout(new BorderLayout());
        this.add(scroll, BorderLayout.CENTER);

        this.setVisible(true);
    }

    private void closeWindow() {
        synchronized (Window.windowCount) {
            if (Window.windowCount == 1) {
                System.exit(0);
            }
            if (serverThread != null) {
                serverCheckBox.setState(false);
                serverThread.interrupt();
                serverCheckBox = null;
            }
            this.dispose();
            Window.windowCount -= 1;
        }
    }

    // This Dialog sets up, and selects the client connection type
    private void connectDialog() {
        // todo: move this method when if gets bloated
        clientFrame = new JFrame();
        clientFrame.setAlwaysOnTop(true);
        clientFrame.setSize(300, 300);
        clientFrame.setLayout(new FlowLayout());
        address = new JTextField("localhost", 20);
        clientFrame.add(address);
        portField = new JTextField("9101", 20);
        clientFrame.add(portField);
        JButton download = new JButton("Download");
        clientFrame.add(download);
        download.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                try {
                    clientConnection(1);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        JButton send = new JButton("Send");
        clientFrame.add(send);
        send.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                try {
                    clientConnection(0);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        clientFrame.setVisible(true);
    }

    private void clientConnection(int mode) throws Exception {

        // this is the client by which a server is accessed
        // both send and receive
        clientFrame.dispose();
        // todo: replace ArrayList with typed linked list
        port = Integer.parseInt(portField.getText());
        String data = "";
        Socket s = new Socket(address.getText(), port);
        OutputStream os = new BufferedOutputStream(s.getOutputStream());
        InputStream is = new BufferedInputStream(s.getInputStream());
        os.write((byte) mode);
        os.flush();
        if (mode == 0) {
            //send
            synchronized (doc) {
                data = doc.getText(0, doc.getLength());
            }
            char datachar[] = data.toCharArray();
            for (char character : datachar) {
                os.write((byte) character);
            }
            os.flush();
        } else if (mode == 1) {
            //download
            byte buffer[] = new byte[1024];
            int len = is.read(buffer);
            while (len >= 0) {
                data += new String(buffer, 0, len);
                len = is.read(buffer);
            }
            new JavaEdit(data, Color.RED);
        }
        s.close();
    }

    private void invokeServer() {
        try {
            if (serverThread != null) {
                serverThread.interrupt();
                serverThread = null;
                serverCheckBox.setState(false);
                Thread.sleep(300);
                return;
            }
            String SPort = JOptionPane.showInputDialog(this, 
                    "Enter port number", "9101");
            if (SPort == null) return;
            JOptionPane.
                    showMessageDialog(this,
                    "Modifications will not be reflected on server until " +
                    "server is reset.");
            port = Integer.parseInt(SPort); //todo: error checking
            Server i = new Server(doc.getText(0, doc.getLength()), port);
            serverThread = new Thread(i, "A server at port "+port);
            serverThread.start();
            serverCheckBox.setState(true);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        } catch (BadLocationException ex) {
            ex.printStackTrace();
        }
    }

    class Highlights implements KeyListener, CaretListener {
        // highlight all matches for string next to caret

        private Highlighter ohl = null;
        private ArrayList<Object> highlights = new ArrayList();
        private int ipos;
        private int istart, stop;
        private char cmatch;
        private String word;
        private String Strcurrent;
        private Highlighter.HighlightPainter paint = 
                new Painter(Color.LIGHT_GRAY);

        public void keyTyped(KeyEvent e) {
            doUpdate();
        }

        public void keyPressed(KeyEvent e) {
        }

        public void keyReleased(KeyEvent e) {
        }

        public void caretUpdate(CaretEvent e) {
            doUpdate();
        }

        private void doUpdate() {
            try {
                // remove old highlights
                ohl = editText.getHighlighter();
                for (int i = highlights.size()-1; i >= 0; i--) {
                    ohl.removeHighlight(highlights.get(i));
                    highlights.remove(highlights.get(i));
                }
                // get caret ipos
                ipos = editText.getCaret().getDot();
                if (doc.getText(ipos, 1).contains(" ") ||
                        ipos == doc.getLength()) {
                    ipos--;
                }
                // no work for empty document
                if (doc.getLength() == 0) {
                    return;
                }
                //get string to match with: word
                istart = stop = ipos;
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package javaedit;

import java.awt.Color;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

/**
 *
 * @author sean
 */
public class Server implements Runnable {
    // this is the server by which sends and receives are hosted
    // both send and receive

    private String content;
    private int port;

    public Server(String content, int port) {
        this.content = content;
        this.port = port;
    }

    public void run() {

        try {
            ServerSocket ss = new ServerSocket(port);
            ss.setSoTimeout(100);
            while (true) {
                nowServe(ss);
                if (Thread.interrupted()) {
                    ss.close();
                    return;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void nowServe(ServerSocket ss) {
        Socket s = null;
        try {
            try {
                s = ss.accept();
            } catch (SocketTimeoutException e) {
                // not an error
                //e.printStackTrace();
                return;
            }
            InputStream is = new BufferedInputStream(s.getInputStream());
            BufferedOutputStream os =
                    new BufferedOutputStream(s.getOutputStream());
            int mode = is.read();
            if (mode == -1) {
                return;
            }
            if (mode == 0) {
                //send at client
                try {
                    byte[] buffer = new byte[1024];
                    int len = is.read(buffer);
                    String temp = "";
                    while (len >= 0) {
                        for (int i = 0; i < len; i++) {
                            temp += (char) buffer[i];
                        }
                        len = is.read(buffer);
                    }
                    new JavaEdit(temp, Color.WHITE);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            } else if (mode == 1) {
                //download at client
                char[] textchars = content.toCharArray();
                for (int i = 0; i < content.length(); i++) {
                    os.write((byte) textchars[i]);
                }
                os.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally  {
            try {
                s.close();
            } catch (Exception e) {
                //e.printStackTrace();
                return;
            }
        }
    }
}
This question has already been answered. Start a new discussion instead.