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