changeset 2309:7cb8987aa6bc

Replace CommandInfo.needsFileTabCompletion with CompleterService Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-April/018639.html Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-May/018762.html
author Andrew Azores <aazores@redhat.com>
date Tue, 24 May 2016 09:17:54 -0400
parents ba8428eae57f
children 04c4c802a499
files client/cli/src/main/java/com/redhat/thermostat/client/cli/FileNameArgument.java client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties client/cli/src/test/java/com/redhat/thermostat/client/cli/FileNameArgumentTest.java common/core/src/main/java/com/redhat/thermostat/common/cli/CompleterService.java common/core/src/main/java/com/redhat/thermostat/common/cli/FileNameTabCompleter.java distribution/docs/thermostat-plugin.xsd launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/BasicCommandInfo.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfo.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfo.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/CompoundCommandInfoSource.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/FileNameCompleterService.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/JLineFileNameCompleter.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfiguration.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParser.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginInfoSource.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/TabCompletion.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/BasicCommandInfoTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/FileNameCompleterServiceTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParserTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/ShellCommandTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/TestCommandInfo.java validate-command/command/pom.xml validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/Activator.java validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/LocaleResources.java validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/ValidateCommand.java validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/ValidateCommandCompleterService.java validate-command/command/src/main/resources/com/redhat/thermostat/validate/locale/strings.properties validate-command/command/src/test/java/com/redhat/thermostat/validate/command/internal/ActivatorTest.java validate-command/command/src/test/java/com/redhat/thermostat/validate/command/internal/ValidateCommandCompleterServiceTest.java validate-command/command/src/test/java/com/redhat/thermostat/validate/command/internal/ValidateCommandTest.java validate-command/distribution/thermostat-plugin.xml vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/SaveHeapDumpToFileCommand.java vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/locale/LocaleResources.java vm-heap-analysis/command/src/main/resources/com/redhat/thermostat/vm/heap/analysis/command/locale/strings.properties vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/SaveHeapDumpToFileCommandTest.java vm-heap-analysis/distribution/thermostat-plugin.xml
diffstat 39 files changed, 699 insertions(+), 347 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/FileNameArgument.java	Tue May 24 09:17:54 2016 -0400
@@ -0,0 +1,77 @@
+/*
+ * 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.cli;
+
+import com.redhat.thermostat.client.cli.internal.LocaleResources;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.shared.locale.Translate;
+
+public class FileNameArgument {
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    public static final String ARGUMENT_NAME = "filename";
+
+    private final String fileName;
+
+    private FileNameArgument(Arguments args, boolean isRequired) throws CommandException {
+        this.fileName = args.getArgument(ARGUMENT_NAME);
+        if (isRequired && fileName == null) {
+            throw new CommandException(translator.localize(LocaleResources.FILENAME_REQUIRED));
+        }
+    }
+
+    public static FileNameArgument required(Arguments args) throws CommandException {
+        return new FileNameArgument(args, true);
+    }
+
+    public static FileNameArgument optional(Arguments args) throws CommandException {
+        return new FileNameArgument(args, false);
+    }
+
+    /**
+     * @return The value of filename stored in this object, which may be null.
+     */
+    public String getFileName() {
+        return fileName;
+    }
+
+    public boolean isPresent() {
+        return fileName != null;
+    }
+
+}
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java	Tue May 24 11:09:57 2016 +0200
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java	Tue May 24 09:17:54 2016 -0400
@@ -102,6 +102,7 @@
     AGENTID_REQUIRED,
     ONE_ID_REQUIRED,
     AGENT_INFO_NOT_FOUND,
+    FILENAME_REQUIRED,
     
     PURGING_AGENT_DATA,
     STORAGE_UNAVAILABLE,
--- a/client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties	Tue May 24 11:09:57 2016 +0200
+++ b/client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties	Tue May 24 09:17:54 2016 -0400
@@ -60,6 +60,7 @@
 VMID_REQUIRED = A vmId is required
 AGENTID_REQUIRED = An agentId is required
 ONE_ID_REQUIRED = An agentId or vmId is required
+FILENAME_REQUIRED = A filename is required
 AGENT_INFO_NOT_FOUND = Agent Information could not be retrieved for the {0} {1}
 
 PURGING_AGENT_DATA = Purging data for agent: 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/FileNameArgumentTest.java	Tue May 24 09:17:54 2016 -0400
@@ -0,0 +1,95 @@
+/*
+ * 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.cli;
+
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandException;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class FileNameArgumentTest {
+
+    private static final String FILENAME = "filename";
+    private static final String PATH = "path/to/file";
+    private Arguments args;
+
+    @Before
+    public void setup() {
+        args = mock(Arguments.class);
+    }
+
+    @Test(expected = CommandException.class)
+    public void testRequiredArgumentNotPresent() throws CommandException {
+        when(args.hasArgument(FILENAME)).thenReturn(false);
+        when(args.getArgument(FILENAME)).thenReturn(null);
+        FileNameArgument.required(args);
+    }
+
+    @Test
+    public void testRequiredArgumentPresent() throws CommandException {
+        when(args.hasArgument(FILENAME)).thenReturn(true);
+        when(args.getArgument(FILENAME)).thenReturn(PATH);
+        FileNameArgument argument = FileNameArgument.required(args);
+        assertThat(argument.isPresent(), is(true));
+        assertThat(argument.getFileName(), is(PATH));
+    }
+
+    @Test
+    public void testOptionalArgumentNotPresent() throws CommandException {
+        when(args.hasArgument(FILENAME)).thenReturn(false);
+        when(args.getArgument(FILENAME)).thenReturn(null);
+        FileNameArgument argument = FileNameArgument.optional(args);
+        assertThat(argument.isPresent(), is(false));
+        assertThat(argument.getFileName(), is(equalTo(null)));
+    }
+
+    @Test
+    public void testOptionalArgumentPresent() throws CommandException {
+        when(args.hasArgument(FILENAME)).thenReturn(true);
+        when(args.getArgument(FILENAME)).thenReturn(PATH);
+        FileNameArgument argument = FileNameArgument.optional(args);
+        assertThat(argument.isPresent(), is(true));
+        assertThat(argument.getFileName(), is(PATH));
+    }
+
+}
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/CompleterService.java	Tue May 24 11:09:57 2016 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/CompleterService.java	Tue May 24 09:17:54 2016 -0400
@@ -47,6 +47,11 @@
  * implementation so that "-f TAB" can produce a list of possible fooIds. This implementation would return a singleton
  * Set of "foo-command" for {@link CompleterService#getCommands()}, and a Map from a CliCommandOption with short-opt "-f"/
  * long-opt "--fooId" to the relevant {@link TabCompleter} instance for {@link CompleterService#getOptionCompleters()}.
+ *
+ * There are several built-in completions which Thermostat provides. If your command uses a vmId, then declaring a
+ * -v/--vmId option in your thermostat-plugin.xml will give you automagic vmId completions in Thermostat shell.
+ * Likewise, -a/--agentId, -d/--dbUrl, and -f/--filename completions are provided if you simply include these options
+ * in your XML.
  */
 @ExtensionPoint
 public interface CompleterService {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/FileNameTabCompleter.java	Tue May 24 09:17:54 2016 -0400
@@ -0,0 +1,55 @@
+/*
+ * 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.common.cli;
+
+import com.redhat.thermostat.annotations.Service;
+
+/**
+ * A stub interface for marking a TabCompleter which specifically provides file name completions.
+ * This is intended to be exported as an OSGi service. If you are writing a {@link CompleterService}
+ * which provides file name completion, you may use this interface as a dependency and use the
+ * Thermostat-provided file name completions this way.
+ *
+ * However, if it is applicable, you may alternatively simply use the convention of naming your
+ * filename option argument -f/--filename (ex. foo-command -f /path/to/file, or
+ * foo-command --filename /path/to/file). If you define your file name completion argument to match
+ * this naming convention exactly in your thermostat-plugin.xml, then the filename completions will
+ * be taken care of automatically by Thermostat.
+ */
+@Service
+public interface FileNameTabCompleter extends TabCompleter {
+}
--- a/distribution/docs/thermostat-plugin.xsd	Tue May 24 11:09:57 2016 +0200
+++ b/distribution/docs/thermostat-plugin.xsd	Tue May 24 09:17:54 2016 -0400
@@ -36,13 +36,6 @@
 
 <xs:element name="required" type="xs:boolean"/>
 
-<xs:element name="add-file-completion" type="xs:boolean">
-  <xs:annotation>
-    <xs:documentation>Determines whether a command gets tab completion added for files.
-      That is, in addition to command option completion.</xs:documentation>
-  </xs:annotation>
-</xs:element>
-
 <xs:element name="id" type="xs:string"/>
 
 <xs:element name="configuration" type="xs:string"/>
@@ -104,7 +97,6 @@
       <xs:element ref="options" minOccurs="0" maxOccurs="1"/>
       <xs:element ref="environments"/>
       <xs:element ref="bundles"/>
-      <xs:element ref="add-file-completion" minOccurs="0" maxOccurs="1"/>
     </xs:sequence>
   </xs:complexType>
 </xs:element> 
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Tue May 24 09:17:54 2016 -0400
@@ -47,6 +47,7 @@
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 import com.redhat.thermostat.common.cli.CompleterService;
+import com.redhat.thermostat.common.cli.FileNameTabCompleter;
 import com.redhat.thermostat.common.config.ClientPreferences;
 import com.redhat.thermostat.common.config.experimental.ConfigurationInfoSource;
 import com.redhat.thermostat.launcher.BundleManager;
@@ -339,11 +340,15 @@
 
         LogLevelCompleterService logLevelCompleterService = new LogLevelCompleterService();
         context.registerService(CompleterService.class.getName(), logLevelCompleterService, null);
+        FileNameCompleterService fileNameCompleterService = new FileNameCompleterService();
+        context.registerService(CompleterService.class.getName(), fileNameCompleterService, null);
 
         context.registerService(CompleterService.class.getName(), vmIdCompleterService, null);
         context.registerService(CompleterService.class.getName(), agentIdCompleterService, null);
         context.registerService(CompleterService.class.getName(), pingCommandCompleterService, null);
         context.registerService(CompleterService.class.getName(), dbUrlCompleterService, null);
+
+        context.registerService(FileNameTabCompleter.class.getName(), new JLineFileNameCompleter(), null);
     }
 
     @Override
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BasicCommandInfo.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BasicCommandInfo.java	Tue May 24 09:17:54 2016 -0400
@@ -52,9 +52,8 @@
     private final Options options;
     private final Set<Environment> environments;
     private final List<BundleInformation> bundles;
-    private final boolean fileTabCompletionNeeded;
 
-    public BasicCommandInfo(String name, String summary, String description, String usage, Options options, Set<Environment> environments, List<BundleInformation> bundles, boolean fileTabCompletionNeeded) {
+    public BasicCommandInfo(String name, String summary, String description, String usage, Options options, Set<Environment> environments, List<BundleInformation> bundles) {
         this.name = name;
         this.summary = summary;
         this.description = description;
@@ -62,7 +61,6 @@
         this.options = options;
         this.environments = environments;
         this.bundles = bundles;
-        this.fileTabCompletionNeeded = fileTabCompletionNeeded;
     }
 
     @Override
@@ -91,11 +89,6 @@
     }
 
     @Override
-    public boolean needsFileTabCompletions() {
-        return fileTabCompletionNeeded;
-    }
-
-    @Override
     public Set<Environment> getEnvironments() {
         return environments;
     }
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfo.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfo.java	Tue May 24 09:17:54 2016 -0400
@@ -63,7 +63,6 @@
     private static final String PROPERTY_USAGE = "usage";
     private static final String PROPERTY_OPTIONS = "options";
     private static final String PROPERTY_ENVIRONMENTS = "environments";
-    private static final String PROPERTY_FILE_COMPLETION = "add-file-completion";
 
     private static final String PROP_SHORTOPT = ".short";
     private static final String PROP_LONGOPT = ".long";
@@ -75,7 +74,6 @@
     private Options options;
     private EnumSet<Environment> environment;
     private List<BundleInformation> dependencies;
-    private boolean fileTabCompletionNeeded;
 
     BuiltInCommandInfo(String commandName, Properties properties) {
         options = new Options();
@@ -94,8 +92,6 @@
                 learnOptions((String) entry.getValue(), properties);
             } else if (key.equals(PROPERTY_ENVIRONMENTS)) {
                 environment = parseEnvironment(properties.getProperty(key));
-            } else if (key.equals(PROPERTY_FILE_COMPLETION)) {
-                fileTabCompletionNeeded = Boolean.parseBoolean(properties.getProperty(key));
             }
         }
     }
@@ -400,9 +396,5 @@
         return dependencies;
     }
 
-    @Override
-    public boolean needsFileTabCompletions() {
-        return fileTabCompletionNeeded;
-    }
 }
 
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfo.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfo.java	Tue May 24 09:17:54 2016 -0400
@@ -80,11 +80,6 @@
      */
     public Options getOptions();
 
-    /**
-     * Returns whether the command will get tab completions for files
-     */
-    public boolean needsFileTabCompletions();
-
     List<BundleInformation> getBundles();
 
 }
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CompoundCommandInfoSource.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CompoundCommandInfoSource.java	Tue May 24 09:17:54 2016 -0400
@@ -138,10 +138,8 @@
         List<BundleInformation> bundles = new ArrayList<>();
         bundles.addAll(info1.getBundles());
         bundles.addAll(info2.getBundles());
-        boolean fileTabCompletionNeeded = info1.needsFileTabCompletions() || info2.needsFileTabCompletions();
 
-
-        return new BasicCommandInfo(name, summary, description, usage, options, environment, bundles, fileTabCompletionNeeded);
+        return new BasicCommandInfo(name, summary, description, usage, options, environment, bundles);
     }
 
     private <T> T selectBest(T first, T second) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/FileNameCompleterService.java	Tue May 24 09:17:54 2016 -0400
@@ -0,0 +1,63 @@
+/*
+ * 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.launcher.internal;
+
+import com.redhat.thermostat.common.cli.AbstractCompleterService;
+import com.redhat.thermostat.common.cli.CliCommandOption;
+import com.redhat.thermostat.common.cli.TabCompleter;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class FileNameCompleterService extends AbstractCompleterService {
+
+    public static final CliCommandOption FILENAME_OPTION = new CliCommandOption("f", "filename", true, "path/to/local/file", false);
+
+    @Override
+    public Set<String> getCommands() {
+        return TabCompletion.ALL_COMMANDS_COMPLETER;
+    }
+
+    @Override
+    public Map<CliCommandOption, ? extends TabCompleter> getOptionCompleters() {
+        CliCommandOption option = FILENAME_OPTION;
+        TabCompleter completer = new JLineFileNameCompleter();
+
+        return Collections.singletonMap(option, completer);
+    }
+}
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/JLineFileNameCompleter.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/JLineFileNameCompleter.java	Tue May 24 09:17:54 2016 -0400
@@ -36,9 +36,10 @@
 
 package com.redhat.thermostat.launcher.internal;
 
+import com.redhat.thermostat.common.cli.FileNameTabCompleter;
 import jline.console.completer.FileNameCompleter;
 
-public class JLineFileNameCompleter extends JLineCompleterWrapper {
+public class JLineFileNameCompleter extends JLineCompleterWrapper implements FileNameTabCompleter {
 
     public JLineFileNameCompleter() {
         super(new FileNameCompleter());
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfiguration.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfiguration.java	Tue May 24 09:17:54 2016 -0400
@@ -120,13 +120,11 @@
         private final Options options;
         private final Set<Environment> environment;
         private final List<BundleInformation> bundles;
-        private final boolean fileTabCompletionNeeded;
 
         public NewCommand(String name, String usage, String summary, String description,
                 List<String> positionalArguments, Options options,
                 Set<Environment> environment,
-                List<BundleInformation> bundles,
-                boolean fileTabCompletionNeeded) {
+                List<BundleInformation> bundles) {
             this.commandName = name;
             this.usage = usage;
             this.summary = summary;
@@ -135,7 +133,6 @@
             this.options = options;
             this.environment = environment;
             this.bundles = bundles;
-            this.fileTabCompletionNeeded = fileTabCompletionNeeded;
         }
 
         public String getCommandName() {
@@ -178,9 +175,6 @@
             return Collections.unmodifiableList(bundles);
         }
 
-        public boolean needsFileTabCompletions() {
-            return fileTabCompletionNeeded;
-        }
     }
 
     public static class PluginID {
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParser.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParser.java	Tue May 24 09:17:54 2016 -0400
@@ -359,7 +359,6 @@
         Options options = new Options();
         Set<Environment> availableInEnvironments = EnumSet.noneOf(Environment.class);
         List<BundleInformation> bundles = new ArrayList<>();
-        boolean fileTabCompletionNeeded = false;
 
         NodeList nodes = commandNode.getChildNodes();
         for (int i = 0; i < nodes.getLength(); i++) {
@@ -380,8 +379,6 @@
                 availableInEnvironments = parseEnvironment(pluginName, name, node);
             } else if (node.getNodeName().equals("bundles")) {
                 bundles.addAll(parseBundles(pluginName, name, node));
-            } else if (node.getNodeName().equals("add-file-completion")) {
-                fileTabCompletionNeeded = Boolean.parseBoolean(node.getTextContent().trim());
             }
         }
 
@@ -394,7 +391,7 @@
                     "name='" + name + "', summary='" + summary + ", description='" + description + "', options='" + options + "'");
             return null;
         } else {
-            return new NewCommand(name, usage, summary, description, arguments, options, availableInEnvironments, bundles, fileTabCompletionNeeded);
+            return new NewCommand(name, usage, summary, description, arguments, options, availableInEnvironments, bundles);
         }
     }
 
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginInfoSource.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginInfoSource.java	Tue May 24 09:17:54 2016 -0400
@@ -178,8 +178,8 @@
                     usage,
                     command.getOptions(),
                     command.getEnvironments(),
-                    command.getBundles(),
-                    command.needsFileTabCompletions());
+                    command.getBundles()
+            );
 
             allNewCommands.put(commandName, info);
         }
@@ -201,8 +201,8 @@
                         old.getUsage(),
                         old.getOptions(),
                         old.getEnvironments(),
-                        updatedBundles,
-                        old.needsFileTabCompletions());
+                        updatedBundles
+                );
                 allNewCommands.put(entry.getKey(), updated);
                 iter.remove();
             }
@@ -232,7 +232,7 @@
     }
 
     private BasicCommandInfo createCommandInfo(String name, List<BundleInformation> bundles) {
-        return new BasicCommandInfo(name, null, null, null, null, null, bundles, false);
+        return new BasicCommandInfo(name, null, null, null, null, null, bundles);
     }
 
     public Map<String, String> getConfiguration(String pluginID, String fileName) throws IOException {
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/TabCompletion.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/TabCompletion.java	Tue May 24 09:17:54 2016 -0400
@@ -36,7 +36,6 @@
 
 package com.redhat.thermostat.launcher.internal;
 
-import com.redhat.thermostat.common.cli.Arguments;
 import com.redhat.thermostat.common.cli.CliCommandOption;
 import com.redhat.thermostat.common.cli.CompleterService;
 import com.redhat.thermostat.common.cli.TabCompleter;
@@ -115,9 +114,6 @@
         if (ALL_COMMANDS_COMPLETER == service.getCommands()) {
             globalCompleterServices.remove(service);
         }
-        if (commandMap.isEmpty()) {
-            return;
-        }
         for (String commandName : service.getCommands()) {
             TreeCompleter.Node command = commandMap.get(commandName);
             if (command != null) {
@@ -157,11 +153,6 @@
                     setupDefaultCompletion(command, option);
                 }
 
-                if (info.needsFileTabCompletions()) {
-                    TreeCompleter.Node files = new TreeCompleter.Node("fileName", new JLineFileNameCompleter());
-                    files.setRestartNode(command);
-                    command.addBranch(files);
-                }
                 treeCompleter.addBranch(command);
 
                 for (CompleterService service : globalCompleterServices) {
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BasicCommandInfoTest.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BasicCommandInfoTest.java	Tue May 24 09:17:54 2016 -0400
@@ -59,9 +59,8 @@
         final Options OPTIONS = new Options();
         final Set<Environment> ENVIRONMENT = EnumSet.noneOf(Environment.class);
         final List<BundleInformation> BUNDLES = Collections.emptyList();
-        final boolean ADD_FILE_COMPLETION = true;
 
-        BasicCommandInfo info = new BasicCommandInfo(NAME, SUMMARY, DESCRIPTION, USAGE, OPTIONS, ENVIRONMENT, BUNDLES, ADD_FILE_COMPLETION);
+        BasicCommandInfo info = new BasicCommandInfo(NAME, SUMMARY, DESCRIPTION, USAGE, OPTIONS, ENVIRONMENT, BUNDLES);
 
         assertEquals(NAME, info.getName());
         assertEquals(SUMMARY, info.getSummary());
@@ -69,7 +68,6 @@
         assertEquals(USAGE, info.getUsage());
         assertEquals(OPTIONS, info.getOptions());
         assertEquals(BUNDLES, info.getBundles());
-        assertEquals(ADD_FILE_COMPLETION, info.needsFileTabCompletions());
 
         assertEquals(String.format("%s (summary='%s', description='%s', dependencies='%s')", NAME, SUMMARY, DESCRIPTION, BUNDLES.toString()),
                 info.toString());
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoTest.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoTest.java	Tue May 24 09:17:54 2016 -0400
@@ -312,15 +312,5 @@
         assertTrue(commandEnv.contains(Environment.SHELL));
     }
 
-    @Test
-    public void verifyFileUsage() {
-        Properties props = new Properties();
-        String name = "name";
-        String usage = "true";
-        props.put("add-file-completion", usage);
-        BuiltInCommandInfo info = new BuiltInCommandInfo(name, props);
-
-        assertTrue(info.needsFileTabCompletions());
-    }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/FileNameCompleterServiceTest.java	Tue May 24 09:17:54 2016 -0400
@@ -0,0 +1,90 @@
+/*
+ * 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.launcher.internal;
+
+import com.redhat.thermostat.common.cli.CliCommandOption;
+import com.redhat.thermostat.common.cli.TabCompleter;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+public class FileNameCompleterServiceTest {
+
+    private FileNameCompleterService completerService;
+
+    @Before
+    public void setup() {
+        completerService = new FileNameCompleterService();
+    }
+
+    @Test
+    public void testProvidesAllCommandsCompletion() {
+        assertThat(completerService.getCommands(), is(equalTo(TabCompletion.ALL_COMMANDS_COMPLETER)));
+    }
+
+    @Test
+    public void testProvidesOnlyOneCompletion() {
+        Map<CliCommandOption, ? extends TabCompleter> map = completerService.getOptionCompleters();
+        assertThat(map.size(), is(1));
+    }
+
+    @Test
+    public void testProvidesCompletionForFileNameArguments() {
+        Map<CliCommandOption, ? extends TabCompleter> map = completerService.getOptionCompleters();
+        assertThat(map.keySet(), is(equalTo(Collections.singleton(FileNameCompleterService.FILENAME_OPTION))));
+    }
+
+    @Test
+    public void testFileNameCompleterIsNotNull() {
+        Map<CliCommandOption, ? extends TabCompleter> map = completerService.getOptionCompleters();
+        assertThat(map.get(FileNameCompleterService.FILENAME_OPTION), is(not(equalTo(null))));
+    }
+
+    @Test
+    public void testFileNameOptionArgumentName() {
+        assertThat(FileNameCompleterService.FILENAME_OPTION.getLongOpt(), is("filename"));
+        assertThat(FileNameCompleterService.FILENAME_OPTION.getOpt(), is("f"));
+    }
+
+}
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParserTest.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParserTest.java	Tue May 24 09:17:54 2016 -0400
@@ -586,7 +586,6 @@
                 "        <environment>shell</environment>" +
                 "        <environment>cli</environment>" +
                 "      </environments>" +
-                "      <add-file-completion>true</add-file-completion>" +
                 "    </command>\n" +
                 "  </commands>\n" +
                 "</plugin>";
@@ -601,7 +600,6 @@
 
         NewCommand command = newCommands.get(0);
         assertEquals("test", command.getCommandName());
-        assertTrue(command.needsFileTabCompletions());
     }
 
     @Test
@@ -617,7 +615,6 @@
                 "        <environment>shell</environment>" +
                 "        <environment>cli</environment>" +
                 "      </environments>" +
-                "      <add-file-completion>false</add-file-completion>" +
                 "    </command>\n" +
                 "  </commands>\n" +
                 "</plugin>";
@@ -632,7 +629,6 @@
 
         NewCommand command = newCommands.get(0);
         assertEquals("test", command.getCommandName());
-        assertFalse(command.needsFileTabCompletions());
     }
 
     @Test
@@ -648,7 +644,6 @@
                 "        <environment>shell</environment>" +
                 "        <environment>cli</environment>" +
                 "      </environments>" +
-                "      <add-file-completion>invalid</add-file-completion>" +
                 "    </command>\n" +
                 "  </commands>\n" +
                 "</plugin>";
@@ -663,7 +658,6 @@
 
         NewCommand command = newCommands.get(0);
         assertEquals("test", command.getCommandName());
-        assertFalse(command.needsFileTabCompletions());
     }
 
 }
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ShellCommandTest.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ShellCommandTest.java	Tue May 24 09:17:54 2016 -0400
@@ -43,7 +43,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -92,7 +91,6 @@
     private Version version;
     private ConfigurationInfoSource config;
     private CommandInfoSource infos;
-    private File dir;
     private ClientPreferences prefs;
     private TabCompletion tabCompletion;
 
@@ -111,10 +109,6 @@
         cmd = new ShellCommand(bundleContext, version, historyProvider, config, prefs);
         cmd.setTabCompletion(tabCompletion);
         setupCommandInfoSource();
-
-        dir = new File(System.getProperty("java.io.tmpdir") + File.separator + "shellcommand");
-        dir.deleteOnExit();
-        dir.mkdirs();
     }
 
     @After
@@ -534,166 +528,6 @@
         assertEquals("", ctxFactory.getError());
     }
 
-    @Test
-    public void testFilesTabComplete() throws CommandException, IOException {
-        ServiceReference ref = mock(ServiceReference.class);
-        when(bundleContext.getServiceReference(Launcher.class.getName())).thenReturn(ref);
-        Launcher launcher = mock(Launcher.class);
-        when(bundleContext.getService(ref)).thenReturn(launcher);
-
-        String filename = "testFilesTabComplete";
-        createTempFile(filename);
-        createTempFile(filename+"12345678");
-
-        TestCommandContextFactory ctxFactory = new TestCommandContextFactory(bundleContext);
-        ctxFactory.setInput("validate " + dir.getAbsolutePath() + File.separator + "testFil\t\nexit\n");
-        Arguments args = new SimpleArguments();
-        CommandContext ctx = ctxFactory.createContext(args);
-        cmd.run(ctx);
-
-        String tabOutput = getTabOutput(getOutputWithoutIntro(ctxFactory));
-        assertTrue(tabOutput.contains(filename));
-        assertEquals("", ctxFactory.getError());
-    }
-
-    @Test
-    public void testFilesTabCompleteAfterOptions() throws CommandException, IOException {
-        ServiceReference ref = mock(ServiceReference.class);
-        when(bundleContext.getServiceReference(Launcher.class.getName())).thenReturn(ref);
-        Launcher launcher = mock(Launcher.class);
-        when(bundleContext.getService(ref)).thenReturn(launcher);
-
-        String filename = "testFilesTabCompleteAfterOptions";
-        createTempFile(filename);
-        createTempFile(filename + "12345678");
-
-        TestCommandContextFactory ctxFactory = new TestCommandContextFactory(bundleContext);
-        ctxFactory.setInput("validate --fake-option -f " + dir.getAbsolutePath() + File.separator + "testFil\t\nexit\n");
-        Arguments args = new SimpleArguments();
-        CommandContext ctx = ctxFactory.createContext(args);
-        cmd.run(ctx);
-
-        String tabOutput = getTabOutput(getOutputWithoutIntro(ctxFactory));
-        assertTrue(tabOutput.contains(filename));
-        assertEquals("", ctxFactory.getError());
-    }
-
-    @Test
-    public void testFilesDoNotTabCompleteWithoutCommand() throws CommandException, IOException {
-        ServiceReference ref = mock(ServiceReference.class);
-        when(bundleContext.getServiceReference(Launcher.class.getName())).thenReturn(ref);
-        Launcher launcher = mock(Launcher.class);
-        when(bundleContext.getService(ref)).thenReturn(launcher);
-
-        String filename = "testFilesDoNotTabCompleteWithoutCommand";
-        createTempFile(filename);
-        createTempFile(filename + "12345678");
-
-        TestCommandContextFactory ctxFactory = new TestCommandContextFactory(bundleContext);
-        ctxFactory.setInput(dir.getAbsolutePath() + File.separator + "testFil\t\nexit\n");
-        Arguments args = new SimpleArguments();
-        CommandContext ctx = ctxFactory.createContext(args);
-        cmd.run(ctx);
-
-        String tabOutput = getTabOutput(getOutputWithoutIntro(ctxFactory));
-        assertFalse(tabOutput.contains(filename));
-        assertEquals("", tabOutput);
-        assertEquals("", ctxFactory.getError());
-    }
-
-    @Test
-    public void testFileTabCompletesInline() throws CommandException, IOException {
-        ServiceReference ref = mock(ServiceReference.class);
-        when(bundleContext.getServiceReference(Launcher.class.getName())).thenReturn(ref);
-        Launcher launcher = mock(Launcher.class);
-        when(bundleContext.getService(ref)).thenReturn(launcher);
-
-        File tempDir = makeTempDir("testFileTabCompletesInline");
-
-        String filename = "testFileTabCompletesInline";
-        createTempFile(filename, tempDir);
-
-        TestCommandContextFactory ctxFactory = new TestCommandContextFactory(bundleContext);
-        String input = "validate " + tempDir.getAbsolutePath() + File.separator + "testFil";
-        ctxFactory.setInput(input + "\t\nexit\n");
-        Arguments args = new SimpleArguments();
-        CommandContext ctx = ctxFactory.createContext(args);
-        cmd.run(ctx);
-
-        String inline = getTabbedInline(ctxFactory, input);
-        String tabOutput = getTabOutput(getOutputWithoutIntro(ctxFactory));
-
-        setupCommandInfoSource();
-        assertEquals(0, tabOutput.length());
-        assertTrue(inline.endsWith(filename));
-        assertEquals("", ctxFactory.getError());
-    }
-
-    @Test
-    public void testFilesDoNotTabCompleteInlineFullCommand() throws CommandException, IOException {
-        ServiceReference ref = mock(ServiceReference.class);
-        when(bundleContext.getServiceReference(Launcher.class.getName())).thenReturn(ref);
-        Launcher launcher = mock(Launcher.class);
-        when(bundleContext.getService(ref)).thenReturn(launcher);
-
-        File tempDir = makeTempDir("testNotFullCommand");
-
-        String filename = "testFileTabCompletesInline";
-        String similarName = "testFileTabNumber";
-        createTempFile(filename, tempDir);
-        createTempFile(similarName + "1234567", tempDir);
-        createTempFile(similarName + "987654321", tempDir);
-        createTempFile(similarName + "456123", tempDir);
-
-        TestCommandContextFactory ctxFactory = new TestCommandContextFactory(bundleContext);
-        String input = "validate " + tempDir.getAbsolutePath() + File.separator + "testFil";
-        ctxFactory.setInput(input + "\t\nexit\n");
-        Arguments args = new SimpleArguments();
-        CommandContext ctx = ctxFactory.createContext(args);
-        cmd.run(ctx);
-
-        String inline = getTabbedInline(ctxFactory, input);
-        String tabOutput = getTabOutput(getOutputWithoutIntro(ctxFactory));
-
-        assertTrue(tabOutput.contains(filename));
-        assertFalse(inline.contains(filename));
-        assertFalse(inline.contains(similarName));
-        assertTrue(inline.endsWith("testFileTab"));
-        assertEquals("", ctxFactory.getError());
-    }
-
-    @Test
-    public void testFilesTabCompleteCommonPortionInline() throws CommandException, IOException {
-        ServiceReference ref = mock(ServiceReference.class);
-        when(bundleContext.getServiceReference(Launcher.class.getName())).thenReturn(ref);
-        Launcher launcher = mock(Launcher.class);
-        when(bundleContext.getService(ref)).thenReturn(launcher);
-
-        File tempDir = makeTempDir("testFilesTabCompleteCommonPortionInline");
-
-        String filename = "testFileTabCompletesInline";
-        String commonPortion = "testFileTab";
-        createTempFile(filename,tempDir);
-        createTempFile(commonPortion + "1234567", tempDir);
-        createTempFile(commonPortion + "987654321", tempDir);
-        createTempFile(commonPortion + "456123", tempDir);
-
-        TestCommandContextFactory ctxFactory = new TestCommandContextFactory(bundleContext);
-        String input = "validate " + tempDir.getAbsolutePath() + File.separator + "testFil";
-        ctxFactory.setInput(input + "\t\nexit\n");
-        Arguments args = new SimpleArguments();
-        CommandContext ctx = ctxFactory.createContext(args);
-        cmd.run(ctx);
-
-        String inline = getTabbedInline(ctxFactory, input);
-        String tabOutput = getTabOutput(getOutputWithoutIntro(ctxFactory));
-
-        assertTrue(tabOutput.contains(filename));
-        assertFalse(inline.contains(filename));
-        assertTrue(inline.endsWith(commonPortion));
-        assertEquals("", ctxFactory.getError());
-    }
-
     private String getOutputWithoutIntro(final TestCommandContextFactory ctxFactory) {
         String[] allOutput = makeNewlinesConsistent(ctxFactory.getOutput()).split("\n");
         String[] outputWithoutIntro = Arrays.copyOfRange(allOutput, 2, allOutput.length);
@@ -728,34 +562,13 @@
         return input.replace("\r\n", "\n");
     }
 
-    private void createTempFile(String name) throws IOException {
-        File file = new File(dir, name);
-        file.deleteOnExit();
-        file.createNewFile();
-    }
-
-    private void createTempFile(String name, File dir) throws IOException {
-        File file = new File(dir, name);
-        file.deleteOnExit();
-        file.createNewFile();
-    }
-
-    private File makeTempDir(String name) {
-        File tempDir = new File(System.getProperty("java.io.tmpdir") + File.separator + "sc-" + name);
-        tempDir.deleteOnExit();
-        tempDir.mkdirs();
-        return tempDir;
-    }
-
     private void setupCommandInfoSource() {
-
         Collection<CommandInfo> infoList = new ArrayList<>();
 
         CommandInfo info1 = mock(CommandInfo.class);
         when(info1.getName()).thenReturn("test1");
         when(info1.getDescription()).thenReturn("test command 1");
         when(info1.getEnvironments()).thenReturn(EnumSet.of(Environment.CLI, Environment.SHELL));
-        when(info1.needsFileTabCompletions()).thenReturn(false);
 
         ArrayList<Option> optionsList1 = new ArrayList<>();
         Option option1 = mock(Option.class);
@@ -782,7 +595,6 @@
         when(info2.getName()).thenReturn("test2longname");
         when(info2.getDescription()).thenReturn("test command 2");
         when(info2.getEnvironments()).thenReturn(EnumSet.of(Environment.CLI, Environment.SHELL));
-        when(info2.needsFileTabCompletions()).thenReturn(false);
 
         ArrayList<Option> optionsList2 = new ArrayList<>();
         Option option4 = mock(Option.class);
@@ -809,7 +621,6 @@
         when(info3.getName()).thenReturn("validate");
         when(info3.getDescription()).thenReturn("mock validate command");
         when(info3.getEnvironments()).thenReturn(EnumSet.of(Environment.CLI, Environment.SHELL));
-        when(info3.needsFileTabCompletions()).thenReturn(true);
 
         ArrayList<Option> optionsList3 = new ArrayList<>();
         Option option7 = mock(Option.class);
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/TestCommandInfo.java	Tue May 24 11:09:57 2016 +0200
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/TestCommandInfo.java	Tue May 24 09:17:54 2016 -0400
@@ -54,7 +54,6 @@
 
     private Options options = new Options();
     private Set<Environment> environments;
-    private boolean fileTabCompletionNeeded;
 
     public TestCommandInfo(String name) {
         this.name = name;
@@ -97,15 +96,6 @@
         return options;
     }
 
-    @Override
-    public boolean needsFileTabCompletions() {
-        return fileTabCompletionNeeded;
-    }
-
-    public void setFileTabCompletionNeeded(boolean fileTabCompletionNeeded) {
-        this.fileTabCompletionNeeded = fileTabCompletionNeeded;
-    }
-
     public void addOptions(Option... options) {
         for (Option option : options) {
             this.options.addOption(option);
--- a/validate-command/command/pom.xml	Tue May 24 11:09:57 2016 +0200
+++ b/validate-command/command/pom.xml	Tue May 24 09:17:54 2016 -0400
@@ -94,6 +94,11 @@
       <artifactId>thermostat-common-core</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
--- a/validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/Activator.java	Tue May 24 11:09:57 2016 +0200
+++ b/validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/Activator.java	Tue May 24 09:17:54 2016 -0400
@@ -38,26 +38,66 @@
 
 import java.util.Hashtable;
 
+import com.redhat.thermostat.common.cli.CompleterService;
+import com.redhat.thermostat.common.cli.FileNameTabCompleter;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.common.cli.Command;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
 public class Activator implements BundleActivator {
 
-    private ServiceRegistration registration;
+    private ServiceRegistration commandRegistration;
+    private ValidateCommandCompleterService completerService;
+    private ServiceTracker fileNameCompleterTracker;
+    private ServiceRegistration completerRegistration;
 
     @Override
+    @SuppressWarnings("unchecked")
     public void start(final BundleContext context) throws Exception {
-        Hashtable<String,String> properties = new Hashtable<>();
+        Hashtable<String, String> properties = new Hashtable<>();
         properties.put(Command.NAME, "validate");
-        registration = context.registerService(Command.class.getName(), new ValidateCommand(), properties);
+
+        commandRegistration = context.registerService(Command.class.getName(), new ValidateCommand(), properties);
+
+        completerService = new ValidateCommandCompleterService();
+
+        fileNameCompleterTracker = new ServiceTracker(context, FileNameTabCompleter.class, new ServiceTrackerCustomizer() {
+            @Override
+            public Object addingService(ServiceReference serviceReference) {
+                FileNameTabCompleter tabCompleter = (FileNameTabCompleter) context.getService(serviceReference);
+                completerService.setFileNameTabCompleter(tabCompleter);
+                completerRegistration = context.registerService(CompleterService.class.getName(), completerService, null);
+                return context.getService(serviceReference);
+            }
+
+            @Override
+            public void modifiedService(ServiceReference serviceReference, Object o) {
+            }
+
+            @Override
+            public void removedService(ServiceReference serviceReference, Object o) {
+                completerService.setFileNameTabCompleter(null);
+            }
+        });
+        fileNameCompleterTracker.open();
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        registration.unregister();
+        if (commandRegistration != null) {
+            commandRegistration.unregister();
+        }
+        if (completerRegistration != null) {
+            completerRegistration.unregister();
+        }
+        if (fileNameCompleterTracker != null) {
+            fileNameCompleterTracker.close();
+        }
     }
 }
 
--- a/validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/LocaleResources.java	Tue May 24 11:09:57 2016 +0200
+++ b/validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/LocaleResources.java	Tue May 24 09:17:54 2016 -0400
@@ -43,9 +43,9 @@
     
     VALIDATION_SUCCESSFUL,
     VALIDATION_FAILED, 
-    
-    FILE_REQUIRED,
-    FILE_NOT_FOUND, 
+
+    ONE_ARGUMENT_EXPECTED,
+    FILE_NOT_FOUND,
     ;
 
     static final String RESOURCE_BUNDLE =
--- a/validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/ValidateCommand.java	Tue May 24 11:09:57 2016 +0200
+++ b/validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/ValidateCommand.java	Tue May 24 09:17:54 2016 -0400
@@ -39,7 +39,6 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 
-import com.redhat.thermostat.common.cli.Arguments;
 import com.redhat.thermostat.common.cli.Command;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
@@ -51,40 +50,39 @@
 
 public class ValidateCommand implements Command {
 
+    public static final String COMMAND_NAME = "validate";
+
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-    private PluginValidator validator;
+    private final PluginValidator validator;
+
+    public ValidateCommand() {
+        this.validator = new PluginValidator();
+    }
 
     public void run(CommandContext ctx) throws CommandException {
-        Arguments args = ctx.getArguments();
-        validator = new PluginValidator();
+        if (ctx.getArguments().getNonOptionArguments().size() != 1) {
+            throw new CommandException(translator.localize(LocaleResources.ONE_ARGUMENT_EXPECTED));
+        }
+        String filename = ctx.getArguments().getNonOptionArguments().get(0);
         File pluginFile = null;
-        String argString = null;
-        
-            try {
-                argString = args.getNonOptionArguments().get(0);
-                pluginFile = new File(argString);
-                validator.validate(pluginFile);
-                ctx.getConsole().getOutput().println(translator.localize(
-                                LocaleResources.VALIDATION_SUCCESSFUL, pluginFile.getAbsolutePath())
-                                .getContents());
-                
-            } catch (PluginConfigurationValidatorException e) {
-                ValidationErrorsFormatter formatter = new ValidationErrorsFormatter();
-                ctx.getConsole().getError().println(formatter.format(e.getAllErrors()));
-                ctx.getConsole().getError().println(translator.localize(
-                                LocaleResources.VALIDATION_FAILED, pluginFile.getAbsolutePath())
-                               .getContents());
-                
-            } catch (IndexOutOfBoundsException | NullPointerException e) {
-                throw new CommandLineArgumentParseException
-                (translator.localize(
-                        LocaleResources.FILE_REQUIRED));
-                
-            } catch (FileNotFoundException fnfe) {
-                throw new CommandLineArgumentParseException
-                (translator.localize(
-                        LocaleResources.FILE_NOT_FOUND, pluginFile.getAbsolutePath()));
-            }        
+        try {
+            pluginFile = new File(filename);
+            validator.validate(pluginFile);
+            ctx.getConsole().getOutput().println(translator.localize(
+                            LocaleResources.VALIDATION_SUCCESSFUL, pluginFile.getAbsolutePath())
+                            .getContents());
+
+        } catch (PluginConfigurationValidatorException e) {
+            ValidationErrorsFormatter formatter = new ValidationErrorsFormatter();
+            ctx.getConsole().getError().println(formatter.format(e.getAllErrors()));
+            ctx.getConsole().getError().println(translator.localize(
+                            LocaleResources.VALIDATION_FAILED, pluginFile.getAbsolutePath())
+                           .getContents());
+
+        } catch (FileNotFoundException fnfe) {
+            throw new CommandLineArgumentParseException(translator.localize(
+                    LocaleResources.FILE_NOT_FOUND, pluginFile.getAbsolutePath()));
+        }
     }
 
     public boolean isStorageRequired() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/validate-command/command/src/main/java/com/redhat/thermostat/validate/command/internal/ValidateCommandCompleterService.java	Tue May 24 09:17:54 2016 -0400
@@ -0,0 +1,70 @@
+/*
+ * 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.validate.command.internal;
+
+import com.redhat.thermostat.common.cli.AbstractCompleterService;
+import com.redhat.thermostat.common.cli.CliCommandOption;
+import com.redhat.thermostat.common.cli.FileNameTabCompleter;
+import com.redhat.thermostat.common.cli.TabCompleter;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class ValidateCommandCompleterService extends AbstractCompleterService {
+
+    private FileNameTabCompleter tabCompleter;
+
+    @Override
+    public Set<String> getCommands() {
+        return Collections.singleton(ValidateCommand.COMMAND_NAME);
+    }
+
+    @Override
+    public Map<CliCommandOption, ? extends TabCompleter> getOptionCompleters() {
+        if (tabCompleter == null) {
+            return Collections.emptyMap();
+        }
+        CliCommandOption option = CliCommandOption.POSITIONAL_ARG_COMPLETION;
+
+        return Collections.singletonMap(option, tabCompleter);
+    }
+
+    public void setFileNameTabCompleter(FileNameTabCompleter tabCompleter) {
+        this.tabCompleter = tabCompleter;
+    }
+}
--- a/validate-command/command/src/main/resources/com/redhat/thermostat/validate/locale/strings.properties	Tue May 24 11:09:57 2016 +0200
+++ b/validate-command/command/src/main/resources/com/redhat/thermostat/validate/locale/strings.properties	Tue May 24 09:17:54 2016 -0400
@@ -1,5 +1,5 @@
 VALIDATION_SUCCESSFUL = Validation successful for file {0}\n
 VALIDATION_FAILED = Validation failed for file {0}\n
 
-FILE_REQUIRED = Could not parse argument: missing file name
 FILE_NOT_FOUND = File {0} not found. Please check the file name and/or the path
+ONE_ARGUMENT_EXPECTED = One non-option argument expected
\ No newline at end of file
--- a/validate-command/command/src/test/java/com/redhat/thermostat/validate/command/internal/ActivatorTest.java	Tue May 24 11:09:57 2016 +0200
+++ b/validate-command/command/src/test/java/com/redhat/thermostat/validate/command/internal/ActivatorTest.java	Tue May 24 09:17:54 2016 -0400
@@ -37,12 +37,18 @@
 package com.redhat.thermostat.validate.command.internal;
 
 import static com.redhat.thermostat.testutils.Asserts.assertCommandIsRegistered;
+import static com.redhat.thermostat.testutils.Asserts.assertServiceIsRegistered;
 import static org.junit.Assert.*;
 
+import com.redhat.thermostat.common.cli.Command;
+import com.redhat.thermostat.common.cli.CompleterService;
+import com.redhat.thermostat.common.cli.FileNameTabCompleter;
 import org.junit.Test;
 
 import com.redhat.thermostat.testutils.StubBundleContext;
 
+import java.util.List;
+
 public class ActivatorTest {
 
     @Test
@@ -60,5 +66,24 @@
         assertEquals(0, ctx.getAllServices().size());
     }
 
+    @Test
+    public void testValidateCompleterBecomesAvailableWhenFileNameCompleterAppears() throws Exception {
+        StubBundleContext ctx = new StubBundleContext();
+        ctx.registerService(FileNameTabCompleter.class, new StubFileNameTabCompleter(), null);
+        Activator activator = new Activator();
+        activator.start(ctx);
+        assertEquals(3, ctx.getAllServices().size());
+        assertServiceIsRegistered(ctx, FileNameTabCompleter.class, StubFileNameTabCompleter.class);
+        assertServiceIsRegistered(ctx, CompleterService.class, ValidateCommandCompleterService.class);
+        assertServiceIsRegistered(ctx, Command.class, ValidateCommand.class);
+    }
+
+    private static class StubFileNameTabCompleter implements FileNameTabCompleter {
+        @Override
+        public int complete(String buffer, int cursor, List<CharSequence> candidates) {
+            return 0;
+        }
+    }
+
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/validate-command/command/src/test/java/com/redhat/thermostat/validate/command/internal/ValidateCommandCompleterServiceTest.java	Tue May 24 09:17:54 2016 -0400
@@ -0,0 +1,93 @@
+/*
+ * 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.validate.command.internal;
+
+import com.redhat.thermostat.common.cli.CliCommandOption;
+import com.redhat.thermostat.common.cli.FileNameTabCompleter;
+import com.redhat.thermostat.common.cli.TabCompleter;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ValidateCommandCompleterServiceTest {
+
+    private ValidateCommandCompleterService completerService;
+
+    @Before
+    public void setup() {
+        completerService = new ValidateCommandCompleterService();
+        completerService.setFileNameTabCompleter(mock(FileNameTabCompleter.class));
+    }
+
+    @Test
+    public void testOnlyProvidesCompletionForValidateCommand() {
+        assertThat(completerService.getCommands(), is(equalTo(Collections.singleton(ValidateCommand.COMMAND_NAME))));
+    }
+
+    @Test
+    public void testProvidesOnlyOneCompleter() {
+        assertThat(completerService.getOptionCompleters().size(), is(1));
+    }
+
+    @Test
+    public void testProvidesCompletionForPositionalArgument() {
+        Map<CliCommandOption, ? extends TabCompleter> map = completerService.getOptionCompleters();
+        assertThat(map.keySet(), is(equalTo(Collections.singleton(CliCommandOption.POSITIONAL_ARG_COMPLETION))));
+    }
+
+    @Test
+    public void testCompleterIsNotNull() {
+        Map<CliCommandOption, ? extends TabCompleter> map = completerService.getOptionCompleters();
+        assertThat(map.get(CliCommandOption.POSITIONAL_ARG_COMPLETION), is(not(equalTo(null))));
+    }
+
+    @Test
+    public void testProvidesNoCompletionWhenFileNameTabCompleterNotAvailable() {
+        completerService.setFileNameTabCompleter(null);
+        Map<CliCommandOption, ? extends TabCompleter> map = completerService.getOptionCompleters();
+        assertThat(map.isEmpty(), is(true));
+    }
+
+}
--- a/validate-command/command/src/test/java/com/redhat/thermostat/validate/command/internal/ValidateCommandTest.java	Tue May 24 11:09:57 2016 +0200
+++ b/validate-command/command/src/test/java/com/redhat/thermostat/validate/command/internal/ValidateCommandTest.java	Tue May 24 09:17:54 2016 -0400
@@ -38,12 +38,14 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.commons.cli.MissingArgumentException;
@@ -64,13 +66,11 @@
     private Arguments mockArgs;
     private Console console;
     private String fileName;
-    private List<String> list = new ArrayList<>();
     private ByteArrayOutputStream outputBaos, errorBaos;
     private PrintStream output, error;
         
     @Before
     public void setUp() {
-        
         cmd = new ValidateCommand();
         ctxt = mock(CommandContext.class);
         mockArgs = mock(Arguments.class);
@@ -86,15 +86,15 @@
         when(ctxt.getConsole()).thenReturn(console);
         when(console.getError()).thenReturn(error);
         when(console.getOutput()).thenReturn(output);
-        when(mockArgs.getNonOptionArguments()).thenReturn(list);
-        
+        when(mockArgs.hasArgument("filename")).thenReturn(true);
     }
     
     @Test
     public void validateIncorrectFile() throws CommandException, MissingArgumentException {
         fileName = PluginValidator.class.getResource("/incorrectPlugin.xml").getPath().toString();
-        list.add(fileName);
-        
+
+        when(mockArgs.getNonOptionArguments()).thenReturn(Collections.singletonList(fileName));
+
         cmd.run(ctxt);
         String errorOutput = new String(errorBaos.toByteArray());
         String validateOutput = buildErrorMessage();
@@ -106,8 +106,9 @@
     @Test
     public void validateCorrectFile() throws CommandException, MissingArgumentException {
         fileName = PluginValidator.class.getResource("/correctPlugin.xml").getPath().toString();
-        list.add(fileName);
-        
+
+        when(mockArgs.getNonOptionArguments()).thenReturn(Collections.singletonList(fileName));
+
         cmd.run(ctxt);
         
         String expected = "Validation successful for file " + fileName + "\n\n";
@@ -120,8 +121,8 @@
     @Test
     public void validateNonExistingFile() throws CommandException, MissingArgumentException {
         fileName = "/nonExistingFile.xml";
-        list.add(fileName);
-        
+        when(mockArgs.getNonOptionArguments()).thenReturn(Collections.singletonList(fileName));
+
         try {
             cmd.run(ctxt);    
         } catch(CommandLineArgumentParseException clpae) {
@@ -131,10 +132,11 @@
     
     @Test
     public void missingFileAsArgument() throws CommandException, MissingArgumentException {
-        
+        when(mockArgs.getNonOptionArguments()).thenReturn(Collections.<String>emptyList());
         try {
-            cmd.run(ctxt);    
-        } catch(CommandLineArgumentParseException clpae) {
+            cmd.run(ctxt);
+            fail();
+        } catch(CommandException clpae) {
             // pass
         }
     }
--- a/validate-command/distribution/thermostat-plugin.xml	Tue May 24 11:09:57 2016 +0200
+++ b/validate-command/distribution/thermostat-plugin.xml	Tue May 24 09:17:54 2016 -0400
@@ -44,20 +44,17 @@
       <name>validate</name>
       <summary>validate a thermostat plug-in XML file</summary>
       <description>Validate a thermostat plug-in XML file against the standard schema and report any issues.</description>
-      <arguments>
-        <argument>file</argument>
-      </arguments>
       <environments>
         <environment>cli</environment>
         <environment>shell</environment>
       </environments>
       <bundles>
+        <bundle><symbolic-name>com.redhat.thermostat.common.core</symbolic-name><version>${project.version}</version></bundle>
+        <bundle><symbolic-name>com.redhat.thermostat.client.cli</symbolic-name><version>${project.version}</version></bundle>
         <bundle><symbolic-name>com.redhat.thermostat.validate.command</symbolic-name><version>${project.version}</version></bundle>
-        <bundle><symbolic-name>com.redhat.thermostat.common.core</symbolic-name><version>${project.version}</version></bundle>
         <bundle><symbolic-name>com.redhat.thermostat.configuration</symbolic-name><version>${project.version}</version></bundle>
         <bundle><symbolic-name>com.redhat.thermostat.plugin.validator</symbolic-name><version>${project.version}</version></bundle>
       </bundles>
-      <add-file-completion>true</add-file-completion>
     </command>
   </commands>
 </plugin>
--- a/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/SaveHeapDumpToFileCommand.java	Tue May 24 11:09:57 2016 +0200
+++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/SaveHeapDumpToFileCommand.java	Tue May 24 09:17:54 2016 -0400
@@ -44,6 +44,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import com.redhat.thermostat.client.cli.FileNameArgument;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
@@ -66,7 +67,6 @@
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
 
     private static final String HEAP_ID_ARGUMENT = "heapId";
-    private static final String FILE_NAME_ARGUMENT = "file";
 
     private final FileStreamCreator creator;
     private final BundleContext context;
@@ -89,7 +89,6 @@
             run(ctx, heapDAO);
         } finally {
             context.ungetService(ref);
-            heapDAO = null;
         }
     }
 
@@ -99,10 +98,8 @@
         if (heapId == null) {
             throw new CommandLineArgumentParseException(translator.localize(LocaleResources.HEAP_ID_REQUIRED));
         }
-        String filename = args.getArgument(FILE_NAME_ARGUMENT);
-        if (filename == null) {
-            throw new CommandLineArgumentParseException(translator.localize(LocaleResources.FILE_REQUIRED));
-        }
+        FileNameArgument fileNameArgument = FileNameArgument.required(args);
+        String filename = fileNameArgument.getFileName();
 
         HeapInfo heapInfo = heapDAO.getHeapInfo(heapId);
         try (InputStream heapStream = heapDAO.getHeapDumpData(heapInfo)) {
@@ -121,7 +118,7 @@
         }
     }
 
-    private void saveHeapDump(InputStream heapStream, String filename) throws FileNotFoundException, IOException {
+    private void saveHeapDump(InputStream heapStream, String filename) throws IOException {
         try (BufferedInputStream bis = new BufferedInputStream(heapStream);
              BufferedOutputStream bout = new BufferedOutputStream(creator.createOutputStream(filename))) {
             StreamUtils.copyStream(bis, bout);
--- a/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/locale/LocaleResources.java	Tue May 24 11:09:57 2016 +0200
+++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/locale/LocaleResources.java	Tue May 24 09:17:54 2016 -0400
@@ -52,7 +52,6 @@
     HEADER_OBJECT_ID,
     HEADER_OBJECT_TYPE,
     
-    FILE_REQUIRED,
     INVALID_LIMIT,
     HEAP_ID_NOT_FOUND,
     HEAP_ID_REQUIRED,
--- a/vm-heap-analysis/command/src/main/resources/com/redhat/thermostat/vm/heap/analysis/command/locale/strings.properties	Tue May 24 11:09:57 2016 +0200
+++ b/vm-heap-analysis/command/src/main/resources/com/redhat/thermostat/vm/heap/analysis/command/locale/strings.properties	Tue May 24 09:17:54 2016 -0400
@@ -11,7 +11,6 @@
 HEADER_OBJECT_ID = ID
 HEADER_OBJECT_TYPE = TYPE
 
-FILE_REQUIRED = A file name is required
 INVALID_LIMIT = Invalid limit {0}
 HEAP_ID_NOT_FOUND = Heap ID not found: {0}
 HEAP_ID_REQUIRED = Heap ID required
--- a/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/SaveHeapDumpToFileCommandTest.java	Tue May 24 11:09:57 2016 +0200
+++ b/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/SaveHeapDumpToFileCommandTest.java	Tue May 24 09:17:54 2016 -0400
@@ -81,7 +81,7 @@
         SimpleArguments args = new SimpleArguments();
         args.addArgument(Arguments.HOST_ID_ARGUMENT, "host-id");
         args.addArgument(VmArgument.ARGUMENT_NAME, "1");
-        args.addArgument("file", "heap-id-1");
+        args.addArgument("filename", "heap-id-1");
 
         HeapDAO heapDAO = mock(HeapDAO.class);
         context.registerService(HeapDAO.class, heapDAO, null);
@@ -125,7 +125,7 @@
 
         SimpleArguments args = new SimpleArguments();
         args.addArgument("heapId", HEAP_ID);
-        args.addArgument("file", FILE_NAME);
+        args.addArgument("filename", FILE_NAME);
 
         FileStreamCreator creator = mock(FileStreamCreator.class);
         when(creator.createOutputStream(FILE_NAME)).thenReturn(heapDumpStream);
@@ -144,7 +144,7 @@
         args.addArgument(Arguments.HOST_ID_ARGUMENT, "host-id");
         args.addArgument(VmArgument.ARGUMENT_NAME, "1");
         args.addArgument("heapId", "heap-id-1");
-        args.addArgument("file", "heap-id-1");
+        args.addArgument("filename", "heap-id-1");
 
         Command command = new SaveHeapDumpToFileCommand(context, mock(FileStreamCreator.class));
         
--- a/vm-heap-analysis/distribution/thermostat-plugin.xml	Tue May 24 11:09:57 2016 +0200
+++ b/vm-heap-analysis/distribution/thermostat-plugin.xml	Tue May 24 09:17:54 2016 -0400
@@ -350,7 +350,7 @@
           <description>the ID of the heapdump to analyze</description>
         </option>
         <option>
-          <long>file</long>
+          <long>filename</long>
           <short>f</short>
           <argument>filename</argument>
           <required>true</required>
@@ -395,7 +395,6 @@
         <bundle><symbolic-name>${lucene-core.bundle.symbolic-name}</symbolic-name><version>${lucene.osgi-version}</version></bundle>
         <bundle><symbolic-name>${lucene-analysis.bundle.symbolic-name}</symbolic-name><version>${lucene.osgi-version}</version></bundle>
       </bundles>
-      <add-file-completion>true</add-file-completion>
     </command>
     <command>
       <name>show-heap-histogram</name>