View Javadoc
1   /*
2    * License : The MIT License
3    * Copyright(c) 2019 Olyutorskii
4    */
5   
6   package io.github.olyutorskii.quetexj;
7   
8   import java.awt.EventQueue;
9   import java.util.Objects;
10  import java.util.Queue;
11  import java.util.concurrent.ConcurrentLinkedQueue;
12  import java.util.logging.Formatter;
13  import java.util.logging.Handler;
14  import java.util.logging.Level;
15  import java.util.logging.LogRecord;
16  import java.util.logging.SimpleFormatter;
17  import javax.swing.text.BadLocationException;
18  import javax.swing.text.Document;
19  import javax.swing.text.PlainDocument;
20  
21  /**
22   * Log handler for Swing text component model(Document).
23   *
24   * <p>Logging is supported from both EDT(Event-Dispatch-Thread) and non-EDT.
25   */
26  public class SwingLogHandler extends Handler {
27  
28      private final Document document;
29      private final Queue<String> msgQueue;
30      private final LogTransferTask transferTask;
31  
32  
33      /**
34       * Constructor.
35       *
36       * <p>PlainDocument is prepared.
37       */
38      public SwingLogHandler() {
39          this(new PlainDocument());
40          return;
41      }
42  
43      /**
44       * Constructor.
45       *
46       * <p>Do not access document via non-EDT.
47       *
48       * @param document Document model of Swing text component.
49       */
50      public SwingLogHandler(Document document) {
51          super();
52  
53          Objects.requireNonNull(document);
54          this.document = document;
55  
56          this.msgQueue = new ConcurrentLinkedQueue<>();
57          this.transferTask =
58                  new LogTransferTask(this.msgQueue);
59  
60          Formatter formatter = new SimpleFormatter();
61          setFormatter(formatter);
62  
63          return;
64      }
65  
66  
67      /**
68       * Return associated document.
69       *
70       * <p>Do not access document via non-EDT.
71       *
72       * @return document
73       */
74      public Document getDocument() {
75          return this.document;
76      }
77  
78      /**
79       * {@inheritDoc}
80       *
81       * <p>This is thread-safe.
82       *
83       * @param logRec {@inheritDoc}
84       */
85      @Override
86      public synchronized void publish(LogRecord logRec) {
87          if (logRec == null) return;
88  
89          if (!isLoggable(logRec)) {
90              return;
91          }
92  
93          Formatter formatter = getFormatter();
94          String message = formatter.format(logRec);
95  
96          publish(message);
97  
98          return;
99      }
100 
101     /**
102      * Publish a log message.
103      *
104      * <p>Document model will be updated later.
105      *
106      * @param message log message
107      */
108     private void publish(String message) {
109         boolean offered = this.msgQueue.offer(message);
110         assert offered;
111 
112         if (EventQueue.isDispatchThread()) {
113             this.transferTask.transferQueueToDoc();
114         } else {
115             EventQueue.invokeLater(this.transferTask);
116         }
117 
118         return;
119     }
120 
121     /**
122      * {@inheritDoc}
123      */
124     @Override
125     public void flush() {
126         return;
127     }
128 
129     /**
130      * {@inheritDoc}
131      *
132      * @throws SecurityException {@inheritDoc}
133      */
134     @Override
135     public void close() throws SecurityException {
136         setLevel(Level.OFF);
137         flush();
138         return;
139     }
140 
141 
142     /**
143      * Transfer log from Queue to Document.
144      *
145      * <p>EDT only supported.
146      */
147     private class LogTransferTask implements Runnable {
148 
149         private final Queue<String> queue;
150         private final StringBuilder msgBuf;
151 
152 
153         /**
154          * Constructor.
155          *
156          * @param queue log message queue
157          */
158         LogTransferTask(Queue<String> queue) {
159             super();
160 
161             this.queue = queue;
162             this.msgBuf = new StringBuilder();
163 
164             return;
165         }
166 
167 
168         /**
169          * {@inheritDoc}
170          */
171         @Override
172         public void run() {
173             transferQueueToDoc();
174             return;
175         }
176 
177         /**
178          * Transfer message from Queue to Document.
179          */
180         void transferQueueToDoc() {
181             int queueSize = this.queue.size();
182             if (queueSize == 1) {   // common case
183                 String msg = this.queue.poll();
184                 appendToDocument(msg);
185                 return;
186             } else if (queueSize <= 0) {
187                 return;
188             }
189 
190             this.msgBuf.setLength(0);
191 
192             while (!this.queue.isEmpty()) {
193                 String msg = this.queue.poll();
194                 if (msg == null) break;
195                 this.msgBuf.append(msg);
196             }
197 
198             appendToDocument(this.msgBuf);
199 
200             return;
201         }
202 
203         /**
204          * Append text to last pos of Document.
205          *
206          * <p>DocumentEvent will happen from Document.
207          *
208          * @param logMessage text
209          */
210         private void appendToDocument(CharSequence logMessage) {
211             if (logMessage == null) return;
212             if (logMessage.length() <= 0) return;
213 
214             Document doc = getDocument();
215             String str = logMessage.toString();
216             int insertPt = doc.getLength();
217 
218             try {
219                 doc.insertString(insertPt, str, null);
220             } catch (BadLocationException e) {
221                 assert false;
222             }
223 
224             return;
225         }
226 
227     }
228 
229 }