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));
+ }
+ }
+
+}
+