965d010d0e7909f96c0b9fd041f8de1bc8264aa6
[jalview.git] / src / jalview / gui / Console.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import jalview.util.MessageManager;
21
22 import java.io.*;
23 import java.awt.*;
24 import java.awt.event.*;
25 import javax.swing.*;
26
27 import org.apache.log4j.SimpleLayout;
28
29 /**
30  * Simple Jalview Java Console. Version 1 - allows viewing of console output
31  * after desktop is created. Acquired with thanks from RJHM's site
32  * http://www.comweb.nl/java/Console/Console.html A simple Java Console for your
33  * application (Swing version) Requires Java 1.1.5 or higher Disclaimer the use
34  * of this source is at your own risk. Permision to use and distribute into your
35  * own applications RJHM van den Bergh , rvdb@comweb.nl
36  */
37
38 public class Console extends WindowAdapter implements WindowListener,
39         ActionListener, Runnable
40 {
41   private JFrame frame;
42
43   private JTextArea textArea;
44
45   /*
46    * unused - tally and limit for lines in console window int lines = 0;
47    * 
48    * int lim = 1000;
49    */
50   int byteslim = 102400, bytescut = 76800; // 100k and 75k cut point.
51
52   private Thread reader, reader2, textAppender;
53
54   private boolean quit;
55
56   private final PrintStream stdout = System.out, stderr = System.err;
57
58   private PipedInputStream pin = new PipedInputStream();
59
60   private PipedInputStream pin2 = new PipedInputStream();
61
62   private StringBuffer displayPipe = new StringBuffer();
63
64   Thread errorThrower; // just for testing (Throws an Exception at this Console
65
66   // are we attached to some parent Desktop
67   Desktop parent = null;
68
69   public Console()
70   {
71     // create all components and add them
72     Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
73     frame = initFrame("Java Console", screenSize.width / 2,
74             screenSize.height / 2, -1, -1);
75     initConsole(true);
76   }
77
78   private void initConsole(boolean visible)
79   {
80     initConsole(visible, true);
81   }
82
83   /**
84    * 
85    * @param visible
86    *          - open the window
87    * @param redirect
88    *          - redirect std*
89    */
90   private void initConsole(boolean visible, boolean redirect)
91   {
92     // CutAndPasteTransfer cpt = new CutAndPasteTransfer();
93     // textArea = cpt.getTextArea();
94     textArea = new JTextArea();
95     textArea.setEditable(false);
96     JButton button = new JButton(MessageManager.getString("action.clear"));
97
98     // frame = cpt;
99     frame.getContentPane().setLayout(new BorderLayout());
100     frame.getContentPane().add(new JScrollPane(textArea),
101             BorderLayout.CENTER);
102     frame.getContentPane().add(button, BorderLayout.SOUTH);
103     frame.setVisible(visible);
104     updateConsole = visible;
105     frame.addWindowListener(this);
106     button.addActionListener(this);
107     if (redirect)
108     {
109       redirectStreams();
110     }
111     else
112     {
113       unredirectStreams();
114     }
115     quit = false; // signals the Threads that they should exit
116
117     // Starting two seperate threads to read from the PipedInputStreams
118     //
119     reader = new Thread(this);
120     reader.setDaemon(true);
121     reader.start();
122     //
123     reader2 = new Thread(this);
124     reader2.setDaemon(true);
125     reader2.start();
126     // and a thread to append text to the textarea
127     textAppender = new Thread(this);
128     textAppender.setDaemon(true);
129     textAppender.start();
130   }
131
132   PipedOutputStream pout = null, perr = null;
133
134   public void redirectStreams()
135   {
136     if (pout == null)
137     {
138       try
139       {
140         pout = new PipedOutputStream(this.pin);
141         System.setOut(new PrintStream(pout, true));
142       } catch (java.io.IOException io)
143       {
144         textArea.append("Couldn't redirect STDOUT to this console\n"
145                 + io.getMessage());
146         io.printStackTrace(stderr);
147       } catch (SecurityException se)
148       {
149         textArea.append("Couldn't redirect STDOUT to this console\n"
150                 + se.getMessage());
151         se.printStackTrace(stderr);
152       }
153
154       try
155       {
156         perr = new PipedOutputStream(this.pin2);
157         System.setErr(new PrintStream(perr, true));
158       } catch (java.io.IOException io)
159       {
160         textArea.append("Couldn't redirect STDERR to this console\n"
161                 + io.getMessage());
162         io.printStackTrace(stderr);
163       } catch (SecurityException se)
164       {
165         textArea.append("Couldn't redirect STDERR to this console\n"
166                 + se.getMessage());
167         se.printStackTrace(stderr);
168       }
169     }
170   }
171
172   public void unredirectStreams()
173   {
174     if (pout != null)
175     {
176       try
177       {
178         System.setOut(stdout);
179         pout.flush();
180         pout.close();
181         pin = new PipedInputStream();
182         pout = null;
183       } catch (java.io.IOException io)
184       {
185         textArea.append("Couldn't unredirect STDOUT to this console\n"
186                 + io.getMessage());
187         io.printStackTrace(stderr);
188       } catch (SecurityException se)
189       {
190         textArea.append("Couldn't unredirect STDOUT to this console\n"
191                 + se.getMessage());
192         se.printStackTrace(stderr);
193       }
194
195       try
196       {
197         System.setErr(stderr);
198         perr.flush();
199         perr.close();
200         pin2 = new PipedInputStream();
201         perr = null;
202       } catch (java.io.IOException io)
203       {
204         textArea.append("Couldn't unredirect STDERR to this console\n"
205                 + io.getMessage());
206         io.printStackTrace(stderr);
207       } catch (SecurityException se)
208       {
209         textArea.append("Couldn't unredirect STDERR to this console\n"
210                 + se.getMessage());
211         se.printStackTrace(stderr);
212       }
213     }
214   }
215
216   public void test()
217   {
218     // testing part
219     // you may omit this part for your application
220     //
221
222     System.out.println("Hello World 2");
223     System.out.println("All fonts available to Graphic2D:\n");
224     GraphicsEnvironment ge = GraphicsEnvironment
225             .getLocalGraphicsEnvironment();
226     String[] fontNames = ge.getAvailableFontFamilyNames();
227     for (int n = 0; n < fontNames.length; n++)
228       System.out.println(fontNames[n]);
229     // Testing part: simple an error thrown anywhere in this JVM will be printed
230     // on the Console
231     // We do it with a seperate Thread becasue we don't wan't to break a Thread
232     // used by the Console.
233     System.out.println("\nLets throw an error on this console");
234     errorThrower = new Thread(this);
235     errorThrower.setDaemon(true);
236     errorThrower.start();
237   }
238
239   private JFrame initFrame(String string, int i, int j, int x, int y)
240   {
241     JFrame frame = new JFrame(string);
242     frame.setName(string);
243     if (x == -1)
244       x = (int) (i / 2);
245     if (y == -1)
246       y = (int) (j / 2);
247     frame.setBounds(x, y, i, j);
248     return frame;
249   }
250
251   /**
252    * attach a console to the desktop - the desktop will open it if requested.
253    * 
254    * @param desktop
255    */
256   public Console(Desktop desktop)
257   {
258     this(desktop, true);
259   }
260
261   /**
262    * attach a console to the desktop - the desktop will open it if requested.
263    * 
264    * @param desktop
265    * @param showjconsole
266    *          - if true, then redirect stdout immediately
267    */
268   public Console(Desktop desktop, boolean showjconsole)
269   {
270     parent = desktop;
271     // window name - get x,y,width, height possibly scaled
272     Rectangle bounds = desktop.getLastKnownDimensions("JAVA_CONSOLE_");
273     if (bounds == null)
274     {
275       frame = initFrame("Jalview Java Console", desktop.getWidth() / 2,
276               desktop.getHeight() / 4, desktop.getX(), desktop.getY());
277     }
278     else
279     {
280       frame = initFrame("Jalview Java Console", bounds.width,
281               bounds.height, bounds.x, bounds.y);
282     }
283     // desktop.add(frame);
284     initConsole(false);
285     JalviewAppender jappender = new JalviewAppender();
286     jappender.setLayout(new SimpleLayout());
287     JalviewAppender.setTextArea(textArea);
288     org.apache.log4j.Logger.getRootLogger().addAppender(jappender);
289   }
290
291   public synchronized void stopConsole()
292   {
293     quit = true;
294     this.notifyAll();
295     /*
296      * reader.notify(); reader2.notify(); if (errorThrower!=null)
297      * errorThrower.notify(); // stop all threads if (textAppender!=null)
298      * textAppender.notify();
299      */
300     if (pout != null)
301     {
302       try
303       {
304         reader.join(10);
305         pin.close();
306       } catch (Exception e)
307       {
308       }
309       try
310       {
311         reader2.join(10);
312         pin2.close();
313       } catch (Exception e)
314       {
315       }
316       try
317       {
318         textAppender.join(10);
319       } catch (Exception e)
320       {
321       }
322     }
323     if (!frame.isVisible())
324     {
325       frame.dispose();
326     }
327     // System.exit(0);
328   }
329
330   public synchronized void windowClosed(WindowEvent evt)
331   {
332     frame.setVisible(false);
333     closeConsoleGui();
334   }
335
336   private void closeConsoleGui()
337   {
338     updateConsole = false;
339     if (parent == null)
340     {
341
342       stopConsole();
343     }
344     else
345     {
346       parent.showConsole(false);
347     }
348   }
349
350   public synchronized void windowClosing(WindowEvent evt)
351   {
352     frame.setVisible(false); // default behaviour of JFrame
353     closeConsoleGui();
354
355     // frame.dispose();
356   }
357
358   public synchronized void actionPerformed(ActionEvent evt)
359   {
360     trimBuffer(true);
361     // textArea.setText("");
362   }
363
364   public synchronized void run()
365   {
366     try
367     {
368       while (Thread.currentThread() == reader)
369       {
370         if (pin == null || pin.available() == 0)
371         {
372           try
373           {
374             this.wait(100);
375             if (pin.available() == 0)
376             {
377               trimBuffer(false);
378             }
379           } catch (InterruptedException ie)
380           {
381           }
382         }
383
384         while (pin.available() != 0)
385         {
386           String input = this.readLine(pin);
387           stdout.print(input);
388           long time = System.nanoTime();
389           appendToTextArea(input);
390           // stderr.println("Time taken to stdout append:\t"
391           // + (System.nanoTime() - time) + " ns");
392           // lines++;
393         }
394         if (quit)
395           return;
396       }
397
398       while (Thread.currentThread() == reader2)
399       {
400         if (pin2.available() == 0)
401         {
402           try
403           {
404             this.wait(100);
405             if (pin2.available() == 0)
406             {
407               trimBuffer(false);
408             }
409           } catch (InterruptedException ie)
410           {
411           }
412         }
413         while (pin2.available() != 0)
414         {
415           String input = this.readLine(pin2);
416           stderr.print(input);
417           long time = System.nanoTime();
418           appendToTextArea(input);
419           // stderr.println("Time taken to stderr append:\t"
420           // + (System.nanoTime() - time) + " ns");
421           // lines++;
422         }
423         if (quit)
424           return;
425       }
426       while (Thread.currentThread() == textAppender)
427       {
428         if (updateConsole)
429         {
430           // check string buffer - if greater than console, clear console and
431           // replace with last segment of content, otherwise, append all to
432           // content.
433           long count;
434           while (displayPipe.length() > 0)
435           {
436             count = 0;
437             StringBuffer tmp = new StringBuffer(), replace;
438             synchronized (displayPipe)
439             {
440               replace = displayPipe;
441               displayPipe = tmp;
442             }
443             // simply append whole buffer
444             textArea.append(replace.toString());
445             count += replace.length();
446             if (count > byteslim)
447             {
448               trimBuffer(false);
449             }
450           }
451           if (displayPipe.length() == 0)
452           {
453             try
454             {
455               this.wait(100);
456               if (displayPipe.length() == 0)
457               {
458                 trimBuffer(false);
459               }
460             } catch (InterruptedException e)
461             {
462             }
463             ;
464           }
465         }
466         else
467         {
468           try
469           {
470             this.wait(100);
471           } catch (InterruptedException e)
472           {
473
474           }
475         }
476         if (quit)
477         {
478           return;
479         }
480
481       }
482     } catch (Exception e)
483     {
484       textArea.append("\nConsole reports an Internal error.");
485       textArea.append("The error is: " + e.getMessage());
486       // Need to uncomment this to ensure that line tally is synched.
487       // lines += 2;
488       stderr.println("Console reports an Internal error.\nThe error is: "
489               + e);
490     }
491
492     // just for testing (Throw a Nullpointer after 1 second)
493     if (Thread.currentThread() == errorThrower)
494     {
495       try
496       {
497         this.wait(1000);
498       } catch (InterruptedException ie)
499       {
500       }
501       throw new NullPointerException(
502               "Application test: throwing an NullPointerException It should arrive at the console");
503     }
504   }
505
506   private void appendToTextArea(final String input)
507   {
508     if (updateConsole == false)
509     {
510       // do nothing;
511       return;
512     }
513     long time = System.nanoTime();
514     javax.swing.SwingUtilities.invokeLater(new Runnable()
515     {
516       public void run()
517       {
518         displayPipe.append(input); // change to stringBuffer
519         // displayPipe.flush();
520
521       }
522     });
523     // stderr.println("Time taken to Spawnappend:\t" + (System.nanoTime() -
524     // time)
525     // + " ns");
526   }
527
528   private String header = null;
529
530   private boolean updateConsole = false;
531
532   private synchronized void trimBuffer(boolean clear)
533   {
534     if (header == null && textArea.getLineCount() > 5)
535     {
536       try
537       {
538         header = textArea.getText(0, textArea.getLineStartOffset(5))
539                 + "\nTruncated...\n";
540       } catch (Exception e)
541       {
542         e.printStackTrace();
543       }
544     }
545     // trim the buffer
546     int tlength = textArea.getDocument().getLength();
547     if (header != null)
548     {
549       if (clear || (tlength > byteslim))
550       {
551         try
552         {
553           if (!clear)
554           {
555             long time = System.nanoTime();
556             textArea.replaceRange(header, 0, tlength - bytescut);
557             // stderr.println("Time taken to cut:\t"
558             // + (System.nanoTime() - time) + " ns");
559           }
560           else
561           {
562             textArea.setText(header);
563           }
564         } catch (Exception e)
565         {
566           e.printStackTrace();
567         }
568         // lines = textArea.getLineCount();
569       }
570     }
571
572   }
573
574   public synchronized String readLine(PipedInputStream in)
575           throws IOException
576   {
577     String input = "";
578     int lp = -1;
579     do
580     {
581       int available = in.available();
582       if (available == 0)
583         break;
584       byte b[] = new byte[available];
585       in.read(b);
586       input = input + new String(b, 0, b.length);
587       // counts lines - we don't do this for speed.
588       // while ((lp = input.indexOf("\n", lp + 1)) > -1)
589       // {
590       // lines++;
591       // }
592     } while (!input.endsWith("\n") && !input.endsWith("\r\n") && !quit);
593     return input;
594   }
595
596   public static void main(String[] arg)
597   {
598     new Console().test(); // create console with not reference
599
600   }
601
602   public void setVisible(boolean selected)
603   {
604     frame.setVisible(selected);
605     if (selected == true)
606     {
607       redirectStreams();
608       updateConsole = true;
609       frame.toFront();
610     }
611     else
612     {
613       unredirectStreams();
614       updateConsole = false;
615     }
616   }
617
618   public Rectangle getBounds()
619   {
620     if (frame != null)
621     {
622       return frame.getBounds();
623     }
624     return null;
625   }
626
627   /**
628    * set the banner that appears at the top of the console output
629    * 
630    * @param string
631    */
632   public void setHeader(String string)
633   {
634     header = string;
635     if (header.charAt(header.length() - 1) != '\n')
636     {
637       header += "\n";
638     }
639     textArea.insert(header, 0);
640   }
641
642   /**
643    * get the banner
644    * 
645    * @return
646    */
647   public String getHeader()
648   {
649     return header;
650   }
651 }