8188083: NullPointerExcpn-java.awt.image.FilteredImageSource.startProduction JDK-8079607
authorpnarayanan
Tue, 12 Dec 2017 14:05:21 +0530
changeset 48289 054fecf0c1d2
parent 48288 752f0e49c3f0
child 48290 dbfe141b1271
8188083: NullPointerExcpn-java.awt.image.FilteredImageSource.startProduction JDK-8079607 Reviewed-by: serb, prr, jdv Contributed-by: prahalad.kumar.narayanan@oracle.com
src/java.desktop/share/classes/java/awt/image/FilteredImageSource.java
test/jdk/java/awt/image/FilteredImageSourceTest.java
--- a/src/java.desktop/share/classes/java/awt/image/FilteredImageSource.java	Tue Dec 12 12:43:05 2017 +0530
+++ b/src/java.desktop/share/classes/java/awt/image/FilteredImageSource.java	Tue Dec 12 14:05:21 2017 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2017, 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
@@ -35,7 +35,8 @@
 /**
  * This class is an implementation of the ImageProducer interface which
  * takes an existing image and a filter object and uses them to produce
- * image data for a new filtered version of the original image.
+ * image data for a new filtered version of the original image. Furthermore,
+ * {@code FilteredImageSource} is safe for use by multiple threads.
  * Here is an example which filters an image by swapping the red and
  * blue components:
  * <pre>
@@ -171,7 +172,7 @@
      * @param ic  the consumer for the filtered image
      * @see ImageConsumer
      */
-    public void startProduction(ImageConsumer ic) {
+    public synchronized void startProduction(ImageConsumer ic) {
         if (proxies == null) {
             proxies = new Hashtable<>();
         }
@@ -198,7 +199,7 @@
      *
      * @see ImageConsumer
      */
-    public void requestTopDownLeftRightResend(ImageConsumer ic) {
+    public synchronized void requestTopDownLeftRightResend(ImageConsumer ic) {
         if (proxies != null) {
             ImageFilter imgf = proxies.get(ic);
             if (imgf != null) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/awt/image/FilteredImageSourceTest.java	Tue Dec 12 14:05:21 2017 +0530
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2017, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8188083
+ * @summary The test checks whether applying image filters using
+ *          FilteredImageSource results in a NullPointerException.
+ * @run main FilteredImageSourceTest
+ */
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.image.ColorModel;
+import java.awt.image.FilteredImageSource;
+import java.awt.image.ImageConsumer;
+import java.awt.image.ImageFilter;
+import java.awt.image.ImageObserver;
+import java.awt.image.ImageProducer;
+import java.util.Hashtable;
+
+/*
+ * An empty image consumer that will be added to the list of consumers
+ * interested in image data for the filtered image.
+ */
+class EmptyImageConsumer implements ImageConsumer {
+    @Override
+    public void setDimensions(int width, int height) {
+    }
+
+    @Override
+    public void setProperties(Hashtable<?, ?> props) {
+    }
+
+    @Override
+    public void setColorModel(ColorModel colorModel) {
+    }
+
+    @Override
+    public void setHints(int hintFlags) {
+    }
+
+    @Override
+    public void setPixels(int x, int y, int width, int height,
+                          ColorModel colorModel, byte[] pixels,
+                          int offset, int scanSize) {
+    }
+
+    @Override
+    public void setPixels(int x, int y, int width, int height,
+                          ColorModel colorModel, int[] pixels,
+                          int offset, int scanSize) {
+    }
+
+    @Override
+    public void imageComplete(int i) {
+    }
+}
+
+/*
+ * An empty image producer whose sole purpose is to provide stub methods
+ * that will be invoked while preparing filtered image.
+ */
+class EmptyImageProducer implements ImageProducer {
+    @Override
+    public void addConsumer(ImageConsumer imageConsumer) {
+    }
+
+    @Override
+    public boolean isConsumer(ImageConsumer imageConsumer) {
+        return false;
+    }
+
+    @Override
+    public void removeConsumer(ImageConsumer imageConsumer) {
+    }
+
+    @Override
+    public void startProduction(ImageConsumer imageConsumer) {
+    }
+
+    @Override
+    public void requestTopDownLeftRightResend(ImageConsumer imageConsumer) {
+    }
+}
+
+/*
+ * Typically, an Image object will contain an ImageProducer that prepares
+ * image data. FilteredImageSource will be set as image producer for images
+ * that require image filter applied to image data.
+ */
+class EmptyFilteredImage extends Image {
+    ImageFilter filter = null;
+    ImageProducer producer = null;
+
+    public EmptyFilteredImage(ImageProducer imgSource) {
+        filter = new ImageFilter();
+        producer = new FilteredImageSource(imgSource, filter);
+    }
+
+    @Override
+    public int getWidth(ImageObserver observer) {
+        return 100;
+    }
+
+    @Override
+    public int getHeight(ImageObserver observer) {
+        return 100;
+    }
+
+    @Override
+    public ImageProducer getSource() {
+        return producer;
+    }
+
+    @Override
+    public Graphics getGraphics() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object getProperty(String name, ImageObserver observer) {
+        return null;
+    }
+}
+
+public final class FilteredImageSourceTest {
+    // Minimum test duration in ms
+    private static final int TEST_MIN_DURATION = 5000;
+
+    /*
+     * A throwable object that will hold any exception generated while
+     * executing methods on FilteredImageSource. The test passes if the
+     * methods execute without any exception
+     */
+    private static volatile Throwable fail = null;
+
+    public static void main(final String[] args)
+            throws InterruptedException {
+        final ImageConsumer ic = new EmptyImageConsumer();
+        final ImageProducer ip = new EmptyImageProducer();
+        final Image image = new EmptyFilteredImage(ip);
+
+        /*
+         * Simulate the framework's operations on FilteredImageSource by
+         * invoking the concerned methods in multiple threads and observe
+         * whether exceptions are generated.
+         */
+        Thread t1 = new Thread(() -> {
+            try {
+                while (true) {
+                    image.getSource().addConsumer(ic);
+                }
+            } catch (Throwable t) {
+                fail = t;
+            }
+        });
+        t1.setDaemon(true);
+
+        Thread t2 = new Thread(() -> {
+            try {
+                while (true) {
+                    image.getSource().removeConsumer(ic);
+                }
+            } catch (Throwable t) {
+                fail = t;
+            }
+        });
+        t2.setDaemon(true);
+
+        Thread t3 = new Thread(() -> {
+            try {
+                while (true) {
+                    image.getSource().startProduction(ic);
+                }
+            } catch (Throwable t) {
+                fail = t;
+            }
+        });
+        t3.setDaemon(true);
+
+        // Start the threads
+        t1.start();
+        t2.start();
+        t3.start();
+
+        // Wait on one of the threads for a specific duration.
+        t1.join(TEST_MIN_DURATION);
+        if (fail != null) {
+            throw new RuntimeException("Test failed with exception: ", fail);
+        }
+    }
+}
\ No newline at end of file