Merge branch 'releases/Release_2_11_4_Branch'
[jalview.git] / 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 implements WindowListener,
57         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,
309               bounds.height, 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         }
503         else
504         {
505           try
506           {
507             this.wait(100);
508           } catch (InterruptedException e)
509           {
510
511           }
512         }
513         if (quit)
514         {
515           return;
516         }
517
518       }
519     } catch (Exception e)
520     {
521       textArea.append("\nConsole reports an Internal error.");
522       textArea.append("The error is: " + e.getMessage());
523       // Need to uncomment this to ensure that line tally is synched.
524       // lines += 2;
525       stderr.println("Console reports an Internal error.\nThe error is: "
526               + e);
527     }
528
529     // just for testing (Throw a Nullpointer after 1 second)
530     if (Thread.currentThread() == errorThrower)
531     {
532       try
533       {
534         this.wait(1000);
535       } catch (InterruptedException ie)
536       {
537       }
538       throw new NullPointerException(
539               MessageManager.getString("exception.application_test_npe"));
540     }
541   }
542
543   private void appendToTextArea(final String input)
544   {
545     if (updateConsole == false)
546     {
547       // do nothing;
548       return;
549     }
550     long time = System.nanoTime();
551     javax.swing.SwingUtilities.invokeLater(new Runnable()
552     {
553       @Override
554       public void run()
555       {
556         displayPipe.append(input); // change to stringBuffer
557         // displayPipe.flush();
558
559       }
560     });
561     // stderr.println("Time taken to Spawnappend:\t" + (System.nanoTime() -
562     // time)
563     // + " ns");
564   }
565
566   private String header = null;
567
568   private boolean updateConsole = false;
569
570   private synchronized void trimBuffer(boolean clear)
571   {
572     if (header == null && textArea.getLineCount() > 5)
573     {
574       try
575       {
576         header = textArea.getText(0, textArea.getLineStartOffset(5))
577                 + "\nTruncated...\n";
578       } catch (Exception e)
579       {
580         e.printStackTrace();
581       }
582     }
583     // trim the buffer
584     int tlength = textArea.getDocument().getLength();
585     if (header != null)
586     {
587       if (clear || (tlength > byteslim))
588       {
589         try
590         {
591           if (!clear)
592           {
593             long time = System.nanoTime();
594             textArea.replaceRange(header, 0, tlength - bytescut);
595             // stderr.println("Time taken to cut:\t"
596             // + (System.nanoTime() - time) + " ns");
597           }
598           else
599           {
600             textArea.setText(header);
601           }
602         } catch (Exception e)
603         {
604           e.printStackTrace();
605         }
606         // lines = textArea.getLineCount();
607       }
608     }
609
610   }
611
612   public synchronized String readLine(PipedInputStream in)
613           throws IOException
614   {
615     String input = "";
616     int lp = -1;
617     do
618     {
619       int available = in.available();
620       if (available == 0)
621       {
622         break;
623       }
624       byte b[] = new byte[available];
625       in.read(b);
626       input = input + new String(b, 0, b.length);
627       // counts lines - we don't do this for speed.
628       // while ((lp = input.indexOf("\n", lp + 1)) > -1)
629       // {
630       // lines++;
631       // }
632     } while (!input.endsWith("\n") && !input.endsWith("\r\n") && !quit);
633     return input;
634   }
635
636   public static void main(String[] arg)
637   {
638     new Console().test(); // create console with not reference
639
640   }
641
642   public void setVisible(boolean selected)
643   {
644     frame.setVisible(selected);
645     if (selected == true)
646     {
647       redirectStreams();
648       updateConsole = true;
649       frame.toFront();
650     }
651     else
652     {
653       unredirectStreams();
654       updateConsole = false;
655     }
656   }
657
658   public Rectangle getBounds()
659   {
660     if (frame != null)
661     {
662       return frame.getBounds();
663     }
664     return null;
665   }
666
667   /**
668    * set the banner that appears at the top of the console output
669    * 
670    * @param string
671    */
672   public void setHeader(String string)
673   {
674     header = string;
675     if (header.charAt(header.length() - 1) != '\n')
676     {
677       header += "\n";
678     }
679     textArea.insert(header, 0);
680   }
681
682   /**
683    * get the banner
684    * 
685    * @return
686    */
687   public String getHeader()
688   {
689     return header;
690   }
691 }