Index: src/org/python/core/PyType.java =================================================================== --- src/org/python/core/PyType.java (revision 6675) +++ src/org/python/core/PyType.java (working copy) @@ -1055,18 +1055,35 @@ return null; } for (PyObject t : mro) { - PyObject dict = t.fastGetDict(); - if (dict != null) { - PyObject obj = dict.__finditem__(name); - if (obj != null) { - where[0] = t; - return obj; - } + PyObject obj; + if (t instanceof PyType) { + obj = ((PyType)t).lookup_for(name, this); + } else { + obj = performLookup(t, name, this); } + if (obj != null) { + where[0] = t; + return obj; + } } return null; } + protected PyObject lookup_for(String name, PyType lookuper) { + return performLookup(this, name, lookuper); + } + + protected static PyObject performLookup(PyObject lookupee, + String name, + PyType lookuper) { + PyObject dict = lookupee.fastGetDict(); + if (dict == null) { + return null; + } + return dict.__finditem__(name); + } + + public PyObject super_lookup(PyType ref, String name) { PyObject[] mro = this.mro; if (mro == null) { Index: src/org/python/core/PyJavaType.java =================================================================== --- src/org/python/core/PyJavaType.java (revision 6675) +++ src/org/python/core/PyJavaType.java (working copy) @@ -179,6 +179,7 @@ name = name.substring("org.python.core.Py".length()).toLowerCase(); } dict = new PyStringMap(); + boolean isPyProxy = false; Class baseClass = forClass.getSuperclass(); if (PyObject.class.isAssignableFrom(forClass)) { // Non-exposed subclasses of PyObject use a simple linear mro to PyObject that ignores @@ -197,6 +198,9 @@ // Don't show the interfaces added by proxy type construction; otherwise Python // subclasses of proxy types and another Java interface can't make a consistent // mro + if (iface == PyProxy.class) { + isPyProxy = true; + } continue; } if (baseClass != null && iface.isAssignableFrom(baseClass)) { @@ -233,13 +237,33 @@ Map props = Generic.map(); Map events = Generic.map(); Method[] methods; - if (Options.respectJavaAccessibility) { + + if (isPyProxy) { + // Grab the __supernames__ list from the proxy's classDictInit, and use that to expose + // only the methods proxied to expose superclass methods. Everything else is already + // exposed by the classes the proxy is extending. + PyStringMap classDict = new PyStringMap(); + callClassDictInit(forClass, classDict); + PyObject supers = classDict.__finditem__("__supernames__"); + Set superMethodNames = Generic.set(); + for (PyObject supername : supers.asIterable()) { + superMethodNames.add(supername.asString()); + } + + List superMethods = Generic.list(); + for (Method method : forClass.getMethods()) { + if (superMethodNames.contains(method.getName())) { + superMethods.add(method); + } + } + methods = superMethods.toArray(new Method[superMethods.size()]); + } else if (Options.respectJavaAccessibility) { // returns just the public methods methods = forClass.getMethods(); } else { // Grab all methods on this class and all of its superclasses and make them accessible List allMethods = Generic.list(); - for(Class c = forClass; c != null; c = c.getSuperclass()) { + for (Class c = forClass; c != null; c = c.getSuperclass()) { for (Method meth : c.getDeclaredMethods()) { allMethods.add(meth); meth.setAccessible(true); @@ -331,7 +355,6 @@ } } } - // Add superclass methods for (Method meth : methods) { String nmethname = normalize(meth.getName()); @@ -441,11 +464,10 @@ // If the parent bean is hiding a static field, we need it as well. prop.field = superProp.field; } - } else if (superForName != null && fromType[0] != this && !(superForName instanceof PyBeanEvent)) { - // There is already an entry for this name - // It came from a type which is not @this; it came from a superclass - // It is not a bean event - // Do not override methods defined in superclass + } else if (superForName != null && fromType[0] != this && + !(superForName instanceof PyBeanEvent)) { + // There is already an entry for this name from a superclass that isn't a bean + // event. Do not override methods defined in superclass continue; } // If the return types on the set and get methods for a property don't agree, the get @@ -494,17 +516,7 @@ } } if (ClassDictInit.class.isAssignableFrom(forClass) && forClass != ClassDictInit.class) { - try { - Method m = forClass.getMethod("classDictInit", PyObject.class); - m.invoke(null, dict); - // allow the class to override its name after it is loaded - PyObject nameSpecified = dict.__finditem__("__name__"); - if (nameSpecified != null) { - name = nameSpecified.toString(); - } - } catch (Exception exc) { - throw Py.JavaError(exc); - } + callClassDictInit(forClass, dict); } if (baseClass != Object.class) { has_set = getDescrMethod(forClass, "__set__", OO) != null @@ -571,6 +583,50 @@ } } + @Override + protected PyObject lookup_for(String name, PyType lookuper) { + PyObject result = super.lookup_for(name, lookuper); + // If result is an abstract method and this isn't the interface or abstract class on which + // it was defined, we need to check if it's been implemented by a class between the lookuper + // and the declaring class. If the lookuper defined it itself, it would've already been + // found. By not returning abstract methods, Python code later in the mro is given a chance + // to handle the call. See test_java_extension_ordering in test_java_visibility for code + // doing that. + if (result instanceof PyReflectedFunction && ((PyReflectedFunction)result).fromAbstract && + lookuper != this) { + Class proxy = (Class)lookuper.javaProxy; + if (proxy != null) {// Proxy can be null if this type is still under construction + Class proxySuper = proxy.getSuperclass(); + // If our superclass isn't abstract, we're guaranteed to have implemented this. + // It doesn't follow that if our superclass is abstract that the method hasn't been + // implemented by a non-visible class in our hierarchy, but there's no way to detect + // that, and it requires a really convoluted inheritance hierarchy to produce it. + // However that means this code is slightly broken. See + // test_hidden_implementation_methods_visible in test_java_visibility for an + // example. + if (Modifier.isAbstract(proxy.getSuperclass().getModifiers()) + || proxySuper == Object.class) { + return null; + } + } + } + return result; + } + + private void callClassDictInit(Class forClass, PyObject dict) { + try { + Method m = forClass.getMethod("classDictInit", PyObject.class); + m.invoke(null, dict); + // allow the class to override its name after it is loaded + PyObject nameSpecified = dict.__finditem__("__name__"); + if (nameSpecified != null) { + name = nameSpecified.toString(); + } + } catch (Exception exc) { + throw Py.JavaError(exc); + } + } + /** * Private, protected or package protected classes that implement public interfaces or extend * public classes can't have their implementations of the methods of their supertypes called Index: src/org/python/core/ClassDictInit.java =================================================================== --- src/org/python/core/ClassDictInit.java (revision 6675) +++ src/org/python/core/ClassDictInit.java (working copy) @@ -12,7 +12,7 @@ * * The method will be called when the class is initialized. The * method can then make changes to the class's __dict__ instance, - * f.example be removing method that should not be avaiable in python + * f.example be removing method that should not be available in python * or by replacing some method with high performance versions. */ Index: src/org/python/core/PyReflectedFunction.java =================================================================== --- src/org/python/core/PyReflectedFunction.java (revision 6675) +++ src/org/python/core/PyReflectedFunction.java (working copy) @@ -17,6 +17,8 @@ public int nargs; + public boolean fromAbstract; + protected PyReflectedFunction(String name) { __name__ = name; } @@ -109,6 +111,7 @@ // library they're using happens to have a method like this. } } + fromAbstract = fromAbstract || Modifier.isAbstract(m.getModifiers()); addArgs(makeArgs(m)); } Index: tests/java/org/python/tests/HiddenImplementation.java =================================================================== --- tests/java/org/python/tests/HiddenImplementation.java (revision 0) +++ tests/java/org/python/tests/HiddenImplementation.java (revision 0) @@ -0,0 +1,20 @@ +package org.python.tests; + +/** + * Used by test_java_visibility + */ +public class HiddenImplementation { + public interface IFace { + String hi(); + } + + private static class Implementor implements IFace { + public String hi() { + return "hi"; + } + } + + public static class Subclass extends Implementor {} + + public static abstract class AbstractSubclass extends Implementor {} +} Index: tests/java/org/python/tests/Runner.java =================================================================== --- tests/java/org/python/tests/Runner.java (revision 0) +++ tests/java/org/python/tests/Runner.java (revision 0) @@ -0,0 +1,9 @@ +package org.python.tests; + + +public class Runner { + + public static void run(Runnable runnable) { + runnable.run(); + } +} Index: Lib/test/test_java_visibility.py =================================================================== --- Lib/test/test_java_visibility.py (revision 6675) +++ Lib/test/test_java_visibility.py (working copy) @@ -3,10 +3,11 @@ import subprocess import sys from test import test_support -from java.lang import Byte, Class, Integer +from java.lang import Byte, Class, Integer, Runnable from java.util import ArrayList, Collections, HashMap, LinkedList, Observable, Observer -from org.python.tests import (Coercions, HiddenSuper, InterfaceCombination, Invisible, Matryoshka, - OnlySubclassable, OtherSubVisible, SomePyMethods, SubVisible, Visible, VisibleOverride) +from org.python.tests import (Coercions, HiddenImplementation, HiddenSuper, InterfaceCombination, + Invisible, Matryoshka, OnlySubclassable, OtherSubVisible, Runner, SomePyMethods, + SubVisible, Visible, VisibleOverride) from org.python.tests import VisibilityResults as Results from org.python.tests.RedundantInterfaceDeclarations import (Implementation, ExtraClass, ExtraString, ExtraStringAndClass, ExtraClassAndString) @@ -138,6 +139,18 @@ '''Bug #222847 - Can't access public member of package private base class''' self.assertEquals("hi", HiddenSuper().hi()) + def test_hidden_implementation_methods_visible(self): + class SubImpl(HiddenImplementation.Subclass): + pass + self.assertEquals("hi", HiddenImplementation.Subclass().hi()) + self.assertEquals("hi", SubImpl().hi()) + class SubAbstractImpl(HiddenImplementation.AbstractSubclass): + pass + # This is broken. PyJavaType checks on the superclass being abstract to determine if a + # method is necessarily implemented by a superclass. Since AbstractSubclass is abstract, + # PyJavaType thinks hi isn't implemented even though it is further up the hierarchy + #self.assertEquals("hi", SubAbstractImpl().hi()) + def test_nested_classes(self): """Test deeply nested classes @@ -167,6 +180,28 @@ self.assertEquals("int", instance.call(7)) self.assertEquals("Class", instance.call(LinkedList)) + def test_java_extension_ordering(self): + from_run = [] + class PyRun(object): + def run(self): + from_run.append("Running") + + class RunnableFirst(Runnable, PyRun): + pass + + class PyRunFirst(PyRun, Runnable): + pass + + def check_run(cls): + runnable = cls() + runnable.run() + Runner.run(runnable) + self.assertEquals(['Running', 'Running'], from_run) + del from_run[:] + check_run(PyRunFirst) + check_run(RunnableFirst) + + class JavaClassTest(unittest.TestCase): def test_class_methods_visible(self): self.assertFalse(HashMap.isInterface(),