changeset 414:79c07f5c005a

Merge
author Roman Kennke <rkennke@redhat.com>
date Thu, 21 Jun 2012 17:41:40 +0200
parents 05d1331db40e (current diff) aebfbb3d4d03 (diff)
children 17313a694032
files tools/src/main/java/com/redhat/thermostat/tools/cli/TableRenderer.java tools/src/test/java/com/redhat/thermostat/tools/cli/TableRendererTest.java
diffstat 14 files changed, 541 insertions(+), 224 deletions(-) [+]
line wrap: on
line diff
--- a/client/heapdumper/pom.xml	Thu Jun 21 17:38:44 2012 +0200
+++ b/client/heapdumper/pom.xml	Thu Jun 21 17:41:40 2012 +0200
@@ -102,7 +102,12 @@
       <version>${project.version}</version>
       <type>bundle</type>
     </dependency>
-    
+        <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-tools</artifactId>
+      <version>${project.version}</version>
+      <type>bundle</type>
+    </dependency>
     <dependency>
       <groupId>org.jfree</groupId>
       <artifactId>jfreechart</artifactId>
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/Activator.java	Thu Jun 21 17:38:44 2012 +0200
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/Activator.java	Thu Jun 21 17:41:40 2012 +0200
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.client.heap;
 
+import java.util.ServiceLoader;
+
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
@@ -47,9 +49,13 @@
 import com.redhat.thermostat.client.osgi.service.ApplicationService;
 import com.redhat.thermostat.client.osgi.service.VmInformationService;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.cli.Command;
+import com.redhat.thermostat.common.cli.CommandRegistry;
+import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
 public class Activator implements BundleActivator {
 
+    private CommandRegistry reg;
     private ServiceRegistration contextServiceReg;
 
     @Override
@@ -59,13 +65,17 @@
             @Override
             public Object addingService(ServiceReference reference) {
                 ApplicationService appService = (ApplicationService) context.getService(reference);
-                
+
                 ApplicationContext.getInstance().getViewFactory().setViewClass(HeapView.class, HeapSwingView.class);
                 context.registerService(VmInformationService.class.getName(), new HeapDumperService(appService), null);
                 return super.addingService(reference);
             }
         };
         tracker.open();
+
+        reg = new CommandRegistryImpl(context);
+        ServiceLoader<Command> cmds = ServiceLoader.load(Command.class, getClass().getClassLoader());
+        reg.registerCommands(cmds);
     }
 
     @Override
@@ -73,5 +83,7 @@
         if (contextServiceReg != null) {
             contextServiceReg.unregister();
         }
+
+        reg.unregisterCommands();
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/ListHeapDumpsCommand.java	Thu Jun 21 17:41:40 2012 +0200
@@ -0,0 +1,122 @@
+/*
+ * 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.heap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.cli.ArgumentSpec;
+import com.redhat.thermostat.common.cli.Command;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.TableRenderer;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.HeapDAO;
+import com.redhat.thermostat.common.dao.HostInfoDAO;
+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.common.model.HeapInfo;
+
+public class ListHeapDumpsCommand implements Command {
+
+    private static final String NAME = "list-heap-dumps";
+    private static final String DESCRIPTION = "list all heap dumps";
+    private static final String USAGE = DESCRIPTION;
+
+    // TODO localize
+    private static final String[] COLUMN_NAMES = {"HOST ID", "VM ID", "HEAP ID", "TIMESTAMP"};
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getDescription() {
+        return DESCRIPTION;
+    }
+
+    @Override
+    public String getUsage() {
+        return USAGE;
+    }
+
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        return new ArrayList<>();
+    }
+
+    @Override
+    public boolean isStorageRequired() {
+        return true;
+    }
+
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        TableRenderer renderer = new TableRenderer(4);
+
+        renderer.printLine(COLUMN_NAMES);
+
+        DAOFactory daoFactory = ApplicationContext.getInstance().getDAOFactory();
+        HostInfoDAO hostDAO = daoFactory.getHostInfoDAO();
+        VmInfoDAO vmDAO = daoFactory.getVmInfoDAO();
+        HeapDAO heapDAO = daoFactory.getHeapDAO();
+
+        for (HostRef hostRef : hostDAO.getHosts()) {
+            for (VmRef vmRef : vmDAO.getVMs(hostRef)) {
+                Collection<HeapInfo> infos = heapDAO.getAllHeapInfo(vmRef);
+                for (HeapInfo info : infos) {
+                    renderer.printLine(hostRef.getStringID(),
+                                       vmRef.getStringID(),
+                                       info.getHeapDumpId(),
+                                       new Date(info.getTimestamp()).toString());
+                }
+            }
+        }
+
+        renderer.render(ctx.getConsole().getOutput());
+    }
+
+    @Override
+    public void disable() {
+        /* NO-OP */
+    }
+
+}
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/StatsPanel.java	Thu Jun 21 17:38:44 2012 +0200
+++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/StatsPanel.java	Thu Jun 21 17:41:40 2012 +0200
@@ -46,6 +46,7 @@
 import javax.swing.JButton;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
+import javax.swing.JScrollPane;
 import javax.swing.LayoutStyle.ComponentPlacement;
 import javax.swing.ListSelectionModel;
 import javax.swing.SwingConstants;
@@ -99,6 +100,7 @@
         
         heapDumpButton = new JButton("Heap Dump");
         
+        JScrollPane dumpListScrollPane = new JScrollPane();
         dumpList = new JList<>();
         listModel = new DefaultListModel<>();
         dumpList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -110,7 +112,7 @@
                 .addGroup(gl_rightPanel.createSequentialGroup()
                     .addContainerGap()
                     .addGroup(gl_rightPanel.createParallelGroup(Alignment.TRAILING)
-                        .addComponent(dumpList, Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 173, Short.MAX_VALUE)
+                        .addComponent(dumpListScrollPane, Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 173, Short.MAX_VALUE)
                         .addComponent(heapDumpButton, GroupLayout.DEFAULT_SIZE, 173, Short.MAX_VALUE)
                         .addGroup(gl_rightPanel.createSequentialGroup()
                             .addGroup(gl_rightPanel.createParallelGroup(Alignment.TRAILING, false)
@@ -136,12 +138,14 @@
                     .addGap(18)
                     .addComponent(heapDumpButton)
                     .addGap(18)
-                    .addComponent(dumpList, GroupLayout.DEFAULT_SIZE, 172, Short.MAX_VALUE)
+                    .addComponent(dumpListScrollPane, GroupLayout.DEFAULT_SIZE, 172, Short.MAX_VALUE)
                     .addContainerGap())
         );
         rightPanel.setLayout(gl_rightPanel);
         setLayout(groupLayout);
 
+        dumpListScrollPane.setViewportView(dumpList);
+
         // initially invisible
         dumpList.setVisible(false);
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command	Thu Jun 21 17:41:40 2012 +0200
@@ -0,0 +1,1 @@
+com.redhat.thermostat.client.heap.ListHeapDumpsCommand
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/ListHeapDumpsCommandTest.java	Thu Jun 21 17:41:40 2012 +0200
@@ -0,0 +1,169 @@
+/*
+ * 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.heap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.common.cli.ArgumentSpec;
+import com.redhat.thermostat.common.cli.Command;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.HeapDAO;
+import com.redhat.thermostat.common.dao.HostInfoDAO;
+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.common.model.HeapInfo;
+import com.redhat.thermostat.test.TestCommandContextFactory;
+
+public class ListHeapDumpsCommandTest {
+
+    private static TimeZone defaultTimezone;
+
+    @BeforeClass
+    public static void setUpClass() {
+        defaultTimezone = TimeZone.getDefault();
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        TimeZone.setDefault(defaultTimezone);
+    }
+
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationContextUtil.resetApplicationContext();
+
+    }
+
+    @Test
+    public void verifyBasics() {
+        Command command = new ListHeapDumpsCommand();
+        assertEquals("list-heap-dumps", command.getName());
+        assertNotNull(command.getDescription());
+        assertNotNull(command.getUsage());
+    }
+
+    @Test
+    public void verifyArguments() {
+        Command command = new ListHeapDumpsCommand();
+        List<ArgumentSpec> arguments = new ArrayList<>(command.getAcceptedArguments());
+        assertTrue(arguments.isEmpty());
+    }
+
+    @Test
+    public void verifyWorksWithoutAnyInformation() throws CommandException {
+        HostInfoDAO hostInfo = mock(HostInfoDAO.class);
+
+        VmInfoDAO vmInfo = mock(VmInfoDAO.class);
+
+        DAOFactory daoFactory = mock(DAOFactory.class);
+        when(daoFactory.getHostInfoDAO()).thenReturn(hostInfo);
+        when(daoFactory.getVmInfoDAO()).thenReturn(vmInfo);
+
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
+
+        Command command = new ListHeapDumpsCommand();
+        TestCommandContextFactory factory = new TestCommandContextFactory();
+        command.run(factory.createContext(null));
+        assertEquals("HOST ID VM ID HEAP ID TIMESTAMP\n", factory.getOutput());
+    }
+
+    @Test
+    public void verifyWorks() throws CommandException {
+        HostRef hostRef = mock(HostRef.class);
+        when(hostRef.getStringID()).thenReturn("host-id");
+        VmRef vmRef = mock(VmRef.class);
+        when(vmRef.getStringID()).thenReturn("vm-id");
+
+        HeapInfo heapInfo = mock(HeapInfo.class);
+        Calendar timestamp = Calendar.getInstance();
+        timestamp.set(2012, 5, 7, 15, 32, 0);
+        when(heapInfo.getTimestamp()).thenReturn(timestamp.getTimeInMillis());
+        when(heapInfo.getHeapDumpId()).thenReturn("0001");
+
+        HeapDAO heapDao = mock(HeapDAO.class);
+
+        VmInfoDAO vmInfo = mock(VmInfoDAO.class);
+        when(vmInfo.getVMs(hostRef)).thenReturn(Arrays.asList(vmRef));
+
+        HostInfoDAO hostInfo = mock(HostInfoDAO.class);
+        when(hostInfo.getHosts()).thenReturn(Arrays.asList(hostRef));
+
+        when(heapDao.getAllHeapInfo(vmRef)).thenReturn(Arrays.asList(heapInfo));
+
+        DAOFactory daoFactory = mock(DAOFactory.class);
+        when(daoFactory.getHostInfoDAO()).thenReturn(hostInfo);
+        when(daoFactory.getVmInfoDAO()).thenReturn(vmInfo);
+        when(daoFactory.getHeapDAO()).thenReturn(heapDao);
+
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
+
+        Command command = new ListHeapDumpsCommand();
+        TestCommandContextFactory factory = new TestCommandContextFactory();
+        command.run(factory.createContext(null));
+
+        String expected = "HOST ID VM ID HEAP ID TIMESTAMP\n" +
+                          "host-id vm-id 0001    Thu Jun 07 15:32:00 UTC 2012\n";
+
+        assertEquals(expected, factory.getOutput());
+    }
+}
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/HelpCommand.java	Thu Jun 21 17:38:44 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/HelpCommand.java	Thu Jun 21 17:41:40 2012 +0200
@@ -44,7 +44,7 @@
 
 public class HelpCommand implements Command {
 
-    private static final int COMMANDS_COLUMNS_WIDTH = 15;
+    private static final int COMMANDS_COLUMNS_WIDTH = 14;
     private static final String NAME = "help";
     private static final String DESCRIPTION = "show help for a given command or help overview";
     private static final String USAGE = DESCRIPTION;
@@ -65,27 +65,22 @@
     private void printCommandSummaries(CommandContext ctx) {
         CommandRegistry cmdRegistry = ctx.getCommandRegistry();
 
-        StringBuilder out = new StringBuilder();
-        out.append("list of commands:\n\n");
+        ctx.getConsole().getOutput().print("list of commands:\n\n");
+
+        TableRenderer renderer = new TableRenderer(2, COMMANDS_COLUMNS_WIDTH);
 
         Collection<Command> commands = cmdRegistry.getRegisteredCommands();
         List<Command> sortedCommands = new ArrayList<>(commands);
 
         Collections.sort(sortedCommands, comparator);
         for (Command cmd : sortedCommands) {
-            printCommandSummary(out, cmd);
+            printCommandSummary(renderer, cmd);
         }
-        ctx.getConsole().getOutput().print(out);
+        renderer.render(ctx.getConsole().getOutput());
     }
 
-    private void printCommandSummary(StringBuilder out, Command cmd) {
-        out.append(" ");
-        out.append(cmd.getName());
-        for (int i = 0; i < COMMANDS_COLUMNS_WIDTH - cmd.getName().length() - 1; i++) {
-            out.append(" ");
-        }
-        out.append(cmd.getDescription());
-        out.append("\n");
+    private void printCommandSummary(TableRenderer renderer, Command cmd) {
+        renderer.printLine(" " + cmd.getName(), cmd.getDescription());
     }
 
     private void printCommandUsage(CommandContext ctx, String cmdName) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/TableRenderer.java	Thu Jun 21 17:41:40 2012 +0200
@@ -0,0 +1,110 @@
+/*
+ * 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.common.cli;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TableRenderer {
+
+    private List<String[]> lines;
+    private int[] maxColumnWidths;
+    private int lastPrintedLine = -1;
+
+    private int numColumns;
+    private int minWidth;
+
+    public TableRenderer(int numColumns) {
+        this(numColumns, 1);
+    }
+
+    public TableRenderer(int numColumns, int minWidth) {
+        this.numColumns = numColumns;
+        lines = new ArrayList<>();
+        maxColumnWidths = new int[numColumns];
+        this.minWidth = minWidth;
+    }
+
+    public void printLine(String... line) {
+        if (line.length != numColumns) {
+            throw new IllegalArgumentException("Invalid number of columns: " + line.length + ", expected: " + numColumns);
+        }
+        lines.add(line);
+        for (int i = 0; i < numColumns; i++) {
+            maxColumnWidths[i] = Math.max(Math.max(maxColumnWidths[i], line[i].length()), minWidth);
+        }
+    }
+
+    public void render(OutputStream os) {
+        PrintStream out = new PrintStream(os);
+        render(out);
+    }
+
+    public void render(PrintStream out) {
+        for (int i = lastPrintedLine + 1; i < lines.size(); i++) {
+            String[] line = lines.get(i);
+            renderLine(out, line);
+            lastPrintedLine = i;
+        }
+    }
+
+    private void renderLine(PrintStream out, String[] line) {
+        for (int i = 0; i < numColumns; i++) {
+            out.print(line[i]);
+            padOrNewline(out, line, i);
+
+        }
+    }
+
+    private void padOrNewline(PrintStream out, String[] line, int i) {
+        if (i < numColumns - 1) {
+            int pad = maxColumnWidths[i] - line[i].length() + 1;
+            fillSpaces(out, pad);
+        } else {
+            out.println();
+        }
+    }
+
+    private void fillSpaces(PrintStream out, int pad) {
+        for (int i = 0; i < pad; i++) {
+            out.print(" ");
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/test/java/com/redhat/thermostat/common/cli/TableRendererTest.java	Thu Jun 21 17:41:40 2012 +0200
@@ -0,0 +1,103 @@
+/*
+ * 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.common.cli;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TableRendererTest {
+
+    private TableRenderer tableRenderer;
+    private ByteArrayOutputStream out;
+
+    @Before
+    public void setUp() {
+        tableRenderer = new TableRenderer(3);
+        out = new ByteArrayOutputStream();
+    }
+
+    @After
+    public void tearDown() {
+        out = null;
+        tableRenderer = null;
+    }
+
+    @Test
+    public void testSingleLine() {
+        tableRenderer.printLine("hello", "fluff", "world");
+        tableRenderer.render(out);
+        assertEquals("hello fluff world\n", new String(out.toByteArray()));
+    }
+
+    @Test
+    public void testMultiLine() {
+        tableRenderer.printLine("hello", "fluff", "world");
+        tableRenderer.printLine("looooooong", "f1", "foobar");
+        tableRenderer.printLine("f2", "shoooooooooooort", "poo");
+        tableRenderer.render(out);
+        assertEquals("hello      fluff            world\n" +
+                     "looooooong f1               foobar\n" +
+                     "f2         shoooooooooooort poo\n", new String(out.toByteArray()));
+    }
+
+    @Test
+    public void testMultiLineContinuous() {
+        tableRenderer.printLine("hello", "fluff", "world");
+        tableRenderer.printLine("looooooong", "f1", "foobar");
+        tableRenderer.printLine("f2", "shoooooooooooort", "poo");
+        tableRenderer.render(out);
+        assertEquals("hello      fluff            world\n" +
+                     "looooooong f1               foobar\n" +
+                     "f2         shoooooooooooort poo\n", new String(out.toByteArray()));
+        tableRenderer.printLine("f3", "foobar", "poo");
+        tableRenderer.render(out);
+        assertEquals("hello      fluff            world\n" +
+                     "looooooong f1               foobar\n" +
+                     "f2         shoooooooooooort poo\n" +
+                     "f3         foobar           poo\n", new String(out.toByteArray()));
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testInvalidLine() {
+        tableRenderer.printLine("hello", "fluff", "world", "boom");
+    }
+}
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/TableRenderer.java	Thu Jun 21 17:38:44 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/*
- * 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.tools.cli;
-
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.List;
-
-class TableRenderer {
-
-    private List<String[]> lines;
-    private int[] maxColumnWidths;
-    private int lastPrintedLine = -1;
-
-    private int numColumns;
-
-    TableRenderer(int numColumns) {
-        this.numColumns = numColumns;
-        lines = new ArrayList<>();
-        maxColumnWidths = new int[numColumns];
-    }
-
-    void printLine(String... line) {
-        if (line.length != numColumns) {
-            throw new IllegalArgumentException("Invalid number of columns: " + line.length + ", expected: " + numColumns);
-        }
-        lines.add(line);
-        for (int i = 0; i < numColumns; i++) {
-            maxColumnWidths[i] = Math.max(maxColumnWidths[i], line[i].length());
-        }
-    }
-
-    void render(OutputStream os) {
-        PrintStream out = new PrintStream(os);
-        render(out);
-    }
-
-    void render(PrintStream out) {
-        for (int i = lastPrintedLine + 1; i < lines.size(); i++) {
-            String[] line = lines.get(i);
-            renderLine(out, line);
-            lastPrintedLine = i;
-        }
-    }
-
-    private void renderLine(PrintStream out, String[] line) {
-        for (int i = 0; i < numColumns; i++) {
-            out.print(line[i]);
-            padOrNewline(out, line, i);
-            
-        }
-    }
-
-    private void padOrNewline(PrintStream out, String[] line, int i) {
-        if (i < numColumns - 1) {
-            int pad = maxColumnWidths[i] - line[i].length() + 1;
-            fillSpaces(out, pad);
-        } else {
-            out.println();
-        }
-    }
-
-    private void fillSpaces(PrintStream out, int pad) {
-        for (int i = 0; i < pad; i++) {
-            out.print(" ");
-        }
-    }
-
-}
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/VMInfoCommand.java	Thu Jun 21 17:38:44 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/VMInfoCommand.java	Thu Jun 21 17:41:40 2012 +0200
@@ -45,6 +45,7 @@
 import com.redhat.thermostat.common.cli.Command;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.TableRenderer;
 import com.redhat.thermostat.common.dao.DAOException;
 import com.redhat.thermostat.common.dao.DAOFactory;
 import com.redhat.thermostat.common.dao.HostRef;
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/VMListFormatter.java	Thu Jun 21 17:38:44 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/VMListFormatter.java	Thu Jun 21 17:41:40 2012 +0200
@@ -38,6 +38,7 @@
 
 import java.io.PrintStream;
 
+import com.redhat.thermostat.common.cli.TableRenderer;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.model.VmInfo;
 
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/VMStatPrinter.java	Thu Jun 21 17:38:44 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/VMStatPrinter.java	Thu Jun 21 17:41:40 2012 +0200
@@ -44,6 +44,7 @@
 import java.util.Iterator;
 import java.util.List;
 
+import com.redhat.thermostat.common.cli.TableRenderer;
 import com.redhat.thermostat.common.dao.VmCpuStatDAO;
 import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
 import com.redhat.thermostat.common.dao.VmRef;
--- a/tools/src/test/java/com/redhat/thermostat/tools/cli/TableRendererTest.java	Thu Jun 21 17:38:44 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/*
- * 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.tools.cli;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.ByteArrayOutputStream;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class TableRendererTest {
-
-    private TableRenderer tableRenderer;
-    private ByteArrayOutputStream out;
-
-    @Before
-    public void setUp() {
-        tableRenderer = new TableRenderer(3);
-        out = new ByteArrayOutputStream();
-    }
-
-    @After
-    public void tearDown() {
-        out = null;
-        tableRenderer = null;
-    }
-
-    @Test
-    public void testSingleLine() {
-        tableRenderer.printLine("hello", "fluff", "world");
-        tableRenderer.render(out);
-        assertEquals("hello fluff world\n", new String(out.toByteArray()));
-    }
-
-    @Test
-    public void testMultiLine() {
-        tableRenderer.printLine("hello", "fluff", "world");
-        tableRenderer.printLine("looooooong", "f1", "foobar");
-        tableRenderer.printLine("f2", "shoooooooooooort", "poo");
-        tableRenderer.render(out);
-        assertEquals("hello      fluff            world\n" +
-                     "looooooong f1               foobar\n" +
-                     "f2         shoooooooooooort poo\n", new String(out.toByteArray()));
-    }
-
-    @Test
-    public void testMultiLineContinuous() {
-        tableRenderer.printLine("hello", "fluff", "world");
-        tableRenderer.printLine("looooooong", "f1", "foobar");
-        tableRenderer.printLine("f2", "shoooooooooooort", "poo");
-        tableRenderer.render(out);
-        assertEquals("hello      fluff            world\n" +
-                     "looooooong f1               foobar\n" +
-                     "f2         shoooooooooooort poo\n", new String(out.toByteArray()));
-        tableRenderer.printLine("f3", "foobar", "poo");
-        tableRenderer.render(out);
-        assertEquals("hello      fluff            world\n" +
-                     "looooooong f1               foobar\n" +
-                     "f2         shoooooooooooort poo\n" +
-                     "f3         foobar           poo\n", new String(out.toByteArray()));
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testInvalidLine() {
-        tableRenderer.printLine("hello", "fluff", "world", "boom");
-    }
-}