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     @Override
082     public synchronized void addInvalidComponent(JComponent component) {
083         checkThreadViolations(component);
084         delegate.addInvalidComponent(component);
085     }
086 
087     @Override
088     public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
089         checkThreadViolations(component);
090         delegate.addDirtyRegion(component, x, y, w, h);
091     }
092 
093     private void checkThreadViolations(JComponent c) {
094         if (!SwingUtilities.isEventDispatchThread() && (completeCheck || c.isShowing())) {
095             boolean repaint = false;
096             boolean fromSwing = false;
097             boolean imageUpdate = false;
098             StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
099             for (StackTraceElement st : stackTrace) {
100                 if (repaint && st.getClassName().startsWith("javax.swing."&&
101                     // for details see
102                     // https://swinghelper.dev.java.net/issues/show_bug.cgi?id=1
103                     !st.getClassName().startsWith("javax.swing.SwingWorker")) {
104                     fromSwing = true;
105                 }
106                 if (repaint && "imageUpdate".equals(st.getMethodName())) {
107                     imageUpdate = true;
108                 }
109                 if ("repaint".equals(st.getMethodName())) {
110                     repaint = true;
111                     fromSwing = false;
112                 }
113             }
114             if (imageUpdate) {
115                 //assuming it is java.awt.image.ImageObserver.imageUpdate(...)
116                 //image was asynchronously updated, that's ok
117                 return;
118             }
119             if (repaint && !fromSwing) {
120                 //no problems here, since repaint() is thread safe
121                 return;
122             }
123             //ignore the last processed component
124             if (lastComponent != null && c == lastComponent.get()) {
125                 return;
126             }
127             lastComponent = new WeakReference<>(c);
128             violationFound(c, stackTrace);
129         }
130     }
131 
132     protected void violationFound(JComponent c, StackTraceElement[] stackTrace) {
133         stackTrace = sanitize(stackTrace);
134         StringBuilder sb = new StringBuilder("EDT violation detected").append('\n');
135         sb.append(c).append('\n');
136         for (StackTraceElement st : stackTrace) {
137             sb.append("\tat ").append(st).append('\n');
138         }
139         if (LOG.isWarnEnabled()) {
140             LOG.warn(sb.toString());
141         }
142     }
143 
144 
145     // -- delegate methods
146 
147     public static RepaintManager currentManager(Component component) {
148         return RepaintManager.currentManager(component);
149     }
150 
151     public static RepaintManager currentManager(JComponent jComponent) {
152         return RepaintManager.currentManager(jComponent);
153     }
154 
155     @Override
156     public Rectangle getDirtyRegion(JComponent jComponent) {
157         return delegate.getDirtyRegion(jComponent);
158     }
159 
160     @Override
161     public Dimension getDoubleBufferMaximumSize() {
162         return delegate.getDoubleBufferMaximumSize();
163     }
164 
165     @Override
166     public Image getOffscreenBuffer(Component component, int i, int i1) {
167         return delegate.getOffscreenBuffer(component, i, i1);
168     }
169 
170     @Override
171     public Image getVolatileOffscreenBuffer(Component component, int i, int i1) {
172         return delegate.getVolatileOffscreenBuffer(component, i, i1);
173     }
174 
175     @Override
176     public boolean isCompletelyDirty(JComponent jComponent) {
177         return delegate.isCompletelyDirty(jComponent);
178     }
179 
180     @Override
181     public boolean isDoubleBufferingEnabled() {
182         return delegate.isDoubleBufferingEnabled();
183     }
184 
185     @Override
186     public void markCompletelyClean(JComponent jComponent) {
187         delegate.markCompletelyClean(jComponent);
188     }
189 
190     @Override
191     public void markCompletelyDirty(JComponent jComponent) {
192         delegate.markCompletelyDirty(jComponent);
193     }
194 
195     @Override
196     public void paintDirtyRegions() {
197         delegate.paintDirtyRegions();
198     }
199 
200     @Override
201     public void removeInvalidComponent(JComponent jComponent) {
202         delegate.removeInvalidComponent(jComponent);
203     }
204 
205     public static void setCurrentManager(RepaintManager repaintManager) {
206         RepaintManager.setCurrentManager(repaintManager);
207     }
208 
209     @Override
210     public void setDoubleBufferingEnabled(boolean b) {
211         delegate.setDoubleBufferingEnabled(b);
212     }
213 
214     @Override
215     public void setDoubleBufferMaximumSize(Dimension dimension) {
216         delegate.setDoubleBufferMaximumSize(dimension);
217     }
218 
219     @Override
220     public String toString() {
221         return delegate.toString();
222     }
223 
224     @Override
225     public void validateInvalidComponents() {
226         delegate.validateInvalidComponents();
227     }
228 
229     @Override
230     public void addDirtyRegion(Window window, int i, int i1, int i2, int i3) {
231         delegate.addDirtyRegion(window, i, i1, i2, i3);
232     }
233 
234     @Override
235     public void addDirtyRegion(Applet applet, int i, int i1, int i2, int i3) {
236         delegate.addDirtyRegion(applet, i, i1, i2, i3);
237     }
238 }