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