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