FELIX-2075: Add a 'new' command to create java objects from the command line
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@910199 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/shell/commands/src/main/java/org/apache/felix/karaf/shell/commands/NewAction.java b/karaf/shell/commands/src/main/java/org/apache/felix/karaf/shell/commands/NewAction.java
new file mode 100644
index 0000000..04d9a93
--- /dev/null
+++ b/karaf/shell/commands/src/main/java/org/apache/felix/karaf/shell/commands/NewAction.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.shell.commands;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.karaf.shell.console.OsgiCommandSupport;
+import org.apache.felix.karaf.shell.console.commands.GenericType;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.osgi.service.blueprint.container.Converter;
+import org.osgi.service.blueprint.container.ReifiedType;
+
+/**
+ * Execute a closure on a list of arguments.
+ */
+@Command(scope = "shell", name = "new", description = "Creates a new java object.")
+public class NewAction extends OsgiCommandSupport {
+
+ @Argument(name = "class", index = 0, multiValued = false, required = true, description = "The object class")
+ Class clazz;
+
+ @Argument(name = "args", index = 1, multiValued = true, required = false, description = "Constructor arguments")
+ List<Object> args;
+
+ boolean reorderArguments;
+
+ protected Converter blueprintConverter;
+
+ public void setBlueprintConverter(Converter blueprintConverter) {
+ this.blueprintConverter = blueprintConverter;
+ }
+
+ @Override
+ protected Object doExecute() throws Exception {
+ // Map of matching constructors
+ Map<Constructor, List<Object>> matches = findMatchingConstructors(clazz, args, Arrays.asList(new ReifiedType[args.size()]));
+ if (matches.size() == 1) {
+ try {
+ Map.Entry<Constructor, List<Object>> match = matches.entrySet().iterator().next();
+ return newInstance(match.getKey(), match.getValue().toArray());
+ } catch (Throwable e) {
+ throw new ComponentDefinitionException("Error when instanciating object of class " + clazz.getName(), getRealCause(e));
+ }
+ } else if (matches.size() == 0) {
+ throw new ComponentDefinitionException("Unable to find a matching constructor on class " + clazz.getName() + " for arguments " + args + " when instanciating object.");
+ } else {
+ throw new ComponentDefinitionException("Multiple matching constructors found on class " + clazz.getName() + " for arguments " + args + " when instanciating object: " + matches.keySet());
+ }
+ }
+
+
+ //
+ // Code below comes from Aries blueprint implementation. Given this code is not available
+ // from a public API it has been copied here.
+ //
+
+ private Object newInstance(Constructor constructor, Object... args) throws Exception {
+ return constructor.newInstance(args);
+ }
+
+ private Map<Constructor, List<Object>> findMatchingConstructors(Class type, List<Object> args, List<ReifiedType> types) {
+ Map<Constructor, List<Object>> matches = new HashMap<Constructor, List<Object>>();
+ // Get constructors
+ List<Constructor> constructors = new ArrayList<Constructor>(Arrays.asList(type.getConstructors()));
+ // Discard any signature with wrong cardinality
+ for (Iterator<Constructor> it = constructors.iterator(); it.hasNext();) {
+ if (it.next().getParameterTypes().length != args.size()) {
+ it.remove();
+ }
+ }
+ // Find a direct match with assignment
+ if (matches.size() != 1) {
+ Map<Constructor, List<Object>> nmatches = new HashMap<Constructor, List<Object>>();
+ for (Constructor cns : constructors) {
+ boolean found = true;
+ List<Object> match = new ArrayList<Object>();
+ for (int i = 0; i < args.size(); i++) {
+ ReifiedType argType = new GenericType(cns.getGenericParameterTypes()[i]);
+ if (types.get(i) != null && !argType.getRawClass().equals(types.get(i).getRawClass())) {
+ found = false;
+ break;
+ }
+ if (!isAssignable(args.get(i), argType)) {
+ found = false;
+ break;
+ }
+ try {
+ match.add(convert(args.get(i), cns.getGenericParameterTypes()[i]));
+ } catch (Throwable t) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ nmatches.put(cns, match);
+ }
+ }
+ if (nmatches.size() > 0) {
+ matches = nmatches;
+ }
+ }
+ // Find a direct match with conversion
+ if (matches.size() != 1) {
+ Map<Constructor, List<Object>> nmatches = new HashMap<Constructor, List<Object>>();
+ for (Constructor cns : constructors) {
+ boolean found = true;
+ List<Object> match = new ArrayList<Object>();
+ for (int i = 0; i < args.size(); i++) {
+ ReifiedType argType = new GenericType(cns.getGenericParameterTypes()[i]);
+ if (types.get(i) != null && !argType.getRawClass().equals(types.get(i).getRawClass())) {
+ found = false;
+ break;
+ }
+ try {
+ Object val = convert(args.get(i), argType);
+ match.add(val);
+ } catch (Throwable t) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ nmatches.put(cns, match);
+ }
+ }
+ if (nmatches.size() > 0) {
+ matches = nmatches;
+ }
+ }
+ // Start reordering with assignment
+ if (matches.size() != 1 && reorderArguments && args.size() > 1) {
+ Map<Constructor, List<Object>> nmatches = new HashMap<Constructor, List<Object>>();
+ for (Constructor cns : constructors) {
+ ArgumentMatcher matcher = new ArgumentMatcher(cns.getGenericParameterTypes(), false);
+ List<Object> match = matcher.match(args, types);
+ if (match != null) {
+ nmatches.put(cns, match);
+ }
+ }
+ if (nmatches.size() > 0) {
+ matches = nmatches;
+ }
+ }
+ // Start reordering with conversion
+ if (matches.size() != 1 && reorderArguments && args.size() > 1) {
+ Map<Constructor, List<Object>> nmatches = new HashMap<Constructor, List<Object>>();
+ for (Constructor cns : constructors) {
+ ArgumentMatcher matcher = new ArgumentMatcher(cns.getGenericParameterTypes(), true);
+ List<Object> match = matcher.match(args, types);
+ if (match != null) {
+ nmatches.put(cns, match);
+ }
+ }
+ if (nmatches.size() > 0) {
+ matches = nmatches;
+ }
+ }
+ return matches;
+ }
+
+ protected Object convert(Object obj, Type type) throws Exception {
+ return blueprintConverter.convert(obj, new GenericType(type));
+ }
+
+ protected Object convert(Object obj, ReifiedType type) throws Exception {
+ return blueprintConverter.convert(obj, type);
+ }
+
+ public static boolean isAssignable(Object source, ReifiedType target) {
+ return source == null
+ || (target.size() == 0
+ && unwrap(target.getRawClass()).isAssignableFrom(unwrap(source.getClass())));
+ }
+
+ private static Class unwrap(Class c) {
+ Class u = primitives.get(c);
+ return u != null ? u : c;
+ }
+
+ private static final Map<Class, Class> primitives;
+ static {
+ primitives = new HashMap<Class, Class>();
+ primitives.put(byte.class, Byte.class);
+ primitives.put(short.class, Short.class);
+ primitives.put(char.class, Character.class);
+ primitives.put(int.class, Integer.class);
+ primitives.put(long.class, Long.class);
+ primitives.put(float.class, Float.class);
+ primitives.put(double.class, Double.class);
+ primitives.put(boolean.class, Boolean.class);
+ }
+
+
+ private static Object UNMATCHED = new Object();
+
+ private class ArgumentMatcher {
+
+ private List<TypeEntry> entries;
+ private boolean convert;
+
+ public ArgumentMatcher(Type[] types, boolean convert) {
+ entries = new ArrayList<TypeEntry>();
+ for (Type type : types) {
+ entries.add(new TypeEntry(new GenericType(type)));
+ }
+ this.convert = convert;
+ }
+
+ public List<Object> match(List<Object> arguments, List<ReifiedType> forcedTypes) {
+ if (find(arguments, forcedTypes)) {
+ return getArguments();
+ }
+ return null;
+ }
+
+ private List<Object> getArguments() {
+ List<Object> list = new ArrayList<Object>();
+ for (TypeEntry entry : entries) {
+ if (entry.argument == UNMATCHED) {
+ throw new RuntimeException("There are unmatched types");
+ } else {
+ list.add(entry.argument);
+ }
+ }
+ return list;
+ }
+
+ private boolean find(List<Object> arguments, List<ReifiedType> forcedTypes) {
+ if (entries.size() == arguments.size()) {
+ boolean matched = true;
+ for (int i = 0; i < arguments.size() && matched; i++) {
+ matched = find(arguments.get(i), forcedTypes.get(i));
+ }
+ return matched;
+ }
+ return false;
+ }
+
+ private boolean find(Object arg, ReifiedType forcedType) {
+ for (TypeEntry entry : entries) {
+ Object val = arg;
+ if (entry.argument != UNMATCHED) {
+ continue;
+ }
+ if (forcedType != null) {
+ if (!forcedType.equals(entry.type)) {
+ continue;
+ }
+ } else if (arg != null) {
+ if (convert) {
+ try {
+ // TODO: call canConvert instead of convert()
+ val = convert(arg, entry.type);
+ } catch (Throwable t) {
+ continue;
+ }
+ } else {
+ if (!isAssignable(arg, entry.type)) {
+ continue;
+ }
+ }
+ }
+ entry.argument = val;
+ return true;
+ }
+ return false;
+ }
+
+ }
+
+ private static class TypeEntry {
+
+ private final ReifiedType type;
+ private Object argument;
+
+ public TypeEntry(ReifiedType type) {
+ this.type = type;
+ this.argument = UNMATCHED;
+ }
+
+ }
+
+ public static Throwable getRealCause(Throwable t) {
+ if (t instanceof InvocationTargetException && t.getCause() != null) {
+ return t.getCause();
+ }
+ return t;
+ }
+}
\ No newline at end of file
diff --git a/karaf/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml b/karaf/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
index 6ed39f8..88aea17 100644
--- a/karaf/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
+++ b/karaf/shell/commands/src/main/resources/OSGI-INF/blueprint/shell-commands.xml
@@ -57,6 +57,11 @@
<action class="org.apache.felix.karaf.shell.commands.MoreAction"/>
</command>
-->
+ <command name="shell/new">
+ <action class="org.apache.felix.karaf.shell.commands.NewAction">
+ <property name="blueprintConverter" ref="blueprintConverter"/>
+ </action>
+ </command>
<command name="shell/logout">
<action class="org.apache.felix.karaf.shell.commands.LogoutAction"/>
</command>