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 }
|