Source code: com/eireneh/swing/DocumentWriter.java
1
2 package com.eireneh.swing;
3
4 import java.io.*;
5 import java.lang.reflect.InvocationTargetException;
6
7 import javax.swing.*;
8 import javax.swing.text.*;
9 import javax.swing.event.*;
10
11 import com.eireneh.util.LogicError;
12
13 /**
14 * A DocumentWriter is-a Writer that uses a Document so all text printed
15 * to the Writer ends up in the JTextArea.
16 * A Document is a Container for text that supports editing and provides
17 * notification of changes (serves as the model in an MVC relationship).
18 *
19 * <table border='1' cellPadding='3' cellSpacing='0' width="100%">
20 * <tr><td bgColor='white'class='TableRowColor'><font size='-7'>
21 * Distribution Licence:<br />
22 * Project B is free software; you can redistribute it
23 * and/or modify it under the terms of the GNU General Public License,
24 * version 2 as published by the Free Software Foundation.<br />
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 * General Public License for more details.<br />
29 * The License is available on the internet
30 * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, by writing to
31 * <i>Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
32 * MA 02111-1307, USA</i>, Or locally at the Licence link below.<br />
33 * The copyright to this program is held by it's authors.
34 * </font></td></tr></table>
35 * @see <a href='http://www.eireneh.com/servlets/Web'>Project B Home</a>
36 * @see docs.Licence
37 * @author Joe Walker
38 */
39 public class DocumentWriter extends Writer
40 {
41 /**
42 * Create the DocumentWriter with no Document, that just
43 * dumps the text it get into the bin
44 */
45 public DocumentWriter()
46 {
47 }
48
49 /**
50 * Create the DocumentWriter with a Document to write to
51 * @param doc The destination Document
52 */
53 public DocumentWriter(Document doc)
54 {
55 this.doc = doc;
56 }
57
58 /**
59 * Accessor for the Document that we are updating
60 * @param doc The current document
61 */
62 public Document getDocument()
63 {
64 return doc;
65 }
66
67 /**
68 * Accessor for the Document that we are updating
69 * @return The new document
70 */
71 public void setDocument(Document doc)
72 {
73 try
74 {
75 flush();
76 }
77 catch (IOException ex)
78 {
79 // we just wanted to make sure that updates to the old
80 // Document didn't go to the new one, so dumping the
81 // exception whilst not ideal seems like the best option.
82 }
83
84 synchronized (lock)
85 {
86 this.doc = doc;
87 }
88 }
89
90 /**
91 * Write a portion of an array of characters.
92 * @param cbuf Array of characters
93 * @param off Offset from which to start writing characters
94 * @param len Number of characters to write
95 * @exception IOException If an I/O error occurs
96 */
97 public void write(char[] cbuf, int off, int len) throws IOException
98 {
99 synchronized (lock)
100 {
101 queue = queue + new String(cbuf, off, len);
102 update();
103 }
104 }
105
106 /**
107 * Write a single character. The character to be written is contained in
108 * the 16 low-order bits of the given integer value; the 16 high-order bits
109 * are ignored.
110 * <p> Subclasses that intend to support efficient single-character output
111 * should override this method.
112 * @exception IOException If an I/O error occurs
113 */
114 public void write(int c) throws IOException
115 {
116 synchronized (lock)
117 {
118 queue = queue + (char) c;
119 update();
120 }
121 }
122
123 /**
124 * Write an array of characters.
125 * @param cbuf Array of characters to be written
126 * @exception IOException If an I/O error occurs
127 */
128 public void write(char cbuf[]) throws IOException
129 {
130 synchronized (lock)
131 {
132 queue = queue + cbuf;
133 update();
134 }
135 }
136
137 /**
138 * Write a string.
139 * @param str String to be written
140 * @exception IOException If an I/O error occurs
141 */
142 public void write(String str) throws IOException
143 {
144 synchronized (lock)
145 {
146 queue = queue + str;
147 update();
148 }
149 }
150
151 /**
152 * Write a portion of a string.
153 * @param str A String
154 * @param off Offset from which to start writing characters
155 * @param len Number of characters to write
156 * @exception IOException If an I/O error occurs
157 */
158 public void write(String str, int off, int len) throws IOException
159 {
160 synchronized (lock)
161 {
162 queue = queue + str.substring(off, off+len);
163 update();
164 }
165 }
166
167 /**
168 * Set up the gui to read an update. Note this must only be called
169 * from within a synchronized (lock) section of code
170 */
171 private void update()
172 {
173 if (updater == null)
174 {
175 updater = new Updater();
176 SwingUtilities.invokeLater(updater);
177 }
178 }
179
180 /**
181 * Flush the stream. If the stream has saved any characters from the
182 * various write() methods in a buffer, write them immediately to their
183 * intended destination. Then, if that destination is another character or
184 * byte stream, flush it. Thus one flush() invocation will flush all the
185 * buffers in a chain of Writers and OutputStreams.
186 * @exception IOException If an I/O error occurs
187 */
188 public void flush() throws IOException
189 {
190 if (updater != null)
191 {
192 // Changes are outstanding. It is OK to force an update using
193 // this method because the scheduled update will kick in
194 // later, find that the queue is empty, and do nothing. No
195 // problem. It would be good to cancel an update but I dont
196 // know of a way to do that.
197 try
198 {
199 SwingUtilities.invokeAndWait(updater);
200 }
201 catch (InterruptedException ex)
202 {
203 throw new IOException(""+ex);
204 }
205 catch (InvocationTargetException ex)
206 {
207 throw new IOException(""+ex);
208 }
209 }
210 }
211
212 /**
213 * Close the stream, flushing it first. Once a stream has been closed,
214 * further write() or flush() invocations will cause an IOException to be
215 * thrown. Closing a previously-closed stream, however, has no effect.
216 * @exception IOException If an I/O error occurs
217 */
218 public void close() throws IOException
219 {
220 closed = true;
221 }
222
223 /** The object to lock on to read or write the queue or the updater */
224 protected Object lock = new Object();
225
226 /** The queue of strings to be added to the GUI */
227 protected String queue = "";
228
229 /** The destination Document */
230 protected Document doc = null;
231
232 /** The destination Document */
233 protected boolean closed = false;
234
235 /** The updater waiting to be run */
236 protected Updater updater = null;
237
238 /**
239 * For Thread/Swing correctness we should only update in the GUI thread
240 */
241 class Updater implements Runnable
242 {
243 public void run()
244 {
245 synchronized (lock)
246 {
247 try
248 {
249 doc.insertString(doc.getLength(), queue, null);
250 }
251 catch (BadLocationException ex)
252 {
253 throw new LogicError();
254 }
255
256 queue = "";
257
258 // This simply releases the pointer that our parent had
259 // to us, it does not affect how this thread is being
260 // executed. The practical effect is that any further
261 // writes know to create a new updater
262 updater = null;
263 }
264 }
265 }
266 }