CheckThreadViolationRepaintManager.java
001 /*
002  * This library is free software; you can redistribute it and/or
003  * modify it under the terms of the GNU Lesser General Public
004  * License as published by the Free Software Foundation; either
005  * version 2.1 of the License, or (at your option) any later version.
006  *
007  * This library is distributed in the hope that it will be useful,
008  * but WITHOUT ANY WARRANTY; without even the implied warranty of
009  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
010  * Lesser General Public License for more details.
011  *
012  * You should have received a copy of the GNU Lesser General Public
013  * License along with this library; if not, write to the Free Software
014  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
015  */
016 
017 package org.jdesktop.swinghelper.debug;
018 
019 import org.slf4j.Logger;
020 import org.slf4j.LoggerFactory;
021 
022 import javax.swing.JComponent;
023 import javax.swing.RepaintManager;
024 import javax.swing.SwingUtilities;
025 import java.applet.Applet;
026 import java.awt.Component;
027 import java.awt.Dimension;
028 import java.awt.Image;
029 import java.awt.Rectangle;
030 import java.awt.Window;
031 import java.lang.ref.WeakReference;
032 
033 import static griffon.core.GriffonExceptionHandler.sanitize;
034 
035 
036 /**
037  <p>This class is used to detect Event Dispatch Thread rule violations<br>
038  * See <a href="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How to Use Threads</a>
039  * for more info</p>
040  <p/>
041  <p>This is a modification of original idea of Scott Delap<br>
042  * Initial version of ThreadCheckingRepaintManager can be found here<br>
043  * <a href="http://www.clientjava.com/blog/2004/08/20/1093059428000.html">Easily Find Swing Threading Mistakes</a>
044  </p>
045  <p/>
046  <p>Links</ul>
047  <li>https://swinghelper.dev.java.net</li>
048  <li>http://weblogs.java.net/blog/alexfromsun/archive/2006/02/debugging_swing.html</li>
049  </ul></p>
050  *
051  @author Scott Delap
052  @author Alexander Potochkin
053  @author Andres Almiray
054  */
055 public class CheckThreadViolationRepaintManager extends RepaintManager {
056     private static final Logger LOG = LoggerFactory.getLogger(CheckThreadViolationRepaintManager.class);
057     // it is recommended to pass the complete check  
058     private boolean completeCheck = true;
059     private WeakReference<JComponent> lastComponent;
060     private final RepaintManager delegate;
061 
062     public CheckThreadViolationRepaintManager() {
063         this(new RepaintManager());
064     }
065 
066     public CheckThreadViolationRepaintManager(RepaintManager delegate) {
067         if (delegate == null || delegate instanceof CheckThreadViolationRepaintManager) {
068             throw new IllegalArgumentException();
069         }
070         this.delegate = delegate;
071     }
072 
073     public boolean isCompleteCheck() {
074         return completeCheck;
075     }
076 
077     public void setCompleteCheck(boolean completeCheck) {
078         this.completeCheck = completeCheck;
079     }
080 
081     public synchronized void addInvalidComponent(JComponent component) {
082         checkThreadViolations(component);
083         delegate.addInvalidComponent(component);
084     }
085 
086     public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
087         checkThreadViolations(component);
088         delegate.addDirtyRegion(component, x, y, w, h);
089     }
090 
091     private void checkThreadViolations(JComponent c) {
092         if (!SwingUtilities.isEventDispatchThread() && (completeCheck || c.isShowing())) {
093             boolean repaint = false;
094             boolean fromSwing = false;
095             boolean imageUpdate = false;
096             StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
097             for (StackTraceElement st : stackTrace) {
098                 if (repaint && st.getClassName().startsWith("javax.swing."&&
099                     // for details see
100                     // https://swinghelper.dev.java.net/issues/show_bug.cgi?id=1
101                     !st.getClassName().startsWith("javax.swing.SwingWorker")) {
102                     fromSwing = true;
103                 }
104                 if (repaint && "imageUpdate".equals(st.getMethodName())) {
105                     imageUpdate = true;
106                 }
107                 if ("repaint".equals(st.getMethodName())) {
108                     repaint = true;
109                     fromSwing = false;
110                 }
111             }
112             if (imageUpdate) {
113                 //assuming it is java.awt.image.ImageObserver.imageUpdate(...)
114                 //image was asynchronously updated, that's ok
115                 return;
116             }
117             if (repaint && !fromSwing) {
118                 //no problems here, since repaint() is thread safe
119                 return;
120             }
121             //ignore the last processed component
122             if (lastComponent != null && c == lastComponent.get()) {
123                 return;
124             }
125             lastComponent = new WeakReference<>(c);
126             violationFound(c, stackTrace);
127         }
128     }
129 
130     protected void violationFound(JComponent c, StackTraceElement[] stackTrace) {
131         stackTrace = sanitize(stackTrace);
132         StringBuilder sb = new StringBuilder("EDT violation detected").append('\n');
133         sb.append(c).append('\n');
134         for (StackTraceElement st : stackTrace) {
135             sb.append("\tat ").append(st).append('\n');
136         }
137         if (LOG.isWarnEnabled()) {
138             LOG.warn(sb.toString());
139         }
140     }
141 
142 
143     // -- delegate methods
144 
145     public static RepaintManager currentManager(Component component) {
146         return RepaintManager.currentManager(component);
147     }
148 
149     public static RepaintManager currentManager(JComponent jComponent) {
150         return RepaintManager.currentManager(jComponent);
151     }
152 
153     @Override
154     public Rectangle getDirtyRegion(JComponent jComponent) {
155         return delegate.getDirtyRegion(jComponent);
156     }
157 
158     @Override
159     public Dimension getDoubleBufferMaximumSize() {
160         return delegate.getDoubleBufferMaximumSize();
161     }
162 
163     @Override
164     public Image getOffscreenBuffer(Component component, int i, int i1) {
165         return delegate.getOffscreenBuffer(component, i, i1);
166     }
167 
168     @Override
169     public Image getVolatileOffscreenBuffer(Component component, int i, int i1) {
170         return delegate.getVolatileOffscreenBuffer(component, i, i1);
171     }
172 
173     @Override
174     public boolean isCompletelyDirty(JComponent jComponent) {
175         return delegate.isCompletelyDirty(jComponent);
176     }
177 
178     @Override
179     public boolean isDoubleBufferingEnabled() {
180         return delegate.isDoubleBufferingEnabled();
181     }
182 
183     @Override
184     public void markCompletelyClean(JComponent jComponent) {
185         delegate.markCompletelyClean(jComponent);
186     }
187 
188     @Override
189     public void markCompletelyDirty(JComponent jComponent) {
190         delegate.markCompletelyDirty(jComponent);
191     }
192 
193     @Override
194     public void paintDirtyRegions() {
195         delegate.paintDirtyRegions();
196     }
197 
198     @Override
199     public void removeInvalidComponent(JComponent jComponent) {
200         delegate.removeInvalidComponent(jComponent);
201     }
202 
203     public static void setCurrentManager(RepaintManager repaintManager) {
204         RepaintManager.setCurrentManager(repaintManager);
205     }
206 
207     @Override
208     public void setDoubleBufferingEnabled(boolean b) {
209         delegate.setDoubleBufferingEnabled(b);
210     }
211 
212     @Override
213     public void setDoubleBufferMaximumSize(Dimension dimension) {
214         delegate.setDoubleBufferMaximumSize(dimension);
215     }
216 
217     @Override
218     public String toString() {
219         return delegate.toString();
220     }
221 
222     @Override
223     public void validateInvalidComponents() {
224         delegate.validateInvalidComponents();
225     }
226 
227     @Override
228     public void addDirtyRegion(Window window, int i, int i1, int i2, int i3) {
229         delegate.addDirtyRegion(window, i, i1, i2, i3);
230     }
231 
232     @Override
233     public void addDirtyRegion(Applet applet, int i, int i1, int i2, int i3) {
234         delegate.addDirtyRegion(applet, i, i1, i2, i3);
235     }
236 }