/*
 * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package javax.swing;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.lang.ref.WeakReference;
import java.util.*;

import com.sun.java.swing.SwingUtilities3;
import sun.awt.AWTAccessor;

import sun.awt.SubRegionShowable;
import sun.java2d.SunGraphics2D;
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
import sun.awt.SunToolkit;
import sun.util.logging.PlatformLogger;

/**
 * A PaintManager implementation that uses a BufferStrategy for
 * rendering.
 *
 * @author Scott Violet
 */
class BufferStrategyPaintManager extends RepaintManager.PaintManager {
    //
    // All drawing is done to a BufferStrategy.  At the end of painting
    // (endPaint) the region that was painted is flushed to the screen
    // (using BufferStrategy.show).
    //
    // PaintManager.show is overridden to show directly from the
    // BufferStrategy (when using blit), if successful true is
    // returned and a paint event will not be generated.  To avoid
    // showing from the buffer while painting a locking scheme is
    // implemented.  When beginPaint is invoked the field painting is
    // set to true.  If painting is true and show is invoked we
    // immediately return false.  This is done to avoid blocking the
    // toolkit thread while painting happens.  In a similar way when
    // show is invoked the field showing is set to true, beginPaint
    // will then block until showing is true.  This scheme ensures we
    // only ever have one thread using the BufferStrategy and it also
    // ensures the toolkit thread remains as responsive as possible.
    //
    // If we're using a flip strategy the contents of the backbuffer may
    // have changed and so show only attempts to show from the backbuffer
    // if we get a blit strategy.
    //

    private static final PlatformLogger LOGGER = PlatformLogger.getLogger(
                           "javax.swing.BufferStrategyPaintManager");

    /**
     * List of BufferInfos.  We don't use a Map primarily because
     * there are typically only a handful of top level components making
     * a Map overkill.
     */
    private ArrayList<BufferInfo> bufferInfos;

    /**
     * Indicates <code>beginPaint</code> has been invoked.  This is
     * set to true for the life of beginPaint/endPaint pair.
     */
    private boolean painting;
    /**
     * Indicates we're in the process of showing.  All painting, on the EDT,
     * is blocked while this is true.
     */
    private boolean showing;

    //
    // Region that we need to flush.  When beginPaint is called these are
    // reset and any subsequent calls to paint/copyArea then update these
    // fields accordingly.  When endPaint is called we then try and show
    // the accumulated region.
    // These fields are in the coordinate system of the root.
    //
    private int accumulatedX;
    private int accumulatedY;
    private int accumulatedMaxX;
    private int accumulatedMaxY;

    //
    // The following fields are set by prepare
    //

    /**
     * Farthest JComponent ancestor for the current paint/copyArea.
     */
    private JComponent rootJ;
    /**
     * Location of component being painted relative to root.
     */
    private int xOffset;
    /**
     * Location of component being painted relative to root.
     */
    private int yOffset;
    /**
     * Graphics from the BufferStrategy.
     */
    private Graphics bsg;
    /**
     * BufferStrategy currently being used.
     */
    private BufferStrategy bufferStrategy;
    /**
     * BufferInfo corresponding to root.
     */
    private BufferInfo bufferInfo;

    /**
     * Set to true if the bufferInfo needs to be disposed when current
     * paint loop is done.
     */
    private boolean disposeBufferOnEnd;

    BufferStrategyPaintManager() {
        bufferInfos = new ArrayList<BufferInfo>(1);
    }

    //
    // PaintManager methods
    //

    /**
     * Cleans up any created BufferStrategies.
     */
    protected void dispose() {
        // dispose can be invoked at any random time. To avoid
        // threading dependencies we do the actual disposing via an
        // invokeLater.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                java.util.List<BufferInfo> bufferInfos;
                synchronized(BufferStrategyPaintManager.this) {
                    while (showing) {
                        try {
                            BufferStrategyPaintManager.this.wait();
                        } catch (InterruptedException ie) {
                        }
                    }
                    bufferInfos = BufferStrategyPaintManager.this.bufferInfos;
                    BufferStrategyPaintManager.this.bufferInfos = null;
                }
                dispose(bufferInfos);
            }
        });
    }

    private void dispose(java.util.List<BufferInfo> bufferInfos) {
        if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
            LOGGER.finer("BufferStrategyPaintManager disposed",
                         new RuntimeException());
        }
        if (bufferInfos != null) {
            for (BufferInfo bufferInfo : bufferInfos) {
                bufferInfo.dispose();
            }
        }
    }

    /**
     * Shows the specified region of the back buffer.  This will return
     * true if successful, false otherwise.  This is invoked on the
     * toolkit thread in response to an expose event.
     */
    public boolean show(Container c, int x, int y, int w, int h) {
        synchronized(this) {
            if (painting) {
                // Don't show from backbuffer while in the process of
                // painting.
                return false;
            }
            showing = true;
        }
        try {
            BufferInfo info = getBufferInfo(c);
            BufferStrategy bufferStrategy;
            if (info != null && info.isInSync() &&
                (bufferStrategy = info.getBufferStrategy(false)) != null) {
                SubRegionShowable bsSubRegion =
                        (SubRegionShowable)bufferStrategy;
                boolean paintAllOnExpose = info.getPaintAllOnExpose();
                info.setPaintAllOnExpose(false);
                if (bsSubRegion.showIfNotLost(x, y, (x + w), (y + h))) {
                    return !paintAllOnExpose;
                }
                // Mark the buffer as needing to be repainted.  We don't
                // immediately do a repaint as this method will return false
                // indicating a PaintEvent should be generated which will
                // trigger a complete repaint.
                bufferInfo.setContentsLostDuringExpose(true);
            }
        }
        finally {
            synchronized(this) {
                showing = false;
                notifyAll();
            }
        }
        return false;
    }

    public boolean paint(JComponent paintingComponent,
                         JComponent bufferComponent, Graphics g,
                         int x, int y, int w, int h) {
        Container root = fetchRoot(paintingComponent);

        if (prepare(paintingComponent, root, true, x, y, w, h)) {
            if ((g instanceof SunGraphics2D) &&
                    ((SunGraphics2D)g).getDestination() == root) {
                // BufferStrategy may have already constrained the Graphics. To
                // account for that we revert the constrain, then apply a
                // constrain for Swing on top of that.
                int cx = ((SunGraphics2D)bsg).constrainX;
                int cy = ((SunGraphics2D)bsg).constrainY;
                if (cx != 0 || cy != 0) {
                    bsg.translate(-cx, -cy);
                }
                ((SunGraphics2D)bsg).constrain(xOffset + cx, yOffset + cy,
                                               x + w, y + h);
                bsg.setClip(x, y, w, h);

                if (!bufferComponent.isOpaque()) {
                    final SunGraphics2D g2d = (SunGraphics2D) bsg;
                    final Color oldBg = g2d.getBackground();
                    g2d.setBackground(paintingComponent.getBackground());
                    g2d.clearRect(x, y, w, h);
                    g2d.setBackground(oldBg);
                }

                paintingComponent.paintToOffscreen(bsg, x, y, w, h,
                                                   x + w, y + h);
                accumulate(xOffset + x, yOffset + y, w, h);
                return true;
            } else {
                // Assume they are going to eventually render to the screen.
                // This disables showing from backbuffer until a complete
                // repaint occurs.
                bufferInfo.setInSync(false);
                // Fall through to old rendering.
            }
        }
        // Invalid root, do what Swing has always done.
        if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
            LOGGER.finer("prepare failed");
        }
        return super.paint(paintingComponent, bufferComponent, g, x, y, w, h);
    }

    public void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
                         int deltaX, int deltaY, boolean clip) {
        // Note: this method is only called internally and we know that
        // g is from a heavyweight Component, so no check is necessary as
        // it is in paint() above.
        //
        // If the buffer isn't in sync there is no point in doing a copyArea,
        // it has garbage.
        Container root = fetchRoot(c);

        if (prepare(c, root, false, 0, 0, 0, 0) && bufferInfo.isInSync()) {
            if (clip) {
                Rectangle cBounds = c.getVisibleRect();
                int relX = xOffset + x;
                int relY = yOffset + y;
                bsg.clipRect(xOffset + cBounds.x,
                             yOffset + cBounds.y,
                             cBounds.width, cBounds.height);
                bsg.copyArea(relX, relY, w, h, deltaX, deltaY);
            }
            else {
                bsg.copyArea(xOffset + x, yOffset + y, w, h, deltaX,
                             deltaY);
            }
            accumulate(x + xOffset + deltaX, y + yOffset + deltaY, w, h);
        } else {
            if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                LOGGER.finer("copyArea: prepare failed or not in sync");
            }
            // Prepare failed, or not in sync. By calling super.copyArea
            // we'll copy on screen. We need to flush any pending paint to
            // the screen otherwise we'll do a copyArea on the wrong thing.
            if (!flushAccumulatedRegion()) {
                // Flush failed, copyArea will be copying garbage,
                // force repaint of all.
                rootJ.repaint();
            } else {
                super.copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
            }
        }
    }

    public void beginPaint() {
        synchronized(this) {
            painting = true;
            // Make sure another thread isn't attempting to show from
            // the back buffer.
            while(showing) {
                try {
                    wait();
                } catch (InterruptedException ie) {
                }
            }
        }
        if (LOGGER.isLoggable(PlatformLogger.Level.FINEST)) {
            LOGGER.finest("beginPaint");
        }
        // Reset the area that needs to be painted.
        resetAccumulated();
    }

    public void endPaint() {
        if (LOGGER.isLoggable(PlatformLogger.Level.FINEST)) {
            LOGGER.finest("endPaint: region " + accumulatedX + " " +
                       accumulatedY + " " +  accumulatedMaxX + " " +
                       accumulatedMaxY);
        }
        if (painting) {
            if (!flushAccumulatedRegion()) {
                if (!isRepaintingRoot()) {
                    repaintRoot(rootJ);
                }
                else {
                    // Contents lost twice in a row, punt.
                    resetDoubleBufferPerWindow();
                    // In case we've left junk on the screen, force a repaint.
                    rootJ.repaint();
                }
            }
        }

        BufferInfo toDispose = null;
        synchronized(this) {
            painting = false;
            if (disposeBufferOnEnd) {
                disposeBufferOnEnd = false;
                toDispose = bufferInfo;
                bufferInfos.remove(toDispose);
            }
        }
        if (toDispose != null) {
            toDispose.dispose();
        }
    }

    /**
     * Renders the BufferStrategy to the screen.
     *
     * @return true if successful, false otherwise.
     */
    private boolean flushAccumulatedRegion() {
        boolean success = true;
        if (accumulatedX != Integer.MAX_VALUE) {
            SubRegionShowable bsSubRegion = (SubRegionShowable)bufferStrategy;
            boolean contentsLost = bufferStrategy.contentsLost();
            if (!contentsLost) {
                bsSubRegion.show(accumulatedX, accumulatedY,
                                 accumulatedMaxX, accumulatedMaxY);
                contentsLost = bufferStrategy.contentsLost();
            }
            if (contentsLost) {
                if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                    LOGGER.finer("endPaint: contents lost");
                }
                // Shown region was bogus, mark buffer as out of sync.
                bufferInfo.setInSync(false);
                success = false;
            }
        }
        resetAccumulated();
        return success;
    }

    private void resetAccumulated() {
        accumulatedX = Integer.MAX_VALUE;
        accumulatedY = Integer.MAX_VALUE;
        accumulatedMaxX = 0;
        accumulatedMaxY = 0;
    }

    /**
     * Invoked when the double buffering or useTrueDoubleBuffering
     * changes for a JRootPane.  If the rootpane is not double
     * buffered, or true double buffering changes we throw out any
     * cache we may have.
     */
    public void doubleBufferingChanged(final JRootPane rootPane) {
        if ((!rootPane.isDoubleBuffered() ||
                !rootPane.getUseTrueDoubleBuffering()) &&
                rootPane.getParent() != null) {
            if (!SwingUtilities.isEventDispatchThread()) {
                Runnable updater = new Runnable() {
                    public void run() {
                        doubleBufferingChanged0(rootPane);
                    }
                };
                SwingUtilities.invokeLater(updater);
            }
            else {
                doubleBufferingChanged0(rootPane);
            }
        }
    }

    /**
     * Does the work for doubleBufferingChanged.
     */
    private void doubleBufferingChanged0(JRootPane rootPane) {
        // This will only happen on the EDT.
        BufferInfo info;
        synchronized(this) {
            // Make sure another thread isn't attempting to show from
            // the back buffer.
            while(showing) {
                try {
                    wait();
                } catch (InterruptedException ie) {
                }
            }
            info = getBufferInfo(rootPane.getParent());
            if (painting && bufferInfo == info) {
                // We're in the process of painting and the user grabbed
                // the Graphics. If we dispose now, endPaint will attempt
                // to show a bogus BufferStrategy. Set a flag so that
                // endPaint knows it needs to dispose this buffer.
                disposeBufferOnEnd = true;
                info = null;
            } else if (info != null) {
                bufferInfos.remove(info);
            }
        }
        if (info != null) {
            info.dispose();
        }
    }

    /**
     * Calculates information common to paint/copyArea.
     *
     * @return true if should use buffering per window in painting.
     */
    private boolean prepare(JComponent c, Container root, boolean isPaint, int x, int y,
                            int w, int h) {
        if (bsg != null) {
            bsg.dispose();
            bsg = null;
        }
        bufferStrategy = null;
        if (root != null) {
            boolean contentsLost = false;
            BufferInfo bufferInfo = getBufferInfo(root);
            if (bufferInfo == null) {
                contentsLost = true;
                bufferInfo = new BufferInfo(root);
                bufferInfos.add(bufferInfo);
                if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                    LOGGER.finer("prepare: new BufferInfo: " + root);
                }
            }
            this.bufferInfo = bufferInfo;
            if (!bufferInfo.hasBufferStrategyChanged()) {
                bufferStrategy = bufferInfo.getBufferStrategy(true);
                if (bufferStrategy != null) {
                    bsg = bufferStrategy.getDrawGraphics();
                    if (bufferStrategy.contentsRestored()) {
                        contentsLost = true;
                        if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                            LOGGER.finer("prepare: contents restored in prepare");
                        }
                    }
                }
                else {
                    // Couldn't create BufferStrategy, fallback to normal
                    // painting.
                    return false;
                }
                if (bufferInfo.getContentsLostDuringExpose()) {
                    contentsLost = true;
                    bufferInfo.setContentsLostDuringExpose(false);
                    if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                        LOGGER.finer("prepare: contents lost on expose");
                    }
                }
                if (isPaint && c == rootJ && x == 0 && y == 0 &&
                      c.getWidth() == w && c.getHeight() == h) {
                    bufferInfo.setInSync(true);
                }
                else if (contentsLost) {
                    // We either recreated the BufferStrategy, or the contents
                    // of the buffer strategy were restored.  We need to
                    // repaint the root pane so that the back buffer is in sync
                    // again.
                    bufferInfo.setInSync(false);
                    if (!isRepaintingRoot()) {
                        repaintRoot(rootJ);
                    }
                    else {
                        // Contents lost twice in a row, punt
                        resetDoubleBufferPerWindow();
                    }
                }
                return (bufferInfos != null);
            }
        }
        return false;
    }

    private Container fetchRoot(JComponent c) {
        boolean encounteredHW = false;
        rootJ = c;
        Container root = c;
        xOffset = yOffset = 0;
        while (root != null &&
               (!(root instanceof Window) &&
                !SunToolkit.isInstanceOf(root, "java.applet.Applet"))) {
            xOffset += root.getX();
            yOffset += root.getY();
            root = root.getParent();
            if (root != null) {
                if (root instanceof JComponent) {
                    rootJ = (JComponent)root;
                }
                else if (!root.isLightweight()) {
                    if (!encounteredHW) {
                        encounteredHW = true;
                    }
                    else {
                        // We've encountered two hws now and may have
                        // a containment hierarchy with lightweights containing
                        // heavyweights containing other lightweights.
                        // Heavyweights poke holes in lightweight
                        // rendering so that if we call show on the BS
                        // (which is associated with the Window) you will
                        // not see the contents over any child
                        // heavyweights.  If we didn't do this when we
                        // went to show the descendants of the nested hw
                        // you would see nothing, so, we bail out here.
                        return null;
                    }
                }
            }
        }
        if ((root instanceof RootPaneContainer) &&
            (rootJ instanceof JRootPane)) {
            // We're in a Swing heavyweight (JFrame/JWindow...), use double
            // buffering if double buffering enabled on the JRootPane and
            // the JRootPane wants true double buffering.
            if (rootJ.isDoubleBuffered() &&
                    ((JRootPane)rootJ).getUseTrueDoubleBuffering()) {
                // Whether or not a component is double buffered is a
                // bit tricky with Swing. This gives a good approximation
                // of the various ways to turn on double buffering for
                // components.
                return root;
            }
        }
        // Don't do true double buffering.
        return null;
    }

    /**
     * Turns off double buffering per window.
     */
    private void resetDoubleBufferPerWindow() {
        if (bufferInfos != null) {
            dispose(bufferInfos);
            bufferInfos = null;
            repaintManager.setPaintManager(null);
        }
    }

    /**
     * Returns the BufferInfo for the specified root or null if one
     * hasn't been created yet.
     */
    private BufferInfo getBufferInfo(Container root) {
        for (int counter = bufferInfos.size() - 1; counter >= 0; counter--) {
            BufferInfo bufferInfo = bufferInfos.get(counter);
            Container biRoot = bufferInfo.getRoot();
            if (biRoot == null) {
                // Window gc'ed
                bufferInfos.remove(counter);
                if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                    LOGGER.finer("BufferInfo pruned, root null");
                }
            }
            else if (biRoot == root) {
                return bufferInfo;
            }
        }
        return null;
    }

    private void accumulate(int x, int y, int w, int h) {
        accumulatedX = Math.min(x, accumulatedX);
        accumulatedY = Math.min(y, accumulatedY);
        accumulatedMaxX = Math.max(accumulatedMaxX, x + w);
        accumulatedMaxY = Math.max(accumulatedMaxY, y + h);
    }



    /**
     * BufferInfo is used to track the BufferStrategy being used for
     * a particular Component.  In addition to tracking the BufferStrategy
     * it will install a WindowListener and ComponentListener.  When the
     * component is hidden/iconified the buffer is marked as needing to be
     * completely repainted.
     */
    private class BufferInfo extends ComponentAdapter implements
                               WindowListener {
        // NOTE: This class does NOT hold a direct reference to the root, if it
        // did there would be a cycle between the BufferPerWindowPaintManager
        // and the Window so that it could never be GC'ed
        //
        // Reference to BufferStrategy is referenced via WeakReference for
        // same reason.
        private WeakReference<BufferStrategy> weakBS;
        private WeakReference<Container> root;
        // Indicates whether or not the backbuffer and display are in sync.
        // This is set to true when a full repaint on the rootpane is done.
        private boolean inSync;
        // Indicates the contents were lost during and expose event.
        private boolean contentsLostDuringExpose;
        // Indicates we need to generate a paint event on expose.
        private boolean paintAllOnExpose;


        public BufferInfo(Container root) {
            this.root = new WeakReference<Container>(root);
            root.addComponentListener(this);
            if (root instanceof Window) {
                ((Window)root).addWindowListener(this);
            }
        }

        public void setPaintAllOnExpose(boolean paintAllOnExpose) {
            this.paintAllOnExpose = paintAllOnExpose;
        }

        public boolean getPaintAllOnExpose() {
            return paintAllOnExpose;
        }

        public void setContentsLostDuringExpose(boolean value) {
            contentsLostDuringExpose = value;
        }

        public boolean getContentsLostDuringExpose() {
            return contentsLostDuringExpose;
        }

        public void setInSync(boolean inSync) {
            this.inSync = inSync;
        }

        /**
         * Whether or not the contents of the buffer strategy
         * is in sync with the window.  This is set to true when the root
         * pane paints all, and false when contents are lost/restored.
         */
        public boolean isInSync() {
            return inSync;
        }

        /**
         * Returns the Root (Window or Applet) that this BufferInfo references.
         */
        public Container getRoot() {
            return (root == null) ? null : root.get();
        }

        /**
         * Returns the BufferStrategy.  This will return null if
         * the BufferStrategy hasn't been created and <code>create</code> is
         * false, or if there is a problem in creating the
         * <code>BufferStrategy</code>.
         *
         * @param create If true and the BufferStrategy is currently null,
         *               one will be created.
         */
        public BufferStrategy getBufferStrategy(boolean create) {
            BufferStrategy bs = (weakBS == null) ? null : weakBS.get();
            if (bs == null && create) {
                bs = createBufferStrategy();
                if (bs != null) {
                    weakBS = new WeakReference<BufferStrategy>(bs);
                }
                if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                    LOGGER.finer("getBufferStrategy: created bs: " + bs);
                }
            }
            return bs;
        }

        /**
         * Returns true if the buffer strategy of the component differs
         * from current buffer strategy.
         */
        public boolean hasBufferStrategyChanged() {
            Container root = getRoot();
            if (root != null) {
                BufferStrategy ourBS = null;
                BufferStrategy componentBS = null;

                ourBS = getBufferStrategy(false);
                if (root instanceof Window) {
                    componentBS = ((Window)root).getBufferStrategy();
                }
                else {
                    componentBS = AWTAccessor.getComponentAccessor().getBufferStrategy(root);
                }
                if (componentBS != ourBS) {
                    // Component has a different BS, dispose ours.
                    if (ourBS != null) {
                        ourBS.dispose();
                    }
                    weakBS = null;
                    return true;
                }
            }
            return false;
        }

        /**
         * Creates the BufferStrategy.  If the appropriate system property
         * has been set we'll try for flip first and then we'll try for
         * blit.
         */
        private BufferStrategy createBufferStrategy() {
            Container root = getRoot();
            if (root == null) {
                return null;
            }
            BufferStrategy bs = null;
            if (SwingUtilities3.isVsyncRequested(root)) {
                bs = createBufferStrategy(root, true);
                if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                    LOGGER.finer("createBufferStrategy: using vsynced strategy");
                }
            }
            if (bs == null) {
                bs = createBufferStrategy(root, false);
            }
            if (!(bs instanceof SubRegionShowable)) {
                // We do this for two reasons:
                // 1. So that we know we can cast to SubRegionShowable and
                //    invoke show with the minimal region to update
                // 2. To avoid the possibility of invoking client code
                //    on the toolkit thread.
                bs = null;
            }
            return bs;
        }

        // Creates and returns a buffer strategy.  If
        // there is a problem creating the buffer strategy this will
        // eat the exception and return null.
        private BufferStrategy createBufferStrategy(Container root,
                boolean isVsynced) {
            BufferCapabilities caps;
            if (isVsynced) {
                caps = new ExtendedBufferCapabilities(
                    new ImageCapabilities(true), new ImageCapabilities(true),
                    BufferCapabilities.FlipContents.COPIED,
                    ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
            } else {
                caps = new BufferCapabilities(
                    new ImageCapabilities(true), new ImageCapabilities(true),
                    null);
            }
            BufferStrategy bs = null;
            if (SunToolkit.isInstanceOf(root, "java.applet.Applet")) {
                try {
                    AWTAccessor.ComponentAccessor componentAccessor
                            = AWTAccessor.getComponentAccessor();
                    componentAccessor.createBufferStrategy(root, 2, caps);
                    bs = componentAccessor.getBufferStrategy(root);
                } catch (AWTException e) {
                    // Type is not supported
                    if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                        LOGGER.finer("createBufferStrategy failed",
                                     e);
                    }
                }
            }
            else {
                try {
                    ((Window)root).createBufferStrategy(2, caps);
                    bs = ((Window)root).getBufferStrategy();
                } catch (AWTException e) {
                    // Type not supported
                    if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                        LOGGER.finer("createBufferStrategy failed",
                                     e);
                    }
                }
            }
            return bs;
        }

        /**
         * Cleans up and removes any references.
         */
        public void dispose() {
            Container root = getRoot();
            if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
                LOGGER.finer("disposed BufferInfo for: " + root);
            }
            if (root != null) {
                root.removeComponentListener(this);
                if (root instanceof Window) {
                    ((Window)root).removeWindowListener(this);
                }
                BufferStrategy bs = getBufferStrategy(false);
                if (bs != null) {
                    bs.dispose();
                }
            }
            this.root = null;
            weakBS = null;
        }

        // We mark the buffer as needing to be painted on a hide/iconify
        // because the developer may have conditionalized painting based on
        // visibility.
        // Ideally we would also move to having the BufferStrategy being
        // a SoftReference in Component here, but that requires changes to
        // Component and the like.
        public void componentHidden(ComponentEvent e) {
            Container root = getRoot();
            if (root != null && root.isVisible()) {
                // This case will only happen if a developer calls
                // hide immediately followed by show.  In this case
                // the event is delivered after show and the window
                // will still be visible.  If a developer altered the
                // contents of the window between the hide/show
                // invocations we won't recognize we need to paint and
                // the contents would be bogus.  Calling repaint here
                // fixs everything up.
                root.repaint();
            }
            else {
                setPaintAllOnExpose(true);
            }
        }

        public void windowIconified(WindowEvent e) {
            setPaintAllOnExpose(true);
        }

        // On a dispose we chuck everything.
        public void windowClosed(WindowEvent e) {
            // Make sure we're not showing.
            synchronized(BufferStrategyPaintManager.this) {
                while (showing) {
                    try {
                        BufferStrategyPaintManager.this.wait();
                    } catch (InterruptedException ie) {
                    }
                }
                bufferInfos.remove(this);
            }
            dispose();
        }

        public void windowOpened(WindowEvent e) {
        }

        public void windowClosing(WindowEvent e) {
        }

        public void windowDeiconified(WindowEvent e) {
        }

        public void windowActivated(WindowEvent e) {
        }

        public void windowDeactivated(WindowEvent e) {
        }
    }
}
