You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by cs...@apache.org on 2014/02/24 11:28:06 UTC

[08/11] KARAF-2772 Extracting command-api

http://git-wip-us.apache.org/repos/asf/karaf/blob/1ee78df9/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/BasicSubShell.java
----------------------------------------------------------------------
diff --git a/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/BasicSubShell.java b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/BasicSubShell.java
new file mode 100644
index 0000000..3e40c5a
--- /dev/null
+++ b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/BasicSubShell.java
@@ -0,0 +1,51 @@
+/**
+ *
+ * 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.commands;
+
+import org.apache.karaf.shell.console.SubShell;
+
+public class BasicSubShell implements SubShell {
+    
+    private String name;
+    private String description;
+    private String detailedDescription;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getDetailedDescription() {
+        return detailedDescription;
+    }
+
+    public void setDetailedDescription(String detailedDescription) {
+        this.detailedDescription = detailedDescription;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/1ee78df9/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/BlueprintCommand.java
----------------------------------------------------------------------
diff --git a/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/BlueprintCommand.java b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/BlueprintCommand.java
new file mode 100644
index 0000000..83fa1ec
--- /dev/null
+++ b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/BlueprintCommand.java
@@ -0,0 +1,110 @@
+/**
+ *
+ * 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.commands;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.gogo.commands.Action;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.commands.basic.AbstractCommand;
+import org.apache.karaf.shell.commands.basic.ActionPreparator;
+import org.apache.karaf.shell.commands.basic.DefaultActionPreparator;
+import org.apache.karaf.shell.console.BlueprintContainerAware;
+import org.apache.karaf.shell.console.BundleContextAware;
+import org.apache.karaf.shell.console.CompletableFunction;
+import org.apache.karaf.shell.console.Completer;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.blueprint.container.BlueprintContainer;
+import org.osgi.service.blueprint.container.Converter;
+
+public class
+        BlueprintCommand extends AbstractCommand implements CompletableFunction
+{
+
+    protected BlueprintContainer blueprintContainer;
+    protected Converter blueprintConverter;
+    protected String actionId;
+    protected String shell;
+    protected List<Completer> completers;
+    protected Map<String,Completer> optionalCompleters;
+
+    public void setBlueprintContainer(BlueprintContainer blueprintContainer) {
+        this.blueprintContainer = blueprintContainer;
+    }
+
+    public void setBlueprintConverter(Converter blueprintConverter) {
+        this.blueprintConverter = blueprintConverter;
+    }
+
+    public void setActionId(String actionId) {
+        this.actionId = actionId;
+    }
+    
+    public void setShell(String shell) {
+        this.shell = shell;
+    }
+
+    public List<Completer> getCompleters() {
+        return completers;
+    }
+
+    public void setCompleters(List<Completer> completers) {
+        this.completers = completers;
+    }
+
+    public Map<String, Completer> getOptionalCompleters() {
+        return optionalCompleters;
+    }
+
+    public void setOptionalCompleters(Map<String, Completer> optionalCompleters) {
+        this.optionalCompleters = optionalCompleters;
+    }
+
+    @Override
+    protected ActionPreparator getPreparator() throws Exception {
+        return new BlueprintActionPreparator();
+    }
+
+    protected class BlueprintActionPreparator extends DefaultActionPreparator {
+
+        @Override
+        protected Object convert(Action action, CommandSession commandSession, Object o, Type type) throws Exception {
+            GenericType t = new GenericType(type);
+            if (t.getRawClass() == String.class) {
+                return o != null ? o.toString() : null;
+            }
+            return blueprintConverter.convert(o, t);
+        }
+
+    }
+
+    public Action createNewAction() {
+        Action action = (Action) blueprintContainer.getComponentInstance(actionId);
+        if (action instanceof BlueprintContainerAware) {
+            ((BlueprintContainerAware) action).setBlueprintContainer(blueprintContainer);
+        }
+        if (action instanceof BundleContextAware) {
+            BundleContext context = (BundleContext) blueprintContainer.getComponentInstance("blueprintBundleContext");
+            ((BundleContextAware) action).setBundleContext(context);
+        }
+        return action;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/1ee78df9/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/GenericType.java
----------------------------------------------------------------------
diff --git a/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/GenericType.java b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/GenericType.java
new file mode 100644
index 0000000..faf7c18
--- /dev/null
+++ b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/GenericType.java
@@ -0,0 +1,195 @@
+/**
+ *
+ * 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.commands;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.blueprint.container.ReifiedType;
+
+public class GenericType extends ReifiedType {
+
+	private static final GenericType[] EMPTY = new GenericType[0];
+
+    private static final Map<String, Class> primitiveClasses = new HashMap<String, Class>();
+
+    static {
+        primitiveClasses.put("int", int.class);
+        primitiveClasses.put("short", short.class);
+        primitiveClasses.put("long", long.class);
+        primitiveClasses.put("byte", byte.class);
+        primitiveClasses.put("char", char.class);
+        primitiveClasses.put("float", float.class);
+        primitiveClasses.put("double", double.class);
+        primitiveClasses.put("boolean", boolean.class);
+    }
+
+    private GenericType[] parameters;
+
+	public GenericType(Type type) {
+		this(getConcreteClass(type), parametersOf(type));
+	}
+
+    public GenericType(Class clazz, GenericType... parameters) {
+        super(clazz);
+        this.parameters = parameters;
+    }
+
+    public static GenericType parse(String type, Object loader) throws ClassNotFoundException, IllegalArgumentException {
+        type = type.trim();
+        // Check if this is an array
+        if (type.endsWith("[]")) {
+            GenericType t = parse(type.substring(0, type.length() - 2), loader);
+            return new GenericType(Array.newInstance(t.getRawClass(), 0).getClass(), t);
+        }
+        // Check if this is a generic
+        int genericIndex = type.indexOf('<');
+        if (genericIndex > 0) {
+            if (!type.endsWith(">")) {
+                throw new IllegalArgumentException("Can not load type: " + type);
+            }
+            GenericType base = parse(type.substring(0, genericIndex), loader);
+            String[] params = type.substring(genericIndex + 1, type.length() - 1).split(",");
+            GenericType[] types = new GenericType[params.length];
+            for (int i = 0; i < params.length; i++) {
+                types[i] = parse(params[i], loader);
+            }
+            return new GenericType(base.getRawClass(), types);
+        }
+        // Primitive
+        if (primitiveClasses.containsKey(type)) {
+            return new GenericType(primitiveClasses.get(type));
+        }
+        // Class
+        if (loader instanceof ClassLoader) {
+            return new GenericType(((ClassLoader) loader).loadClass(type));
+        } else if (loader instanceof Bundle) {
+            return new GenericType(((Bundle) loader).loadClass(type));
+        } else {
+            throw new IllegalArgumentException("Unsupported loader: " + loader);
+        }
+    }
+
+    @Override
+    public ReifiedType getActualTypeArgument(int i) {
+        if (parameters.length == 0) {
+            return super.getActualTypeArgument(i);
+        }
+        return parameters[i];
+    }
+
+    @Override
+    public int size() {
+        return parameters.length;
+    }
+
+    @Override
+    public String toString() {
+        Class cl = getRawClass();
+        if (cl.isArray()) {
+            if (parameters.length > 0) {
+                return parameters[0].toString() + "[]";
+            } else {
+                return cl.getComponentType().getName() + "[]";
+            }
+        }
+        if (parameters.length > 0) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(cl.getName());
+            sb.append("<");
+            for (int i = 0; i < parameters.length; i++) {
+                if (i > 0) {
+                    sb.append(",");
+                }
+                sb.append(parameters[i].toString());
+            }
+            sb.append(">");
+            return sb.toString();
+        }
+        return cl.getName();
+    }
+
+    static GenericType[] parametersOf(Type type ) {
+		if ( type instanceof Class ) {
+		    Class clazz = (Class) type;
+		    if (clazz.isArray()) {
+                GenericType t = new GenericType(clazz.getComponentType());
+                if (t.size() > 0) {
+		            return new GenericType[] { t };
+                } else {
+                    return EMPTY;
+                }
+		    } else {
+		        return EMPTY;
+		    }
+		}
+        if ( type instanceof ParameterizedType ) {
+            ParameterizedType pt = (ParameterizedType) type;
+            Type [] parameters = pt.getActualTypeArguments();
+            GenericType[] gts = new GenericType[parameters.length];
+            for ( int i =0; i<gts.length; i++) {
+                gts[i] = new GenericType(parameters[i]);
+            }
+            return gts;
+        }
+        if ( type instanceof GenericArrayType ) {
+            return new GenericType[] { new GenericType(((GenericArrayType) type).getGenericComponentType()) };
+        }
+        throw new IllegalStateException();
+	}
+
+	static Class<?> getConcreteClass(Type type) {
+		Type ntype = collapse(type);
+		if ( ntype instanceof Class )
+			return (Class<?>) ntype;
+
+		if ( ntype instanceof ParameterizedType )
+			return getConcreteClass(collapse(((ParameterizedType)ntype).getRawType()));
+
+		throw new RuntimeException("Unknown type " + type );
+	}
+
+	static Type collapse(Type target) {
+		if (target instanceof Class || target instanceof ParameterizedType ) {
+			return target;
+		} else if (target instanceof TypeVariable) {
+			return collapse(((TypeVariable<?>) target).getBounds()[0]);
+		} else if (target instanceof GenericArrayType) {
+			Type t = collapse(((GenericArrayType) target)
+					.getGenericComponentType());
+			while ( t instanceof ParameterizedType )
+				t = collapse(((ParameterizedType)t).getRawType());
+			return Array.newInstance((Class<?>)t, 0).getClass();
+		} else if (target instanceof WildcardType) {
+			WildcardType wct = (WildcardType) target;
+			if (wct.getLowerBounds().length == 0)
+				return collapse(wct.getUpperBounds()[0]);
+			else
+				return collapse(wct.getLowerBounds()[0]);
+		}
+		throw new RuntimeException("Huh? " + target);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/1ee78df9/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/NamespaceHandler.java
----------------------------------------------------------------------
diff --git a/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/NamespaceHandler.java b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/NamespaceHandler.java
new file mode 100644
index 0000000..6c3a5b3
--- /dev/null
+++ b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/NamespaceHandler.java
@@ -0,0 +1,523 @@
+/*
+ * 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.commands;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.aries.blueprint.ParserContext;
+import org.apache.aries.blueprint.PassThroughMetadata;
+import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
+import org.apache.aries.blueprint.mutable.MutableCollectionMetadata;
+import org.apache.aries.blueprint.mutable.MutableIdRefMetadata;
+import org.apache.aries.blueprint.mutable.MutablePassThroughMetadata;
+import org.apache.aries.blueprint.mutable.MutableRefMetadata;
+import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
+import org.apache.aries.blueprint.mutable.MutableServiceMetadata;
+import org.apache.aries.blueprint.mutable.MutableValueMetadata;
+import org.apache.felix.gogo.commands.Action;
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.SubShellAction;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.inject.Destroy;
+import org.apache.karaf.shell.inject.Init;
+import org.apache.karaf.shell.inject.Reference;
+import org.apache.karaf.shell.inject.Service;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.osgi.service.blueprint.reflect.BeanArgument;
+import org.osgi.service.blueprint.reflect.BeanMetadata;
+import org.osgi.service.blueprint.reflect.BeanProperty;
+import org.osgi.service.blueprint.reflect.ComponentMetadata;
+import org.osgi.service.blueprint.reflect.IdRefMetadata;
+import org.osgi.service.blueprint.reflect.MapMetadata;
+import org.osgi.service.blueprint.reflect.Metadata;
+import org.osgi.service.blueprint.reflect.NullMetadata;
+import org.osgi.service.blueprint.reflect.RefMetadata;
+import org.osgi.service.blueprint.reflect.ReferenceMetadata;
+import org.osgi.service.blueprint.reflect.ServiceMetadata;
+import org.osgi.service.blueprint.reflect.ValueMetadata;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+public class NamespaceHandler implements org.apache.aries.blueprint.NamespaceHandler {
+
+    public static final String ID = "id";
+    public static final String ACTION = "action";
+    public static final String ACTION_ID = "actionId";
+    public static final String COMMAND_BUNDLE = "command-bundle";
+    public static final String SCAN = "scan";
+    public static final String NAME = "name";
+    public static final String COMMAND = "command";
+    public static final String COMPLETERS = "completers";
+    public static final String OPTIONAL_COMPLETERS = "optional-completers";
+    public static final String OPTIONAL_COMPLETERS_PROPERTY = "optionalCompleters";
+    public static final String BEAN = "bean";
+    public static final String REF = "ref";
+    public static final String NULL = "null";
+    public static final String MAP = "map";
+    public static final String BLUEPRINT_CONTAINER = "blueprintContainer";
+    public static final String BLUEPRINT_CONVERTER = "blueprintConverter";
+
+    public static final String SHELL_NAMESPACE_1_0_0 = "http://karaf.apache.org/xmlns/shell/v1.0.0";
+    public static final String SHELL_NAMESPACE_1_1_0 = "http://karaf.apache.org/xmlns/shell/v1.1.0";
+    public static final String SHELL_NAMESPACE_1_2_0 = "http://karaf.apache.org/xmlns/shell/v1.2.0";
+
+    private int nameCounter = 0;
+
+    public URL getSchemaLocation(String namespace) {
+        if(SHELL_NAMESPACE_1_0_0.equals(namespace)) {
+            return getClass().getResource("karaf-shell-1.0.0.xsd");
+        } else if(SHELL_NAMESPACE_1_1_0.equals(namespace)) {
+            return getClass().getResource("karaf-shell-1.1.0.xsd");
+        } else if(SHELL_NAMESPACE_1_2_0.equals(namespace)) {
+            return getClass().getResource("karaf-shell-1.2.0.xsd");
+        }
+        return getClass().getResource("karaf-shell-1.2.0.xsd");
+    }
+
+	public Set<Class> getManagedClasses() {
+		return new HashSet<Class>(Arrays.asList(
+			BlueprintCommand.class
+		));
+	}
+
+    public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
+        throw new ComponentDefinitionException("Bad xml syntax: node decoration is not supported");
+    }
+
+    public Metadata parse(Element element, ParserContext context) {
+        if (nodeNameEquals(element, COMMAND_BUNDLE)) {
+            NamedNodeMap attrs = element.getAttributes();
+            for (int i = 0; i < attrs.getLength(); i++) {
+                Node child = attrs.item(i);
+                if (child instanceof Attr) {
+                    Attr childAttr = (Attr) child;
+                    parseChildAttr(childAttr, context);
+                }
+            }
+            NodeList children = element.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child  = children.item(i);
+                if (child instanceof Element) {
+                    Element childElement = (Element) child;
+                    parseChildElement(childElement, context);
+                }
+            }
+            registerConverters(context);
+            return null;
+        } else {
+            throw new IllegalStateException("Unexpected element " + element.getNodeName());
+        }
+    }
+
+    private void parseChildAttr(Attr attr, ParserContext context) {
+        if (nodeNameEquals(attr, SCAN)) {
+            scan(attr, context);
+        }
+    }
+
+    private void scan(Attr attr, ParserContext context) {
+        try {
+            Bundle bundle = getBundle(context);
+            BundleWiring wiring = bundle.adapt(BundleWiring.class);
+            for (String pkg : attr.getValue().split(" ")) {
+                String name = pkg;
+                int options = BundleWiring.LISTRESOURCES_LOCAL;
+                name = name.replace('.', '/');
+                if (name.endsWith("*")) {
+                    options |= BundleWiring.LISTRESOURCES_RECURSE;
+                    name = name.substring(0, name.length() - 1);
+                }
+                if (!name.startsWith("/")) {
+                    name = "/" + name;
+                }
+                if (name.endsWith("/")) {
+                    name = name.substring(0, name.length() - 1);
+                }
+                Collection<String> classes = wiring.listResources(name, "*.class", options);
+                for (String className : classes) {
+                    className = className.replace('/', '.').replace(".class", "");
+                    inspectClass(context, bundle.loadClass(className));
+                }
+            }
+        } catch (ComponentDefinitionException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ComponentDefinitionException("Unable to scan commands", e);
+        }
+    }
+
+    private void inspectClass(ParserContext context, Class<?> clazz) throws Exception {
+        Service reg = clazz.getAnnotation(Service.class);
+        if (reg == null) {
+            return;
+        }
+        if (Action.class.isAssignableFrom(clazz)) {
+            final Command cmd = clazz.getAnnotation(Command.class);
+            if (cmd == null) {
+                throw new IllegalArgumentException("Command " + clazz.getName() + " is not annotated with @Command");
+            }
+            String scope = cmd.scope();
+            String function = cmd.name();
+            // Create action
+            MutableBeanMetadata action = context.createMetadata(MutableBeanMetadata.class);
+            action.setId(getName());
+            action.setActivation(MutableBeanMetadata.ACTIVATION_LAZY);
+            action.setScope(MutableBeanMetadata.SCOPE_PROTOTYPE);
+            action.setRuntimeClass(clazz);
+            for (Class<?> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
+                for (Field field : cl.getDeclaredFields()) {
+                    if (field.getAnnotation(Reference.class) != null) {
+                        if (field.getType() == BundleContext.class) {
+                            action.addProperty(field.getName(), createRef(context, "blueprintBundleContext"));
+                        } else {
+                            action.addProperty(field.getName(), createRef(context, createServiceRef(context, field.getType()).getId()));
+                        }
+                    }
+                }
+                for (Method method : cl.getDeclaredMethods()) {
+                    if (method.getAnnotation(Init.class) != null) {
+                        if (action.getInitMethod() == null) {
+                            action.setInitMethod(method.getName());
+                        }
+                    }
+                    if (method.getAnnotation(Destroy.class) != null) {
+                        if (action.getDestroyMethod() == null) {
+                            action.setDestroyMethod(method.getName());
+                        }
+                    }
+                }
+            }
+            context.getComponentDefinitionRegistry().registerComponentDefinition(action);
+            // Create command
+            MutableBeanMetadata command = context.createMetadata(MutableBeanMetadata.class);
+            command.setRuntimeClass(BlueprintCommand.class);
+            command.addProperty(BLUEPRINT_CONTAINER, createRef(context, BLUEPRINT_CONTAINER));
+            command.addProperty(BLUEPRINT_CONVERTER, createRef(context, BLUEPRINT_CONVERTER));
+            command.addProperty(ACTION_ID, createIdRef(context, action.getId()));
+            // Create command service
+            MutableServiceMetadata commandService = context.createMetadata(MutableServiceMetadata.class);
+            commandService.setActivation(MutableServiceMetadata.ACTIVATION_LAZY);
+            commandService.setId(getName());
+            commandService.setAutoExport(ServiceMetadata.AUTO_EXPORT_ALL_CLASSES);
+            commandService.setServiceComponent(command);
+            commandService.addServiceProperty(createStringValue(context, "osgi.command.scope"),
+                    createStringValue(context, scope));
+            commandService.addServiceProperty(createStringValue(context, "osgi.command.function"),
+                    createStringValue(context, function));
+            context.getComponentDefinitionRegistry().registerComponentDefinition(commandService);
+
+            // create the sub-shell action
+            createSubShell(context, scope);
+        }
+        if (Completer.class.isAssignableFrom(clazz)) {
+            MutableBeanMetadata completer = context.createMetadata(MutableBeanMetadata.class);
+            completer.setId(getName());
+            completer.setActivation(MutableBeanMetadata.ACTIVATION_LAZY);
+            completer.setScope(MutableBeanMetadata.SCOPE_SINGLETON);
+            completer.setRuntimeClass(clazz);
+            // Create completer
+            for (Class<?> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
+                for (Field field : cl.getDeclaredFields()) {
+                    if (field.getAnnotation(Reference.class) != null) {
+                        if (field.getType() == BundleContext.class) {
+                            completer.addProperty(field.getName(), createRef(context, "blueprintBundleContext"));
+                        } else {
+                            completer.addProperty(field.getName(), createRef(context, createServiceRef(context, field.getType()).getId()));
+                        }
+                    }
+                }
+                for (Method method : cl.getDeclaredMethods()) {
+                    if (method.getAnnotation(Init.class) != null) {
+                        if (completer.getInitMethod() == null) {
+                            completer.setInitMethod(method.getName());
+                        }
+                    }
+                    if (method.getAnnotation(Destroy.class) != null) {
+                        if (completer.getDestroyMethod() == null) {
+                            completer.setDestroyMethod(method.getName());
+                        }
+                    }
+                }
+            }
+            context.getComponentDefinitionRegistry().registerComponentDefinition(completer);
+            // Create completer service
+            MutableServiceMetadata completerService = context.createMetadata(MutableServiceMetadata.class);
+            completerService.setActivation(MutableServiceMetadata.ACTIVATION_LAZY);
+            completerService.setId(getName());
+            completerService.setAutoExport(ServiceMetadata.AUTO_EXPORT_ALL_CLASSES);
+            completerService.setServiceComponent(createRef(context, completer.getId()));
+            context.getComponentDefinitionRegistry().registerComponentDefinition(completerService);
+        }
+    }
+
+    private ComponentMetadata createServiceRef(ParserContext context, Class<?> cls) {
+        String id = ".serviceref." + cls.getName();
+        ComponentMetadata metadata = context.getComponentDefinitionRegistry().getComponentDefinition(id);
+        if (metadata == null) {
+            MutableReferenceMetadata m = context.createMetadata(MutableReferenceMetadata.class);
+            m.setRuntimeInterface(cls);
+            m.setInterface(cls.getName());
+            m.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
+            m.setAvailability(ReferenceMetadata.AVAILABILITY_MANDATORY);
+            m.setId(id);
+            context.getComponentDefinitionRegistry().registerComponentDefinition(m);
+            return m;
+        } else {
+            return metadata;
+        }
+    }
+
+    private Bundle getBundle(ParserContext context) {
+        PassThroughMetadata ptm = (PassThroughMetadata) context.getComponentDefinitionRegistry().getComponentDefinition("blueprintBundle");
+        return (Bundle) ptm.getObject();
+    }
+
+    private void parseChildElement(Element element, ParserContext context) {
+        if (nodeNameEquals(element, COMMAND)) {
+            parseCommand(element, context);
+        }
+    }
+
+    private void registerConverters(ParserContext context) {
+        String converterName = "." + NumberToStringConverter.class.getName();
+        if (!context.getComponentDefinitionRegistry().containsComponentDefinition(converterName)) {
+            MutablePassThroughMetadata cnv = context.createMetadata(MutablePassThroughMetadata.class);
+            cnv.setId(converterName);
+            cnv.setObject(new NumberToStringConverter());
+            context.getComponentDefinitionRegistry().registerTypeConverter(cnv);
+        }
+    }
+
+    private void parseCommand(Element element, ParserContext context) {
+        MutableBeanMetadata command = context.createMetadata(MutableBeanMetadata.class);
+        command.setRuntimeClass(BlueprintCommand.class);
+        command.addProperty(BLUEPRINT_CONTAINER, createRef(context, BLUEPRINT_CONTAINER));
+        command.addProperty(BLUEPRINT_CONVERTER, createRef(context, BLUEPRINT_CONVERTER));
+
+        NodeList children = element.getChildNodes();
+        MutableBeanMetadata action = null;
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child  = children.item(i);
+            if (child instanceof Element) {
+                Element childElement = (Element) child;
+                if (nodeNameEquals(childElement, ACTION)) {
+                    action = parseAction(context, command, childElement);
+                    action.setId(getName());
+                    context.getComponentDefinitionRegistry().registerComponentDefinition(action);
+                    command.addProperty(ACTION_ID, createIdRef(context, action.getId()));
+                } else if (nodeNameEquals(childElement, COMPLETERS)) {
+                    command.addProperty(COMPLETERS, parseCompleters(context, command, childElement));
+                } else if (nodeNameEquals(childElement, OPTIONAL_COMPLETERS)) {
+                    command.addProperty(OPTIONAL_COMPLETERS_PROPERTY, parseOptionalCompleters(context, command, childElement));
+                }
+                else {
+                    throw new ComponentDefinitionException("Bad xml syntax: unknown element '" + childElement.getNodeName() + "'");
+                }
+            }
+        }
+
+        MutableServiceMetadata commandService = context.createMetadata(MutableServiceMetadata.class);
+        commandService.setActivation(MutableServiceMetadata.ACTIVATION_LAZY);
+        commandService.setId(getName());
+        commandService.setAutoExport(ServiceMetadata.AUTO_EXPORT_ALL_CLASSES);
+        commandService.setServiceComponent(command);
+
+        String scope;
+        String function;
+        if (SHELL_NAMESPACE_1_0_0.equals(element.getNamespaceURI())) {
+            String location = element.getAttribute(NAME);
+            location = location.replace('/', ':');
+            if (location.lastIndexOf(':') >= 0) {
+                scope = location.substring(0, location.lastIndexOf(':'));
+                function = location.substring(location.lastIndexOf(':') + 1);
+            } else {
+                scope = "";
+                function = location;
+            }
+        } else {
+            try {
+                Class actionClass = getBundle(context).loadClass(action.getClassName());
+                scope = getScope(actionClass);
+                function = getName(actionClass);
+            } catch (Throwable e) {
+                throw new ComponentDefinitionException("Unable to introspect action " + action.getClassName(), e);
+            }
+        }
+        commandService.addServiceProperty(createStringValue(context, "osgi.command.scope"),
+                createStringValue(context, scope));
+        commandService.addServiceProperty(createStringValue(context, "osgi.command.function"),
+                createStringValue(context, function));
+
+        context.getComponentDefinitionRegistry().registerComponentDefinition(commandService);
+
+        // create the sub-shell action
+        createSubShell(context, scope);
+    }
+
+    private void createSubShell(ParserContext context, String scope) {
+        String subShellName = ".subshell." + scope;
+        if (context.getComponentDefinitionRegistry().containsComponentDefinition(subShellName)) {
+            return;
+        }
+        MutableBeanMetadata subShellAction = context.createMetadata(MutableBeanMetadata.class);
+        subShellAction.setRuntimeClass(SubShellAction.class);
+        subShellAction.setActivation(MutableBeanMetadata.ACTIVATION_LAZY);
+        subShellAction.setScope(MutableBeanMetadata.SCOPE_PROTOTYPE);
+        subShellAction.setId(getName());
+        subShellAction.addProperty("subShell", createStringValue(context, scope));
+        context.getComponentDefinitionRegistry().registerComponentDefinition(subShellAction);
+        // generate the sub-shell command
+        MutableBeanMetadata subShellCommand = context.createMetadata(MutableBeanMetadata.class);
+        subShellCommand.setId(getName());
+        subShellCommand.setRuntimeClass(BlueprintCommand.class);
+        subShellCommand.addProperty(BLUEPRINT_CONTAINER, createRef(context, BLUEPRINT_CONTAINER));
+        subShellCommand.addProperty(BLUEPRINT_CONVERTER, createRef(context, BLUEPRINT_CONVERTER));
+        subShellCommand.addProperty(ACTION_ID, createIdRef(context, subShellAction.getId()));
+        context.getComponentDefinitionRegistry().registerComponentDefinition(subShellCommand);
+        // generate the sub-shell OSGi service
+        MutableServiceMetadata subShellCommandService = context.createMetadata(MutableServiceMetadata.class);
+        subShellCommandService.setActivation(MutableServiceMetadata.ACTIVATION_LAZY);
+        subShellCommandService.setId(subShellName);
+        subShellCommandService.setAutoExport(ServiceMetadata.AUTO_EXPORT_ALL_CLASSES);
+        subShellCommandService.setServiceComponent(subShellCommand);
+        subShellCommandService.addServiceProperty(createStringValue(context, "osgi.command.scope"), createStringValue(context, "*"));
+        subShellCommandService.addServiceProperty(createStringValue(context, "osgi.command.function"), createStringValue(context, scope));
+        context.getComponentDefinitionRegistry().registerComponentDefinition(subShellCommandService);
+    }
+
+    private MutableBeanMetadata getInvocationValue(ParserContext context, String method, String className) {
+        MutableBeanMetadata scope = context.createMetadata(MutableBeanMetadata.class);
+        scope.setRuntimeClass(NamespaceHandler.class);
+        scope.setFactoryMethod(method);
+        scope.addArgument(createStringValue(context, className), null, 0);
+        return scope;
+    }
+
+    private MutableBeanMetadata parseAction(ParserContext context, ComponentMetadata enclosingComponent, Element element) {
+        MutableBeanMetadata action = context.createMetadata(MutableBeanMetadata.class);
+        action.setActivation(MutableBeanMetadata.ACTIVATION_LAZY);
+        action.setScope(MutableBeanMetadata.SCOPE_PROTOTYPE);
+        action.setClassName(element.getAttribute("class"));
+        NodeList children = element.getChildNodes();
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child  = children.item(i);
+            if (child instanceof Element) {
+                Element childElement = (Element) child;
+                if (nodeNameEquals(childElement, "argument")) {
+                    action.addArgument(context.parseElement(BeanArgument.class, enclosingComponent, childElement));
+                } else if (nodeNameEquals(childElement, "property")) {
+                    action.addProperty(context.parseElement(BeanProperty.class, enclosingComponent, childElement));
+                }
+            }
+        }
+        return action;
+    }
+
+    private Metadata parseOptionalCompleters(ParserContext context, ComponentMetadata enclosingComponent, Element element) {
+        Metadata metadata = context.parseElement(MapMetadata.class, context.getEnclosingComponent(), (Element) element);
+        return metadata;
+    }
+
+    private Metadata parseCompleters(ParserContext context, ComponentMetadata enclosingComponent, Element element) {
+        MutableCollectionMetadata collection = context.createMetadata(MutableCollectionMetadata.class);
+        collection.setCollectionClass(List.class);
+        NodeList children = element.getChildNodes();
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (child instanceof Element) {
+                Metadata metadata;
+                if (nodeNameEquals(child, REF)) {
+                    metadata = context.parseElement(RefMetadata.class, context.getEnclosingComponent(), (Element) child);
+                } else if (nodeNameEquals(child, NULL)) {
+                    metadata = context.parseElement(NullMetadata.class, context.getEnclosingComponent(), (Element) child);
+                } else if (nodeNameEquals(child, BEAN)) {
+                    metadata = context.parseElement(BeanMetadata.class, enclosingComponent, (Element) child);
+                } else {
+                    throw new IllegalStateException("Unexpected element " + child.getNodeName());
+                }
+                collection.addValue(metadata);
+            }
+        }
+        return collection;
+    }
+
+    private ValueMetadata createStringValue(ParserContext context, String str) {
+        MutableValueMetadata value = context.createMetadata(MutableValueMetadata.class);
+        value.setStringValue(str);
+        return value;
+    }
+
+    private RefMetadata createRef(ParserContext context, String id) {
+        MutableRefMetadata idref = context.createMetadata(MutableRefMetadata.class);
+        idref.setComponentId(id);
+        return idref;
+    }
+
+    private IdRefMetadata createIdRef(ParserContext context, String id) {
+        MutableIdRefMetadata idref = context.createMetadata(MutableIdRefMetadata.class);
+        idref.setComponentId(id);
+        return idref;
+    }
+
+    public synchronized String getName() {
+        return "shell-" + ++nameCounter;
+    }
+    
+    private static boolean nodeNameEquals(Node node, String name) {
+        return (name.equals(node.getNodeName()) || name.equals(node.getLocalName()));
+    }
+
+    public static String getScope(Class<?> action) {
+        Command command = action.getAnnotation(Command.class);
+        if (command != null) {
+            return command.scope();
+        }
+        org.apache.felix.gogo.commands.Command command2 = action.getAnnotation(org.apache.felix.gogo.commands.Command.class);
+        if (command2 != null) {
+            return command2.scope();
+        }
+        return null;
+    }
+
+    public static String getName(Class<?> action) {
+        Command command = action.getAnnotation(Command.class);
+        if (command != null) {
+            return command.name();
+        }
+        org.apache.felix.gogo.commands.Command command2 = action.getAnnotation(org.apache.felix.gogo.commands.Command.class);
+        if (command2 != null) {
+            return command2.name();
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/1ee78df9/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/NumberToStringConverter.java
----------------------------------------------------------------------
diff --git a/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/NumberToStringConverter.java b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/NumberToStringConverter.java
new file mode 100644
index 0000000..fb68c3a
--- /dev/null
+++ b/shell/command-api/src/main/java/org/apache/karaf/shell/console/commands/NumberToStringConverter.java
@@ -0,0 +1,36 @@
+/*
+ * 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.commands;
+
+import org.osgi.service.blueprint.container.Converter;
+import org.osgi.service.blueprint.container.ReifiedType;
+
+public class NumberToStringConverter implements Converter {
+
+    @Override
+    public boolean canConvert(Object sourceObject, ReifiedType targetType) {
+        return sourceObject != null && sourceObject instanceof Number
+                && targetType.getRawClass() == String.class;
+    }
+
+    @Override
+    public Object convert(Object sourceObject, ReifiedType targetType) throws Exception {
+        return sourceObject.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/1ee78df9/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/AggregateCompleter.java
----------------------------------------------------------------------
diff --git a/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/AggregateCompleter.java b/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/AggregateCompleter.java
new file mode 100644
index 0000000..dbba436
--- /dev/null
+++ b/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/AggregateCompleter.java
@@ -0,0 +1,91 @@
+/*
+ * 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.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Collection;
+
+import org.apache.karaf.shell.console.Completer;
+
+/**
+ * Completer which contains multipule completers and aggregates them together.
+ */
+public class AggregateCompleter implements Completer
+{
+    private final Collection<Completer> completers;
+
+    public AggregateCompleter(final Collection<Completer> completers) {
+        assert completers != null;
+        this.completers = completers;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public int complete(final String buffer, final int cursor, final List candidates) {
+        // buffer could be null
+        assert candidates != null;
+
+        List<Completion> completions = new ArrayList<Completion>(completers.size());
+
+        // Run each completer, saving its completion results
+        int max = -1;
+        for (Completer completer : completers) {
+            Completion completion = new Completion(candidates);
+            completion.complete(completer, buffer, cursor);
+
+            // Compute the max cursor position
+            max = Math.max(max, completion.cursor);
+
+            completions.add(completion);
+        }
+
+        // Append candiates from completions which have the same cursor position as max
+        for (Completion completion : completions) {
+            if (completion.cursor == max) {
+                // noinspection unchecked
+                candidates.addAll(completion.candidates);
+            }
+        }
+
+        return max;
+    }
+
+    private class Completion
+    {
+        public final List<String> candidates;
+
+        public int cursor;
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public Completion(final List candidates) {
+            assert candidates != null;
+
+            // noinspection unchecked
+            this.candidates = new LinkedList<String>(candidates);
+        }
+
+        public void complete(final Completer completer, final String buffer, final int cursor) {
+            assert completer != null;
+
+            this.cursor = completer.complete(buffer, cursor, candidates);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/1ee78df9/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java
----------------------------------------------------------------------
diff --git a/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java b/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java
new file mode 100644
index 0000000..745489e
--- /dev/null
+++ b/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/ArgumentCompleter.java
@@ -0,0 +1,567 @@
+/*
+ * 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.lang.reflect.Modifier;
+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.service.command.Function;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.CommandWithAction;
+import org.apache.karaf.shell.commands.CompleterValues;
+import org.apache.karaf.shell.commands.HelpOption;
+import org.apache.karaf.shell.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.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.karaf.shell.console.completer.CommandsCompleter.unProxy;
+
+public class ArgumentCompleter implements Completer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentCompleter.class);
+
+    public static final String ARGUMENTS_LIST = "ARGUMENTS_LIST";
+
+    public static final String COMMANDS = ".commands";
+
+    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 ArgumentCompleter(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
+        List<Completer> argsCompleters = null;
+        Map<String, Completer> optionalCompleters = null;
+
+        if (function instanceof CompletableFunction) {
+            Map<String, Completer> focl = ((CompletableFunction) function).getOptionalCompleters();
+            List<Completer> fcl = ((CompletableFunction) function).getCompleters();
+            if (focl != null || fcl != null) {
+                argsCompleters = new ArrayList<Completer>();
+                if (fcl != null) {
+                    for (Completer c : fcl) {
+                        argsCompleters.add(c == null ? NullCompleter.INSTANCE : c);
+                    }
+                }
+                optionalCompleters = focl;
+            }
+        }
+        if (argsCompleters == null) {
+            final Map<Integer, Object> values = getCompleterValues(function);
+            argsCompleters = new ArrayList<Completer>();
+            boolean multi = false;
+            for (int key = 0; key < arguments.size(); key++) {
+                Completer completer = null;
+                Field field = arguments.get(key);
+                if (field != null) {
+                    Argument argument = field.getAnnotation(Argument.class);
+                    multi = (argument != null && argument.multiValued());
+                    org.apache.karaf.shell.commands.Completer ann = field.getAnnotation(org.apache.karaf.shell.commands.Completer.class);
+                    if (ann != null) {
+                        Class clazz = ann.value();
+                        String[] value = ann.values();
+                        if (clazz != null) {
+                            if (value.length > 0 && clazz == StringsCompleter.class) {
+                                completer = new StringsCompleter(value, ann.caseSensitive());
+                            } else {
+                                BundleContext context = FrameworkUtil.getBundle(function.getClass()).getBundleContext();
+                                completer = new ProxyServiceCompleter(context, clazz);
+                            }
+                        }
+                    } else if (values.containsKey(key)) {
+                        Object value = values.get(key);
+                        if (value instanceof String[]) {
+                            completer = new StringsCompleter((String[]) value);
+                        } else if (value instanceof Collection) {
+                            completer = new StringsCompleter((Collection<String>) value);
+                        } else {
+                            LOGGER.warn("Could not use value " + value + " as set of completions!");
+                        }
+                    } else {
+                        completer = getDefaultCompleter(session, field);
+                    }
+                }
+                if (completer == null) {
+                    completer = NullCompleter.INSTANCE;
+                }
+                argsCompleters.add(completer);
+            }
+            if (argsCompleters.isEmpty() || !multi) {
+                argsCompleters.add(NullCompleter.INSTANCE);
+            }
+            optionalCompleters = new HashMap<String, Completer>();
+            for (Option option : fields.keySet()) {
+                Completer completer = null;
+                Field field = fields.get(option);
+                if (field != null) {
+                    org.apache.karaf.shell.commands.Completer ann = field.getAnnotation(org.apache.karaf.shell.commands.Completer.class);
+                    if (ann != null) {
+                        Class clazz = ann.value();
+                        String[] value = ann.values();
+                        if (clazz != null) {
+                            if (value.length > 0 && clazz == StringsCompleter.class) {
+                                completer = new StringsCompleter(value, ann.caseSensitive());
+                            } else {
+                                BundleContext context = FrameworkUtil.getBundle(function.getClass()).getBundleContext();
+                                completer = new ProxyServiceCompleter(context, clazz);
+                            }
+                        }
+                    }
+                }
+                if (completer == null) {
+                    completer = NullCompleter.INSTANCE;
+                }
+                optionalCompleters.put(option.name(), completer);
+                if (option.aliases() != null) {
+                    for (String alias : option.aliases()) {
+                        optionalCompleters.put(alias, completer);
+                    }
+                }
+            }
+        }
+        this.argsCompleters = argsCompleters;
+        this.optionalCompleters = optionalCompleters;
+    }
+
+    private Map<Integer, Object> getCompleterValues(CommandWithAction function) {
+        final Map<Integer, Object> values = new HashMap<Integer, Object>();
+        Action action = null;
+        try {
+            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);
+                        } else if (values.containsKey(key)) {
+                            LOGGER.warn("Duplicate @CompleterMethod annotations on class " + type.getName() + " for index: " + key + " see: " + method);
+                        } else {
+                            try {
+                                Object value;
+                                if (Modifier.isStatic(method.getModifiers())) {
+                                    value = method.invoke(null);
+                                } else {
+                                    if (action == null) {
+                                        action = function.createNewAction();
+                                    }
+                                    value = method.invoke(action);
+                                }
+                                values.put(key, value);
+                            } 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 {
+            if (action != null) {
+                try {
+                    function.releaseAction(action);
+                } catch (Exception e) {
+                    LOGGER.warn("Failed to release action: " + action + ". " + e, e);
+                }
+            }
+        }
+        return values;
+    }
+
+    private Completer getDefaultCompleter(CommandSession session, Field field) {
+        Completer completer = null;
+        Class<?> type = field.getType();
+        if (type.isAssignableFrom(File.class)) {
+            completer = new FileCompleter(session);
+        } else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
+            completer = 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());
+            }
+            completer = new StringsCompleter(values, false);
+        } else {
+            // TODO any other completers we can add?
+        }
+        return completer;
+    }
+
+    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;
+            }
+            // Verify scope if
+            // - the command name has no scope
+            // - we have a session
+            if (!args[index].contains(":") && commandSession != null) {
+                Function f1 = unProxy((Function) commandSession.get("*:" + args[index]));
+                Function f2 = unProxy(this.function);
+                if (f1 != null && f1 != f2) {
+                    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;
+        }
+    }
+
+    public static class ProxyServiceCompleter implements Completer {
+        private final BundleContext context;
+        private final Class<? extends Completer> clazz;
+
+        public ProxyServiceCompleter(BundleContext context, Class<? extends Completer> clazz) {
+            this.context = context;
+            this.clazz = clazz;
+        }
+
+        @Override
+        public int complete(String buffer, int cursor, List<String> candidates) {
+            ServiceReference<? extends Completer> ref = context.getServiceReference(clazz);
+            if (ref != null) {
+                Completer completer = context.getService(ref);
+                if (completer != null) {
+                    try {
+                        return completer.complete(buffer, cursor, candidates);
+                    } finally {
+                        context.ungetService(ref);
+                    }
+                }
+            }
+            return -1;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/1ee78df9/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/CommandNamesCompleter.java
----------------------------------------------------------------------
diff --git a/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/CommandNamesCompleter.java b/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/CommandNamesCompleter.java
new file mode 100644
index 0000000..23f5447
--- /dev/null
+++ b/shell/command-api/src/main/java/org/apache/karaf/shell/console/completer/CommandNamesCompleter.java
@@ -0,0 +1,103 @@
+/*
+ * 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.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.shell.console.CommandSessionHolder;
+import org.apache.karaf.shell.console.Completer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+
+/**
+ * Completes command names
+ */
+public class CommandNamesCompleter implements Completer {
+
+    public static final String COMMANDS = ".commands";
+
+    private CommandSession session;
+    private final Set<String> commands = new CopyOnWriteArraySet<String>();
+
+    public CommandNamesCompleter() {
+        this(CommandSessionHolder.getSession());
+    }
+
+    public CommandNamesCompleter(CommandSession session) {
+        this.session = session;
+
+        try {
+            new CommandTracker();
+        } catch (Throwable t) {
+            // Ignore in case we're not in OSGi
+        }
+    }
+
+
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        if (session == null) {
+            session = CommandSessionHolder.getSession();
+        }
+        checkData();
+        int res = new StringsCompleter(commands).complete(buffer, cursor, candidates);
+        Collections.sort(candidates);
+        return res;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected void checkData() {
+        if (commands.isEmpty()) {
+            Set<String> names = new HashSet<String>((Set<String>) session.get(COMMANDS));
+            for (String name : names) {
+                commands.add(name);
+                if (name.indexOf(':') > 0) {
+                    commands.add(name.substring(0, name.indexOf(':')));
+                }
+            }
+        }
+    }
+
+    private class CommandTracker {
+        public CommandTracker() throws Exception {
+            BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
+            if (context == null) {
+                throw new IllegalStateException("Bundle is stopped");
+            }
+            ServiceListener listener = new ServiceListener() {
+                public void serviceChanged(ServiceEvent event) {
+                    commands.clear();
+                }
+            };
+            context.addServiceListener(listener,
+                    String.format("(&(%s=*)(%s=*))",
+                            CommandProcessor.COMMAND_SCOPE,
+                            CommandProcessor.COMMAND_FUNCTION));
+        }
+    }
+
+}
+