View Javadoc
1   //
2   // $Id: JUploadTextArea.java 95 2007-05-02 03:27:05Z
3   // /C=DE/ST=Baden-Wuerttemberg/O=ISDN4Linux/OU=Fritz
4   // Elfert/CN=svn-felfert@isdn4linux.de/emailAddress=fritz@fritz-elfert.de $
5   //
6   // jupload - A file upload applet.
7   // Copyright 2007 The JUpload Team
8   //
9   // Created: ?
10  // Creator: William JinHua Kwong
11  // Last modified: $Date: 2011-01-19 15:52:15 +0100 (mer., 19 janv. 2011) $
12  //
13  // This program is free software; you can redistribute it and/or modify it under
14  // the terms of the GNU General Public License as published by the Free Software
15  // Foundation; either version 2 of the License, or (at your option) any later
16  // version. This program is distributed in the hope that it will be useful, but
17  // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19  // details. You should have received a copy of the GNU General Public License
20  // along with this program; if not, write to the Free Software Foundation, Inc.,
21  // 675 Mass Ave, Cambridge, MA 02139, USA.
22  
23  package wjhk.jupload2.gui;
24  
25  import java.awt.Color;
26  import java.text.SimpleDateFormat;
27  import java.util.Date;
28  import java.util.concurrent.BlockingQueue;
29  import java.util.concurrent.LinkedBlockingQueue;
30  
31  import javax.swing.JTextArea;
32  
33  import wjhk.jupload2.policies.UploadPolicy;
34  
35  /**
36   * This class represents the text area for debug output.
37   */
38  @SuppressWarnings("serial")
39  public class JUploadTextArea extends JTextArea {
40  
41      /**
42       * Maximum number of characters in the logWindow.
43       */
44      public final static int MAX_LOG_WINDOW_LENGTH = 800000;
45  
46      /**
47       * The size we truncate the output to, when the maximum size of debug output
48       * is reach. We remove 20%.
49       */
50      public final static int SIZE_TO_TRUNCATE_TO = (int) (MAX_LOG_WINDOW_LENGTH * 0.8);
51  
52      /**
53       * The current upload policy
54       */
55      UploadPolicy uploadPolicy;
56  
57      /**
58       * Indicates whether the logging in the LogMessageThread is active or not.
59       * It's marked as active before starting this thread. It's marked as
60       * non-active, when this thread is interrupted, in {@link #unload()}
61       */
62      boolean loggingActive = false;
63  
64      /**
65       * The ConcurrentLinkedQueue that'll contain the messages.
66       */
67      private BlockingQueue<String> messages;
68  
69      /**
70       * This value is logged in the debug file, and in the debug output, for each
71       * line. This allows to sort the outputed line correctly.
72       * 
73       * @see #displayMsg(String, String)
74       */
75      private int nextMessageId = 1;
76  
77      /**
78       * A thread, that will be called in the EventDispatcherThread, to have a
79       * tread-safe update of the GUI. This thread is responsible to display one
80       * String.
81       */
82      static class LogMessageThread extends Thread {
83  
84          /**
85           * The text area that'll contain the messages.
86           */
87          private JUploadTextArea textArea;
88  
89          /**
90           * @param textArea
91           */
92          LogMessageThread(JUploadTextArea textArea) {
93              this.textArea = textArea;
94              setDaemon(true);
95          }
96  
97          /** The run method of the Runnable Interface */
98          @Override
99          public void run() {
100             String nextMessage = null;
101 
102             if (this.textArea.uploadPolicy.getDebugLevel() >= 30) {
103                 int nextMessageIdBackup = this.textArea.nextMessageId;
104                 this.textArea.nextMessageId = 0;
105                 this.textArea.setText(this.textArea.formatMessageOutput(
106                         "[DEBUG]", "Logging system is initialized") + "\n");
107                 this.textArea.nextMessageId = nextMessageIdBackup;
108             }
109 
110             while (this.textArea.loggingActive) {
111                 try {
112                     nextMessage = this.textArea.messages.take() + "\n";
113 
114                     // Ah, a new message has been delivered...
115 
116                     synchronized (this.textArea) {
117                         String content = this.textArea.getText();
118                         int contentLength = content.length();
119                         // If the current content is too long, we truncate it.
120                         if (contentLength > JUploadTextArea.MAX_LOG_WINDOW_LENGTH) {
121                             content += nextMessage;
122                             String newContent = content.substring(content
123                                     .length()
124                                     - SIZE_TO_TRUNCATE_TO, content.length());
125                             this.textArea.setText(newContent);
126                             contentLength = SIZE_TO_TRUNCATE_TO;
127                         } else {
128                             // The result is not too long
129                             this.textArea.append(nextMessage);
130                             contentLength += nextMessage.length();
131                         }
132                         this.textArea.setCaretPosition(contentLength - 1);
133                     } // synchronized
134                 } catch (InterruptedException e) {
135                     // If we're not running any more, then this 'stop' is
136                     // not a
137                     // problem any more. We're then just notified we must
138                     // stop
139                     // the thread.
140                     if (this.textArea.loggingActive) {
141                         // This should not happen, and we can not put in the
142                         // standard JUpload output, as this thread is
143                         // responsible for it.
144                         e.printStackTrace();
145                     }
146                 }// try
147             }// while
148         }
149     }
150 
151     /**
152      * The thread, that will put messages in the debug log.
153      */
154     LogMessageThread logMessageThread = null;
155 
156     /**
157      * Constructs a new empty TextArea with the specified number of rows and
158      * columns.
159      * 
160      * @param rows The desired number of text rows (lines).
161      * @param columns The desired number of columns.
162      * @param uploadPolicy The current uploadPolicy
163      */
164     public JUploadTextArea(int rows, int columns, UploadPolicy uploadPolicy) {
165         super(rows, columns);
166         this.uploadPolicy = uploadPolicy;
167         this.messages = new LinkedBlockingQueue<String>();
168         setBackground(new Color(255, 255, 203));
169         setEditable(false);
170         setLineWrap(true);
171         setWrapStyleWord(true);
172 
173         // The queue, where messages to display will be posted.
174         this.logMessageThread = new LogMessageThread(this);
175         this.logMessageThread.setName(this.logMessageThread.getClass()
176                 .getName());
177         // NO START HERE: the logMessageThread needs to know the upload policy,
178         // to run properly. The thread is started in the setUploadPolicy method.
179 
180         // The unload callback will be registered, once the uploadPolicy has
181         // been built, by DefaultJUploadContext.init(JUploadApplet)
182     }
183 
184     /**
185      * Add a string to the queue of string to be added to the logWindow. This is
186      * necessary, to manage the non-thread-safe Swing environment.
187      * 
188      * @param tag The tag (eg: INFO, DEBUG...)
189      * @param msg The message to add, at the end of the JUploadTextArea.
190      * @return The formatted text that was added to the log window.
191      */
192     public final String displayMsg(String tag, String msg) {
193         String fullMessage = formatMessageOutput(tag, msg);
194 
195         try {
196             // messages is a BlockingQueue. So the next line may 'block' the
197             // applet main thread. But, we're optimistic: this should not happen
198             // as we instanciate an unbound LinkedBlockingQueue. We'll be
199             // blocked at Integer.MAX_VALUE, that is ... much after an
200             // OutOfMemory is thrown !
201             this.messages.put(fullMessage);
202         } catch (InterruptedException e) {
203             System.out.println("WARNING - [" + this.getClass().getName()
204                     + "] Message lost due to " + e.getClass().getName() + " ("
205                     + fullMessage + ")");
206         }
207         return fullMessage;
208     }
209 
210     /**
211      * This call must be synchronized, so that there is no interaction with the
212      * LogMessageThread thread.
213      * 
214      * @see JTextArea#append(String)
215      */
216     synchronized public void append(String t) {
217         super.append(t);
218     }
219 
220     /** @see JUploadPanel#copyLogWindow() */
221     public synchronized void copyLogWindow() {
222         selectAll();
223         copy();
224     }
225 
226     /**
227      * This call must be synchronized, so that there is no interaction with the
228      * LogMessageThread thread.
229      * 
230      * @see JTextArea#insert(String, int)
231      */
232     synchronized public void insert(String str, int pos) {
233         super.insert(str, pos);
234     }
235 
236     /**
237      * This call must be synchronized, so that there is no interaction with the
238      * LogMessageThread thread.
239      * 
240      * @see JTextArea#replaceRange(String, int, int)
241      */
242     synchronized public void replaceRange(String str, int start, int end) {
243         super.replaceRange(str, start, end);
244     }
245 
246     /**
247      * This call must be synchronized, so that there is no interaction with the
248      * LogMessageThread thread.
249      * 
250      * @see JTextArea#setText(String)
251      */
252     synchronized public void setText(String t) {
253         super.setText(t);
254     }
255 
256     /**
257      * @param uploadPolicy the uploadPolicy to set
258      */
259     public void setUploadPolicy(UploadPolicy uploadPolicy) {
260         this.uploadPolicy = uploadPolicy;
261         this.uploadPolicy.getContext().registerUnload(this, "unload");
262         // We can now start the log thread.
263         this.loggingActive = true;
264         this.logMessageThread.start();
265     }
266 
267     /**
268      * Free any used ressources. Actually close the LogMessageThread thread.
269      */
270     public synchronized void unload() {
271         this.loggingActive = false;
272         this.logMessageThread.interrupt();
273     }
274 
275     /**
276      * Format the message, with the given tag. This method also add the time and
277      * the Thread name.<BR>
278      * e.g.:<BR>
279      * nextMessageId[tab]14:04:30.718[tab]FileUploadManagerThread[tab][DEBUG][tab]
280      * Found one reader for jpg extension
281      * 
282      * @param tag The tag ([WARN], [ERROR]...)
283      * @param msg The message to format.
284      * @return The formatted message, without trailing EOL character.
285      */
286     String formatMessageOutput(String tag, String msg) {
287         final String stamp = String.format("%1$05d", this.nextMessageId++) + " \t"
288                 + new SimpleDateFormat("HH:mm:ss.SSS ").format(new Date())
289                 + "\t" + Thread.currentThread().getName() + "\t" + tag + " \t";
290         while (msg.endsWith("\n")) {
291             msg = msg.substring(0, msg.length() - 1);
292         }
293         return (stamp + msg.replaceAll("\n", "\n" + stamp));
294     }
295 
296 }