# HG changeset patch # User Andrew Azores # Date 1484329291 18000 # Node ID fdb31d869bf91fde76cb253ab19f7a2491cd6d38 # Parent f04119b95df6ce1150c8606524858dfa000375f3 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 diff -r f04119b95df6 -r fdb31d869bf9 client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/BasicEventTimelineUI.java --- 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(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 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 computeNewDetailRange(long min, long max) { long diff = max - min; return new Range<>(min - diff / 2, max + diff / 2); - }; + } }); zoomInButton.addActionListener(new DetailChangeListener() { protected Range 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 range = eventTimeline.getModel().getDetailRange(); + EventTimelineModel model = eventTimeline.getModel(); + + if (model.getEvents().isEmpty()) { + return; + } + + Range 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 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 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 range = new Range(positionToTimeStamp(left), positionToTimeStamp(right)); + Range range = new Range<>(positionToTimeStamp(left), positionToTimeStamp(right)); eventTimeline.getModel().setDetailRange(range); refresh(); } diff -r f04119b95df6 -r fdb31d869bf9 client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineTest.java --- /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 + * . + * + * 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"))); + } + +}