JBR-5213  JBR API v1.5.0 - add GraphicsUtils (#210)

* relax type constraints in BltBufferStrategy.getDrawGraphics
diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java b/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java
new file mode 100644
index 0000000..6751da5
--- /dev/null
+++ b/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 JetBrains s.r.o.
+ * 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.
+ *
+ * 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 com.jetbrains.desktop;
+
+import sun.awt.ConstrainableGraphics;
+
+import java.awt.geom.Rectangle2D;
+
+public interface ConstrainableGraphics2D extends ConstrainableGraphics {
+    public void constrain(Rectangle2D region);
+    public Object getDestination();
+}
diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java
index 67aaf9f..36ebe0c 100644
--- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java
+++ b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java
@@ -42,6 +42,9 @@
                     .withStatic("overrideGraphicsEnvironment", "overrideLocalGraphicsEnvironment", "java.awt.GraphicsEnvironment")
                     .withStatic("setLocalGraphicsEnvironmentProvider", "setLocalGraphicsEnvironmentProvider", "java.awt.GraphicsEnvironment")
                 .service("com.jetbrains.AccessibleAnnouncer")
-                    .withStatic("announce", "announce", "sun.swing.AccessibleAnnouncer");
+                    .withStatic("announce", "announce", "sun.swing.AccessibleAnnouncer")
+                .service("com.jetbrains.GraphicsUtils")
+                    .withStatic("createConstrainableGraphics", "create", "com.jetbrains.desktop.JBRGraphicsDelegate")
+                .clientProxy("com.jetbrains.desktop.ConstrainableGraphics2D", "com.jetbrains.GraphicsUtils$ConstrainableGraphics2D");
     }
 }
diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java
new file mode 100644
index 0000000..6f35e1e
--- /dev/null
+++ b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright 2023 JetBrains s.r.o.
+ * 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.
+ *
+ * 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 com.jetbrains.desktop;
+
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ImageObserver;
+import java.awt.image.RenderedImage;
+import java.awt.image.renderable.RenderableImage;
+import java.text.AttributedCharacterIterator;
+import java.util.Map;
+
+
+public class JBRGraphicsDelegate extends Graphics2D implements ConstrainableGraphics2D {
+    public static Graphics2D create(Graphics2D graphics2D,
+                                    ConstrainableGraphics2D constrainable) {
+        return new JBRGraphicsDelegate(graphics2D, constrainable);
+    }
+
+    private final Graphics2D delegate;
+    private final ConstrainableGraphics2D constrainable;
+
+    public JBRGraphicsDelegate(Graphics2D delegate,
+                               ConstrainableGraphics2D constrainable) {
+        this.delegate = delegate;
+        this.constrainable = constrainable;
+    }
+
+    @Override
+    public Object getDestination() {
+        return constrainable.getDestination();
+    }
+
+    @Override
+    public void constrain(Rectangle2D region) {
+        constrainable.constrain(region);
+    }
+
+    @Override
+    public void constrain(int x, int y, int w, int h) {
+        constrainable.constrain(x, y, w, h);
+    }
+
+    @Override
+    public void draw3DRect(int x, int y, int width, int height, boolean raised) {
+        delegate.draw3DRect(x, y, width, height, raised);
+    }
+
+    @Override
+    public void fill3DRect(int x, int y, int width, int height, boolean raised) {
+        delegate.fill3DRect(x, y, width, height, raised);
+    }
+
+    @Override
+    public void draw(Shape s) {
+        delegate.draw(s);
+    }
+
+    @Override
+    public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
+        return delegate.drawImage(img, xform, obs);
+    }
+
+    @Override
+    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
+        delegate.drawImage(img, op, x, y);
+    }
+
+    @Override
+    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
+        delegate.drawRenderedImage(img, xform);
+    }
+
+    @Override
+    public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
+        delegate.drawRenderableImage(img, xform);
+    }
+
+    @Override
+    public void drawString(String str, int x, int y) {
+        delegate.drawString(str, x, y);
+    }
+
+    @Override
+    public void drawString(String str, float x, float y) {
+        delegate.drawString(str, x, y);
+    }
+
+    @Override
+    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
+        delegate.drawString(iterator, x, y);
+    }
+
+    @Override
+    public void drawString(AttributedCharacterIterator iterator, float x, float y) {
+        delegate.drawString(iterator, x, y);
+    }
+
+    @Override
+    public void drawGlyphVector(GlyphVector g, float x, float y) {
+        delegate.drawGlyphVector(g, x, y);
+    }
+
+    @Override
+    public void fill(Shape s) {
+        delegate.fill(s);
+    }
+
+    @Override
+    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
+        return delegate.hit(rect, s, onStroke);
+    }
+
+    @Override
+    public GraphicsConfiguration getDeviceConfiguration() {
+        return delegate.getDeviceConfiguration();
+    }
+
+    @Override
+    public void setComposite(Composite comp) {
+        delegate.setComposite(comp);
+    }
+
+    @Override
+    public void setPaint(Paint paint) {
+        delegate.setPaint(paint);
+    }
+
+    @Override
+    public void setStroke(Stroke s) {
+        delegate.setStroke(s);
+    }
+
+    @Override
+    public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {
+        delegate.setRenderingHint(hintKey, hintValue);
+    }
+
+    @Override
+    public Object getRenderingHint(RenderingHints.Key hintKey) {
+        return delegate.getRenderingHint(hintKey);
+    }
+
+    @Override
+    public void setRenderingHints(Map<?, ?> hints) {
+        delegate.setRenderingHints(hints);
+    }
+
+    @Override
+    public void addRenderingHints(Map<?, ?> hints) {
+        delegate.addRenderingHints(hints);
+    }
+
+    @Override
+    public RenderingHints getRenderingHints() {
+        return delegate.getRenderingHints();
+    }
+
+    @Override
+    public void translate(int x, int y) {
+        delegate.translate(x, y);
+    }
+
+    @Override
+    public void translate(double tx, double ty) {
+        delegate.translate(tx, ty);
+    }
+
+    @Override
+    public void rotate(double theta) {
+        delegate.rotate(theta);
+    }
+
+    @Override
+    public void rotate(double theta, double x, double y) {
+        delegate.rotate(theta, x, y);
+    }
+
+    @Override
+    public void scale(double sx, double sy) {
+        delegate.scale(sx, sy);
+    }
+
+    @Override
+    public void shear(double shx, double shy) {
+        delegate.shear(shx, shy);
+    }
+
+    @Override
+    public void transform(AffineTransform Tx) {
+        delegate.transform(Tx);
+    }
+
+    @Override
+    public void setTransform(AffineTransform Tx) {
+        delegate.setTransform(Tx);
+    }
+
+    @Override
+    public AffineTransform getTransform() {
+        return delegate.getTransform();
+    }
+
+    @Override
+    public Paint getPaint() {
+        return delegate.getPaint();
+    }
+
+    @Override
+    public Composite getComposite() {
+        return delegate.getComposite();
+    }
+
+    @Override
+    public void setBackground(Color color) {
+        delegate.setBackground(color);
+    }
+
+    @Override
+    public Color getBackground() {
+        return delegate.getBackground();
+    }
+
+    @Override
+    public Stroke getStroke() {
+        return delegate.getStroke();
+    }
+
+    @Override
+    public void clip(Shape s) {
+        delegate.clip(s);
+    }
+
+    @Override
+    public FontRenderContext getFontRenderContext() {
+        return delegate.getFontRenderContext();
+    }
+
+    @Override
+    public Graphics create() {
+        return delegate.create();
+    }
+
+    @Override
+    public Graphics create(int x, int y, int width, int height) {
+        return delegate.create(x, y, width, height);
+    }
+
+    @Override
+    public Color getColor() {
+        return delegate.getColor();
+    }
+
+    @Override
+    public void setColor(Color c) {
+        delegate.setColor(c);
+    }
+
+    @Override
+    public void setPaintMode() {
+        delegate.setPaintMode();
+    }
+
+    @Override
+    public void setXORMode(Color c1) {
+        delegate.setXORMode(c1);
+    }
+
+    @Override
+    public Font getFont() {
+        return delegate.getFont();
+    }
+
+    @Override
+    public void setFont(Font font) {
+        delegate.setFont(font);
+    }
+
+    @Override
+    public FontMetrics getFontMetrics() {
+        return delegate.getFontMetrics();
+    }
+
+    @Override
+    public FontMetrics getFontMetrics(Font f) {
+        return delegate.getFontMetrics(f);
+    }
+
+    @Override
+    public Rectangle getClipBounds() {
+        return delegate.getClipBounds();
+    }
+
+    @Override
+    public void clipRect(int x, int y, int width, int height) {
+        delegate.clipRect(x, y, width, height);
+    }
+
+    @Override
+    public void setClip(int x, int y, int width, int height) {
+        delegate.setClip(x, y, width, height);
+    }
+
+    @Override
+    public Shape getClip() {
+        return delegate.getClip();
+    }
+
+    @Override
+    public void setClip(Shape clip) {
+        delegate.setClip(clip);
+    }
+
+    @Override
+    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
+        delegate.copyArea(x, y, width, height, dx, dy);
+    }
+
+    @Override
+    public void drawLine(int x1, int y1, int x2, int y2) {
+        delegate.drawLine(x1, y1, x2, y2);
+    }
+
+    @Override
+    public void fillRect(int x, int y, int width, int height) {
+        delegate.fillRect(x, y, width, height);
+    }
+
+    @Override
+    public void drawRect(int x, int y, int width, int height) {
+        delegate.drawRect(x, y, width, height);
+    }
+
+    @Override
+    public void clearRect(int x, int y, int width, int height) {
+        delegate.clearRect(x, y, width, height);
+    }
+
+    @Override
+    public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
+        delegate.drawRoundRect(x, y, width, height, arcWidth, arcHeight);
+    }
+
+    @Override
+    public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
+        delegate.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
+    }
+
+    @Override
+    public void drawOval(int x, int y, int width, int height) {
+        delegate.drawOval(x, y, width, height);
+    }
+
+    @Override
+    public void fillOval(int x, int y, int width, int height) {
+        delegate.fillOval(x, y, width, height);
+    }
+
+    @Override
+    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
+        delegate.drawArc(x, y, width, height, startAngle, arcAngle);
+    }
+
+    @Override
+    public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
+        delegate.fillArc(x, y, width, height, startAngle, arcAngle);
+    }
+
+    @Override
+    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
+        delegate.drawPolyline(xPoints, yPoints, nPoints);
+    }
+
+    @Override
+    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
+        delegate.drawPolygon(xPoints, yPoints, nPoints);
+    }
+
+    @Override
+    public void drawPolygon(Polygon p) {
+        delegate.drawPolygon(p);
+    }
+
+    @Override
+    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
+        delegate.fillPolygon(xPoints, yPoints, nPoints);
+    }
+
+    @Override
+    public void fillPolygon(Polygon p) {
+        delegate.fillPolygon(p);
+    }
+
+    @Override
+    public void drawChars(char[] data, int offset, int length, int x, int y) {
+        delegate.drawChars(data, offset, length, x, y);
+    }
+
+    @Override
+    public void drawBytes(byte[] data, int offset, int length, int x, int y) {
+        delegate.drawBytes(data, offset, length, x, y);
+    }
+
+    @Override
+    public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
+        return delegate.drawImage(img, x, y, observer);
+    }
+
+    @Override
+    public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
+        return delegate.drawImage(img, x, y, width, height, observer);
+    }
+
+    @Override
+    public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
+        return delegate.drawImage(img, x, y, bgcolor, observer);
+    }
+
+    @Override
+    public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
+        return delegate.drawImage(img, x, y, width, height, bgcolor, observer);
+    }
+
+    @Override
+    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
+        return delegate.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
+    }
+
+    @Override
+    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) {
+        return delegate.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer);
+    }
+
+    @Override
+    public void dispose() {
+        delegate.dispose();
+    }
+
+    @Override
+    public String toString() {
+        return delegate.toString();
+    }
+
+    @Override
+    @Deprecated
+    public Rectangle getClipRect() {
+        return delegate.getClipRect();
+    }
+
+    @Override
+    public boolean hitClip(int x, int y, int width, int height) {
+        return delegate.hitClip(x, y, width, height);
+    }
+
+    @Override
+    public Rectangle getClipBounds(Rectangle r) {
+        return delegate.getClipBounds(r);
+    }
+}
diff --git a/src/java.desktop/share/classes/java/awt/Component.java b/src/java.desktop/share/classes/java/awt/Component.java
index 38068cd..4134fa0 100644
--- a/src/java.desktop/share/classes/java/awt/Component.java
+++ b/src/java.desktop/share/classes/java/awt/Component.java
@@ -4472,10 +4472,10 @@
             if (backBuffer == null) {
                 return getGraphics();
             }
-            SunGraphics2D g = (SunGraphics2D)backBuffer.getGraphics();
-            g.constrain(-insets.left, -insets.top,
-                        backBuffer.getWidth(null) + insets.left,
-                        backBuffer.getHeight(null) + insets.top);
+            Graphics g = backBuffer.getGraphics();
+            ((ConstrainableGraphics)g).constrain(-insets.left, -insets.top,
+                                                 backBuffer.getWidth(null) + insets.left,
+                                                 backBuffer.getHeight(null) + insets.top);
             return g;
         }
 
diff --git a/src/java.desktop/share/classes/javax/swing/BufferStrategyPaintManager.java b/src/java.desktop/share/classes/javax/swing/BufferStrategyPaintManager.java
index 9ea050b..97f5a37 100644
--- a/src/java.desktop/share/classes/javax/swing/BufferStrategyPaintManager.java
+++ b/src/java.desktop/share/classes/javax/swing/BufferStrategyPaintManager.java
@@ -33,6 +33,7 @@
 import com.sun.java.swing.SwingUtilities3;
 import sun.awt.AWTAccessor;
 
+import com.jetbrains.desktop.ConstrainableGraphics2D;
 import sun.awt.SubRegionShowable;
 import sun.java2d.SunGraphics2D;
 import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
@@ -247,6 +248,15 @@
                                                    x + w, y + h);
                 accumulate(xOffset + x, yOffset + y, w, h);
                 return true;
+            } else if ((g instanceof ConstrainableGraphics2D) &&
+                    ((ConstrainableGraphics2D)g).getDestination() == root) {
+                ((ConstrainableGraphics2D)bsg).constrain(new Rectangle.Double(xOffset, yOffset,
+                                                                              x + w, y + h));
+                bsg.setClip(x, y, w, h);
+                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
diff --git a/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java b/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java
new file mode 100644
index 0000000..902b042
--- /dev/null
+++ b/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 JetBrains s.r.o.
+ * 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.
+ *
+ * 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 com.jetbrains;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+public interface GraphicsUtils {
+    Graphics2D createConstrainableGraphics(Graphics2D graphics2D,
+                                           ConstrainableGraphics2D constrainable);
+
+    public interface ConstrainableGraphics2D {
+        Object getDestination();
+        void constrain(Rectangle2D region);
+        void constrain(int x, int y, int w, int h);
+    }
+}
diff --git a/src/jetbrains.api/version.properties b/src/jetbrains.api/version.properties
index 093fd8d..13453f7 100644
--- a/src/jetbrains.api/version.properties
+++ b/src/jetbrains.api/version.properties
@@ -6,9 +6,9 @@
 # 2. When only new API is added, or some existing API was @Deprecated - increment MINOR, reset PATCH to 0
 # 3. For major backwards incompatible API changes - increment MAJOR, reset MINOR and PATCH to 0
 
-VERSION = 1.4.0
+VERSION = 1.5.0
 
 # Hash is used to track changes to jetbrains.api, so you would not forget to update version when needed.
 # When you make any changes, "make jbr-api" will fail and ask you to update hash and version number here.
 
-HASH = D58E645CE5499261B882439A3FE4A24
+HASH = 8745B8BB7DC111BC8FD75FADEC1FCB