Richard S. Hall | af656a0 | 2009-06-11 16:07:20 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Licensed to the Apache Software Foundation (ASF) under one |
| 3 | * or more contributor license agreements. See the NOTICE file |
| 4 | * distributed with this work for additional information |
| 5 | * regarding copyright ownership. The ASF licenses this file |
| 6 | * to you under the Apache License, Version 2.0 (the |
| 7 | * "License"); you may not use this file except in compliance |
| 8 | * with the License. You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, |
| 13 | * software distributed under the License is distributed on an |
| 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | * KIND, either express or implied. See the License for the |
| 16 | * specific language governing permissions and limitations |
| 17 | * under the License. |
| 18 | */ |
| 19 | package aQute.shell.runtime; |
| 20 | |
| 21 | import java.lang.reflect.*; |
| 22 | import java.util.*; |
| 23 | |
| 24 | import org.osgi.service.command.*; |
| 25 | |
| 26 | public class Reflective { |
| 27 | public final static Object NO_MATCH = new Object(); |
| 28 | public final static Set<String> KEYWORDS = new HashSet<String>(Arrays |
| 29 | .asList(new String[] { |
| 30 | "abstract", "continue", "for", "new", "switch", "assert", |
| 31 | "default", "goto", "package", "synchronized", "boolean", "do", |
| 32 | "if", "private", "this", "break", "double", "implements", |
| 33 | "protected", "throw", "byte", "else", "import", "public", "throws", |
| 34 | "case", "enum", "instanceof", "return", "transient", "catch", |
| 35 | "extends", "int", "short", "try", "char", "final", "interface", |
| 36 | "static", "void", "class", "finally", "long", "strictfp", |
| 37 | "volatile", "const", "float", "native", "super", "while" })); |
| 38 | |
| 39 | public Object method(CommandSession session, Object target, String name, |
| 40 | List<Object> args) throws IllegalArgumentException, |
| 41 | IllegalAccessException, InvocationTargetException, Exception { |
| 42 | Method[] methods = target.getClass().getMethods(); |
| 43 | name = name.toLowerCase(); |
| 44 | |
| 45 | String get = "get" + name; |
| 46 | String is = "is" + name; |
| 47 | String set = "set" + name; |
| 48 | |
| 49 | if (KEYWORDS.contains(name)) |
| 50 | name = "_" + name; |
| 51 | |
| 52 | Method bestMethod = null; |
| 53 | Object[] bestArgs = null; |
| 54 | int match = -1; |
| 55 | for (Method m : methods) { |
| 56 | String mname = m.getName().toLowerCase(); |
| 57 | if (mname.equals(name) || mname.equals(get) || mname.equals(set) |
| 58 | || mname.equals(is)) { |
| 59 | Class<?>[] types = m.getParameterTypes(); |
| 60 | |
| 61 | // Check if the command takes a session |
| 62 | if (types.length > 0 |
| 63 | && CommandSession.class.isAssignableFrom(types[0])) { |
| 64 | args.add(0, session); |
| 65 | } |
| 66 | |
| 67 | Object[] parms = new Object[types.length]; |
| 68 | // if (types.length >= args.size() ) { |
| 69 | int local = coerce(session, target, types, parms, args); |
| 70 | if (local == types.length || local > match) { |
| 71 | bestMethod = m; |
| 72 | bestArgs = parms; |
| 73 | match = local; |
| 74 | } |
| 75 | // } |
| 76 | // if (match == -1 && types.length == 1 |
| 77 | // && types[0] == Object[].class) { |
| 78 | // bestMethod = m; |
| 79 | // Object value = args.toArray(); |
| 80 | // bestArgs = new Object[] { value }; |
| 81 | // } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | if (bestMethod != null) { |
| 86 | bestMethod.setAccessible(true); |
| 87 | return bestMethod.invoke(target, bestArgs); |
| 88 | } else |
| 89 | throw new IllegalArgumentException("Cannot find command:" + name |
| 90 | + " with args:" + args); |
| 91 | } |
| 92 | |
| 93 | /** |
| 94 | * Complex routein to convert the arguments given from the command line to |
| 95 | * the arguments of the method call. First, an attempt is made to convert |
| 96 | * each argument. If this fails, a check is made to see if varargs can be |
| 97 | * applied. This happens when the last method argument is an array. |
| 98 | * |
| 99 | * @param session |
| 100 | * @param target |
| 101 | * @param types |
| 102 | * @param out |
| 103 | * @param in |
| 104 | * @return |
| 105 | * @throws Exception |
| 106 | */ |
| 107 | private int coerce(CommandSession session, Object target, Class<?> types[], |
| 108 | Object out[], List<Object> in) throws Exception { |
| 109 | int i = 0; |
| 110 | while (i < out.length) { |
| 111 | out[i] = null; |
| 112 | try { |
| 113 | // Try to convert one argument |
| 114 | out[i] = coerce(session, target, types[i], in.get(i)); |
| 115 | if (out[i] == NO_MATCH) { |
| 116 | // Failed |
| 117 | // No match, check for varargs |
| 118 | if (types[i].isArray() && i == types.length - 1) { |
| 119 | // Try to parse the remaining arguments in an array |
| 120 | Class<?> component = types[i].getComponentType(); |
| 121 | Object components = Array.newInstance(component, in |
| 122 | .size() |
| 123 | - i); |
| 124 | int n = i; |
| 125 | while (i < in.size()) { |
| 126 | Object t = coerce(session, target, component, in |
| 127 | .get(i)); |
| 128 | if (t == NO_MATCH) |
| 129 | return -1; |
| 130 | Array.set(components, i - n, t); |
| 131 | i++; |
| 132 | } |
| 133 | out[n] = components; |
| 134 | // Is last element, so we will quite hereafter |
| 135 | return n; |
| 136 | } |
| 137 | return -1; |
| 138 | } |
| 139 | i++; |
| 140 | } catch (Exception e) { |
| 141 | // e.printStackTrace(); |
| 142 | System.err.println(e); |
| 143 | // should get rid of those exceptions, but requires |
| 144 | // reg ex matching to see if it throws an exception. |
| 145 | // dont know what is better |
| 146 | return -1; |
| 147 | } |
| 148 | } |
| 149 | return i; |
| 150 | } |
| 151 | |
| 152 | Object coerce(CommandSession session, Object target, Class<?> type, |
| 153 | Object arg) throws Exception { |
| 154 | if (arg == null) |
| 155 | return null; |
| 156 | |
| 157 | if (type.isAssignableFrom(arg.getClass())) |
| 158 | return arg; |
| 159 | |
| 160 | Object converted = session.convert(type, arg); |
| 161 | if (converted != null) |
| 162 | return converted; |
| 163 | |
| 164 | String string = arg.toString(); |
| 165 | if (type.isAssignableFrom(String.class)) |
| 166 | return string; |
| 167 | |
| 168 | if (type.isArray()) { |
| 169 | // Must handle array types |
| 170 | return NO_MATCH; |
| 171 | } else if (!type.isPrimitive()) { |
| 172 | try { |
| 173 | return type.getConstructor(String.class).newInstance(string); |
| 174 | } catch (Exception e) { |
| 175 | return NO_MATCH; |
| 176 | } |
| 177 | } |
| 178 | if (type == boolean.class) |
| 179 | return new Boolean(string); |
| 180 | else if (type == byte.class) |
| 181 | return new Byte(string); |
| 182 | else if (type == char.class) { |
| 183 | if (string.length() == 1) |
| 184 | return string.charAt(0); |
| 185 | } else if (type == short.class) |
| 186 | return new Short(string); |
| 187 | else if (type == int.class) |
| 188 | return new Integer(string); |
| 189 | else if (type == float.class) |
| 190 | return new Float(string); |
| 191 | else if (type == double.class) |
| 192 | return new Double(string); |
| 193 | else if (type == long.class) |
| 194 | return new Long(string); |
| 195 | |
| 196 | return NO_MATCH; |
| 197 | } |
| 198 | |
| 199 | public static boolean hasCommand(Object target, String function) { |
| 200 | Method[] methods = target.getClass().getMethods(); |
| 201 | for (Method m : methods) { |
| 202 | if (m.getName().equals(function)) |
| 203 | return true; |
| 204 | } |
| 205 | return false; |
| 206 | } |
| 207 | } |