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