Source code: jpicedt/ui/util/SystemOutUtilities.java
1 /* jPicEdt version 1.3.2, a picture editor for LaTeX.
2 Copyright (C) 1999-2002 Sylvain Reynal
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 Sylvain Reynal
19 Département de Physique
20 Ecole Nationale Supérieure de l'Electronique et de ses Applications (ENSEA)
21 6, avenue du Ponceau
22 95014 CERGY CEDEX
23 FRANCE
24
25 Tel : 00 +33 130 736 245
26 Fax : 00 +33 130 736 667
27 e-mail : reynal@ensea.fr
28 jPicEdt web page : http://trashx.ensea.fr/jpicedt/
29 */
30
31 package jpicedt.ui.util;
32
33 import java.awt.*;
34 import java.awt.event.*;
35 import javax.swing.*;
36 import java.io.*;
37 import java.util.*;
38
39 /**
40 * A class that allow redirection of stderr and/or stdout to a log file.
41 * Invokation of this class must be done with "SystemOutUtilities.instance()" which return a reference to the singleton.
42 *
43 * @author Sylvain Reynal
44 * @since PicEdt 1.3
45 */
46 public class SystemOutUtilities {
47
48 /** doesn't redirect stdout to a file, i.e. redirect to console ; instead open a frame and displays the error message in it */
49 public static final int STANDARD = 1;
50
51 /** redirects stdout to a file named "jPicedtError.log" */
52 public static final int FILE = 2;
53
54
55 /* DEBUG flag (this amongst other thing redirects only System.err, so that we can print debug messages to System.out) */
56 private static final boolean DEBUG=false;
57
58
59
60
61
62
63
64
65
66 ////////////////////////////////////////
67 //// PUBLIC STATIC METHODS
68 ////////////////////////////////////////
69 private static SystemOutUtilities singleton = null;
70
71 /**
72 * intanciates singleton if it's null, then returns it
73 */
74 public static SystemOutUtilities instance(){
75
76 if (singleton == null) singleton = new SystemOutUtilities();
77 return singleton;
78 }
79
80 //////////////////////////////////////////////////////////////////////
81 //// PUBLIC NON-STATIC METHODS (accessed through a call to instance())
82 //////////////////////////////////////////////////////////////////////
83 private int currentRedir = STANDARD; // when this class get accessed for the first time, System.out = standard out
84
85 /**
86 * Redirect to the stream of the given type
87 * @param type one of the predefinite SystemOutUtilities's redirection types
88 */
89 public void redirect(int type){
90
91 if (DEBUG) System.out.println("SystemOutUtilities.Redirecting System.out : type = " + (type==FILE ? "FILE" : "STANDARD"));
92
93 // first close open streams and release resource (currentRedir = old redir type)
94 switch(currentRedir){
95 case FILE :
96 stopSystemOutToFileRedirection();
97 break;
98 default:
99 // this was standard out => nothing to close
100 break;
101 }
102 // now process redirection :
103 currentRedir = type;
104 switch(currentRedir){
105 case FILE :
106 redirectSystemOutToFile();
107 break;
108 default:
109 // standard out
110 break;
111 }
112 }
113
114
115 /**
116 * Sets the "displayDialog" flag, i.e. what must happen when an error message comes up and redir=FILE : do we open a JDialog
117 * or simply write the error message to the log file ?
118 * (obviously, calling this method if redir=STANDARD simply makes no sense)
119 *
120 * @param state if currentRedir = FILE and :
121 * - state==TRUE, enables opening a dialog box whenever some String is written to System.out
122 * A "watchdog" Thread is created for that purpose : System.out is redirected to a pipe, which the Thread watches periodically.
123 * - state==FALSE, redirect System.out directly to the "toFilePrintStream" FOS, and kill the watchdog Thread if it's still alive.
124 *
125 * Otherwise (i.e. currentRedir = STANDARD), we do nothing.
126 */
127 public void displayDialog(boolean state){
128
129 if (DEBUG) System.out.println("SystemOutUtilities.displayDialog(" + state + ")");
130
131 if (currentRedir==STANDARD) return; // this may also happen if "redirectSystemOutToFile" previously failed
132
133 // flag to signals Dialog enabling
134 displayDialog = state;
135 // redirect System.out to pipe
136 if (state==true){
137 try{
138 // create output pipe for System.out
139 PipedOutputStream pos = new PipedOutputStream(); // not yet connected
140 newSystemOut = new PrintStream(new BufferedOutputStream(pos,1024), true); // autoflush = true
141 // create input pipe (we'll read from it and send date to file/JTextPane/etc...)
142 PipedInputStream pis = new PipedInputStream();
143 bufReader = new BufferedReader(new InputStreamReader(pis),1024);
144 // connect pipes
145 pos.connect(pis);
146 // start watchdog timer
147 lt = new ListenerThread();
148 lt.start();
149 }
150 catch(IOException e){
151 System.err.println("SystemOutUtilities.Cannot redirect System.out and System.err to pipe !");
152 e.printStackTrace();
153 newSystemOut = null;
154 return;
155 }
156 // set System.out and System.err to the previously created pipe, from which we'll read (through "pis") text
157 // and then send it to a JTextArea :
158 if (!DEBUG) System.setOut(newSystemOut);
159 System.setErr(newSystemOut);
160 }
161
162 // we switch back to "redirection to toFilePrintStream"
163 else{
164 newSystemOut = toFilePrintStream;
165 if (!DEBUG) System.setOut(newSystemOut);
166 System.setErr(newSystemOut);
167 lt = null; // stop thread
168 }
169 }
170
171
172
173
174
175
176
177
178 ////////////////////////////////////////////////////////////
179 //// PRIVATE METHODS AND FIELDS
180 ///////////////////////////////////////////////////////////
181
182 /**
183 * constructor is protected since the only way to access this class is by using SystemOutUtilities.instance()
184 */
185 private SystemOutUtilities() {
186
187 // remember default System.out and System.err
188 oldSystemOut = System.out;
189 oldSystemErr = System.err;
190 }
191
192 /* private variables */
193 private PrintStream oldSystemOut; // save old System.out
194 private PrintStream newSystemOut; // new System.out (with redirection)
195 private PrintStream oldSystemErr; // save old System.err
196 private BufferedReader bufReader; // a buffer reader that is connected, via a pipe, to System.out, and allow us to display error message in a JTextArea
197 private PrintStream toFilePrintStream; // a print stream that writes to the log file
198 private ListenerThread lt; // a watchdog thread that listen to incoming error messages from System.out, via a pipe
199 private boolean displayDialog = false; // if TRUE, a dialog box opens whenever a message comes up (or within a short delay)
200 private SystemOutToFrame systemOutToFrameInstance=null; // an instance of the JFrame used to display messages ; if it's null, we create a new one; otherwise we use the existing one, so as to avoid opening more than one frame.
201
202 /**
203 * Redirect System.out to a file named "jPicEdtError.log" located in user's home directory
204 * - displayDialog = false by default (i.e. no dialog box gets opened when an error message occurs)
205 * - we init "toFilePrintStream" so that it points to "user's setting dir/error.log"
206 * - we set System.out to this stream (redirection)
207 */
208 private void redirectSystemOutToFile(){
209
210 if (DEBUG) System.out.println("SystemOutUtilities.redirectSystemOutToFile()...");
211 try{
212 // create output stream to log file
213 String fileName = getErrorLogFile();
214 toFilePrintStream = new PrintStream(new BufferedOutputStream(new FileOutputStream(fileName), 1024), true); // autoflush = true
215 newSystemOut = toFilePrintStream;
216 }
217 // if an exception occurs, we switch back to "redirect to console"
218 catch(IOException e){
219 System.err.println("Can not redirect System.out and System.err to a file !");
220 e.printStackTrace();
221 newSystemOut = null;
222 currentRedir = STANDARD;
223 return;
224 }
225 // and redirect System.out if success
226 if (!DEBUG) System.setOut(newSystemOut);
227 System.setErr(newSystemOut);
228
229 }
230
231 /**
232 * Return the path to error.log
233 */
234 public static String getErrorLogFile(){
235 return jpicedt.JPicEdt.getUserSettingsDirectory() + File.separator + "error.log";
236 }
237
238 /**
239 * After a redirection to a pipe, sets back System.out and System.err to their default value
240 */
241 private void stopSystemOutToFileRedirection(){
242
243 if (DEBUG) System.out.println("SystemOutUtilities.stopSystemOutToFileRedirection() : closing previous streams...");
244
245 if (newSystemOut == null) return;
246 if (lt!= null) lt.interrupt(); // flush bufReader
247
248 if (!DEBUG) System.setOut(oldSystemOut); // restore System.out default PrintStream
249 System.setErr(oldSystemErr); // restore System.err default PrintStream
250 newSystemOut.flush();
251
252 // flush'n close previous stream (so that pending messages get written to jPicEdtError.log)
253 toFilePrintStream.flush();
254 toFilePrintStream.close();
255
256 // kill thread
257 lt = null;
258 }
259
260 /**
261 * the watchdog thread that periodically listens to incoming error messages (currently set to 3")
262 * then writes them to file and/or to a JTextArea (see inner class SystemOutToFrame)
263 * kill it by setting "lt = null"
264 */
265 class ListenerThread extends Thread {
266
267 public ListenerThread() {
268 super("SystemOutUtilities.ListenerThread");
269 if (DEBUG) System.out.println("SystemOutUtilities : new ListenerThread()");
270 setPriority(Thread.MIN_PRIORITY);
271 // start(); // now done by caller (due to contention pbs)
272 }
273
274 public void run(){
275
276 if (DEBUG) System.out.println("SystemOutUtilities.ListenerThread started...");
277
278 Thread c = Thread.currentThread();
279 while(c == lt){ // i.e. while this Thread is still a live
280 if (DEBUG) System.out.println("SystemOutUtilities.ListenerThread is alive !");
281 try{
282 if (DEBUG) System.out.println("SystemOutUtilities : there's nothing in System.out pipe !");
283 // if there's nothing in bufReader (i.e. no incoming message)
284 while(bufReader != null && !bufReader.ready()){
285 sleep(2999);
286 }
287 }
288 catch(InterruptedException ie){ie.printStackTrace();}
289 catch(IOException ioe){ioe.printStackTrace();}
290
291 // if something has been written to System.out...
292 if (bufReader != null){
293 String str;
294 StringBuffer buf = new StringBuffer(100);
295 try{
296 // read as much line as possible from bufReader (that is, from the pipe)
297 // then, 1°) write them to file 2°) display them in a JDialog
298 while(bufReader.ready() && (str = bufReader.readLine()) != null){
299 if (DEBUG) System.out.println("SystemOutUtilities.Writing to jPicEdtError.log : " + str);
300 toFilePrintStream.println(str);
301 buf.append(str);
302 buf.append("\n");
303 }
304 }
305 catch(Exception e){e.printStackTrace();}
306 if (displayDialog) {
307 if (systemOutToFrameInstance == null) systemOutToFrameInstance = new SystemOutToFrame();
308 systemOutToFrameInstance.println(buf.toString());
309 }
310 }
311 }
312 if (DEBUG) System.out.println("SystemOutUtilities.ListenerThread killed...");
313 }
314 }
315
316 /**
317 * A JDialog box which is used to display error message in a JTextPane or to a file.
318 */
319 class SystemOutToFrame extends JDialog {
320
321 JTextArea streamTA = new JTextArea(50,20);
322 JCheckBox dontShowAgainCB;
323
324 /**
325 * build then asyncrhonously show a JDialog
326 * this method is thread-safe regarding Swing paint mechanism
327 */
328 SystemOutToFrame(){
329 super();
330 setTitle("jPicEdt Error Log");
331 setModal(true);
332 if (DEBUG) System.out.println("SystemOutUtilities.Opening a new SystemOutToFrame...");
333 enableEvents(AWTEvent.WINDOW_EVENT_MASK);
334
335 JPanel panelStream = new JPanel(new BorderLayout(5,5));
336 panelStream.setBorder(BorderFactory.createEtchedBorder());
337
338 // init text area :
339 streamTA.setEditable(false);
340 JScrollPane scrollStream = new JScrollPane(streamTA);
341 panelStream.add(scrollStream, BorderLayout.CENTER);
342
343 // init widgets
344 JPanel p = new JPanel(new GridLayout(2,1,5,5));
345 p.add(new JTextArea("Beta release BUG REPORT : please send "
346 + System.getProperty("user.home") + System.getProperty("file.separator")
347 + "jPicEdtError.log to reynal@ensea.fr, it'll be very helpful !\n"
348 + "Getting annoyed ? Well, launching jpicedt with '-redir=standard' as the first arg simply writes these messages to the console."));
349 dontShowAgainCB = new JCheckBox("Pretty boring indeed, so please don't show me the same message again and again :-((");
350 dontShowAgainCB.addActionListener(new ActionListener(){
351 public void actionPerformed(ActionEvent e){SystemOutUtilities.instance().displayDialog(false);}});
352 p.add(dontShowAgainCB);
353 panelStream.add(p,BorderLayout.SOUTH);
354
355 // add panelStream to the frame :
356 getContentPane().add(panelStream,BorderLayout.NORTH);
357 // ensure calling SystemOutToFrame is thread-safe
358 SwingUtilities.invokeLater(new Runnable(){
359 public void run(){
360 pack();
361 setVisible(true);}});
362 }
363
364 /**
365 * print out the given text in this JDialog
366 */
367 public void println(String text){
368
369 streamTA.append(text);
370 streamTA.append("\n");
371 }
372
373 /**
374 * dispose this frame
375 */
376 protected void processWindowEvent(WindowEvent e) {
377
378 super.processWindowEvent(e);
379 if (e.getID() == WindowEvent.WINDOW_CLOSING) {
380 dispose();
381 systemOutToFrameInstance = null;
382 }
383 }
384 } //SystemOutToFrame
385
386 } // class SystemOutUtilities
387
388
389