/* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swinghelper.debug; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.JComponent; import javax.swing.RepaintManager; import javax.swing.SwingUtilities; import java.applet.Applet; import java.awt.Component; import java.awt.Dimension; import java.awt.Image; import java.awt.Rectangle; import java.awt.Window; import java.lang.ref.WeakReference; import static griffon.core.GriffonExceptionHandler.sanitize; /** * <p>This class is used to detect Event Dispatch Thread rule violations<br> * See <a href="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How to Use Threads</a> * for more info</p> * <p/> * <p>This is a modification of original idea of Scott Delap<br> * Initial version of ThreadCheckingRepaintManager can be found here<br> * <a href="http://www.clientjava.com/blog/2004/08/20/1093059428000.html">Easily Find Swing Threading Mistakes</a> * </p> * <p/> * <p>Links</ul> * <li>https://swinghelper.dev.java.net</li> * <li>http://weblogs.java.net/blog/alexfromsun/archive/2006/02/debugging_swing.html</li> * </ul></p> * * @author Scott Delap * @author Alexander Potochkin * @author Andres Almiray */ public class CheckThreadViolationRepaintManager extends RepaintManager { private static final Logger LOG = LoggerFactory.getLogger(CheckThreadViolationRepaintManager.class); // it is recommended to pass the complete check private boolean completeCheck = true; private WeakReference<JComponent> lastComponent; private final RepaintManager delegate; public CheckThreadViolationRepaintManager() { this(new RepaintManager()); } public CheckThreadViolationRepaintManager(RepaintManager delegate) { if (delegate == null || delegate instanceof CheckThreadViolationRepaintManager) { throw new IllegalArgumentException(); } this.delegate = delegate; } public boolean isCompleteCheck() { return completeCheck; } public void setCompleteCheck(boolean completeCheck) { this.completeCheck = completeCheck; } public synchronized void addInvalidComponent(JComponent component) { checkThreadViolations(component); delegate.addInvalidComponent(component); } public void addDirtyRegion(JComponent component, int x, int y, int w, int h) { checkThreadViolations(component); delegate.addDirtyRegion(component, x, y, w, h); } private void checkThreadViolations(JComponent c) { if (!SwingUtilities.isEventDispatchThread() && (completeCheck || c.isShowing())) { boolean repaint = false; boolean fromSwing = false; boolean imageUpdate = false; StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (StackTraceElement st : stackTrace) { if (repaint && st.getClassName().startsWith("javax.swing.") && // for details see // https://swinghelper.dev.java.net/issues/show_bug.cgi?id=1 !st.getClassName().startsWith("javax.swing.SwingWorker")) { fromSwing = true; } if (repaint && "imageUpdate".equals(st.getMethodName())) { imageUpdate = true; } if ("repaint".equals(st.getMethodName())) { repaint = true; fromSwing = false; } } if (imageUpdate) { //assuming it is java.awt.image.ImageObserver.imageUpdate(...) //image was asynchronously updated, that's ok return; } if (repaint && !fromSwing) { //no problems here, since repaint() is thread safe return; } //ignore the last processed component if (lastComponent != null && c == lastComponent.get()) { return; } lastComponent = new WeakReference<>(c); violationFound(c, stackTrace); } } protected void violationFound(JComponent c, StackTraceElement[] stackTrace) { stackTrace = sanitize(stackTrace); StringBuilder sb = new StringBuilder("EDT violation detected").append('\n'); sb.append(c).append('\n'); for (StackTraceElement st : stackTrace) { sb.append("\tat ").append(st).append('\n'); } if (LOG.isWarnEnabled()) { LOG.warn(sb.toString()); } } // -- delegate methods public static RepaintManager currentManager(Component component) { return RepaintManager.currentManager(component); } public static RepaintManager currentManager(JComponent jComponent) { return RepaintManager.currentManager(jComponent); } @Override public Rectangle getDirtyRegion(JComponent jComponent) { return delegate.getDirtyRegion(jComponent); } @Override public Dimension getDoubleBufferMaximumSize() { return delegate.getDoubleBufferMaximumSize(); } @Override public Image getOffscreenBuffer(Component component, int i, int i1) { return delegate.getOffscreenBuffer(component, i, i1); } @Override public Image getVolatileOffscreenBuffer(Component component, int i, int i1) { return delegate.getVolatileOffscreenBuffer(component, i, i1); } @Override public boolean isCompletelyDirty(JComponent jComponent) { return delegate.isCompletelyDirty(jComponent); } @Override public boolean isDoubleBufferingEnabled() { return delegate.isDoubleBufferingEnabled(); } @Override public void markCompletelyClean(JComponent jComponent) { delegate.markCompletelyClean(jComponent); } @Override public void markCompletelyDirty(JComponent jComponent) { delegate.markCompletelyDirty(jComponent); } @Override public void paintDirtyRegions() { delegate.paintDirtyRegions(); } @Override public void removeInvalidComponent(JComponent jComponent) { delegate.removeInvalidComponent(jComponent); } public static void setCurrentManager(RepaintManager repaintManager) { RepaintManager.setCurrentManager(repaintManager); } @Override public void setDoubleBufferingEnabled(boolean b) { delegate.setDoubleBufferingEnabled(b); } @Override public void setDoubleBufferMaximumSize(Dimension dimension) { delegate.setDoubleBufferMaximumSize(dimension); } @Override public String toString() { return delegate.toString(); } @Override public void validateInvalidComponents() { delegate.validateInvalidComponents(); } @Override public void addDirtyRegion(Window window, int i, int i1, int i2, int i3) { delegate.addDirtyRegion(window, i, i1, i2, i3); } @Override public void addDirtyRegion(Applet applet, int i, int i1, int i2, int i3) { delegate.addDirtyRegion(applet, i, i1, i2, i3); } }