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