Skip to content

Commit 117782b

Browse files
committedMar 8, 2023
Fix calling commands in gogo under jdk 17
Workaround for https://issues.apache.org/jira/browse/FELIX-6597 until it is released
1 parent 6c5a275 commit 117782b

File tree

1 file changed

+660
-0
lines changed

1 file changed

+660
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,660 @@
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 org.apache.felix.gogo.runtime;
20+
21+
import java.lang.annotation.Annotation;
22+
import java.lang.reflect.Array;
23+
import java.lang.reflect.Field;
24+
import java.lang.reflect.InvocationTargetException;
25+
import java.lang.reflect.Method;
26+
import java.lang.reflect.Modifier;
27+
import java.util.AbstractList;
28+
import java.util.ArrayList;
29+
import java.util.Arrays;
30+
import java.util.Collection;
31+
import java.util.Collections;
32+
import java.util.HashSet;
33+
import java.util.List;
34+
import java.util.LinkedHashSet;
35+
import java.util.Locale;
36+
import java.util.Map;
37+
import java.util.Set;
38+
39+
import org.apache.felix.service.command.CommandSession;
40+
import org.apache.felix.service.command.Parameter;
41+
42+
public final class Reflective
43+
{
44+
public final static Object NO_MATCH = new Object();
45+
public final static String MAIN = "_main";
46+
public final static Set<String> KEYWORDS = new HashSet<>(
47+
Arrays.asList("abstract", "continue", "for", "new", "switch",
48+
"assert", "default", "goto", "package", "synchronized", "boolean", "do",
49+
"if", "private", "this", "break", "double", "implements", "protected",
50+
"throw", "byte", "else", "import", "public", "throws", "case", "enum",
51+
"instanceof", "return", "transient", "catch", "extends", "int", "short",
52+
"try", "char", "final", "interface", "static", "void", "class",
53+
"finally", "long", "strictfp", "volatile", "const", "float", "native",
54+
"super", "while"));
55+
56+
/**
57+
* invokes the named method on the given target using the supplied args,
58+
* which are converted if necessary.
59+
* @param session the session
60+
* @param target the target
61+
* @param name the name
62+
* @param args the args
63+
* @return the result of the invoked method
64+
* @throws Exception on exception
65+
*/
66+
public static Object invoke(CommandSession session, Object target, String name,
67+
List<Object> args) throws Exception
68+
{
69+
name = name.toLowerCase(Locale.ENGLISH);
70+
71+
String org = name;
72+
String get = "get" + name;
73+
String is = "is" + name;
74+
String set = "set" + name;
75+
76+
if (KEYWORDS.contains(name))
77+
{
78+
name = "_" + name;
79+
}
80+
81+
Set<Class<?>> publicClasses = new LinkedHashSet<>();
82+
Set<Class<?>> nonPublicClasses = new LinkedHashSet<>();
83+
getClassAndAncestors(publicClasses, nonPublicClasses, target.getClass());
84+
List<Method> methods = new ArrayList<>();
85+
for (Class<?> cl : publicClasses) {
86+
Collections.addAll(methods, cl.getMethods());
87+
}
88+
for (Class<?> cl : nonPublicClasses) {
89+
Collections.addAll(methods, cl.getMethods());
90+
}
91+
92+
Method bestMethod = null;
93+
Object[] bestArgs = null;
94+
int lowestMatch = Integer.MAX_VALUE;
95+
ArrayList<Class<?>[]> possibleTypes = new ArrayList<>();
96+
97+
for (Method m : methods)
98+
{
99+
String mname = m.getName().toLowerCase(Locale.ENGLISH);
100+
if (mname.equals(name) || mname.equals(get) || mname.equals(set)
101+
|| mname.equals(is) || mname.equals(MAIN))
102+
{
103+
Class<?>[] types = m.getParameterTypes();
104+
ArrayList<Object> xargs = new ArrayList<>(args);
105+
106+
// pass command name as argv[0] to main, so it can handle
107+
// multiple commands
108+
if (mname.equals(MAIN))
109+
{
110+
xargs.add(0, org);
111+
}
112+
113+
Object[] parms = new Object[types.length];
114+
int match = coerce(session, target, m, types, parms, xargs);
115+
116+
if (match < 0)
117+
{
118+
// coerce failed
119+
possibleTypes.add(types);
120+
}
121+
else
122+
{
123+
if (match < lowestMatch)
124+
{
125+
lowestMatch = match;
126+
bestMethod = m;
127+
bestArgs = parms;
128+
}
129+
130+
if (match == 0)
131+
break; // can't get better score
132+
}
133+
}
134+
}
135+
136+
if (bestMethod != null)
137+
{
138+
bestMethod.setAccessible(true);
139+
try
140+
{
141+
return bestMethod.invoke(target, bestArgs);
142+
}
143+
catch (InvocationTargetException e)
144+
{
145+
Throwable cause = e.getCause();
146+
if (cause instanceof Exception)
147+
{
148+
throw (Exception) cause;
149+
}
150+
throw e;
151+
}
152+
}
153+
else
154+
{
155+
if (args.isEmpty())
156+
{
157+
Field[] fields;
158+
if (target instanceof Class<?>)
159+
{
160+
fields = ((Class<?>) target).getFields();
161+
}
162+
else
163+
{
164+
fields = target.getClass().getFields();
165+
}
166+
for (Field f : fields)
167+
{
168+
String mname = f.getName().toLowerCase(Locale.ENGLISH);
169+
if (mname.equals(name))
170+
{
171+
return f.get(target);
172+
}
173+
}
174+
}
175+
ArrayList<String> list = new ArrayList<>();
176+
for (Class<?>[] types : possibleTypes)
177+
{
178+
StringBuilder buf = new StringBuilder();
179+
buf.append('(');
180+
for (Class<?> type : types)
181+
{
182+
if (buf.length() > 1)
183+
{
184+
buf.append(", ");
185+
}
186+
buf.append(type.getSimpleName());
187+
}
188+
buf.append(')');
189+
list.add(buf.toString());
190+
}
191+
192+
StringBuilder params = new StringBuilder();
193+
for (Object arg : args)
194+
{
195+
if (params.length() > 1)
196+
{
197+
params.append(", ");
198+
}
199+
params.append(arg == null ? "null" : arg.getClass().getSimpleName());
200+
}
201+
202+
throw new IllegalArgumentException(String.format(
203+
"Cannot coerce %s(%s) to any of %s", name, params, list));
204+
}
205+
}
206+
207+
private static void getClassAndAncestors(Set<Class<?>> publicClasses, Set<Class<?>> nonPublicClasses, Class<?> aClass)
208+
{
209+
for (Class<?> itf : aClass.getInterfaces())
210+
{
211+
getClassAndAncestors(publicClasses, nonPublicClasses, itf);
212+
}
213+
if (aClass.getSuperclass() != null)
214+
{
215+
getClassAndAncestors(publicClasses, nonPublicClasses, aClass.getSuperclass());
216+
}
217+
if (Modifier.isPublic(aClass.getModifiers()))
218+
{
219+
publicClasses.add(aClass);
220+
}
221+
else
222+
{
223+
nonPublicClasses.add(aClass);
224+
}
225+
}
226+
227+
/**
228+
* transform name/value parameters into ordered argument list.
229+
* params: --param2, value2, --flag1, arg3
230+
* args: true, value2, arg3
231+
* @return new ordered list of args.
232+
*/
233+
private static List<Object> transformParameters(Method method, List<Object> in)
234+
{
235+
Annotation[][] pas = method.getParameterAnnotations();
236+
ArrayList<Object> out = new ArrayList<>();
237+
ArrayList<Object> parms = new ArrayList<>(in);
238+
239+
for (Annotation as[] : pas)
240+
{
241+
for (Annotation a : as)
242+
{
243+
if (a instanceof Parameter)
244+
{
245+
int i = -1;
246+
Parameter p = (Parameter) a;
247+
for (String name : p.names())
248+
{
249+
i = parms.indexOf(name);
250+
if (i >= 0)
251+
break;
252+
}
253+
254+
if (i >= 0)
255+
{
256+
// parameter present
257+
parms.remove(i);
258+
Object value = p.presentValue();
259+
if (Parameter.UNSPECIFIED.equals(value))
260+
{
261+
if (i >= parms.size())
262+
return null; // missing parameter, so try other methods
263+
value = parms.remove(i);
264+
}
265+
out.add(value);
266+
}
267+
else
268+
{
269+
out.add(p.absentValue());
270+
}
271+
272+
}
273+
}
274+
}
275+
276+
out.addAll(parms);
277+
278+
return out;
279+
}
280+
281+
/**
282+
* Complex routein to convert the arguments given from the command line to
283+
* the arguments of the method call. First, an attempt is made to convert
284+
* each argument. If this fails, a check is made to see if varargs can be
285+
* applied. This happens when the last method argument is an array.
286+
* @return -1 if arguments can't be coerced; 0 if no coercion was necessary;
287+
* > 0 if coercion was needed.
288+
*/
289+
private static int coerce(CommandSession session, Object target, Method m,
290+
Class<?> types[], Object out[], List<Object> in)
291+
{
292+
List<Object> cnvIn = new ArrayList<>();
293+
List<Object> cnvIn2 = new ArrayList<>();
294+
int different = 0;
295+
for (Object obj : in)
296+
{
297+
if (obj instanceof Token)
298+
{
299+
Object s1 = Closure.eval(obj);
300+
Object s2 = obj.toString();
301+
cnvIn.add(s1);
302+
cnvIn2.add(s2);
303+
different += s2.equals(s1) ? 0 : 1;
304+
} else
305+
{
306+
cnvIn.add(obj);
307+
cnvIn2.add(obj);
308+
}
309+
}
310+
311+
cnvIn = transformParameters(m, cnvIn);
312+
if (different != 0)
313+
{
314+
cnvIn2 = transformParameters(m, cnvIn2);
315+
}
316+
if (cnvIn == null || cnvIn2 == null)
317+
{
318+
// missing parameter argument?
319+
return -1;
320+
}
321+
322+
int res;
323+
324+
res = docoerce(session, target, m, types, out, cnvIn);
325+
// Without conversion
326+
if (different != 0 && res < 0)
327+
{
328+
res = docoerce(session, target, m, types, out, cnvIn2);
329+
}
330+
else if (different != 0 && res > 0)
331+
{
332+
int res2;
333+
Object[] out2 = out.clone();
334+
res2 = docoerce(session, target, m, types, out2, cnvIn2) + different * 2;
335+
if (res >= 0 && res2 <= res)
336+
{
337+
res = res2;
338+
System.arraycopy(out2, 0, out, 0, out.length);
339+
}
340+
}
341+
// Check if the command takes a session
342+
if (res < 0 && (types.length > 0) && types[0].isInterface()
343+
&& types[0].isAssignableFrom(session.getClass()))
344+
{
345+
cnvIn.add(0, session);
346+
res = docoerce(session, target, m, types, out, cnvIn);
347+
if (different != 0 && res < 0)
348+
{
349+
cnvIn2.add(0, session);
350+
res = docoerce(session, target, m, types, out, cnvIn2);
351+
}
352+
else if (different != 0 && res > 0)
353+
{
354+
int res2;
355+
cnvIn2.add(0, session);
356+
Object[] out2 = out.clone();
357+
res2 = docoerce(session, target, m, types, out2, cnvIn2) + different * 2;
358+
if (res >= 0 && res2 <= res)
359+
{
360+
res = res2;
361+
System.arraycopy(out2, 0, out, 0, out.length);
362+
}
363+
}
364+
}
365+
return res;
366+
}
367+
368+
private static int docoerce(CommandSession session, Object target, Method m,
369+
Class<?> types[], Object out[], List<Object> in)
370+
{
371+
int[] convert = { 0 };
372+
373+
int i = 0;
374+
while (i < out.length)
375+
{
376+
out[i] = null;
377+
378+
// Try to convert one argument
379+
if (in.size() == 0 || i == types.length - 1 && types[i].isArray() && in.size() > 1)
380+
{
381+
out[i] = NO_MATCH;
382+
}
383+
else
384+
{
385+
out[i] = coerce(session, types[i], in.get(0), convert);
386+
387+
if (out[i] == null && types[i].isArray() && in.size() > 0)
388+
{
389+
// don't coerce null to array FELIX-2432
390+
out[i] = NO_MATCH;
391+
}
392+
393+
if (out[i] != NO_MATCH)
394+
{
395+
in.remove(0);
396+
}
397+
}
398+
399+
if (out[i] == NO_MATCH)
400+
{
401+
// No match, check for varargs
402+
if (types[i].isArray() && (i == types.length - 1))
403+
{
404+
// Try to parse the remaining arguments in an array
405+
Class<?> ctype = types[i].getComponentType();
406+
int asize = in.size();
407+
Object array = Array.newInstance(ctype, asize);
408+
int n = i;
409+
while (in.size() > 0)
410+
{
411+
Object t = coerce(session, ctype, in.remove(0), convert);
412+
if (t == NO_MATCH)
413+
{
414+
return -1;
415+
}
416+
Array.set(array, i - n, t);
417+
i++;
418+
}
419+
out[n] = array;
420+
421+
/*
422+
* 1. prefer f() to f(T[]) with empty array
423+
* 2. prefer f(T) to f(T[1])
424+
* 3. prefer f(T) to f(Object[1]) even if there is a conversion cost for T
425+
*
426+
* 1 & 2 require to add 1 to conversion cost, but 3 also needs to match
427+
* the conversion cost for T.
428+
*/
429+
return convert[0] + 1 + (asize * 2);
430+
}
431+
return -1;
432+
}
433+
i++;
434+
}
435+
436+
if (in.isEmpty())
437+
return convert[0];
438+
return -1;
439+
}
440+
441+
/**
442+
* converts given argument to specified type and increments convert[0] if any conversion was needed.
443+
* @param session the session
444+
* @param type the type
445+
* @param arg the arg
446+
* @param convert convert[0] is incremented according to the conversion needed,
447+
* to allow the "best" conversion to be determined.
448+
* @return converted arg or NO_MATCH if no conversion possible.
449+
*/
450+
public static Object coerce(CommandSession session, Class<?> type, final Object arg,
451+
int[] convert)
452+
{
453+
if (arg == null)
454+
{
455+
return null;
456+
}
457+
458+
if (type.isAssignableFrom(arg.getClass()))
459+
{
460+
return arg;
461+
}
462+
463+
if (type.isArray() && arg instanceof Collection)
464+
{
465+
Collection<?> col = (Collection<?>) arg;
466+
return col.toArray((Object[]) Array.newInstance(type.getComponentType(), col.size()));
467+
}
468+
469+
if (type.isAssignableFrom(List.class) && arg.getClass().isArray())
470+
{
471+
return new AbstractList<Object>()
472+
{
473+
@Override
474+
public Object get(int index)
475+
{
476+
return Array.get(arg, index);
477+
}
478+
479+
@Override
480+
public int size()
481+
{
482+
return Array.getLength(arg);
483+
}
484+
};
485+
}
486+
487+
if (type.isArray())
488+
{
489+
return NO_MATCH;
490+
}
491+
492+
if (type.isPrimitive() && arg instanceof Long)
493+
{
494+
// no-cost conversions between integer types
495+
Number num = (Number) arg;
496+
497+
if (type == short.class)
498+
{
499+
return num.shortValue();
500+
}
501+
if (type == int.class)
502+
{
503+
return num.intValue();
504+
}
505+
if (type == long.class)
506+
{
507+
return num.longValue();
508+
}
509+
}
510+
511+
// all following conversions cost 2 points
512+
convert[0] += 2;
513+
514+
Object converted = ((CommandSessionImpl) session).doConvert(type, arg);
515+
if (converted != null)
516+
{
517+
return converted;
518+
}
519+
520+
String string = toString(arg);
521+
522+
if (type.isAssignableFrom(String.class))
523+
{
524+
return string;
525+
}
526+
527+
if (type.isEnum())
528+
{
529+
for (Object o : type.getEnumConstants())
530+
{
531+
if (o.toString().equalsIgnoreCase(string))
532+
{
533+
return o;
534+
}
535+
}
536+
}
537+
538+
if (type.isPrimitive())
539+
{
540+
type = primitiveToObject(type);
541+
}
542+
543+
try
544+
{
545+
return type.getConstructor(String.class).newInstance(string);
546+
}
547+
catch (Exception e)
548+
{
549+
}
550+
551+
if (type == Character.class && string.length() == 1)
552+
{
553+
return string.charAt(0);
554+
}
555+
556+
return NO_MATCH;
557+
}
558+
559+
private static String toString(Object arg)
560+
{
561+
if (arg instanceof Map)
562+
{
563+
StringBuilder sb = new StringBuilder();
564+
sb.append("[");
565+
boolean first = true;
566+
for (Map.Entry<?,?> entry : ((Map<?,?>) arg).entrySet())
567+
{
568+
if (!first) {
569+
sb.append(" ");
570+
}
571+
first = false;
572+
writeValue(sb, entry.getKey());
573+
sb.append("=");
574+
writeValue(sb, entry.getValue());
575+
}
576+
sb.append("]");
577+
return sb.toString();
578+
}
579+
else if (arg instanceof Collection)
580+
{
581+
StringBuilder sb = new StringBuilder();
582+
sb.append("[");
583+
boolean first = true;
584+
for (Object o : ((Collection<?>) arg))
585+
{
586+
if (!first) {
587+
sb.append(" ");
588+
}
589+
first = false;
590+
writeValue(sb, o);
591+
}
592+
sb.append("]");
593+
return sb.toString();
594+
}
595+
else
596+
{
597+
return arg.toString();
598+
}
599+
}
600+
601+
private static void writeValue(StringBuilder sb, Object o) {
602+
if (o == null || o instanceof Boolean || o instanceof Number)
603+
{
604+
sb.append(o);
605+
}
606+
else
607+
{
608+
String s = o.toString();
609+
sb.append("\"");
610+
for (int i = 0; i < s.length(); i++)
611+
{
612+
char c = s.charAt(i);
613+
if (c == '\"' || c == '=')
614+
{
615+
sb.append("\\");
616+
}
617+
sb.append(c);
618+
}
619+
sb.append("\"");
620+
}
621+
}
622+
623+
private static Class<?> primitiveToObject(Class<?> type)
624+
{
625+
if (type == boolean.class)
626+
{
627+
return Boolean.class;
628+
}
629+
if (type == byte.class)
630+
{
631+
return Byte.class;
632+
}
633+
if (type == char.class)
634+
{
635+
return Character.class;
636+
}
637+
if (type == short.class)
638+
{
639+
return Short.class;
640+
}
641+
if (type == int.class)
642+
{
643+
return Integer.class;
644+
}
645+
if (type == float.class)
646+
{
647+
return Float.class;
648+
}
649+
if (type == double.class)
650+
{
651+
return Double.class;
652+
}
653+
if (type == long.class)
654+
{
655+
return Long.class;
656+
}
657+
return null;
658+
}
659+
660+
}

0 commit comments

Comments
 (0)
Please sign in to comment.