changeset 2045:fdb31d869bf9

Compute new detail range only if events are present Avoids a NullPointerException due to the detail range being unset before any events have been processed. Backport of http://icedtea.classpath.org/hg/thermostat/rev/252478620e7a PR3282 Reviewed-by: neugens, jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-January/021947.html
author Andrew Azores <aazores@redhat.com>
date Fri, 13 Jan 2017 12:41:31 -0500
parents f04119b95df6
children 7a19d9e8fb9b
files client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/BasicEventTimelineUI.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineTest.java
diffstat 2 files changed, 206 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/BasicEventTimelineUI.java	Tue Dec 20 15:30:11 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/BasicEventTimelineUI.java	Fri Jan 13 12:41:31 2017 -0500
@@ -38,7 +38,6 @@
 
 import java.awt.BasicStroke;
 import java.awt.Color;
-import java.awt.Cursor;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
@@ -58,7 +57,6 @@
 import java.util.concurrent.TimeUnit;
 
 import javax.swing.JButton;
-import javax.swing.JComponent;
 import javax.swing.JPanel;
 
 import com.redhat.thermostat.client.swing.components.experimental.EventTimelineModel.Event;
@@ -92,28 +90,33 @@
     protected void installComponents(EventTimeline component) {
         eventTimeline = component;
 
-        overviewRuler = new Timeline(new Range<Long>(1l, 2l));
+        overviewRuler = new Timeline(new Range<>(1L, 2L));
 
         moveLeftButton = new JButton("<");
+        moveLeftButton.setName("moveLeftButton");
         moveLeftButton.setMargin(new Insets(0, 0, 0, 0));
         moveRightButton = new JButton(">");
+        moveRightButton.setName("moveRightButton");
         moveRightButton.setMargin(new Insets(0, 0, 0, 0));
 
         JPanel buttonPanel = new JPanel();
         buttonPanel.setLayout(new GridLayout());
 
         zoomInButton = new JButton("+");
+        zoomInButton.setName("zoomInButton");
         zoomInButton.setToolTipText(translate.localize(LocaleResources.ZOOM_IN).getContents());
         zoomInButton.setMargin(new Insets(2, 2, 2, 2));
 
         buttonPanel.add(zoomInButton);
 
         zoomOutButton = new JButton("-");
+        zoomOutButton.setName("zoomOutButton");
         zoomOutButton.setToolTipText(translate.localize(LocaleResources.ZOOM_OUT).getContents());
         zoomOutButton.setMargin(new Insets(2, 2, 2, 2));
         buttonPanel.add(zoomOutButton);
 
         resetZoomButton = new JButton("R");
+        resetZoomButton.setName("zoomResetButton");
         resetZoomButton.setToolTipText(translate.localize(LocaleResources.RESET_ZOOM).getContents());
         resetZoomButton.setMargin(new Insets(2, 2, 2, 2));
         buttonPanel.add(resetZoomButton);
@@ -193,20 +196,20 @@
             protected Range<Long> computeNewDetailRange(long min, long max) {
                 long diff = (long) ((max - min) * 0.1);
                 return new Range<>(min - diff, max - diff);
-            };
+            }
         });
         zoomOutButton.addActionListener(new DetailChangeListener() {
            protected Range<Long> computeNewDetailRange(long min, long max) {
                long diff = max - min;
                return new Range<>(min - diff / 2, max + diff / 2);
-           };
+           }
         });
 
         zoomInButton.addActionListener(new DetailChangeListener() {
             protected Range<Long> computeNewDetailRange(long min, long max) {
                 long diff = max - min;
                 return new Range<>(min + diff / 4, max - diff / 4);
-            };
+            }
         });
 
         resetZoomButton.addActionListener(new DetailChangeListener() {
@@ -228,12 +231,18 @@
     private abstract class DetailChangeListener implements ActionListener {
         @Override
         public void actionPerformed(ActionEvent arg0) {
-            Range<Long> range = eventTimeline.getModel().getDetailRange();
+            EventTimelineModel model = eventTimeline.getModel();
+
+            if (model.getEvents().isEmpty()) {
+                return;
+            }
+
+            Range<Long> range = model.getDetailRange();
 
             long min = range.getMin();
             long max = range.getMax();
 
-            eventTimeline.getModel().setDetailRange(computeNewDetailRange(min, max));
+            model.setDetailRange(computeNewDetailRange(min, max));
 
             overviewPanel.refresh();
         }
@@ -255,16 +264,14 @@
 
     private long positionToTimeStamp(int position) {
         Range<Long> range = eventTimeline.getModel().getTotalRange();
-        LongRangeNormalizer normalizer = new LongRangeNormalizer(new Range<>(0l, (long)overviewPanel.getWidth()), range);
-        long result = normalizer.getValueNormalized(position);
-        return result;
+        LongRangeNormalizer normalizer = new LongRangeNormalizer(new Range<>(0L, (long)overviewPanel.getWidth()), range);
+        return normalizer.getValueNormalized(position);
     }
 
     private int timeStampToPosition(long timeStamp) {
         Range<Long> range = eventTimeline.getModel().getTotalRange();
-        LongRangeNormalizer normalizer = new LongRangeNormalizer(range, new Range<>(0l, (long)overviewPanel.getWidth()));
-        int result = (int) normalizer.getValueNormalized(timeStamp);
-        return result;
+        LongRangeNormalizer normalizer = new LongRangeNormalizer(range, new Range<>(0L, (long)overviewPanel.getWidth()));
+        return (int) normalizer.getValueNormalized(timeStamp);
     }
 
     private static class Refresher implements HierarchyBoundsListener, HierarchyListener, AdjustmentListener, EventTimelineDataChangeListener {
@@ -409,7 +416,7 @@
         public void updateSelectionPosition(int newLeft, int newRight) {
             left = newLeft;
             right = newRight;
-            Range<Long> range = new Range<Long>(positionToTimeStamp(left), positionToTimeStamp(right));
+            Range<Long> range = new Range<>(positionToTimeStamp(left), positionToTimeStamp(right));
             eventTimeline.getModel().setDetailRange(range);
             refresh();
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineTest.java	Fri Jan 13 12:41:31 2017 -0500
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import com.redhat.thermostat.annotations.internal.CacioTest;
+import com.redhat.thermostat.test.Bug;
+import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
+import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.fixture.FrameFixture;
+import org.fest.swing.fixture.JButtonFixture;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import javax.swing.JFrame;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+@Category(CacioTest.class)
+@RunWith(CacioFESTRunner.class)
+public class EventTimelineTest {
+
+    private EventTimeline timeline;
+
+    private JFrame frame;
+    private FrameFixture fixture;
+
+    private PrintStream origOut;
+    private PrintStream origErr;
+    private ByteArrayOutputStream out;
+    private ByteArrayOutputStream err;
+
+    @BeforeClass
+    public static void setupOnce() {
+        FailOnThreadViolationRepaintManager.install();
+    }
+
+    @Before
+    public void setup() {
+        origOut = System.out;
+        origErr = System.err;
+
+        out = new ByteArrayOutputStream();
+        err = new ByteArrayOutputStream();
+
+        System.setOut(new PrintStream(out));
+        System.setErr(new PrintStream(err));
+
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                timeline = new EventTimeline();
+
+                frame = new JFrame();
+                frame.add(timeline);
+            }
+        });
+        fixture = new FrameFixture(frame);
+        fixture.show();
+    }
+
+    @After
+    public void teardown() {
+        System.setOut(origOut);
+        System.setErr(origErr);
+
+        fixture.cleanUp();
+        fixture = null;
+    }
+
+    @Test
+    @Bug(id = "PR3282",
+            url = "http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=3282",
+            summary = "An NPE may occur when interacting with timeline controls where no JMX events are present"
+    )
+    public void testZoomInWithNoEvents() {
+        JButtonFixture buttonFixture = fixture.button("zoomInButton");
+        buttonFixture.click();
+
+        assertNoNullPointerExceptions(out);
+        assertNoNullPointerExceptions(err);
+    }
+
+    @Test
+    @Bug(id = "PR3282",
+            url = "http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=3282",
+            summary = "An NPE may occur when interacting with timeline controls where no JMX events are present"
+    )
+    public void testZoomOutWithNoEvents() {
+        JButtonFixture buttonFixture = fixture.button("zoomOutButton");
+        buttonFixture.click();
+
+        assertNoNullPointerExceptions(out);
+        assertNoNullPointerExceptions(err);
+    }
+
+    @Test
+    @Bug(id = "PR3282",
+            url = "http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=3282",
+            summary = "An NPE may occur when interacting with timeline controls where no JMX events are present"
+    )
+    public void testZoomResetWithNoEvents() {
+        JButtonFixture buttonFixture = fixture.button("zoomResetButton");
+        buttonFixture.click();
+
+        assertNoNullPointerExceptions(out);
+        assertNoNullPointerExceptions(err);
+    }
+
+    @Test
+    @Bug(id = "PR3282",
+            url = "http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=3282",
+            summary = "An NPE may occur when interacting with timeline controls where no JMX events are present"
+    )
+    public void testMoveLeftWithNoEvents() {
+        JButtonFixture buttonFixture = fixture.button("moveLeftButton");
+        buttonFixture.click();
+
+        assertNoNullPointerExceptions(out);
+        assertNoNullPointerExceptions(err);
+    }
+
+    @Test
+    @Bug(id = "PR3282",
+            url = "http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=3282",
+            summary = "An NPE may occur when interacting with timeline controls where no JMX events are present"
+    )
+    public void testMoveRightWithNoEvents() {
+        JButtonFixture buttonFixture = fixture.button("moveRightButton");
+        buttonFixture.click();
+
+        assertNoNullPointerExceptions(out);
+        assertNoNullPointerExceptions(err);
+    }
+
+    private static void assertNoNullPointerExceptions(ByteArrayOutputStream byteArrayOutputStream) {
+        String contents = byteArrayOutputStream.toString();
+        assertThat(contents, not(containsString("NullPointerException")));
+    }
+
+}