FELIX-1749: Improve log:set command with completer and support for lower case

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@824811 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/Level.java b/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/Level.java
new file mode 100644
index 0000000..b3192d1
--- /dev/null
+++ b/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/Level.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.shell.log;
+
+/**
+ * Enumeration of available log levels for the log:set command and
+ * the command completer
+ */
+public enum Level {
+
+    TRACE,
+    DEBUG,
+    INFO,
+    WARN,
+    ERROR,
+    DEFAULT;
+    
+    /**
+     * Convert the list of values into a String array
+     * 
+     * @return all the values as a String array
+     */
+    public static String[] strings() {
+        String[] values = new String[values().length];
+        for (int i = 0 ; i < values.length ; i++) {
+            values[i] = values()[i].name();
+        }
+        return values;
+    }
+    
+    /**
+     * Check if the string value represents the default level
+     * 
+     * @param level the level value
+     * @return <code>true</code> if the value represents the {@link #DEFAULT} level
+     */
+    public static boolean isDefault(String level) {
+        return valueOf(level).equals(DEFAULT);
+    }
+}
diff --git a/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/SetLogLevel.java b/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/SetLogLevel.java
index 6227d60..7a41d4c 100644
--- a/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/SetLogLevel.java
+++ b/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/SetLogLevel.java
@@ -43,27 +43,22 @@
     static final String LOGGER_PREFIX      = "log4j.logger.";
     static final String ROOT_LOGGER        = "ROOT";
 
-    static final String TRACE = "TRACE";
-    static final String DEBUG = "DEBUG";
-    static final String INFO = "INFO";
-    static final String WARN = "WARN";
-    static final String ERROR = "ERROR";
-    static final String DEFAULT = "DEFAULT";
-
     protected Object doExecute() throws Exception {
         if (ROOT_LOGGER.equalsIgnoreCase(this.logger)) {
             this.logger = null;
         }
-        if (!TRACE.equals(level) &&
-                !DEBUG.equals(level) &&
-                !INFO.equals(level) &&
-                !WARN.equals(level) &&
-                !ERROR.equals(level) &&
-                !DEFAULT.equals(level)) {
+        
+        // make sure both uppercase and lowercase levels are supported
+        level = level.toUpperCase();
+        
+        try {
+            Level.valueOf(level);
+        } catch (IllegalArgumentException e) {
             System.err.println("level must be set to TRACE, DEBUG, INFO, WARN or ERROR (or DEFAULT to unset it)");
             return null;
         }
-        if (DEFAULT.equals(level) && logger == null) {
+        
+        if (Level.isDefault(level) && logger == null) {
             System.err.println("Can not unset the ROOT logger");
             return null;
         }
@@ -80,14 +75,14 @@
             prop = LOGGER_PREFIX + logger;
         }
         val = (String) props.get(prop);
-        if (DEFAULT.equals(level)) {
+        if (Level.isDefault(level)) {
             if (val != null) {
                 val = val.trim();
                 int idx = val.indexOf(",");
-                if (idx > 0) {
-                    val = val.substring(idx);
-                } else {
+                if (idx < 0) {
                     val = null;
+                } else {
+                    val = val.substring(idx);
                 }
             }
         } else {
diff --git a/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/completers/LogLevelCompleter.java b/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/completers/LogLevelCompleter.java
new file mode 100644
index 0000000..122d448
--- /dev/null
+++ b/karaf/shell/log/src/main/java/org/apache/felix/karaf/shell/log/completers/LogLevelCompleter.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.shell.log.completers;
+
+import java.util.List;
+
+import org.apache.felix.karaf.shell.console.Completer;
+import org.apache.felix.karaf.shell.console.completer.StringsCompleter;
+import org.apache.felix.karaf.shell.log.Level;
+
+/**
+ * {@link Completer} implementation for completing log levels  
+ */
+public class LogLevelCompleter extends StringsCompleter {
+    
+    public LogLevelCompleter() {
+        super(Level.strings());
+    }
+    
+    @Override @SuppressWarnings("unchecked")
+    public int complete(String buffer, int cursor, List candidates) {
+        if (buffer == null) {
+            return super.complete(null, cursor, candidates);
+        } else {
+            // support completing lower case as well with the toUpperCase() call
+            return super.complete(buffer.toUpperCase(), cursor, candidates);
+        }
+    }
+}
diff --git a/karaf/shell/log/src/main/resources/OSGI-INF/blueprint/shell-log.xml b/karaf/shell/log/src/main/resources/OSGI-INF/blueprint/shell-log.xml
index 01885da..232f365 100644
--- a/karaf/shell/log/src/main/resources/OSGI-INF/blueprint/shell-log.xml
+++ b/karaf/shell/log/src/main/resources/OSGI-INF/blueprint/shell-log.xml
@@ -48,6 +48,10 @@
         </command>
         <command name="log/set">
             <action class="org.apache.felix.karaf.shell.log.SetLogLevel" />
+            <completers>
+            	<ref component-id="logLevelCompleter"/>
+            	<null/>
+            </completers>
         </command>
 
         <alias name="ld" alias="log/d"/>
@@ -61,6 +65,8 @@
     <bean id="events" class="org.apache.felix.karaf.shell.log.LruList">
         <argument value="${size}"/>
     </bean>
+    
+    <bean id="logLevelCompleter" class="org.apache.felix.karaf.shell.log.completers.LogLevelCompleter"/>
 
     <service ref="vmLogAppender" interface="org.ops4j.pax.logging.spi.PaxAppender">
         <service-properties>
diff --git a/karaf/shell/log/src/test/java/org/apache/felix/karaf/shell/log/SetLogLevelTest.java b/karaf/shell/log/src/test/java/org/apache/felix/karaf/shell/log/SetLogLevelTest.java
index 0f2058b..db95924 100644
--- a/karaf/shell/log/src/test/java/org/apache/felix/karaf/shell/log/SetLogLevelTest.java
+++ b/karaf/shell/log/src/test/java/org/apache/felix/karaf/shell/log/SetLogLevelTest.java
@@ -16,7 +16,9 @@
  */
 package org.apache.felix.karaf.shell.log;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.PrintStream;
 import java.util.Hashtable;
 
 import junit.framework.TestCase;
@@ -32,14 +34,18 @@
     
     private static final String ROOT_LOGGER = "log4j.rootLogger";
     private static final String PACKAGE_LOGGER = "log4j.logger.org.apache.karaf.test";
+    private static final PrintStream ORIGINAL_STDERR = System.err;
     
     private SetLogLevel command;
-    private Hashtable properties = new Hashtable();
+    private Hashtable properties;
+    private ByteArrayOutputStream stderr;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        properties.clear();
+        properties = new Hashtable();
+        stderr = new ByteArrayOutputStream();
+        System.setErr(new PrintStream(stderr));
 
         final Configuration configuration = EasyMock.createMock(Configuration.class);
         EasyMock.expect(configuration.getProperties()).andReturn(properties);
@@ -54,6 +60,18 @@
         };
     }
     
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        System.setErr(ORIGINAL_STDERR);
+    }
+    
+    public void testInvalidLogLevel() throws Exception {
+        runCommand("log:set INVALID");
+        assertTrue("Expected an error message on System.err",
+                   stderr.toString().contains("level must be set to"));
+    }
+    
     public void testSetLogLevel() throws Exception {
         runCommand("log:set INFO org.apache.karaf.test");
         
@@ -66,6 +84,18 @@
         assertEquals("INFO", properties.get(ROOT_LOGGER));
     }
     
+    public void testSetLogLevelLowerCase() throws Exception {
+        runCommand("log:set info org.apache.karaf.test");
+        
+        assertEquals("INFO", properties.get(PACKAGE_LOGGER));
+    }
+    
+    public void testSetRootLogLevelLowerCase() throws Exception {
+        runCommand("log:set info");
+        
+        assertEquals("INFO", properties.get(ROOT_LOGGER));
+    }
+    
     public void testChangeLogLevel() throws Exception {
         properties.put(PACKAGE_LOGGER, "DEBUG");
         
@@ -116,6 +146,8 @@
         
         assertEquals("Configuration for root logger should not be removed",
                      "INFO", properties.get(ROOT_LOGGER));
+        assertTrue("Expected an error message on System.err",
+                   stderr.toString().contains("Can not unset the ROOT logger"));
     }
     
     /*
diff --git a/karaf/shell/log/src/test/java/org/apache/felix/karaf/shell/log/completers/LogLevelCompleterTest.java b/karaf/shell/log/src/test/java/org/apache/felix/karaf/shell/log/completers/LogLevelCompleterTest.java
new file mode 100644
index 0000000..a7a1cf5
--- /dev/null
+++ b/karaf/shell/log/src/test/java/org/apache/felix/karaf/shell/log/completers/LogLevelCompleterTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.shell.log.completers;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.felix.karaf.shell.log.Level;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for {@link LogLevelCompleter}
+ */
+public class LogLevelCompleterTest extends TestCase {
+    
+    private final LogLevelCompleter completer = new LogLevelCompleter();
+    
+    public void testComplete() throws Exception {
+        assertCompletions("I", Level.INFO.name());
+        assertCompletions("D", Level.DEBUG.name(), Level.DEFAULT.name());
+    }
+    
+    public void testCompleteLowerCase() throws Exception {
+        assertCompletions("i", Level.INFO.name());
+        assertCompletions("d", Level.DEBUG.name(), Level.DEFAULT.name());
+    }
+    
+    public void testCompleteWithNullBuffer() throws Exception {
+        // an empty buffer should return all available options
+        assertCompletions(null, Level.strings());
+    }
+
+    private void assertCompletions(String buffer, String... results) {
+        List<String> candidates = new LinkedList<String>();
+        assertEquals("Completer should have found a match", 0, completer.complete(buffer, 0, candidates));
+        assertEquals(results.length, candidates.size());
+        for (String result : results) {
+            assertContains(result, candidates);
+        }
+    }
+    
+    private void assertContains(String value, List<String> values) {
+        for (String element : values) {
+            if (value.trim().equals(element.trim())) {
+                return;
+            }
+        }
+        fail("Element " + value + " not found in array");
+    }
+}