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