// Copyright (c) Corporation for National Research Initiatives package org.python.core; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Set; import org.python.util.Generic; public class PyReflectedFunction extends PyObject { public String __name__; public PyObject __doc__ = Py.None; public ReflectedArgs[] argslist = new ReflectedArgs[1]; public int nargs; /** Whether __call__ should act as if this is called as a static method. */ private boolean calledStatically; protected PyReflectedFunction(String name) { __name__ = name; } public PyReflectedFunction(Method... methods) { this(methods[0].getName()); for (Method meth : methods) { addMethod(meth); } if (nargs == 0) { String msg = String.format("Attempted to make Java method visible, but it isn't " + "callable[method=%s, class=%s]", methods[0].getName(), methods[0].getDeclaringClass()); throw Py.SystemError(msg); } } @Override public PyObject _doget(PyObject container) { return _doget(container, null); } @Override public PyObject _doget(PyObject container, PyObject wherefound) { // NOTE: this calledStatically business is pretty hacky if (container == null) { return calledStatically ? this : copyWithCalledStatically(true); } return new PyMethod(calledStatically ? copyWithCalledStatically(false) : this, container, wherefound); } private ReflectedArgs makeArgs(Method m) { return new ReflectedArgs(m, m.getParameterTypes(), m.getDeclaringClass(), Modifier.isStatic(m.getModifiers()), m.isVarArgs()); } public PyReflectedFunction copy() { PyReflectedFunction func = new PyReflectedFunction(__name__); func.__doc__ = __doc__; func.nargs = nargs; func.argslist = new ReflectedArgs[nargs]; System.arraycopy(argslist, 0, func.argslist, 0, nargs); return func; } private PyReflectedFunction copyWithCalledStatically(boolean calledStatically) { PyReflectedFunction copy = copy(); copy.calledStatically = calledStatically; return copy; } public boolean handles(Method method) { return handles(makeArgs(method)); } protected boolean handles(ReflectedArgs args) { for (int i = 0; i < nargs; i++) { int cmp = args.compareTo(argslist[i]); if (cmp == 0) { return true; } else if (cmp == 1) { return false; } } return false; } public void addMethod(Method m) { // Only add public methods unless we're overriding if (!Modifier.isPublic(m.getModifiers()) && Options.respectJavaAccessibility) { return; } if (isPackagedProtected(m.getDeclaringClass())) { /* * Set public methods on package protected classes accessible so that reflected calls to * the method in subclasses of the package protected class will succeed. Yes, it's * convoluted. * * This fails when done through reflection due to Sun JVM bug * 4071957(http://tinyurl.com/le9vo). 4533479 actually describes the problem we're * seeing, but there are a bevy of reflection bugs that stem from 4071957. Supposedly * it'll be fixed in Dolphin but it's been promised in every version since Tiger * so don't hold your breath. */ try { m.setAccessible(true); } catch (SecurityException se) { // This case is pretty far in the corner, so don't scream if we can't set the method // accessible due to a security manager. Any calls to it will fail with an // IllegalAccessException, so it'll become visible there. This way we don't spam // people who aren't calling methods like this from Python with warnings if a // library they're using happens to have a method like this. } } addArgs(makeArgs(m)); } public static boolean isPackagedProtected(Class<?> c) { int mods = c.getModifiers(); return !(Modifier.isPublic(mods) || Modifier.isPrivate(mods) || Modifier.isProtected(mods)); } protected void addArgs(ReflectedArgs args) { int i; for (i = 0; i < nargs; i++) { int cmp = args.compareTo(argslist[i]); if (cmp == 0) { return; } else if (cmp == ReflectedArgs.REPLACE) { argslist[i] = args; return; } else if (cmp == -1) { break; } } int nn = nargs + 1; if (nn > argslist.length) { ReflectedArgs[] newargslist = new ReflectedArgs[nn + 2]; System.arraycopy(argslist, 0, newargslist, 0, nargs); argslist = newargslist; } for (int j = nargs; j > i; j--) { argslist[j] = argslist[j - 1]; } argslist[i] = args; nargs = nn; } @Override public PyObject __call__(PyObject self, PyObject[] args, String[] keywords) { ReflectedCallData callData = new ReflectedCallData(); ReflectedArgs match = null; for (int i = 0; i < nargs && match == null; i++) { // System.err.println(rargs.toString()); if (argslist[i].matches(self, args, keywords, callData)) { match = argslist[i]; } } if (match == null) { throwError(callData.errArg, args.length, self != null, keywords.length != 0); } Object cself = callData.self; Method m = (Method)match.data; // If this is a direct call to a Java class instance method with a PyProxy instance as the // arg, use the super__ version to actually route this through the method on the class. if (self == null && cself != null && cself instanceof PyProxy && !__name__.startsWith("super__") && match.declaringClass != cself.getClass()) { String mname = ("super__" + __name__); try { m = cself.getClass().getMethod(mname, m.getParameterTypes()); } catch (Exception e) { throw Py.JavaError(e); } } Object o; try { o = m.invoke(cself, callData.getArgsArray()); } catch (Throwable t) { throw Py.JavaError(t); } return Py.java2py(o); } @Override public PyObject __call__(PyObject[] args, String[] keywords) { PyObject self; if (calledStatically) { self = null; } else { PyObject[] unboundArgs = new PyObject[args.length - 1]; System.arraycopy(args, 1, unboundArgs, 0, unboundArgs.length); self = args[0]; args = unboundArgs; } return __call__(self, args, keywords); } // A bunch of code to make error handling prettier protected void throwError(String message) { throw Py.TypeError(__name__ + "(): " + message); } private static void addRange(StringBuilder buf, int min, int max, String sep) { if (buf.length() > 0) { buf.append(sep); } if (min < max) { buf.append(Integer.toString(min)).append('-').append(max); } else { buf.append(min); } } protected void throwArgCountError(int nArgs, boolean self) { // Assume no argument lengths greater than 40... boolean[] legalArgs = new boolean[40]; int maxArgs = -1; int minArgs = 40; for (int i = 0; i < nargs; i++) { ReflectedArgs rargs = argslist[i]; int l = rargs.args.length; if (!self && !rargs.isStatic) { l += 1; } legalArgs[l] = true; if (l > maxArgs) { maxArgs = l; } if (l < minArgs) { minArgs = l; } } StringBuilder buf = new StringBuilder(); int startRange = minArgs; int a = minArgs + 1; while (a < maxArgs) { if (legalArgs[a]) { a++; continue; } else { addRange(buf, startRange, a - 1, ", "); a++; while (a <= maxArgs) { if (legalArgs[a]) { startRange = a; break; } a++; } } } addRange(buf, startRange, maxArgs, " or "); throwError("expected " + buf + " args; got " + nArgs); } private static String ordinal(int n) { switch(n + 1){ case 0: return "self"; case 1: return "1st"; case 2: return "2nd"; case 3: return "3rd"; default: return Integer.toString(n + 1) + "th"; } } private static String niceName(Class<?> arg) { if (arg == String.class || arg == PyString.class) { return "String"; } if (arg.isArray()) { return niceName(arg.getComponentType()) + "[]"; } return arg.getName(); } protected void throwBadArgError(int errArg, int nArgs, boolean self) { Set<Class<?>> argTypes = Generic.set(); for (int i = 0; i < nargs; i++) { // This check works almost all the time. // I'm still a little worried about non-static methods called with an explicit self... if (argslist[i].args.length == nArgs || (!argslist[i].isStatic && !self && argslist[i].args.length == nArgs - 1)) { if (errArg == ReflectedCallData.UNABLE_TO_CONVERT_SELF) { argTypes.add(argslist[i].declaringClass); } else { argTypes.add(argslist[i].args[errArg]); } } } StringBuilder buf = new StringBuilder(); for (Class<?> arg : argTypes) { buf.append(niceName(arg)); buf.append(", "); } if (buf.length() > 2) { buf.setLength(buf.length() - 2); } throwError(ordinal(errArg) + " arg can't be coerced to " + buf); } protected void throwError(int errArg, int nArgs, boolean self, boolean keywords) { if (keywords) { throwError("takes no keyword arguments"); } else if (errArg == ReflectedCallData.BAD_ARG_COUNT) { throwArgCountError(nArgs, self); } else { throwBadArgError(errArg, nArgs, self); } } // Included only for debugging purposes... public void printArgs() { System.err.println("nargs: " + nargs); for (int i = 0; i < nargs; i++) { ReflectedArgs args = argslist[i]; System.err.println(args.toString()); } } @Override public String toString() { return "<java function " + __name__ + " " + Py.idstr(this) + ">"; } }