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