You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2013/12/11 13:24:36 UTC

svn commit: r1550118 - in /karaf/trunk/shell/console/src/main/java/org/apache: felix/gogo/commands/ felix/gogo/commands/basic/ karaf/shell/console/completer/

Author: gnodet
Date: Wed Dec 11 12:24:36 2013
New Revision: 1550118

URL: http://svn.apache.org/r1550118
Log:
[KARAF-2307] Try to make old commands work with completion

Added:
    karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CommandWithAction.java
    karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CompleterValues.java
    karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/OldArgumentCompleter.java
Modified:
    karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/basic/AbstractCommand.java
    karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java

Added: karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CommandWithAction.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CommandWithAction.java?rev=1550118&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CommandWithAction.java (added)
+++ karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CommandWithAction.java Wed Dec 11 12:24:36 2013
@@ -0,0 +1,32 @@
+/*
+ * 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.gogo.commands;
+
+import org.apache.felix.service.command.Function;
+
+@Deprecated
+public interface CommandWithAction extends Function {
+
+    Class<? extends Action> getActionClass();
+
+    Action createNewAction();
+
+    void releaseAction(Action action) throws Exception;
+
+}

Added: karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CompleterValues.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CompleterValues.java?rev=1550118&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CompleterValues.java (added)
+++ karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/CompleterValues.java Wed Dec 11 12:24:36 2013
@@ -0,0 +1,37 @@
+/*
+ * 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.gogo.commands;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Represents a method which can return a List or Array of values used for a
+ * {@link org.apache.karaf.shell.console.Completer}
+ * which is associated with the index of an
+ * {@link org.apache.karaf.shell.commands.Argument}
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.FIELD})
+public @interface CompleterValues
+{
+    int index() default 0;
+}

Modified: karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/basic/AbstractCommand.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/basic/AbstractCommand.java?rev=1550118&r1=1550117&r2=1550118&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/basic/AbstractCommand.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/commands/basic/AbstractCommand.java Wed Dec 11 12:24:36 2013
@@ -21,11 +21,12 @@ package org.apache.felix.gogo.commands.b
 import java.util.List;
 
 import org.apache.felix.gogo.commands.Action;
+import org.apache.felix.gogo.commands.CommandWithAction;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Function;
 
 @Deprecated
-public abstract class AbstractCommand implements Function {
+public abstract class AbstractCommand implements Function, CommandWithAction {
 
     public Object execute(CommandSession session, List<Object> arguments) throws Exception {
         Action action = createNewAction();

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java?rev=1550118&r1=1550117&r2=1550118&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/CommandsCompleter.java Wed Dec 11 12:24:36 2013
@@ -140,6 +140,39 @@ public class CommandsCompleter implement
                         }
                     }
                 }
+                if (function instanceof org.apache.felix.gogo.commands.CommandWithAction) {
+                    if (completion.equalsIgnoreCase("GLOBAL") ||
+                            (completion.equalsIgnoreCase("FIRST") && (subshell == null || subshell.length() == 0))) {
+                        try {
+                            completers.add(new OldArgumentCompleter(session, (org.apache.felix.gogo.commands.CommandWithAction) function, command));
+                        } catch (Throwable t) {
+                            LOGGER.debug("Unable to create completers for command '{}'", command, t);
+                        }
+                    } else {
+                        if (command.startsWith(subshell)) {
+
+                            if (subshell.length() > 1 && command.length() > subshell.length()) {
+                                command = command.substring(subshell.length() + 1);
+                            }
+
+                            if (completion.equalsIgnoreCase("SUBSHELL")) {
+                                // filter on subshell
+                                // as the completion mode is set to SUBSHELL, we complete only with the commands local
+                                // to the current subshell
+                                if (command.contains(":")) {
+                                    int index = command.indexOf(':');
+                                    command = command.substring(0, index);
+                                }
+                                command.trim();
+                            }
+                            try {
+                                completers.add(new OldArgumentCompleter(session, (org.apache.felix.gogo.commands.CommandWithAction) function, command));
+                            } catch (Throwable t) {
+                                LOGGER.debug("Unable to create completers for command '{}'", command, t);
+                            }
+                        }
+                    }
+                }
                 commands.add(command);
             }
         }

Added: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/OldArgumentCompleter.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/OldArgumentCompleter.java?rev=1550118&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/OldArgumentCompleter.java (added)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/completer/OldArgumentCompleter.java Wed Dec 11 12:24:36 2013
@@ -0,0 +1,441 @@
+/*
+ * 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.karaf.shell.console.completer;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.gogo.commands.Action;
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.CommandWithAction;
+import org.apache.felix.gogo.commands.CompleterValues;
+import org.apache.felix.gogo.commands.Option;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.console.CommandSessionHolder;
+import org.apache.karaf.shell.console.CompletableFunction;
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.NameScoping;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OldArgumentCompleter implements Completer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(OldArgumentCompleter.class);
+
+    public static final String ARGUMENTS_LIST = "ARGUMENTS_LIST";
+
+    final Completer commandCompleter;
+    final Completer optionsCompleter;
+    final List<Completer> argsCompleters;
+    final Map<String, Completer> optionalCompleters;
+    final CommandWithAction function;
+    final Map<Option, Field> fields = new HashMap<Option, Field>();
+    final Map<String, Option> options = new HashMap<String, Option>();
+    final Map<Integer, Field> arguments = new HashMap<Integer, Field>();
+    boolean strict = true;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public OldArgumentCompleter(CommandSession session, CommandWithAction function, String command) {
+        this.function = function;
+        // Command name completer
+        commandCompleter = new StringsCompleter(getNames(session, command));
+        // Build options completer
+        for (Class<?> type = function.getActionClass(); type != null; type = type.getSuperclass()) {
+            for (Field field : type.getDeclaredFields()) {
+                Option option = field.getAnnotation(Option.class);
+                if (option != null) {
+                    fields.put(option, field);
+                    options.put(option.name(), option);
+                    String[] aliases = option.aliases();
+                    if (aliases != null) {
+                        for (String alias : aliases) {
+                            options.put(alias, option);
+                        }
+                    }
+                }
+                Argument argument = field.getAnnotation(Argument.class);
+                if (argument != null) {
+                    Integer key = argument.index();
+                    if (arguments.containsKey(key)) {
+                        LOGGER.warn("Duplicate @Argument annotations on class " + type.getName() + " for index: " + key + " see: " + field);
+                    } else {
+                        arguments.put(key, field);
+                    }
+                }
+            }
+        }
+//        options.put(HelpOption.HELP.name(), HelpOption.HELP);
+        optionsCompleter = new StringsCompleter(options.keySet());
+        // Build arguments completers
+        argsCompleters = new ArrayList<Completer>();
+
+        if (function instanceof CompletableFunction) {
+            optionalCompleters = ((CompletableFunction) function).getOptionalCompleters();
+            List<Completer> fcl = ((CompletableFunction) function).getCompleters();
+            if (fcl != null) {
+                for (Completer c : fcl) {
+                    argsCompleters.add(c == null ? NullCompleter.INSTANCE : c);
+                }
+            } else {
+                argsCompleters.add(NullCompleter.INSTANCE);
+            }
+        } else {
+            optionalCompleters = new HashMap<String, Completer>();
+            final Map<Integer, Method> methods = new HashMap<Integer, Method>();
+            for (Class<?> type = function.getActionClass(); type != null; type = type.getSuperclass()) {
+                for (Method method : type.getDeclaredMethods()) {
+                    CompleterValues completerMethod = method.getAnnotation(CompleterValues.class);
+                    if (completerMethod != null) {
+                        int index = completerMethod.index();
+                        Integer key = index;
+                        if (index >= arguments.size() || index < 0) {
+                            LOGGER.warn("Index out of range on @CompleterValues on class " + type.getName() + " for index: " + key + " see: " + method);
+                        }
+                        if (methods.containsKey(key)) {
+                            LOGGER.warn("Duplicate @CompleterMethod annotations on class " + type.getName() + " for index: " + key + " see: " + method);
+                        } else {
+                            methods.put(key, method);
+                        }
+                    }
+                }
+            }
+            for (int i = 0, size = arguments.size(); i < size; i++) {
+                Completer argCompleter = NullCompleter.INSTANCE;
+                Method method = methods.get(i);
+                if (method != null) {
+                    // lets invoke the method
+                    Action action = function.createNewAction();
+                    try {
+                        Object value = method.invoke(action);
+                        if (value instanceof String[]) {
+                            argCompleter = new StringsCompleter((String[]) value);
+                        } else if (value instanceof Collection) {
+                            argCompleter = new StringsCompleter((Collection<String>) value);
+                        } else {
+                            LOGGER.warn("Could not use value " + value + " as set of completions!");
+                        }
+                    } catch (IllegalAccessException e) {
+                        LOGGER.warn("Could not invoke @CompleterMethod on " + function + ". " + e, e);
+                    } catch (InvocationTargetException e) {
+                        Throwable target = e.getTargetException();
+                        if (target == null) {
+                            target = e;
+                        }
+                        LOGGER.warn("Could not invoke @CompleterMethod on " + function + ". " + target, target);
+                    } finally {
+                        try {
+                            function.releaseAction(action);
+                        } catch (Exception e) {
+                            LOGGER.warn("Failed to release action: " + action + ". " + e, e);
+                        }
+                    }
+                } else {
+                    Field field = arguments.get(i);
+                    Class<?> type = field.getType();
+                    if (type.isAssignableFrom(File.class)) {
+                        argCompleter = new FileCompleter(session);
+                    } else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
+                        argCompleter = new StringsCompleter(new String[] {"false", "true"}, false);
+                    } else if (type.isAssignableFrom(Enum.class)) {
+                        Set<String> values = new HashSet<String>();
+                        for (Object o : EnumSet.allOf((Class<Enum>) type)) {
+                            values.add(o.toString());
+                        }
+                        argCompleter = new StringsCompleter(values, false);
+                    } else {
+                        // TODO any other completers we can add?
+                    }
+                }
+                argsCompleters.add(argCompleter);
+            }
+        }
+    }
+
+    private String[] getNames(CommandSession session, String scopedCommand) {
+        String command = NameScoping.getCommandNameWithoutGlobalPrefix(session, scopedCommand);
+        String[] s = command.split(":");
+        if (s.length == 1) {
+            return s;
+        } else {
+            return new String[] { command, s[1] };
+        }
+    }
+
+    /**
+     *  If true, a completion at argument index N will only succeed
+     *  if all the completions from 0-(N-1) also succeed.
+     */
+    public void setStrict(final boolean strict) {
+        this.strict = strict;
+    }
+
+    /**
+     *  Returns whether a completion at argument index N will succees
+     *  if all the completions from arguments 0-(N-1) also succeed.
+     */
+    public boolean getStrict() {
+        return this.strict;
+    }
+
+    public int complete(final String buffer, final int cursor,
+                        final List<String> candidates) {
+        ArgumentList list = delimit(buffer, cursor);
+        int argpos = list.getArgumentPosition();
+        int argIndex = list.getCursorArgumentIndex();
+
+        //Store the argument list so that it can be used by completers.
+        CommandSession commandSession = CommandSessionHolder.getSession();
+        if(commandSession != null) {
+            commandSession.put(ARGUMENTS_LIST,list);
+        }
+
+        Completer comp = null;
+        String[] args = list.getArguments();
+        int index = 0;
+        // First argument is command name
+        if (index < argIndex) {
+            // Verify command name
+            if (!verifyCompleter(commandCompleter, args[index])) {
+                return -1;
+            }
+            index++;
+        } else {
+            comp = commandCompleter;
+        }
+        // Now, check options
+        if (comp == null) {
+            while (index < argIndex && args[index].startsWith("-")) {
+                if (!verifyCompleter(optionsCompleter, args[index])) {
+                    return -1;
+                }
+                Option option = options.get(args[index]);
+                if (option == null) {
+                    return -1;
+                }
+                Field field = fields.get(option);
+                if (field != null && field.getType() != boolean.class && field.getType() != Boolean.class) {
+                    if (++index == argIndex) {
+                        comp = NullCompleter.INSTANCE;
+                    }
+                }
+                index++;
+            }
+            if (comp == null && index >= argIndex && index < args.length && args[index].startsWith("-")) {
+                comp = optionsCompleter;
+            }
+        }
+        //Now check for if last Option has a completer
+        int lastAgurmentIndex = argIndex - 1;
+        if (lastAgurmentIndex >= 1) {
+            Option lastOption = options.get(args[lastAgurmentIndex]);
+            if (lastOption != null) {
+
+                Field lastField = fields.get(lastOption);
+                if (lastField != null && lastField.getType() != boolean.class && lastField.getType() != Boolean.class) {
+                    Option option = lastField.getAnnotation(Option.class);
+                    if (option != null) {
+                        Completer optionValueCompleter = null;
+                        String name = option.name();
+                        if (optionalCompleters != null && name != null) {
+                            optionValueCompleter = optionalCompleters.get(name);
+                            if (optionValueCompleter == null) {
+                                String[] aliases = option.aliases();
+                                if (aliases.length > 0) {
+                                    for (int i = 0; i < aliases.length && optionValueCompleter == null; i++) {
+                                        optionValueCompleter = optionalCompleters.get(option.aliases()[i]);
+                                    }
+                                }
+                            }
+                        }
+                        if(optionValueCompleter != null) {
+                            comp = optionValueCompleter;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Check arguments
+        if (comp == null) {
+            int indexArg = 0;
+            while (index < argIndex) {
+                Completer sub = argsCompleters.get(indexArg >= argsCompleters.size() ? argsCompleters.size() - 1 : indexArg);
+                if (!verifyCompleter(sub, args[index])) {
+                    return -1;
+                }
+                index++;
+                indexArg++;
+            }
+            comp = argsCompleters.get(indexArg >= argsCompleters.size() ? argsCompleters.size() - 1 : indexArg);
+        }
+
+        int ret = comp.complete(list.getCursorArgument(), argpos, candidates);
+
+        if (ret == -1) {
+            return -1;
+        }
+
+        int pos = ret + (list.getBufferPosition() - argpos);
+
+        /**
+         *  Special case: when completing in the middle of a line, and the
+         *  area under the cursor is a delimiter, then trim any delimiters
+         *  from the candidates, since we do not need to have an extra
+         *  delimiter.
+         *
+         *  E.g., if we have a completion for "foo", and we
+         *  enter "f bar" into the buffer, and move to after the "f"
+         *  and hit TAB, we want "foo bar" instead of "foo  bar".
+         */
+
+        if ((buffer != null) && (cursor != buffer.length()) && isDelimiter(buffer, cursor)) {
+            for (int i = 0; i < candidates.size(); i++) {
+                String val = candidates.get(i);
+
+                while ((val.length() > 0)
+                    && isDelimiter(val, val.length() - 1)) {
+                    val = val.substring(0, val.length() - 1);
+                }
+
+                candidates.set(i, val);
+            }
+        }
+
+        return pos;
+    }
+
+    protected boolean verifyCompleter(Completer completer, String argument) {
+        List<String> candidates = new ArrayList<String>();
+        return completer.complete(argument, argument.length(), candidates) != -1 && !candidates.isEmpty();
+    }
+
+    public ArgumentList delimit(final String buffer, final int cursor) {
+        Parser parser = new Parser(buffer, cursor);
+        try {
+            List<List<List<String>>> program = parser.program();
+            List<String> pipe = program.get(parser.c0).get(parser.c1);
+            return new ArgumentList(pipe.toArray(new String[pipe.size()]), parser.c2, parser.c3, cursor);
+        } catch (Throwable t) {
+            return new ArgumentList(new String[] { buffer }, 0, cursor, cursor);
+        }
+    }
+
+    /**
+     *  Returns true if the specified character is a whitespace
+     *  parameter. Check to ensure that the character is not
+     *  escaped and returns true from
+     *  {@link #isDelimiterChar}.
+     *
+     *  @param  buffer the complete command buffer
+     *  @param  pos    the index of the character in the buffer
+     *  @return        true if the character should be a delimiter
+     */
+    public boolean isDelimiter(final String buffer, final int pos) {
+        return !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
+    }
+
+    public boolean isEscaped(final String buffer, final int pos) {
+        return pos > 0 && buffer.charAt(pos) == '\\' && !isEscaped(buffer, pos - 1);
+    }
+
+    /**
+     *  The character is a delimiter if it is whitespace, and the
+     *  preceeding character is not an escape character.
+     */
+    public boolean isDelimiterChar(String buffer, int pos) {
+        return Character.isWhitespace(buffer.charAt(pos));
+    }
+
+    /**
+     *  The result of a delimited buffer.
+     */
+    public static class ArgumentList {
+        private String[] arguments;
+        private int cursorArgumentIndex;
+        private int argumentPosition;
+        private int bufferPosition;
+
+        /**
+         *  @param  arguments           the array of tokens
+         *  @param  cursorArgumentIndex the token index of the cursor
+         *  @param  argumentPosition    the position of the cursor in the
+         *                              current token
+         *  @param  bufferPosition      the position of the cursor in
+         *                              the whole buffer
+         */
+        public ArgumentList(String[] arguments, int cursorArgumentIndex,
+            int argumentPosition, int bufferPosition) {
+            this.arguments = arguments;
+            this.cursorArgumentIndex = cursorArgumentIndex;
+            this.argumentPosition = argumentPosition;
+            this.bufferPosition = bufferPosition;
+        }
+
+        public void setCursorArgumentIndex(int cursorArgumentIndex) {
+            this.cursorArgumentIndex = cursorArgumentIndex;
+        }
+
+        public int getCursorArgumentIndex() {
+            return this.cursorArgumentIndex;
+        }
+
+        public String getCursorArgument() {
+            if ((cursorArgumentIndex < 0)
+                || (cursorArgumentIndex >= arguments.length)) {
+                return null;
+            }
+
+            return arguments[cursorArgumentIndex];
+        }
+
+        public void setArgumentPosition(int argumentPosition) {
+            this.argumentPosition = argumentPosition;
+        }
+
+        public int getArgumentPosition() {
+            return this.argumentPosition;
+        }
+
+        public void setArguments(String[] arguments) {
+            this.arguments = arguments;
+        }
+
+        public String[] getArguments() {
+            return this.arguments;
+        }
+
+        public void setBufferPosition(int bufferPosition) {
+            this.bufferPosition = bufferPosition;
+        }
+
+        public int getBufferPosition() {
+            return this.bufferPosition;
+        }
+    }
+}