1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing;
  26 
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.awt.image.VolatileImage;
  31 import java.security.AccessControlContext;
  32 import java.security.AccessController;
  33 import java.security.PrivilegedAction;
  34 import java.util.*;
  35 import java.util.concurrent.atomic.AtomicInteger;
  36 import java.applet.*;
  37 
  38 import jdk.internal.misc.JavaSecurityAccess;
  39 import jdk.internal.misc.SharedSecrets;
  40 import sun.awt.AWTAccessor;
  41 import sun.awt.AppContext;
  42 import sun.awt.DisplayChangedListener;
  43 import sun.awt.SunToolkit;
  44 import sun.java2d.SunGraphicsEnvironment;
  45 import sun.security.action.GetPropertyAction;
  46 
  47 import com.sun.java.swing.SwingUtilities3;
  48 import sun.swing.SwingAccessor;
  49 import sun.swing.SwingUtilities2.RepaintListener;
  50 
  51 /**
  52  * This class manages repaint requests, allowing the number
  53  * of repaints to be minimized, for example by collapsing multiple
  54  * requests into a single repaint for members of a component tree.
  55  * <p>
  56  * As of 1.6 <code>RepaintManager</code> handles repaint requests
  57  * for Swing's top level components (<code>JApplet</code>,
  58  * <code>JWindow</code>, <code>JFrame</code> and <code>JDialog</code>).
  59  * Any calls to <code>repaint</code> on one of these will call into the
  60  * appropriate <code>addDirtyRegion</code> method.
  61  *
  62  * @author Arnaud Weber
  63  * @since 1.2
  64  */
  65 public class RepaintManager
  66 {
  67     /**
  68      * Whether or not the RepaintManager should handle paint requests
  69      * for top levels.
  70      */
  71     static final boolean HANDLE_TOP_LEVEL_PAINT;
  72 
  73     private static final short BUFFER_STRATEGY_NOT_SPECIFIED = 0;
  74     private static final short BUFFER_STRATEGY_SPECIFIED_ON = 1;
  75     private static final short BUFFER_STRATEGY_SPECIFIED_OFF = 2;
  76 
  77     private static final short BUFFER_STRATEGY_TYPE;
  78 
  79     /**
  80      * Maps from GraphicsConfiguration to VolatileImage.
  81      */
  82     private Map<GraphicsConfiguration,VolatileImage> volatileMap = new
  83                         HashMap<GraphicsConfiguration,VolatileImage>(1);
  84 
  85     //
  86     // As of 1.6 Swing handles scheduling of paint events from native code.
  87     // That is, SwingPaintEventDispatcher is invoked on the toolkit thread,
  88     // which in turn invokes nativeAddDirtyRegion.  Because this is invoked
  89     // from the native thread we can not invoke any public methods and so
  90     // we introduce these added maps.  So, any time nativeAddDirtyRegion is
  91     // invoked the region is added to hwDirtyComponents and a work request
  92     // is scheduled.  When the work request is processed all entries in
  93     // this map are pushed to the real map (dirtyComponents) and then
  94     // painted with the rest of the components.
  95     //
  96     private Map<Container,Rectangle> hwDirtyComponents;
  97 
  98     private Map<Component,Rectangle> dirtyComponents;
  99     private Map<Component,Rectangle> tmpDirtyComponents;
 100     private java.util.List<Component> invalidComponents;
 101 
 102     // List of Runnables that need to be processed before painting from AWT.
 103     private java.util.List<Runnable> runnableList;
 104 
 105     boolean   doubleBufferingEnabled = true;
 106 
 107     private Dimension doubleBufferMaxSize;
 108 
 109     // Support for both the standard and volatile offscreen buffers exists to
 110     // provide backwards compatibility for the [rare] programs which may be
 111     // calling getOffScreenBuffer() and not expecting to get a VolatileImage.
 112     // Swing internally is migrating to use *only* the volatile image buffer.
 113 
 114     // Support for standard offscreen buffer
 115     //
 116     DoubleBufferInfo standardDoubleBuffer;
 117 
 118     /**
 119      * Object responsible for hanlding core paint functionality.
 120      */
 121     private PaintManager paintManager;
 122 
 123     private static final Object repaintManagerKey = RepaintManager.class;
 124 
 125     // Whether or not a VolatileImage should be used for double-buffered painting
 126     static boolean volatileImageBufferEnabled = true;
 127     /**
 128      * Type of VolatileImage which should be used for double-buffered
 129      * painting.
 130      */
 131     private static final int volatileBufferType;
 132     /**
 133      * Value of the system property awt.nativeDoubleBuffering.
 134      */
 135     private static boolean nativeDoubleBuffering;
 136 
 137     // The maximum number of times Swing will attempt to use the VolatileImage
 138     // buffer during a paint operation.
 139     private static final int VOLATILE_LOOP_MAX = 2;
 140 
 141     /**
 142      * Number of <code>beginPaint</code> that have been invoked.
 143      */
 144     private int paintDepth = 0;
 145 
 146     /**
 147      * Type of buffer strategy to use.  Will be one of the BUFFER_STRATEGY_
 148      * constants.
 149      */
 150     private short bufferStrategyType;
 151 
 152     //
 153     // BufferStrategyPaintManager has the unique characteristic that it
 154     // must deal with the buffer being lost while painting to it.  For
 155     // example, if we paint a component and show it and the buffer has
 156     // become lost we must repaint the whole window.  To deal with that
 157     // the PaintManager calls into repaintRoot, and if we're still in
 158     // the process of painting the repaintRoot field is set to the JRootPane
 159     // and after the current JComponent.paintImmediately call finishes
 160     // paintImmediately will be invoked on the repaintRoot.  In this
 161     // way we don't try to show garbage to the screen.
 162     //
 163     /**
 164      * True if we're in the process of painting the dirty regions.  This is
 165      * set to true in <code>paintDirtyRegions</code>.
 166      */
 167     private boolean painting;
 168     /**
 169      * If the PaintManager calls into repaintRoot during painting this field
 170      * will be set to the root.
 171      */
 172     private JComponent repaintRoot;
 173 
 174     /**
 175      * The Thread that has initiated painting.  If null it
 176      * indicates painting is not currently in progress.
 177      */
 178     private Thread paintThread;
 179 
 180     /**
 181      * Runnable used to process all repaint/revalidate requests.
 182      */
 183     private final ProcessingRunnable processingRunnable;
 184 
 185     private static final JavaSecurityAccess javaSecurityAccess =
 186             SharedSecrets.getJavaSecurityAccess();
 187 
 188     /**
 189      * Listener installed to detect display changes. When display changes,
 190      * schedules a callback to notify all RepaintManagers of the display
 191      * changes.
 192      */
 193     private static final DisplayChangedListener displayChangedHandler =
 194             new DisplayChangedHandler();
 195 
 196     static {
 197         SwingAccessor.setRepaintManagerAccessor(new SwingAccessor.RepaintManagerAccessor() {
 198             @Override
 199             public void addRepaintListener(RepaintManager rm, RepaintListener l) {
 200                 rm.addRepaintListener(l);
 201             }
 202             @Override
 203             public void removeRepaintListener(RepaintManager rm, RepaintListener l) {
 204                 rm.removeRepaintListener(l);
 205             }
 206         });
 207 
 208         volatileImageBufferEnabled = "true".equals(AccessController.
 209                 doPrivileged(new GetPropertyAction(
 210                 "swing.volatileImageBufferEnabled", "true")));
 211         boolean headless = GraphicsEnvironment.isHeadless();
 212         if (volatileImageBufferEnabled && headless) {
 213             volatileImageBufferEnabled = false;
 214         }
 215         nativeDoubleBuffering = "true".equals(AccessController.doPrivileged(
 216                     new GetPropertyAction("awt.nativeDoubleBuffering")));
 217         String bs = AccessController.doPrivileged(
 218                           new GetPropertyAction("swing.bufferPerWindow"));
 219         if (headless) {
 220             BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
 221         }
 222         else if (bs == null) {
 223             BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_NOT_SPECIFIED;
 224         }
 225         else if ("true".equals(bs)) {
 226             BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_ON;
 227         }
 228         else {
 229             BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
 230         }
 231         HANDLE_TOP_LEVEL_PAINT = "true".equals(AccessController.doPrivileged(
 232                new GetPropertyAction("swing.handleTopLevelPaint", "true")));
 233         GraphicsEnvironment ge = GraphicsEnvironment.
 234                 getLocalGraphicsEnvironment();
 235         if (ge instanceof SunGraphicsEnvironment) {
 236             ((SunGraphicsEnvironment) ge).addDisplayChangedListener(
 237                     displayChangedHandler);
 238         }
 239         Toolkit tk = Toolkit.getDefaultToolkit();
 240         if ((tk instanceof SunToolkit)
 241                 && ((SunToolkit) tk).isSwingBackbufferTranslucencySupported()) {
 242             volatileBufferType = Transparency.TRANSLUCENT;
 243         } else {
 244             volatileBufferType = Transparency.OPAQUE;
 245         }
 246     }
 247 
 248     /**
 249      * Return the RepaintManager for the calling thread given a Component.
 250      *
 251      * @param c a Component -- unused in the default implementation, but could
 252      *          be used by an overridden version to return a different RepaintManager
 253      *          depending on the Component
 254      * @return the RepaintManager object
 255      */
 256     public static RepaintManager currentManager(Component c) {
 257         // Note: DisplayChangedRunnable passes in null as the component, so if
 258         // component is ever used to determine the current
 259         // RepaintManager, DisplayChangedRunnable will need to be modified
 260         // accordingly.
 261         return currentManager(AppContext.getAppContext());
 262     }
 263 
 264     /**
 265      * Returns the RepaintManager for the specified AppContext.  If
 266      * a RepaintManager has not been created for the specified
 267      * AppContext this will return null.
 268      */
 269     static RepaintManager currentManager(AppContext appContext) {
 270         RepaintManager rm = (RepaintManager)appContext.get(repaintManagerKey);
 271         if (rm == null) {
 272             rm = new RepaintManager(BUFFER_STRATEGY_TYPE);
 273             appContext.put(repaintManagerKey, rm);
 274         }
 275         return rm;
 276     }
 277 
 278     /**
 279      * Return the RepaintManager for the calling thread given a JComponent.
 280      * <p>
 281     * Note: This method exists for backward binary compatibility with earlier
 282      * versions of the Swing library. It simply returns the result returned by
 283      * {@link #currentManager(Component)}.
 284      *
 285      * @param c a JComponent -- unused
 286      * @return the RepaintManager object
 287      */
 288     public static RepaintManager currentManager(JComponent c) {
 289         return currentManager((Component)c);
 290     }
 291 
 292 
 293     /**
 294      * Set the RepaintManager that should be used for the calling
 295      * thread. <b>aRepaintManager</b> will become the current RepaintManager
 296      * for the calling thread's thread group.
 297      * @param aRepaintManager  the RepaintManager object to use
 298      */
 299     public static void setCurrentManager(RepaintManager aRepaintManager) {
 300         if (aRepaintManager != null) {
 301             SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
 302         } else {
 303             SwingUtilities.appContextRemove(repaintManagerKey);
 304         }
 305     }
 306 
 307     /**
 308      * Create a new RepaintManager instance. You rarely call this constructor.
 309      * directly. To get the default RepaintManager, use
 310      * RepaintManager.currentManager(JComponent) (normally "this").
 311      */
 312     public RepaintManager() {
 313         // Because we can't know what a subclass is doing with the
 314         // volatile image we immediately punt in subclasses.  If this
 315         // poses a problem we'll need a more sophisticated detection algorithm,
 316         // or API.
 317         this(BUFFER_STRATEGY_SPECIFIED_OFF);
 318     }
 319 
 320     private RepaintManager(short bufferStrategyType) {
 321         // If native doublebuffering is being used, do NOT use
 322         // Swing doublebuffering.
 323         doubleBufferingEnabled = !nativeDoubleBuffering;
 324         synchronized(this) {
 325             dirtyComponents = new IdentityHashMap<Component,Rectangle>();
 326             tmpDirtyComponents = new IdentityHashMap<Component,Rectangle>();
 327             this.bufferStrategyType = bufferStrategyType;
 328             hwDirtyComponents = new IdentityHashMap<Container,Rectangle>();
 329         }
 330         processingRunnable = new ProcessingRunnable();
 331     }
 332 
 333     private void displayChanged() {
 334         clearImages();
 335     }
 336 
 337     /**
 338      * Mark the component as in need of layout and queue a runnable
 339      * for the event dispatching thread that will validate the components
 340      * first isValidateRoot() ancestor.
 341      *
 342      * @param invalidComponent a component
 343      * @see JComponent#isValidateRoot
 344      * @see #removeInvalidComponent
 345      */
 346     public synchronized void addInvalidComponent(JComponent invalidComponent)
 347     {
 348         RepaintManager delegate = getDelegate(invalidComponent);
 349         if (delegate != null) {
 350             delegate.addInvalidComponent(invalidComponent);
 351             return;
 352         }
 353         Component validateRoot =
 354             SwingUtilities.getValidateRoot(invalidComponent, true);
 355 
 356         if (validateRoot == null) {
 357             return;
 358         }
 359 
 360         /* Lazily create the invalidateComponents vector and add the
 361          * validateRoot if it's not there already.  If this validateRoot
 362          * is already in the vector, we're done.
 363          */
 364         if (invalidComponents == null) {
 365             invalidComponents = new ArrayList<Component>();
 366         }
 367         else {
 368             int n = invalidComponents.size();
 369             for(int i = 0; i < n; i++) {
 370                 if(validateRoot == invalidComponents.get(i)) {
 371                     return;
 372                 }
 373             }
 374         }
 375         invalidComponents.add(validateRoot);
 376 
 377         // Queue a Runnable to invoke paintDirtyRegions and
 378         // validateInvalidComponents.
 379         scheduleProcessingRunnable(SunToolkit.targetToAppContext(invalidComponent));
 380     }
 381 
 382 
 383     /**
 384      * Remove a component from the list of invalid components.
 385      *
 386      * @param component a component
 387      * @see #addInvalidComponent
 388      */
 389     public synchronized void removeInvalidComponent(JComponent component) {
 390         RepaintManager delegate = getDelegate(component);
 391         if (delegate != null) {
 392             delegate.removeInvalidComponent(component);
 393             return;
 394         }
 395         if(invalidComponents != null) {
 396             int index = invalidComponents.indexOf(component);
 397             if(index != -1) {
 398                 invalidComponents.remove(index);
 399             }
 400         }
 401     }
 402 
 403 
 404     /**
 405      * Add a component in the list of components that should be refreshed.
 406      * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
 407      * will be unioned with the region that should be redrawn.
 408      *
 409      * @see JComponent#repaint
 410      */
 411     @SuppressWarnings("deprecation")
 412     private void addDirtyRegion0(Container c, int x, int y, int w, int h) {
 413         /* Special cases we don't have to bother with.
 414          */
 415         if ((w <= 0) || (h <= 0) || (c == null)) {
 416             return;
 417         }
 418 
 419         if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {
 420             return;
 421         }
 422 
 423         if (extendDirtyRegion(c, x, y, w, h)) {
 424             // Component was already marked as dirty, region has been
 425             // extended, no need to continue.
 426             return;
 427         }
 428 
 429         /* Make sure that c and all it ancestors (up to an Applet or
 430          * Window) are visible.  This loop has the same effect as
 431          * checking c.isShowing() (and note that it's still possible
 432          * that c is completely obscured by an opaque ancestor in
 433          * the specified rectangle).
 434          */
 435         Component root = null;
 436 
 437         // Note: We can't synchronize around this, Frame.getExtendedState
 438         // is synchronized so that if we were to synchronize around this
 439         // it could lead to the possibility of getting locks out
 440         // of order and deadlocking.
 441         for (Container p = c; p != null; p = p.getParent()) {
 442             if (!p.isVisible() || !p.isDisplayable()) {
 443                 return;
 444             }
 445             if ((p instanceof Window) || (p instanceof Applet)) {
 446                 // Iconified frames are still visible!
 447                 if (p instanceof Frame &&
 448                         (((Frame)p).getExtendedState() & Frame.ICONIFIED) ==
 449                                     Frame.ICONIFIED) {
 450                     return;
 451                 }
 452                 root = p;
 453                 break;
 454             }
 455         }
 456 
 457         if (root == null) return;
 458 
 459         synchronized(this) {
 460             if (extendDirtyRegion(c, x, y, w, h)) {
 461                 // In between last check and this check another thread
 462                 // queued up runnable, can bail here.
 463                 return;
 464             }
 465             dirtyComponents.put(c, new Rectangle(x, y, w, h));
 466         }
 467 
 468         // Queue a Runnable to invoke paintDirtyRegions and
 469         // validateInvalidComponents.
 470         scheduleProcessingRunnable(SunToolkit.targetToAppContext(c));
 471     }
 472 
 473     /**
 474      * Add a component in the list of components that should be refreshed.
 475      * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
 476      * will be unioned with the region that should be redrawn.
 477      *
 478      * @param c Component to repaint, null results in nothing happening.
 479      * @param x X coordinate of the region to repaint
 480      * @param y Y coordinate of the region to repaint
 481      * @param w Width of the region to repaint
 482      * @param h Height of the region to repaint
 483      * @see JComponent#repaint
 484      */
 485     public void addDirtyRegion(JComponent c, int x, int y, int w, int h)
 486     {
 487         RepaintManager delegate = getDelegate(c);
 488         if (delegate != null) {
 489             delegate.addDirtyRegion(c, x, y, w, h);
 490             return;
 491         }
 492         addDirtyRegion0(c, x, y, w, h);
 493     }
 494 
 495     /**
 496      * Adds <code>window</code> to the list of <code>Component</code>s that
 497      * need to be repainted.
 498      *
 499      * @param window Window to repaint, null results in nothing happening.
 500      * @param x X coordinate of the region to repaint
 501      * @param y Y coordinate of the region to repaint
 502      * @param w Width of the region to repaint
 503      * @param h Height of the region to repaint
 504      * @see JFrame#repaint
 505      * @see JWindow#repaint
 506      * @see JDialog#repaint
 507      * @since 1.6
 508      */
 509     public void addDirtyRegion(Window window, int x, int y, int w, int h) {
 510         addDirtyRegion0(window, x, y, w, h);
 511     }
 512 
 513     /**
 514      * Adds <code>applet</code> to the list of <code>Component</code>s that
 515      * need to be repainted.
 516      *
 517      * @param applet Applet to repaint, null results in nothing happening.
 518      * @param x X coordinate of the region to repaint
 519      * @param y Y coordinate of the region to repaint
 520      * @param w Width of the region to repaint
 521      * @param h Height of the region to repaint
 522      * @see JApplet#repaint
 523      * @since 1.6
 524      *
 525      * @deprecated The Applet API is deprecated. See the
 526      * <a href="../../java/applet/package-summary.html"> java.applet package
 527      * documentation</a> for further information.
 528      */
 529     @Deprecated(since = "9")
 530     public void addDirtyRegion(Applet applet, int x, int y, int w, int h) {
 531         addDirtyRegion0(applet, x, y, w, h);
 532     }
 533 
 534     @SuppressWarnings("deprecation")
 535     void scheduleHeavyWeightPaints() {
 536         Map<Container,Rectangle> hws;
 537 
 538         synchronized(this) {
 539             if (hwDirtyComponents.size() == 0) {
 540                 return;
 541             }
 542             hws = hwDirtyComponents;
 543             hwDirtyComponents =  new IdentityHashMap<Container,Rectangle>();
 544         }
 545         for (Container hw : hws.keySet()) {
 546             Rectangle dirty = hws.get(hw);
 547             if (hw instanceof Window) {
 548                 addDirtyRegion((Window)hw, dirty.x, dirty.y,
 549                                dirty.width, dirty.height);
 550             }
 551             else if (hw instanceof Applet) {
 552                 addDirtyRegion((Applet)hw, dirty.x, dirty.y,
 553                                dirty.width, dirty.height);
 554             }
 555             else { // SwingHeavyWeight
 556                 addDirtyRegion0(hw, dirty.x, dirty.y,
 557                                 dirty.width, dirty.height);
 558             }
 559         }
 560     }
 561 
 562     //
 563     // This is called from the toolkit thread when a native expose is
 564     // received.
 565     //
 566     void nativeAddDirtyRegion(AppContext appContext, Container c,
 567                               int x, int y, int w, int h) {
 568         if (w > 0 && h > 0) {
 569             synchronized(this) {
 570                 Rectangle dirty = hwDirtyComponents.get(c);
 571                 if (dirty == null) {
 572                     hwDirtyComponents.put(c, new Rectangle(x, y, w, h));
 573                 }
 574                 else {
 575                     hwDirtyComponents.put(c, SwingUtilities.computeUnion(
 576                                               x, y, w, h, dirty));
 577                 }
 578             }
 579             scheduleProcessingRunnable(appContext);
 580         }
 581     }
 582 
 583     //
 584     // This is called from the toolkit thread when awt needs to run a
 585     // Runnable before we paint.
 586     //
 587     void nativeQueueSurfaceDataRunnable(AppContext appContext,
 588                                         final Component c, final Runnable r)
 589     {
 590         synchronized(this) {
 591             if (runnableList == null) {
 592                 runnableList = new LinkedList<Runnable>();
 593             }
 594             runnableList.add(new Runnable() {
 595                 public void run() {
 596                     AccessControlContext stack = AccessController.getContext();
 597                     AccessControlContext acc =
 598                         AWTAccessor.getComponentAccessor().getAccessControlContext(c);
 599                     javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction<Void>() {
 600                         public Void run() {
 601                             r.run();
 602                             return null;
 603                         }
 604                     }, stack, acc);
 605                 }
 606             });
 607         }
 608         scheduleProcessingRunnable(appContext);
 609     }
 610 
 611     /**
 612      * Extends the dirty region for the specified component to include
 613      * the new region.
 614      *
 615      * @return false if <code>c</code> is not yet marked dirty.
 616      */
 617     private synchronized boolean extendDirtyRegion(
 618         Component c, int x, int y, int w, int h) {
 619         Rectangle r = dirtyComponents.get(c);
 620         if (r != null) {
 621             // A non-null r implies c is already marked as dirty,
 622             // and that the parent is valid. Therefore we can
 623             // just union the rect and bail.
 624             SwingUtilities.computeUnion(x, y, w, h, r);
 625             return true;
 626         }
 627         return false;
 628     }
 629 
 630     /**
 631      * Return the current dirty region for a component.
 632      * Return an empty rectangle if the component is not
 633      * dirty.
 634      *
 635      * @param aComponent a component
 636      * @return the region
 637      */
 638     public Rectangle getDirtyRegion(JComponent aComponent) {
 639         RepaintManager delegate = getDelegate(aComponent);
 640         if (delegate != null) {
 641             return delegate.getDirtyRegion(aComponent);
 642         }
 643         Rectangle r;
 644         synchronized(this) {
 645             r = dirtyComponents.get(aComponent);
 646         }
 647         if(r == null)
 648             return new Rectangle(0,0,0,0);
 649         else
 650             return new Rectangle(r);
 651     }
 652 
 653     /**
 654      * Mark a component completely dirty. <b>aComponent</b> will be
 655      * completely painted during the next paintDirtyRegions() call.
 656      *
 657      * @param aComponent a component
 658      */
 659     public void markCompletelyDirty(JComponent aComponent) {
 660         RepaintManager delegate = getDelegate(aComponent);
 661         if (delegate != null) {
 662             delegate.markCompletelyDirty(aComponent);
 663             return;
 664         }
 665         addDirtyRegion(aComponent,0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
 666     }
 667 
 668     /**
 669      * Mark a component completely clean. <b>aComponent</b> will not
 670      * get painted during the next paintDirtyRegions() call.
 671      *
 672      * @param aComponent a component
 673      */
 674     public void markCompletelyClean(JComponent aComponent) {
 675         RepaintManager delegate = getDelegate(aComponent);
 676         if (delegate != null) {
 677             delegate.markCompletelyClean(aComponent);
 678             return;
 679         }
 680         synchronized(this) {
 681                 dirtyComponents.remove(aComponent);
 682         }
 683     }
 684 
 685     /**
 686      * Convenience method that returns true if <b>aComponent</b> will be completely
 687      * painted during the next paintDirtyRegions(). If computing dirty regions is
 688      * expensive for your component, use this method and avoid computing dirty region
 689      * if it return true.
 690      *
 691      * @param aComponent a component
 692      * @return {@code true} if <b>aComponent</b> will be completely
 693      *         painted during the next paintDirtyRegions().
 694      */
 695     public boolean isCompletelyDirty(JComponent aComponent) {
 696         RepaintManager delegate = getDelegate(aComponent);
 697         if (delegate != null) {
 698             return delegate.isCompletelyDirty(aComponent);
 699         }
 700         Rectangle r;
 701 
 702         r = getDirtyRegion(aComponent);
 703         if(r.width == Integer.MAX_VALUE &&
 704            r.height == Integer.MAX_VALUE)
 705             return true;
 706         else
 707             return false;
 708     }
 709 
 710 
 711     /**
 712      * Validate all of the components that have been marked invalid.
 713      * @see #addInvalidComponent
 714      */
 715     public void validateInvalidComponents() {
 716         final java.util.List<Component> ic;
 717         synchronized(this) {
 718             if (invalidComponents == null) {
 719                 return;
 720             }
 721             ic = invalidComponents;
 722             invalidComponents = null;
 723         }
 724         int n = ic.size();
 725         for(int i = 0; i < n; i++) {
 726             final Component c = ic.get(i);
 727             AccessControlContext stack = AccessController.getContext();
 728             AccessControlContext acc =
 729                 AWTAccessor.getComponentAccessor().getAccessControlContext(c);
 730             javaSecurityAccess.doIntersectionPrivilege(
 731                 new PrivilegedAction<Void>() {
 732                     public Void run() {
 733                         c.validate();
 734                         return null;
 735                     }
 736                 }, stack, acc);
 737         }
 738     }
 739 
 740 
 741     /**
 742      * This is invoked to process paint requests.  It's needed
 743      * for backward compatibility in so far as RepaintManager would previously
 744      * not see paint requests for top levels, so, we have to make sure
 745      * a subclass correctly paints any dirty top levels.
 746      */
 747     private void prePaintDirtyRegions() {
 748         Map<Component,Rectangle> dirtyComponents;
 749         java.util.List<Runnable> runnableList;
 750         synchronized(this) {
 751             dirtyComponents = this.dirtyComponents;
 752             runnableList = this.runnableList;
 753             this.runnableList = null;
 754         }
 755         if (runnableList != null) {
 756             for (Runnable runnable : runnableList) {
 757                 runnable.run();
 758             }
 759         }
 760         paintDirtyRegions();
 761         if (dirtyComponents.size() > 0) {
 762             // This'll only happen if a subclass isn't correctly dealing
 763             // with toplevels.
 764             paintDirtyRegions(dirtyComponents);
 765         }
 766     }
 767 
 768     private void updateWindows(Map<Component,Rectangle> dirtyComponents) {
 769         Toolkit toolkit = Toolkit.getDefaultToolkit();
 770         if (!(toolkit instanceof SunToolkit &&
 771               ((SunToolkit)toolkit).needUpdateWindow()))
 772         {
 773             return;
 774         }
 775 
 776         Set<Window> windows = new HashSet<Window>();
 777         Set<Component> dirtyComps = dirtyComponents.keySet();
 778         for (Iterator<Component> it = dirtyComps.iterator(); it.hasNext();) {
 779             Component dirty = it.next();
 780             Window window = dirty instanceof Window ?
 781                 (Window)dirty :
 782                 SwingUtilities.getWindowAncestor(dirty);
 783             if (window != null &&
 784                 !window.isOpaque())
 785             {
 786                 windows.add(window);
 787             }
 788         }
 789 
 790         for (Window window : windows) {
 791             AWTAccessor.getWindowAccessor().updateWindow(window);
 792         }
 793     }
 794 
 795     boolean isPainting() {
 796         return painting;
 797     }
 798 
 799     /**
 800      * Paint all of the components that have been marked dirty.
 801      *
 802      * @see #addDirtyRegion
 803      */
 804     public void paintDirtyRegions() {
 805         synchronized(this) {  // swap for thread safety
 806             Map<Component,Rectangle> tmp = tmpDirtyComponents;
 807             tmpDirtyComponents = dirtyComponents;
 808             dirtyComponents = tmp;
 809             dirtyComponents.clear();
 810         }
 811         paintDirtyRegions(tmpDirtyComponents);
 812     }
 813 
 814     private void paintDirtyRegions(
 815         final Map<Component,Rectangle> tmpDirtyComponents)
 816     {
 817         if (tmpDirtyComponents.isEmpty()) {
 818             return;
 819         }
 820 
 821         final java.util.List<Component> roots =
 822             new ArrayList<Component>(tmpDirtyComponents.size());
 823         for (Component dirty : tmpDirtyComponents.keySet()) {
 824             collectDirtyComponents(tmpDirtyComponents, dirty, roots);
 825         }
 826 
 827         final AtomicInteger count = new AtomicInteger(roots.size());
 828         painting = true;
 829         try {
 830             for (int j=0 ; j < count.get(); j++) {
 831                 final int i = j;
 832                 final Component dirtyComponent = roots.get(j);
 833                 AccessControlContext stack = AccessController.getContext();
 834                 AccessControlContext acc =
 835                     AWTAccessor.getComponentAccessor().getAccessControlContext(dirtyComponent);
 836                 javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction<Void>() {
 837                     public Void run() {
 838                         Rectangle rect = tmpDirtyComponents.get(dirtyComponent);
 839                         // Sometimes when RepaintManager is changed during the painting
 840                         // we may get null here, see #6995769 for details
 841                         if (rect == null) {
 842                             return null;
 843                         }
 844 
 845                         int localBoundsH = dirtyComponent.getHeight();
 846                         int localBoundsW = dirtyComponent.getWidth();
 847                         SwingUtilities.computeIntersection(0,
 848                                                            0,
 849                                                            localBoundsW,
 850                                                            localBoundsH,
 851                                                            rect);
 852                         if (dirtyComponent instanceof JComponent) {
 853                             ((JComponent)dirtyComponent).paintImmediately(
 854                                 rect.x,rect.y,rect.width, rect.height);
 855                         }
 856                         else if (dirtyComponent.isShowing()) {
 857                             Graphics g = JComponent.safelyGetGraphics(
 858                                     dirtyComponent, dirtyComponent);
 859                             // If the Graphics goes away, it means someone disposed of
 860                             // the window, don't do anything.
 861                             if (g != null) {
 862                                 g.setClip(rect.x, rect.y, rect.width, rect.height);
 863                                 try {
 864                                     dirtyComponent.paint(g);
 865                                 } finally {
 866                                     g.dispose();
 867                                 }
 868                             }
 869                         }
 870                         // If the repaintRoot has been set, service it now and
 871                         // remove any components that are children of repaintRoot.
 872                         if (repaintRoot != null) {
 873                             adjustRoots(repaintRoot, roots, i + 1);
 874                             count.set(roots.size());
 875                             paintManager.isRepaintingRoot = true;
 876                             repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(),
 877                                                          repaintRoot.getHeight());
 878                             paintManager.isRepaintingRoot = false;
 879                             // Only service repaintRoot once.
 880                             repaintRoot = null;
 881                         }
 882 
 883                         return null;
 884                     }
 885                 }, stack, acc);
 886             }
 887         } finally {
 888             painting = false;
 889         }
 890 
 891         updateWindows(tmpDirtyComponents);
 892 
 893         tmpDirtyComponents.clear();
 894     }
 895 
 896 
 897     /**
 898      * Removes any components from roots that are children of
 899      * root.
 900      */
 901     private void adjustRoots(JComponent root,
 902                              java.util.List<Component> roots, int index) {
 903         for (int i = roots.size() - 1; i >= index; i--) {
 904             Component c = roots.get(i);
 905             for(;;) {
 906                 if (c == root || c == null || !(c instanceof JComponent)) {
 907                     break;
 908                 }
 909                 c = c.getParent();
 910             }
 911             if (c == root) {
 912                 roots.remove(i);
 913             }
 914         }
 915     }
 916 
 917     Rectangle tmp = new Rectangle();
 918 
 919     void collectDirtyComponents(Map<Component,Rectangle> dirtyComponents,
 920                                 Component dirtyComponent,
 921                                 java.util.List<Component> roots) {
 922         int dx, dy, rootDx, rootDy;
 923         Component component, rootDirtyComponent,parent;
 924         Rectangle cBounds;
 925 
 926         // Find the highest parent which is dirty.  When we get out of this
 927         // rootDx and rootDy will contain the translation from the
 928         // rootDirtyComponent's coordinate system to the coordinates of the
 929         // original dirty component.  The tmp Rect is also used to compute the
 930         // visible portion of the dirtyRect.
 931 
 932         component = rootDirtyComponent = dirtyComponent;
 933 
 934         int x = dirtyComponent.getX();
 935         int y = dirtyComponent.getY();
 936         int w = dirtyComponent.getWidth();
 937         int h = dirtyComponent.getHeight();
 938 
 939         dx = rootDx = 0;
 940         dy = rootDy = 0;
 941         tmp.setBounds(dirtyComponents.get(dirtyComponent));
 942 
 943         // System.out.println("Collect dirty component for bound " + tmp +
 944         //                                   "component bounds is " + cBounds);;
 945         SwingUtilities.computeIntersection(0,0,w,h,tmp);
 946 
 947         if (tmp.isEmpty()) {
 948             // System.out.println("Empty 1");
 949             return;
 950         }
 951 
 952         for(;;) {
 953             if(!(component instanceof JComponent))
 954                 break;
 955 
 956             parent = component.getParent();
 957             if(parent == null)
 958                 break;
 959 
 960             component = parent;
 961 
 962             dx += x;
 963             dy += y;
 964             tmp.setLocation(tmp.x + x, tmp.y + y);
 965 
 966             x = component.getX();
 967             y = component.getY();
 968             w = component.getWidth();
 969             h = component.getHeight();
 970             tmp = SwingUtilities.computeIntersection(0,0,w,h,tmp);
 971 
 972             if (tmp.isEmpty()) {
 973                 // System.out.println("Empty 2");
 974                 return;
 975             }
 976 
 977             if (dirtyComponents.get(component) != null) {
 978                 rootDirtyComponent = component;
 979                 rootDx = dx;
 980                 rootDy = dy;
 981             }
 982         }
 983 
 984         if (dirtyComponent != rootDirtyComponent) {
 985             Rectangle r;
 986             tmp.setLocation(tmp.x + rootDx - dx,
 987                             tmp.y + rootDy - dy);
 988             r = dirtyComponents.get(rootDirtyComponent);
 989             SwingUtilities.computeUnion(tmp.x,tmp.y,tmp.width,tmp.height,r);
 990         }
 991 
 992         // If we haven't seen this root before, then we need to add it to the
 993         // list of root dirty Views.
 994 
 995         if (!roots.contains(rootDirtyComponent))
 996             roots.add(rootDirtyComponent);
 997     }
 998 
 999 
1000     /**
1001      * Returns a string that displays and identifies this
1002      * object's properties.
1003      *
1004      * @return a String representation of this object
1005      */
1006     public synchronized String toString() {
1007         StringBuilder sb = new StringBuilder();
1008         if(dirtyComponents != null)
1009             sb.append("" + dirtyComponents);
1010         return sb.toString();
1011     }
1012 
1013 
1014     /**
1015      * Return the offscreen buffer that should be used as a double buffer with
1016      * the component <code>c</code>.
1017      * By default there is a double buffer per RepaintManager.
1018      * The buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>
1019      * This happens when the maximum double buffer size as been set for the receiving
1020      * repaint manager.
1021      *
1022      * @param c the component
1023      * @param proposedWidth the width of the buffer
1024      * @param proposedHeight the height of the buffer
1025      *
1026      * @return the image
1027      */
1028     public Image getOffscreenBuffer(Component c,int proposedWidth,int proposedHeight) {
1029         RepaintManager delegate = getDelegate(c);
1030         if (delegate != null) {
1031             return delegate.getOffscreenBuffer(c, proposedWidth, proposedHeight);
1032         }
1033         return _getOffscreenBuffer(c, proposedWidth, proposedHeight);
1034     }
1035 
1036     /**
1037      * Return a volatile offscreen buffer that should be used as a
1038      * double buffer with the specified component <code>c</code>.
1039      * The image returned will be an instance of VolatileImage, or null
1040      * if a VolatileImage object could not be instantiated.
1041      * This buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>.
1042      * This happens when the maximum double buffer size has been set for this
1043      * repaint manager.
1044      *
1045      * @param c the component
1046      * @param proposedWidth the width of the buffer
1047      * @param proposedHeight the height of the buffer
1048      *
1049      * @return the volatile image
1050      * @see java.awt.image.VolatileImage
1051      * @since 1.4
1052      */
1053     public Image getVolatileOffscreenBuffer(Component c,
1054                                             int proposedWidth,int proposedHeight) {
1055         RepaintManager delegate = getDelegate(c);
1056         if (delegate != null) {
1057             return delegate.getVolatileOffscreenBuffer(c, proposedWidth,
1058                                                         proposedHeight);
1059         }
1060 
1061         // If the window is non-opaque, it's double-buffered at peer's level
1062         Window w = (c instanceof Window) ? (Window)c : SwingUtilities.getWindowAncestor(c);
1063         if (!w.isOpaque()) {
1064             Toolkit tk = Toolkit.getDefaultToolkit();
1065             if ((tk instanceof SunToolkit) && (((SunToolkit)tk).needUpdateWindow())) {
1066                 return null;
1067             }
1068         }
1069 
1070         GraphicsConfiguration config = c.getGraphicsConfiguration();
1071         if (config == null) {
1072             config = GraphicsEnvironment.getLocalGraphicsEnvironment().
1073                             getDefaultScreenDevice().getDefaultConfiguration();
1074         }
1075         Dimension maxSize = getDoubleBufferMaximumSize();
1076         int width = proposedWidth < 1 ? 1 :
1077             (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
1078         int height = proposedHeight < 1 ? 1 :
1079             (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
1080         VolatileImage image = volatileMap.get(config);
1081         if (image == null || image.getWidth() < width ||
1082                              image.getHeight() < height) {
1083             if (image != null) {
1084                 image.flush();
1085             }
1086             image = config.createCompatibleVolatileImage(width, height,
1087                                                          volatileBufferType);
1088             volatileMap.put(config, image);
1089         }
1090         return image;
1091     }
1092 
1093     private Image _getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
1094         Dimension maxSize = getDoubleBufferMaximumSize();
1095         DoubleBufferInfo doubleBuffer;
1096         int width, height;
1097 
1098         // If the window is non-opaque, it's double-buffered at peer's level
1099         Window w = (c instanceof Window) ? (Window)c : SwingUtilities.getWindowAncestor(c);
1100         if (!w.isOpaque()) {
1101             Toolkit tk = Toolkit.getDefaultToolkit();
1102             if ((tk instanceof SunToolkit) && (((SunToolkit)tk).needUpdateWindow())) {
1103                 return null;
1104             }
1105         }
1106 
1107         if (standardDoubleBuffer == null) {
1108             standardDoubleBuffer = new DoubleBufferInfo();
1109         }
1110         doubleBuffer = standardDoubleBuffer;
1111 
1112         width = proposedWidth < 1? 1 :
1113                   (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
1114         height = proposedHeight < 1? 1 :
1115                   (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
1116 
1117         if (doubleBuffer.needsReset || (doubleBuffer.image != null &&
1118                                         (doubleBuffer.size.width < width ||
1119                                          doubleBuffer.size.height < height))) {
1120             doubleBuffer.needsReset = false;
1121             if (doubleBuffer.image != null) {
1122                 doubleBuffer.image.flush();
1123                 doubleBuffer.image = null;
1124             }
1125             width = Math.max(doubleBuffer.size.width, width);
1126             height = Math.max(doubleBuffer.size.height, height);
1127         }
1128 
1129         Image result = doubleBuffer.image;
1130 
1131         if (doubleBuffer.image == null) {
1132             result = c.createImage(width , height);
1133             doubleBuffer.size = new Dimension(width, height);
1134             if (c instanceof JComponent) {
1135                 ((JComponent)c).setCreatedDoubleBuffer(true);
1136                 doubleBuffer.image = result;
1137             }
1138             // JComponent will inform us when it is no longer valid
1139             // (via removeNotify) we have no such hook to other components,
1140             // therefore we don't keep a ref to the Component
1141             // (indirectly through the Image) by stashing the image.
1142         }
1143         return result;
1144     }
1145 
1146 
1147     /**
1148      * Set the maximum double buffer size.
1149      *
1150      * @param d the dimension
1151      */
1152     public void setDoubleBufferMaximumSize(Dimension d) {
1153         doubleBufferMaxSize = d;
1154         if (doubleBufferMaxSize == null) {
1155             clearImages();
1156         } else {
1157             clearImages(d.width, d.height);
1158         }
1159     }
1160 
1161     private void clearImages() {
1162         clearImages(0, 0);
1163     }
1164 
1165     private void clearImages(int width, int height) {
1166         if (standardDoubleBuffer != null && standardDoubleBuffer.image != null) {
1167             if (standardDoubleBuffer.image.getWidth(null) > width ||
1168                 standardDoubleBuffer.image.getHeight(null) > height) {
1169                 standardDoubleBuffer.image.flush();
1170                 standardDoubleBuffer.image = null;
1171             }
1172         }
1173         // Clear out the VolatileImages
1174         Iterator<GraphicsConfiguration> gcs = volatileMap.keySet().iterator();
1175         while (gcs.hasNext()) {
1176             GraphicsConfiguration gc = gcs.next();
1177             VolatileImage image = volatileMap.get(gc);
1178             if (image.getWidth() > width || image.getHeight() > height) {
1179                 image.flush();
1180                 gcs.remove();
1181             }
1182         }
1183     }
1184 
1185     /**
1186      * Returns the maximum double buffer size.
1187      *
1188      * @return a Dimension object representing the maximum size
1189      */
1190     public Dimension getDoubleBufferMaximumSize() {
1191         if (doubleBufferMaxSize == null) {
1192             try {
1193                 Rectangle virtualBounds = new Rectangle();
1194                 GraphicsEnvironment ge = GraphicsEnvironment.
1195                                                  getLocalGraphicsEnvironment();
1196                 for (GraphicsDevice gd : ge.getScreenDevices()) {
1197                     GraphicsConfiguration gc = gd.getDefaultConfiguration();
1198                     virtualBounds = virtualBounds.union(gc.getBounds());
1199                 }
1200                 doubleBufferMaxSize = new Dimension(virtualBounds.width,
1201                                                     virtualBounds.height);
1202             } catch (HeadlessException e) {
1203                 doubleBufferMaxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
1204             }
1205         }
1206         return doubleBufferMaxSize;
1207     }
1208 
1209     /**
1210      * Enables or disables double buffering in this RepaintManager.
1211      * CAUTION: The default value for this property is set for optimal
1212      * paint performance on the given platform and it is not recommended
1213      * that programs modify this property directly.
1214      *
1215      * @param aFlag  true to activate double buffering
1216      * @see #isDoubleBufferingEnabled
1217      */
1218     public void setDoubleBufferingEnabled(boolean aFlag) {
1219         doubleBufferingEnabled = aFlag;
1220         PaintManager paintManager = getPaintManager();
1221         if (!aFlag && paintManager.getClass() != PaintManager.class) {
1222             setPaintManager(new PaintManager());
1223         }
1224     }
1225 
1226     /**
1227      * Returns true if this RepaintManager is double buffered.
1228      * The default value for this property may vary from platform
1229      * to platform.  On platforms where native double buffering
1230      * is supported in the AWT, the default value will be <code>false</code>
1231      * to avoid unnecessary buffering in Swing.
1232      * On platforms where native double buffering is not supported,
1233      * the default value will be <code>true</code>.
1234      *
1235      * @return true if this object is double buffered
1236      */
1237     public boolean isDoubleBufferingEnabled() {
1238         return doubleBufferingEnabled;
1239     }
1240 
1241     /**
1242      * This resets the double buffer. Actually, it marks the double buffer
1243      * as invalid, the double buffer will then be recreated on the next
1244      * invocation of getOffscreenBuffer.
1245      */
1246     void resetDoubleBuffer() {
1247         if (standardDoubleBuffer != null) {
1248             standardDoubleBuffer.needsReset = true;
1249         }
1250     }
1251 
1252     /**
1253      * This resets the volatile double buffer.
1254      */
1255     void resetVolatileDoubleBuffer(GraphicsConfiguration gc) {
1256         Image image = volatileMap.remove(gc);
1257         if (image != null) {
1258             image.flush();
1259         }
1260     }
1261 
1262     /**
1263      * Returns true if we should use the <code>Image</code> returned
1264      * from <code>getVolatileOffscreenBuffer</code> to do double buffering.
1265      */
1266     boolean useVolatileDoubleBuffer() {
1267         return volatileImageBufferEnabled;
1268     }
1269 
1270     /**
1271      * Returns true if the current thread is the thread painting.  This
1272      * will return false if no threads are painting.
1273      */
1274     private synchronized boolean isPaintingThread() {
1275         return (Thread.currentThread() == paintThread);
1276     }
1277     //
1278     // Paint methods.  You very, VERY rarely need to invoke these.
1279     // They are invoked directly from JComponent's painting code and
1280     // when painting happens outside the normal flow: DefaultDesktopManager
1281     // and JViewport.  If you end up needing these methods in other places be
1282     // careful that you don't get stuck in a paint loop.
1283     //
1284 
1285     /**
1286      * Paints a region of a component
1287      *
1288      * @param paintingComponent Component to paint
1289      * @param bufferComponent Component to obtain buffer for
1290      * @param g Graphics to paint to
1291      * @param x X-coordinate
1292      * @param y Y-coordinate
1293      * @param w Width
1294      * @param h Height
1295      */
1296     void paint(JComponent paintingComponent,
1297                JComponent bufferComponent, Graphics g,
1298                int x, int y, int w, int h) {
1299         PaintManager paintManager = getPaintManager();
1300         if (!isPaintingThread()) {
1301             // We're painting to two threads at once.  PaintManager deals
1302             // with this a bit better than BufferStrategyPaintManager, use
1303             // it to avoid possible exceptions/corruption.
1304             if (paintManager.getClass() != PaintManager.class) {
1305                 paintManager = new PaintManager();
1306                 paintManager.repaintManager = this;
1307             }
1308         }
1309         if (!paintManager.paint(paintingComponent, bufferComponent, g,
1310                                 x, y, w, h)) {
1311             g.setClip(x, y, w, h);
1312             paintingComponent.paintToOffscreen(g, x, y, w, h, x + w, y + h);
1313         }
1314     }
1315 
1316     /**
1317      * Does a copy area on the specified region.
1318      *
1319      * @param clip Whether or not the copyArea needs to be clipped to the
1320      *             Component's bounds.
1321      */
1322     void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
1323                   int deltaX, int deltaY, boolean clip) {
1324         getPaintManager().copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
1325     }
1326 
1327     private java.util.List<RepaintListener> repaintListeners = new ArrayList<>(1);
1328 
1329     private void addRepaintListener(RepaintListener l) {
1330         repaintListeners.add(l);
1331     }
1332 
1333     private void removeRepaintListener(RepaintListener l) {
1334         repaintListeners.remove(l);
1335     }
1336 
1337     /**
1338      * Notify the attached repaint listeners that an area of the {@code c} component
1339      * has been immediately repainted, that is without scheduling a repaint runnable,
1340      * due to performing a "blit" (via calling the {@code copyArea} method).
1341      *
1342      * @param c the component
1343      * @param x the x coordinate of the area
1344      * @param y the y coordinate of the area
1345      * @param w the width of the area
1346      * @param h the height of the area
1347      */
1348     void notifyRepaintPerformed(JComponent c, int x, int y, int w, int h) {
1349         for (RepaintListener l : repaintListeners) {
1350             l.repaintPerformed(c, x, y, w, h);
1351         }
1352     }
1353 
1354     /**
1355      * Invoked prior to any paint/copyArea method calls.  This will
1356      * be followed by an invocation of <code>endPaint</code>.
1357      * <b>WARNING</b>: Callers of this method need to wrap the call
1358      * in a <code>try/finally</code>, otherwise if an exception is thrown
1359      * during the course of painting the RepaintManager may
1360      * be left in a state in which the screen is not updated, eg:
1361      * <pre>
1362      * repaintManager.beginPaint();
1363      * try {
1364      *   repaintManager.paint(...);
1365      * } finally {
1366      *   repaintManager.endPaint();
1367      * }
1368      * </pre>
1369      */
1370     void beginPaint() {
1371         boolean multiThreadedPaint = false;
1372         int paintDepth;
1373         Thread currentThread = Thread.currentThread();
1374         synchronized(this) {
1375             paintDepth = this.paintDepth;
1376             if (paintThread == null || currentThread == paintThread) {
1377                 paintThread = currentThread;
1378                 this.paintDepth++;
1379             } else {
1380                 multiThreadedPaint = true;
1381             }
1382         }
1383         if (!multiThreadedPaint && paintDepth == 0) {
1384             getPaintManager().beginPaint();
1385         }
1386     }
1387 
1388     /**
1389      * Invoked after <code>beginPaint</code> has been invoked.
1390      */
1391     void endPaint() {
1392         if (isPaintingThread()) {
1393             PaintManager paintManager = null;
1394             synchronized(this) {
1395                 if (--paintDepth == 0) {
1396                     paintManager = getPaintManager();
1397                 }
1398             }
1399             if (paintManager != null) {
1400                 paintManager.endPaint();
1401                 synchronized(this) {
1402                     paintThread = null;
1403                 }
1404             }
1405         }
1406     }
1407 
1408     /**
1409      * If possible this will show a previously rendered portion of
1410      * a Component.  If successful, this will return true, otherwise false.
1411      * <p>
1412      * WARNING: This method is invoked from the native toolkit thread, be
1413      * very careful as to what methods this invokes!
1414      */
1415     boolean show(Container c, int x, int y, int w, int h) {
1416         return getPaintManager().show(c, x, y, w, h);
1417     }
1418 
1419     /**
1420      * Invoked when the doubleBuffered or useTrueDoubleBuffering
1421      * properties of a JRootPane change.  This may come in on any thread.
1422      */
1423     void doubleBufferingChanged(JRootPane rootPane) {
1424         getPaintManager().doubleBufferingChanged(rootPane);
1425     }
1426 
1427     /**
1428      * Sets the <code>PaintManager</code> that is used to handle all
1429      * double buffered painting.
1430      *
1431      * @param paintManager The PaintManager to use.  Passing in null indicates
1432      *        the fallback PaintManager should be used.
1433      */
1434     void setPaintManager(PaintManager paintManager) {
1435         if (paintManager == null) {
1436             paintManager = new PaintManager();
1437         }
1438         PaintManager oldPaintManager;
1439         synchronized(this) {
1440             oldPaintManager = this.paintManager;
1441             this.paintManager = paintManager;
1442             paintManager.repaintManager = this;
1443         }
1444         if (oldPaintManager != null) {
1445             oldPaintManager.dispose();
1446         }
1447     }
1448 
1449     private synchronized PaintManager getPaintManager() {
1450         if (paintManager == null) {
1451             PaintManager paintManager = null;
1452             if (doubleBufferingEnabled && !nativeDoubleBuffering) {
1453                 switch (bufferStrategyType) {
1454                 case BUFFER_STRATEGY_NOT_SPECIFIED:
1455                     Toolkit tk = Toolkit.getDefaultToolkit();
1456                     if (tk instanceof SunToolkit) {
1457                         SunToolkit stk = (SunToolkit) tk;
1458                         if (stk.useBufferPerWindow()) {
1459                             paintManager = new BufferStrategyPaintManager();
1460                         }
1461                     }
1462                     break;
1463                 case BUFFER_STRATEGY_SPECIFIED_ON:
1464                     paintManager = new BufferStrategyPaintManager();
1465                     break;
1466                 default:
1467                     break;
1468                 }
1469             }
1470             // null case handled in setPaintManager
1471             setPaintManager(paintManager);
1472         }
1473         return paintManager;
1474     }
1475 
1476     private void scheduleProcessingRunnable(AppContext context) {
1477         if (processingRunnable.markPending()) {
1478             Toolkit tk = Toolkit.getDefaultToolkit();
1479             if (tk instanceof SunToolkit) {
1480                 SunToolkit.getSystemEventQueueImplPP(context).
1481                   postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
1482                                                 processingRunnable));
1483             } else {
1484                 Toolkit.getDefaultToolkit().getSystemEventQueue().
1485                       postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
1486                                                     processingRunnable));
1487             }
1488         }
1489     }
1490 
1491 
1492     /**
1493      * PaintManager is used to handle all double buffered painting for
1494      * Swing.  Subclasses should call back into the JComponent method
1495      * <code>paintToOffscreen</code> to handle the actual painting.
1496      */
1497     static class PaintManager {
1498         /**
1499          * RepaintManager the PaintManager has been installed on.
1500          */
1501         protected RepaintManager repaintManager;
1502         boolean isRepaintingRoot;
1503 
1504         /**
1505          * Paints a region of a component
1506          *
1507          * @param paintingComponent Component to paint
1508          * @param bufferComponent Component to obtain buffer for
1509          * @param g Graphics to paint to
1510          * @param x X-coordinate
1511          * @param y Y-coordinate
1512          * @param w Width
1513          * @param h Height
1514          * @return true if painting was successful.
1515          */
1516         public boolean paint(JComponent paintingComponent,
1517                              JComponent bufferComponent, Graphics g,
1518                              int x, int y, int w, int h) {
1519             // First attempt to use VolatileImage buffer for performance.
1520             // If this fails (which should rarely occur), fallback to a
1521             // standard Image buffer.
1522             boolean paintCompleted = false;
1523             Image offscreen;
1524             if (repaintManager.useVolatileDoubleBuffer() &&
1525                 (offscreen = getValidImage(repaintManager.
1526                 getVolatileOffscreenBuffer(bufferComponent, w, h))) != null) {
1527                 VolatileImage vImage = (java.awt.image.VolatileImage)offscreen;
1528                 GraphicsConfiguration gc = bufferComponent.
1529                                             getGraphicsConfiguration();
1530                 for (int i = 0; !paintCompleted &&
1531                          i < RepaintManager.VOLATILE_LOOP_MAX; i++) {
1532                     if (vImage.validate(gc) ==
1533                                    VolatileImage.IMAGE_INCOMPATIBLE) {
1534                         repaintManager.resetVolatileDoubleBuffer(gc);
1535                         offscreen = repaintManager.getVolatileOffscreenBuffer(
1536                             bufferComponent,w, h);
1537                         vImage = (java.awt.image.VolatileImage)offscreen;
1538                     }
1539                     paintDoubleBuffered(paintingComponent, vImage, g, x, y,
1540                                         w, h);
1541                     paintCompleted = !vImage.contentsLost();
1542                 }
1543             }
1544             // VolatileImage painting loop failed, fallback to regular
1545             // offscreen buffer
1546             if (!paintCompleted && (offscreen = getValidImage(
1547                       repaintManager.getOffscreenBuffer(
1548                       bufferComponent, w, h))) != null) {
1549                 paintDoubleBuffered(paintingComponent, offscreen, g, x, y, w,
1550                                     h);
1551                 paintCompleted = true;
1552             }
1553             return paintCompleted;
1554         }
1555 
1556         /**
1557          * Does a copy area on the specified region.
1558          */
1559         public void copyArea(JComponent c, Graphics g, int x, int y, int w,
1560                              int h, int deltaX, int deltaY, boolean clip) {
1561             g.copyArea(x, y, w, h, deltaX, deltaY);
1562         }
1563 
1564         /**
1565          * Invoked prior to any calls to paint or copyArea.
1566          */
1567         public void beginPaint() {
1568         }
1569 
1570         /**
1571          * Invoked to indicate painting has been completed.
1572          */
1573         public void endPaint() {
1574         }
1575 
1576         /**
1577          * Shows a region of a previously rendered component.  This
1578          * will return true if successful, false otherwise.  The default
1579          * implementation returns false.
1580          */
1581         public boolean show(Container c, int x, int y, int w, int h) {
1582             return false;
1583         }
1584 
1585         /**
1586          * Invoked when the doubleBuffered or useTrueDoubleBuffering
1587          * properties of a JRootPane change.  This may come in on any thread.
1588          */
1589         public void doubleBufferingChanged(JRootPane rootPane) {
1590         }
1591 
1592         /**
1593          * Paints a portion of a component to an offscreen buffer.
1594          */
1595         protected void paintDoubleBuffered(JComponent c, Image image,
1596                             Graphics g, int clipX, int clipY,
1597                             int clipW, int clipH) {
1598             Graphics osg = image.getGraphics();
1599             int bw = Math.min(clipW, image.getWidth(null));
1600             int bh = Math.min(clipH, image.getHeight(null));
1601             int x,y,maxx,maxy;
1602 
1603             try {
1604                 for(x = clipX, maxx = clipX+clipW; x < maxx ;  x += bw ) {
1605                     for(y=clipY, maxy = clipY + clipH; y < maxy ; y += bh) {
1606                         osg.translate(-x, -y);
1607                         osg.setClip(x,y,bw,bh);
1608                         if (volatileBufferType != Transparency.OPAQUE
1609                                 && osg instanceof Graphics2D) {
1610                             final Graphics2D g2d = (Graphics2D) osg;
1611                             final Color oldBg = g2d.getBackground();
1612                             g2d.setBackground(c.getBackground());
1613                             g2d.clearRect(x, y, bw, bh);
1614                             g2d.setBackground(oldBg);
1615                         }
1616                         c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);
1617                         g.setClip(x, y, bw, bh);
1618                         if (volatileBufferType != Transparency.OPAQUE
1619                                 && g instanceof Graphics2D) {
1620                             final Graphics2D g2d = (Graphics2D) g;
1621                             final Composite oldComposite = g2d.getComposite();
1622                             g2d.setComposite(AlphaComposite.Src);
1623                             g2d.drawImage(image, x, y, c);
1624                             g2d.setComposite(oldComposite);
1625                         } else {
1626                             g.drawImage(image, x, y, c);
1627                         }
1628                         osg.translate(x, y);
1629                     }
1630                 }
1631             } finally {
1632                 osg.dispose();
1633             }
1634         }
1635 
1636         /**
1637          * If <code>image</code> is non-null with a positive size it
1638          * is returned, otherwise null is returned.
1639          */
1640         private Image getValidImage(Image image) {
1641             if (image != null && image.getWidth(null) > 0 &&
1642                                  image.getHeight(null) > 0) {
1643                 return image;
1644             }
1645             return null;
1646         }
1647 
1648         /**
1649          * Schedules a repaint for the specified component.  This differs
1650          * from <code>root.repaint</code> in that if the RepaintManager is
1651          * currently processing paint requests it'll process this request
1652          * with the current set of requests.
1653          */
1654         protected void repaintRoot(JComponent root) {
1655             assert (repaintManager.repaintRoot == null);
1656             if (repaintManager.painting) {
1657                 repaintManager.repaintRoot = root;
1658             }
1659             else {
1660                 root.repaint();
1661             }
1662         }
1663 
1664         /**
1665          * Returns true if the component being painted is the root component
1666          * that was previously passed to <code>repaintRoot</code>.
1667          */
1668         protected boolean isRepaintingRoot() {
1669             return isRepaintingRoot;
1670         }
1671 
1672         /**
1673          * Cleans up any state.  After invoked the PaintManager will no
1674          * longer be used anymore.
1675          */
1676         protected void dispose() {
1677         }
1678     }
1679 
1680 
1681     private class DoubleBufferInfo {
1682         public Image image;
1683         public Dimension size;
1684         public boolean needsReset = false;
1685     }
1686 
1687 
1688     /**
1689      * Listener installed to detect display changes. When display changes,
1690      * schedules a callback to notify all RepaintManagers of the display
1691      * changes. Only one DisplayChangedHandler is ever installed. The
1692      * singleton instance will schedule notification for all AppContexts.
1693      */
1694     private static final class DisplayChangedHandler implements
1695                                              DisplayChangedListener {
1696         // Empty non private constructor was added because access to this
1697         // class shouldn't be generated by the compiler using synthetic
1698         // accessor method
1699         DisplayChangedHandler() {
1700         }
1701 
1702         public void displayChanged() {
1703             scheduleDisplayChanges();
1704         }
1705 
1706         public void paletteChanged() {
1707         }
1708 
1709         private static void scheduleDisplayChanges() {
1710             // To avoid threading problems, we notify each RepaintManager
1711             // on the thread it was created on.
1712             for (AppContext context : AppContext.getAppContexts()) {
1713                 synchronized(context) {
1714                     if (!context.isDisposed()) {
1715                         EventQueue eventQueue = (EventQueue)context.get(
1716                             AppContext.EVENT_QUEUE_KEY);
1717                         if (eventQueue != null) {
1718                             eventQueue.postEvent(new InvocationEvent(
1719                                 Toolkit.getDefaultToolkit(),
1720                                 new DisplayChangedRunnable()));
1721                         }
1722                     }
1723                 }
1724             }
1725         }
1726     }
1727 
1728 
1729     private static final class DisplayChangedRunnable implements Runnable {
1730         public void run() {
1731             RepaintManager.currentManager((JComponent)null).displayChanged();
1732         }
1733     }
1734 
1735 
1736     /**
1737      * Runnable used to process all repaint/revalidate requests.
1738      */
1739     private final class ProcessingRunnable implements Runnable {
1740         // If true, we're wainting on the EventQueue.
1741         private boolean pending;
1742 
1743         /**
1744          * Marks this processing runnable as pending. If this was not
1745          * already marked as pending, true is returned.
1746          */
1747         public synchronized boolean markPending() {
1748             if (!pending) {
1749                 pending = true;
1750                 return true;
1751             }
1752             return false;
1753         }
1754 
1755         public void run() {
1756             synchronized (this) {
1757                 pending = false;
1758             }
1759             // First pass, flush any heavy paint events into real paint
1760             // events.  If there are pending heavy weight requests this will
1761             // result in q'ing this request up one more time.  As
1762             // long as no other requests come in between now and the time
1763             // the second one is processed nothing will happen.  This is not
1764             // ideal, but the logic needed to suppress the second request is
1765             // more headache than it's worth.
1766             scheduleHeavyWeightPaints();
1767             // Do the actual validation and painting.
1768             validateInvalidComponents();
1769             prePaintDirtyRegions();
1770         }
1771     }
1772     private RepaintManager getDelegate(Component c) {
1773         RepaintManager delegate = SwingUtilities3.getDelegateRepaintManager(c);
1774         if (this == delegate) {
1775             delegate = null;
1776         }
1777         return delegate;
1778     }
1779 }