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