changeset 263:9a1a69250ee2

Merge
author Roman Kennke <rkennke@redhat.com>
date Tue, 24 Apr 2012 14:15:34 +0200
parents 8bd725ac3bdf (current diff) 0ad7fdcad47e (diff)
children 4d82fc25b016
files
diffstat 17 files changed, 582 insertions(+), 111 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java	Tue Apr 24 14:15:34 2012 +0200
@@ -182,6 +182,7 @@
                 case SHUTDOWN:
                     view.hideMainWindow();
                     stop();
+                    ApplicationContext.getInstance().getTimerFactory().shutdown();
                     break;
                 default:
                     throw new IllegalStateException("unhandled action");
--- a/client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Tue Apr 24 14:15:34 2012 +0200
@@ -61,7 +61,7 @@
     MENU_EDIT_ENABLE_HISTORY_MODE,
     MENU_HELP,
     MENU_HELP_ABOUT,
-    
+
     GARBAGE_COLLECTION,
     YOUNG_GEN,
     EDEN_GEN,
@@ -164,11 +164,10 @@
     VM_CPU_CHART_LOAD_LABEL,
     VM_CPU_CHART_TIME_LABEL,
 
-    VM_CURRENT_MEMORY_CHART_USED,
-    VM_CURRENT_MEMORY_CHART_CAPACITY,
-    VM_CURRENT_MEMORY_CHART_MAX_CAPACITY,
-    VM_CURRENT_MEMORY_CHART_SPACE,
-    VM_CURRENT_MEMORY_CHART_SIZE,
+    VM_MEMORY_SPACE_TITLE,
+    VM_MEMORY_SPACE_USED,
+    VM_MEMORY_SPACE_FREE,
+    VM_MEMORY_SPACE_ADDITIONAL,
 
     VM_GC_COLLECTOR_OVER_GENERATION,
     VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL,
--- a/client/src/main/java/com/redhat/thermostat/client/ui/IconResource.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/IconResource.java	Tue Apr 24 14:15:34 2012 +0200
@@ -58,6 +58,8 @@
     public static final IconResource NETWORK_SERVER = new IconResource(ICON_PREFIX + "48x48/places/network-server.png");
     public static final IconResource NETWORK_GROUP = new IconResource(ICON_PREFIX + "48x48/places/network-workgroup.png");
 
+    public static final IconResource ARROW_RIGHT = new IconResource(ICON_PREFIX + "48x48/actions/go-next.png");
+
     public static final IconResource SEARCH = new IconResource(ICON_PREFIX + "16x16/actions/search.png");
 
     private final String path;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/MemorySpacePanel.java	Tue Apr 24 14:15:34 2012 +0200
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 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.ui;
+
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.LayoutStyle.ComponentPlacement;
+
+public class MemorySpacePanel extends JPanel {
+
+    private final JProgressBar percentagePanel;
+    private final JLabel additionalDetailsIcon;
+    private final JLabel lblUsed;
+    private final JLabel lblAvailable;
+
+    public MemorySpacePanel(String regionName) {
+        JLabel lblRegionName = new JLabel(regionName);
+
+        percentagePanel = new JProgressBar(0, 100);
+
+        additionalDetailsIcon = new JLabel(IconResource.ARROW_RIGHT.getIcon());
+
+        lblUsed = new JLabel("${USED}");
+        lblAvailable = new JLabel("${AVAILABLE}");
+
+        GroupLayout groupLayout = new GroupLayout(this);
+        groupLayout.setHorizontalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                        .addComponent(lblRegionName)
+                        .addGroup(groupLayout.createSequentialGroup()
+                            .addGap(12)
+                            .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                                .addComponent(percentagePanel, GroupLayout.DEFAULT_SIZE, 574, Short.MAX_VALUE)
+                                .addGroup(groupLayout.createSequentialGroup()
+                                    .addComponent(lblUsed)
+                                    .addPreferredGap(ComponentPlacement.RELATED, 441, Short.MAX_VALUE)
+                                    .addComponent(lblAvailable)))))
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addComponent(additionalDetailsIcon)
+                    .addContainerGap())
+        );
+        groupLayout.setVerticalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addComponent(lblRegionName)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                        .addComponent(additionalDetailsIcon, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                        .addComponent(percentagePanel, GroupLayout.DEFAULT_SIZE, 44, Short.MAX_VALUE))
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE)
+                        .addComponent(lblUsed)
+                        .addComponent(lblAvailable))
+                    .addGap(5))
+        );
+        setLayout(groupLayout);
+    }
+
+
+    public void updateRegionData(int percentageUsed, String currentlyUsed, String currentlyAvailable, String allocatable) {
+        percentagePanel.setValue(percentageUsed);
+
+        lblUsed.setText(currentlyUsed);
+        lblAvailable.setText(currentlyAvailable);
+        additionalDetailsIcon.setToolTipText(allocatable);
+    }
+
+}
--- a/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryController.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryController.java	Tue Apr 24 14:15:34 2012 +0200
@@ -36,13 +36,17 @@
 
 package com.redhat.thermostat.client.ui;
 
+import static com.redhat.thermostat.client.locale.Translate.localize;
+
 import java.awt.Component;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
 import java.util.concurrent.TimeUnit;
 
 import com.redhat.thermostat.client.AsyncUiFacade;
+import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
 import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
 import com.redhat.thermostat.common.dao.VmRef;
@@ -52,21 +56,19 @@
 
 class VmMemoryController implements AsyncUiFacade {
 
-    private final VmRef ref;
     private final VmMemoryView view;
     private final VmMemoryStatDAO dao;
 
-    private final Timer timer = new Timer();
+    private final Timer timer;
+
+    private final List<String> spacesInView =  new ArrayList<>();
 
-    public VmMemoryController(VmRef ref) {
-        this.ref = ref;
+    public VmMemoryController(final VmRef ref) {
         dao = ApplicationContext.getInstance().getDAOFactory().getVmMemoryStatDAO();
+        timer = ApplicationContext.getInstance().getTimerFactory().createTimer();
         view = ApplicationContext.getInstance().getViewFactory().getView(VmMemoryView.class);
-    }
 
-    @Override
-    public void start() {
-        timer.scheduleAtFixedRate(new TimerTask() {
+        timer.setAction(new Runnable() {
             @Override
             public void run() {
                 VmMemoryStat info = dao.getLatestMemoryStat(ref);
@@ -74,18 +76,37 @@
                 for (Generation generation: generations) {
                     List<Space> spaces = generation.spaces;
                     for (Space space: spaces) {
-                        view.setMemoryRegionSize(space.name, space.used, space.capacity, space.maxCapacity);
+
+                        if (!spacesInView.contains(space.name)) {
+                            view.addRegion(space.name);
+                            spacesInView.add(space.name);
+                        }
+
+                        int percentageUsed = (int) (100.0 * space.used/space.capacity);
+                        String currentlyUsed = localize(LocaleResources.VM_MEMORY_SPACE_USED, String.valueOf(space.used));
+                        String currentlyUnused = localize(LocaleResources.VM_MEMORY_SPACE_FREE, String.valueOf(space.capacity - space.used));
+                        String allocatable = localize(LocaleResources.VM_MEMORY_SPACE_ADDITIONAL, String.valueOf(space.maxCapacity-space.capacity));
+                        String name = space.name; // FIXME
+                        view.updateRegionSize(name, percentageUsed, currentlyUsed, currentlyUnused, allocatable);
+
                     }
                 }
+            }
+        });
+        timer.setInitialDelay(0);
+        timer.setDelay(10);
+        timer.setTimeUnit(TimeUnit.MILLISECONDS);
+        timer.setSchedulingType(SchedulingType.FIXED_RATE);
+    }
 
-            }
-
-        }, 0, TimeUnit.SECONDS.toMillis(5));
+    @Override
+    public void start() {
+        timer.start();
     }
 
     @Override
     public void stop() {
-        timer.cancel();
+        timer.stop();
     }
 
     public Component getComponent() {
--- a/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryPanel.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryPanel.java	Tue Apr 24 14:15:34 2012 +0200
@@ -37,18 +37,17 @@
 package com.redhat.thermostat.client.ui;
 
 import static com.redhat.thermostat.client.locale.Translate.localize;
+import java.awt.Component;
+import java.util.HashMap;
+import java.util.Map;
 
-import java.awt.Component;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JLabel;
 import javax.swing.JPanel;
-
-import org.jfree.chart.ChartFactory;
-import org.jfree.chart.ChartPanel;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.plot.PlotOrientation;
-import org.jfree.data.category.DefaultCategoryDataset;
+import javax.swing.LayoutStyle.ComponentPlacement;
+import javax.swing.BoxLayout;
+import javax.swing.SwingUtilities;
 
 import com.redhat.thermostat.client.locale.LocaleResources;
 
@@ -56,58 +55,77 @@
 
     private static final long serialVersionUID = -2882890932814218436L;
 
-    private final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
+    private final Map<String, MemorySpacePanel> regions = new HashMap<>();
+
+    private final JPanel currentRegionSizePanel;
 
     public VmMemoryPanel() {
-        initializePanel();
+        JLabel lblMem = new JLabel(localize(LocaleResources.VM_MEMORY_SPACE_TITLE));
+
+        currentRegionSizePanel = new JPanel();
+
+        GroupLayout groupLayout = new GroupLayout(this);
+        groupLayout.setHorizontalGroup(
+            groupLayout.createParallelGroup(Alignment.TRAILING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                        .addGroup(groupLayout.createSequentialGroup()
+                            .addComponent(currentRegionSizePanel, GroupLayout.DEFAULT_SIZE, 630, Short.MAX_VALUE)
+                            .addContainerGap())
+                        .addGroup(Alignment.TRAILING, groupLayout.createSequentialGroup()
+                            .addComponent(lblMem)
+                            .addGap(491))))
+        );
+        groupLayout.setVerticalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addComponent(lblMem)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addComponent(currentRegionSizePanel, GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE)
+                    .addContainerGap())
+        );
+        currentRegionSizePanel.setLayout(new BoxLayout(currentRegionSizePanel, BoxLayout.PAGE_AXIS));
+        setLayout(groupLayout);
+
     }
 
     @Override
-    public void setMemoryRegionSize(String name, long used, long allocated, long max) {
-        dataset.addValue(used, localize(LocaleResources.VM_CURRENT_MEMORY_CHART_USED), name);
-        dataset.addValue(allocated - used,
-                localize(LocaleResources.VM_CURRENT_MEMORY_CHART_CAPACITY), name);
-        dataset.addValue(max - allocated,
-                localize(LocaleResources.VM_CURRENT_MEMORY_CHART_MAX_CAPACITY), name);
-    }
-
-    private void initializePanel() {
-        JPanel panel = this;
-        panel.setLayout(new GridBagLayout());
-        GridBagConstraints c = new GridBagConstraints();
-        c.gridx = 0;
-        c.gridy = 0;
-        c.fill = GridBagConstraints.BOTH;
-        c.weightx = 1;
-        c.weighty = 1;
-        panel.add(createCurrentMemoryDisplay(), c);
-        c.gridy++;
-        panel.add(createMemoryHistoryPanel(), c);
+    public void addRegion(final String humanReadableName) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                MemorySpacePanel regionInfo = new MemorySpacePanel(humanReadableName);
+                regions.put(humanReadableName, regionInfo);
+                currentRegionSizePanel.add(regionInfo);
+                currentRegionSizePanel.revalidate();
+            }
+        });
     }
 
-    private Component createCurrentMemoryDisplay() {
 
-        JFreeChart chart = ChartFactory.createStackedBarChart(
-                null,
-                localize(LocaleResources.VM_CURRENT_MEMORY_CHART_SPACE),
-                localize(LocaleResources.VM_CURRENT_MEMORY_CHART_SIZE),
-                dataset,
-                PlotOrientation.HORIZONTAL, true, false, false);
-
-        ChartPanel chartPanel = new ChartPanel(chart);
-        // make this chart non-interactive
-        chartPanel.setDisplayToolTips(true);
-        chartPanel.setDoubleBuffered(true);
-        chartPanel.setMouseZoomable(false);
-        chartPanel.setPopupMenu(null);
-
-        return chartPanel;
+    @Override
+    public void removeAllRegions() {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                regions.clear();
+                currentRegionSizePanel.removeAll();
+                currentRegionSizePanel.revalidate();
+            }
+        });
     }
 
-    private Component createMemoryHistoryPanel() {
-        JPanel historyPanel = new JPanel();
-        // TODO implement this
-        return historyPanel;
+    @Override
+    public void updateRegionSize(final String name, final int percentageUsed, final String currentlyUsed, final String currentlyAvailable, final String allocatable) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                regions.get(name).updateRegionData(percentageUsed, currentlyUsed, currentlyAvailable, allocatable);
+            }
+        });
+
     }
 
     @Override
--- a/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryView.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmMemoryView.java	Tue Apr 24 14:15:34 2012 +0200
@@ -42,7 +42,11 @@
 
 public interface VmMemoryView extends View {
 
-    void setMemoryRegionSize(String name, long used, long allocated, long max);
+    void addRegion(String humanReadableName);
+
+    void removeAllRegions();
+
+    void updateRegionSize(String name, int percentageUsed, String currentlyUsed, String currentlyAvailable, String allocatable);
 
     Component getUiComponent();
 
--- a/client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Tue Apr 24 14:14:13 2012 +0200
+++ b/client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Tue Apr 24 14:15:34 2012 +0200
@@ -125,11 +125,10 @@
 VM_CPU_CHART_LOAD_LABEL = % CPU
 VM_CPU_CHART_TIME_LABEL = Time
 
-VM_CURRENT_MEMORY_CHART_USED = Used
-VM_CURRENT_MEMORY_CHART_CAPACITY = Capacity
-VM_CURRENT_MEMORY_CHART_MAX_CAPACITY = Max Capacity
-VM_CURRENT_MEMORY_CHART_SPACE = Memory Region
-VM_CURRENT_MEMORY_CHART_SIZE = Size
+VM_MEMORY_SPACE_TITLE = Memory Region Sizes
+VM_MEMORY_SPACE_USED = {0} bytes used
+VM_MEMORY_SPACE_FREE = {0} bytes unused
+VM_MEMORY_SPACE_ADDITIONAL = An additional {0} bytes can be allocated
 
 VM_GC_COLLECTOR_OVER_GENERATION = Collector {0} running on {1}
 VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL = Time
--- a/client/src/test/java/com/redhat/thermostat/client/MainWindowControllerImplTest.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/client/src/test/java/com/redhat/thermostat/client/MainWindowControllerImplTest.java	Tue Apr 24 14:15:34 2012 +0200
@@ -68,6 +68,7 @@
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.test.Bug;
 
 public class MainWindowControllerImplTest {
 
@@ -237,10 +238,11 @@
     }
     
     @Test
-    public void bug954() {
+    @Bug(id="954",
+         summary="Thermostat GUI client should remember my last panel selected",
+         url="http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=954")
+    public void verifyOpenSameHostVMTab() {
 
-        // see http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=954
-        
         VmRef vmRef = mock(VmRef.class);
         when(view.getSelectedHostOrVm()).thenReturn(vmRef);
                 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/ui/VmMemoryControllerTest.java	Tue Apr 24 14:15:34 2012 +0200
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 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.ui;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.contains;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.TimerFactory;
+import com.redhat.thermostat.common.ViewFactory;
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.model.VmMemoryStat;
+import com.redhat.thermostat.common.model.VmMemoryStat.Generation;
+import com.redhat.thermostat.common.model.VmMemoryStat.Space;
+
+public class VmMemoryControllerTest {
+
+    private final long TIMESTAMP = 1;
+    private final int VM_ID = 99;
+
+    private List<Generation> generations = new ArrayList<>();
+
+    private Timer timer;
+    private Space space;
+    private Generation gen;
+    private VmMemoryController controller;
+    private VmMemoryView view;
+    private Runnable timerAction;
+
+
+    @Before()
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+
+        // Setup timer.
+        timer = mock(Timer.class);
+        ArgumentCaptor<Runnable> actionCaptor = ArgumentCaptor.forClass(Runnable.class);
+        doNothing().when(timer).setAction(actionCaptor.capture());
+
+        TimerFactory timerFactory = mock(TimerFactory.class);
+        when(timerFactory.createTimer()).thenReturn(timer);
+        ApplicationContext.getInstance().setTimerFactory(timerFactory);
+
+        space = new Space();
+        space.name = "space";
+        space.index = 0;
+        space.used = 10;
+        space.capacity = 100;
+        space.maxCapacity = 1000;
+
+        gen = new Generation();
+        gen.spaces = new ArrayList<>();
+        gen.spaces.add(space);
+
+        generations.add(gen);
+
+        // Setup dao
+        VmMemoryStat vmMemory = new VmMemoryStat(TIMESTAMP, VM_ID, generations);
+        VmMemoryStatDAO memoryStatDao = mock(VmMemoryStatDAO.class);
+        when(memoryStatDao.getLatestMemoryStat(any(VmRef.class))).thenReturn(vmMemory);
+
+        DAOFactory daoFactory = mock(DAOFactory.class);
+        when(daoFactory.getVmMemoryStatDAO()).thenReturn(memoryStatDao);
+
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
+
+        // Setup view
+        view = mock(VmMemoryView.class);
+        ViewFactory viewFactory = mock(ViewFactory.class);
+        when(viewFactory.getView(eq(VmMemoryView.class))).thenReturn(view);
+        ApplicationContext.getInstance().setViewFactory(viewFactory);
+
+
+        VmRef ref = mock(VmRef.class);
+
+        controller = new VmMemoryController(ref);
+        timerAction = actionCaptor.getValue();
+
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
+    @Test
+    public void testTimer() {
+
+        controller.start();
+
+        verify(timer).start();
+        verify(timer).setSchedulingType(SchedulingType.FIXED_RATE);
+
+        controller.stop();
+
+        verify(timer).stop();
+    }
+
+
+    @Test
+    public void testControllerUpdatesView() {
+
+        timerAction.run();
+
+        verify(view).addRegion(eq("space"));
+        verify(view).updateRegionSize(eq("space"), eq(10), contains("10"), contains("90"), contains("900"));
+    }
+}
--- a/common/src/main/java/com/redhat/thermostat/common/ThreadPoolTimerFactory.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/ThreadPoolTimerFactory.java	Tue Apr 24 14:15:34 2012 +0200
@@ -39,12 +39,23 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 public class ThreadPoolTimerFactory implements TimerFactory {
 
     public ThreadPoolTimerFactory(int poolSize) {
-        timerThreadPool = Executors.newScheduledThreadPool(poolSize);
+        this(poolSize, Thread.currentThread().getThreadGroup());
+    }
+
+    ThreadPoolTimerFactory(int poolSize, final ThreadGroup group) {
+        timerThreadPool = Executors.newScheduledThreadPool(poolSize, new ThreadFactory() {
+            
+            @Override
+            public Thread newThread(Runnable r) {
+                return new Thread(group, r);
+            }
+        });
     }
 
     private class ThreadPoolTimer implements Timer {
@@ -126,4 +137,9 @@
         return new ThreadPoolTimer();
     }
 
+    @Override
+    public void shutdown() {
+        timerThreadPool.shutdown();
+    }
+
 }
--- a/common/src/main/java/com/redhat/thermostat/common/TimerFactory.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/TimerFactory.java	Tue Apr 24 14:15:34 2012 +0200
@@ -39,4 +39,6 @@
 public interface TimerFactory {
 
     Timer createTimer();
+
+    void shutdown();
 }
--- a/common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatConverter.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatConverter.java	Tue Apr 24 14:15:34 2012 +0200
@@ -115,7 +115,7 @@
         newGen.collector = chunk.get(VmMemoryStatDAO.edenCollectorKey);
 
         space = new Space();
-        space.name = chunk.get(VmMemoryStatDAO.edenGenKey);
+        space.name = VmMemoryStatDAO.edenKey.getName();
         space.capacity = chunk.get(VmMemoryStatDAO.edenCapacityKey);
         space.maxCapacity = chunk.get(VmMemoryStatDAO.edenMaxCapacityKey);
         space.used = chunk.get(VmMemoryStatDAO.edenUsedKey);
@@ -124,7 +124,7 @@
         newGen.maxCapacity += space.maxCapacity;
 
         space = new Space();
-        space.name = chunk.get(VmMemoryStatDAO.s0GenKey);
+        space.name = VmMemoryStatDAO.s0Key.getName();
         space.capacity = chunk.get(VmMemoryStatDAO.s0CapacityKey);
         space.maxCapacity = chunk.get(VmMemoryStatDAO.s0MaxCapacityKey);
         space.used = chunk.get(VmMemoryStatDAO.s0UsedKey);
@@ -133,7 +133,7 @@
         newGen.maxCapacity += space.maxCapacity;
 
         space = new Space();
-        space.name = chunk.get(VmMemoryStatDAO.s1GenKey);
+        space.name = VmMemoryStatDAO.s1Key.getName();
         space.capacity = chunk.get(VmMemoryStatDAO.s1CapacityKey);
         space.maxCapacity = chunk.get(VmMemoryStatDAO.s1MaxCapacityKey);
         space.used = chunk.get(VmMemoryStatDAO.s1UsedKey);
@@ -150,7 +150,7 @@
         oldGen.collector = chunk.get(VmMemoryStatDAO.oldCollectorKey);
 
         space = new Space();
-        space.name = chunk.get(VmMemoryStatDAO.oldGenKey);
+        space.name = VmMemoryStatDAO.oldKey.getName();
         space.capacity = chunk.get(VmMemoryStatDAO.oldCapacityKey);
         space.maxCapacity = chunk.get(VmMemoryStatDAO.oldMaxCapacityKey);
         space.used = chunk.get(VmMemoryStatDAO.oldUsedKey);
@@ -167,7 +167,7 @@
         permGen.collector = chunk.get(VmMemoryStatDAO.permCollectorKey);
 
         space = new Space();
-        space.name = chunk.get(VmMemoryStatDAO.permGenKey);
+        space.name = VmMemoryStatDAO.permKey.getName();
         space.capacity = chunk.get(VmMemoryStatDAO.permCapacityKey);
         space.maxCapacity = chunk.get(VmMemoryStatDAO.permMaxCapacityKey);
         space.used = chunk.get(VmMemoryStatDAO.permUsedKey);
--- a/common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAO.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAO.java	Tue Apr 24 14:15:34 2012 +0200
@@ -42,30 +42,35 @@
 
 public interface VmMemoryStatDAO {
 
+    static final Key<String> edenKey = new Key<>("eden", false);
     static final Key<String> edenGenKey = new Key<>("eden.gen", false);
     static final Key<String> edenCollectorKey = new Key<>("eden.collector", false);
     static final Key<Long> edenCapacityKey = new Key<>("eden.capacity", false);
     static final Key<Long> edenMaxCapacityKey = new Key<>("eden.max-capacity", false);
     static final Key<Long> edenUsedKey = new Key<>("eden.used", false);
 
+    static final Key<String> s0Key = new Key<>("s0", false);
     static final Key<String> s0GenKey = new Key<>("s0.gen", false);
     static final Key<String> s0CollectorKey = new Key<>("s0.collector", false);
     static final Key<Long> s0CapacityKey = new Key<>("s0.capacity", false);
     static final Key<Long> s0MaxCapacityKey = new Key<>("s0.max-capacity", false);
     static final Key<Long> s0UsedKey = new Key<>("s0.used", false);
 
+    static final Key<String> s1Key = new Key<>("s1", false);
     static final Key<String> s1GenKey = new Key<>("s1.gen", false);
     static final Key<String> s1CollectorKey = new Key<>("s1.collector", false);
     static final Key<Long> s1CapacityKey = new Key<>("s1.capacity", false);
     static final Key<Long> s1MaxCapacityKey = new Key<>("s1.max-capacity", false);
     static final Key<Long> s1UsedKey = new Key<>("s1.used", false);
 
+    static final Key<String> oldKey = new Key<>("old", false);
     static final Key<String> oldGenKey = new Key<>("old.gen", false);
     static final Key<String> oldCollectorKey = new Key<>("old.collector", false);
     static final Key<Long> oldCapacityKey = new Key<>("old.capacity", false);
     static final Key<Long> oldMaxCapacityKey = new Key<>("old.max-capacity", false);
     static final Key<Long> oldUsedKey = new Key<>("old.used", false);
 
+    static final Key<String> permKey = new Key<>("perm", false);
     static final Key<String> permGenKey = new Key<>("perm.gen", false);
     static final Key<String> permCollectorKey = new Key<>("perm.collector", false);
     static final Key<Long> permCapacityKey = new Key<>("perm.capacity", false);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/test/Bug.java	Tue Apr 24 14:15:34 2012 +0200
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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.test;
+
+public @interface Bug {
+
+    String id();
+    String url();
+    String summary();
+}
--- a/common/src/test/java/com/redhat/thermostat/common/ThreadPoolTimerFactoryTest.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/ThreadPoolTimerFactoryTest.java	Tue Apr 24 14:15:34 2012 +0200
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.common;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -51,6 +53,7 @@
 import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.test.Bug;
 
 public class ThreadPoolTimerFactoryTest {
 
@@ -58,15 +61,24 @@
 
     private Timer timer;
 
+    private ThreadGroup threadGroup;
+
+    private TimerFactory timerFactory;
+
     @Before
     public void setUp() {
-        ThreadPoolTimerFactory timerFactory = new ThreadPoolTimerFactory(1);
+        threadGroup = new ThreadGroup("test");
+        timerFactory = new ThreadPoolTimerFactory(1, threadGroup);
         timer = timerFactory.createTimer();
     }
 
     @After
     public void tearDown() {
+        
         timer = null;
+        timerFactory.shutdown();
+        timerFactory = null;
+        threadGroup = null;
     }
 
     @Test
@@ -187,4 +199,24 @@
         Thread.sleep(DELAY);
         verify(action, times(2)).run();
     }
+
+    @Bug(id="957",
+         summary="Thermostat GUI doesn't exit when closed, needs killing",
+         url="http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=957")
+    @Test
+    public void verifyShutdownKillsThreads() throws InterruptedException {
+
+        Runnable action = mock(Runnable.class);
+        timer.setAction(action);
+        timer.setInitialDelay(DELAY / 2);
+        timer.start();
+
+        assertTrue(threadGroup.activeCount() > 0);
+
+        timerFactory.shutdown();
+
+        Thread.sleep(DELAY);
+
+        assertEquals(0, threadGroup.activeCount());
+    }
 }
--- a/common/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatConverterTest.java	Tue Apr 24 14:14:13 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatConverterTest.java	Tue Apr 24 14:15:34 2012 +0200
@@ -122,6 +122,26 @@
         final long TIMESTAMP = 1234l;
         final int VM_ID = 4567;
 
+        final long EDEN_USED = 1;
+        final long EDEN_CAPACITY = 2;
+        final long EDEN_MAX_CAPACITY = 3;
+        
+        final long S0_USED = 4;
+        final long S0_CAPACITY = 5;
+        final long S0_MAX_CAPACITY = 6;
+        
+        final long S1_USED = 7;
+        final long S1_CAPACITY = 8;
+        final long S1_MAX_CAPACITY = 9;
+        
+        final long OLD_USED = 10;
+        final long OLD_CAPACITY = 11;
+        final long OLD_MAX_CAPACITY = 12;
+        
+        final long PERM_USED = 13;
+        final long PERM_CAPACITY = 14;
+        final long PERM_MAX_CAPACITY = 15;
+        
         Chunk chunk = new Chunk(VmMemoryStatDAO.vmMemoryStatsCategory, false);
 
         chunk.put(Key.TIMESTAMP, TIMESTAMP);
@@ -129,33 +149,33 @@
 
         chunk.put(VmMemoryStatDAO.edenGenKey, "new");
         chunk.put(VmMemoryStatDAO.edenCollectorKey, "new-collector");
-        chunk.put(VmMemoryStatDAO.edenUsedKey, 1l);
-        chunk.put(VmMemoryStatDAO.edenCapacityKey, 2l);
-        chunk.put(VmMemoryStatDAO.edenMaxCapacityKey, 3l);
+        chunk.put(VmMemoryStatDAO.edenUsedKey, EDEN_USED);
+        chunk.put(VmMemoryStatDAO.edenCapacityKey, EDEN_CAPACITY);
+        chunk.put(VmMemoryStatDAO.edenMaxCapacityKey, EDEN_MAX_CAPACITY);
 
         chunk.put(VmMemoryStatDAO.s0GenKey, "new");
         chunk.put(VmMemoryStatDAO.s0CollectorKey, "new-collector");
-        chunk.put(VmMemoryStatDAO.s0UsedKey, 4l);
-        chunk.put(VmMemoryStatDAO.s0CapacityKey, 5l);
-        chunk.put(VmMemoryStatDAO.s0MaxCapacityKey, 6l);
+        chunk.put(VmMemoryStatDAO.s0UsedKey, S0_USED);
+        chunk.put(VmMemoryStatDAO.s0CapacityKey, S0_CAPACITY);
+        chunk.put(VmMemoryStatDAO.s0MaxCapacityKey, S0_MAX_CAPACITY);
 
         chunk.put(VmMemoryStatDAO.s1GenKey, "new");
         chunk.put(VmMemoryStatDAO.s1CollectorKey, "new-collector");
-        chunk.put(VmMemoryStatDAO.s1UsedKey, 7l);
-        chunk.put(VmMemoryStatDAO.s1CapacityKey, 8l);
-        chunk.put(VmMemoryStatDAO.s1MaxCapacityKey, 9l);
+        chunk.put(VmMemoryStatDAO.s1UsedKey, S1_USED);
+        chunk.put(VmMemoryStatDAO.s1CapacityKey, S1_CAPACITY);
+        chunk.put(VmMemoryStatDAO.s1MaxCapacityKey, S1_MAX_CAPACITY);
 
         chunk.put(VmMemoryStatDAO.oldGenKey, "old");
         chunk.put(VmMemoryStatDAO.oldCollectorKey, "old-collector");
-        chunk.put(VmMemoryStatDAO.oldUsedKey, 10l);
-        chunk.put(VmMemoryStatDAO.oldCapacityKey, 11l);
-        chunk.put(VmMemoryStatDAO.oldMaxCapacityKey, 12l);
+        chunk.put(VmMemoryStatDAO.oldUsedKey, OLD_USED);
+        chunk.put(VmMemoryStatDAO.oldCapacityKey, OLD_CAPACITY);
+        chunk.put(VmMemoryStatDAO.oldMaxCapacityKey, OLD_MAX_CAPACITY);
 
         chunk.put(VmMemoryStatDAO.permGenKey, "perm");
         chunk.put(VmMemoryStatDAO.permCollectorKey, "perm-collector");
-        chunk.put(VmMemoryStatDAO.permUsedKey, 13l);
-        chunk.put(VmMemoryStatDAO.permCapacityKey, 14l);
-        chunk.put(VmMemoryStatDAO.permMaxCapacityKey, 15l);
+        chunk.put(VmMemoryStatDAO.permUsedKey, PERM_USED);
+        chunk.put(VmMemoryStatDAO.permCapacityKey, PERM_CAPACITY);
+        chunk.put(VmMemoryStatDAO.permMaxCapacityKey, PERM_MAX_CAPACITY);
 
         VmMemoryStat stat = new VmMemoryStatConverter().fromChunk(chunk);
 
@@ -165,13 +185,49 @@
 
         assertEquals(3, stat.getGenerations().size());
 
-        assertEquals(3, stat.getGeneration("new").spaces.size());
-        assertEquals("new-collector", stat.getGeneration("new").collector);
+        Generation newGen = stat.getGeneration("new");
+        assertNotNull(newGen);
+        assertEquals(3, newGen.spaces.size());
+        assertEquals("new-collector", newGen.collector);
+        
+        Space eden = newGen.getSpace("eden");
+        assertNotNull(eden);
+        assertEquals(EDEN_USED, eden.used);
+        assertEquals(EDEN_CAPACITY, eden.capacity);
+        assertEquals(EDEN_MAX_CAPACITY, eden.maxCapacity);
+        
+        Space s0 = newGen.getSpace("s0");
+        assertNotNull(s0);
+        assertEquals(S0_USED, s0.used);
+        assertEquals(S0_CAPACITY, s0.capacity);
+        assertEquals(S0_MAX_CAPACITY, s0.maxCapacity);
+        
+        Space s1 = newGen.getSpace("s1");
+        assertNotNull(s1);
+        assertEquals(S1_USED, s1.used);
+        assertEquals(S1_CAPACITY, s1.capacity);
+        assertEquals(S1_MAX_CAPACITY, s1.maxCapacity);
+        
+        Generation oldGen = stat.getGeneration("old");
+        assertNotNull(oldGen);
+        assertEquals(1, oldGen.spaces.size());
+        assertEquals("old-collector", oldGen.collector);
 
-        assertEquals(1, stat.getGeneration("old").spaces.size());
-        assertEquals("old-collector", stat.getGeneration("old").collector);
-
-        assertEquals(1, stat.getGeneration("perm").spaces.size());
-        assertEquals("perm-collector", stat.getGeneration("perm").collector);
+        Space old = oldGen.getSpace("old");
+        assertNotNull(old);
+        assertEquals(OLD_USED, old.used);
+        assertEquals(OLD_CAPACITY, old.capacity);
+        assertEquals(OLD_MAX_CAPACITY, old.maxCapacity);
+        
+        Generation permGen = stat.getGeneration("perm");
+        assertNotNull(permGen);
+        assertEquals(1, permGen.spaces.size());
+        assertEquals("perm-collector", permGen.collector);
+        
+        Space permSpace = permGen.getSpace("perm");
+        assertNotNull(permSpace);
+        assertEquals(PERM_USED, permSpace.used);
+        assertEquals(PERM_CAPACITY, permSpace.capacity);
+        assertEquals(PERM_MAX_CAPACITY, permSpace.maxCapacity);
     }
 }