[Jython-checkins] jython: Comments and minor re-write of PyJavaType.handleMroError

jeff.allen jython-checkins at python.org
Fri Apr 19 13:32:03 EDT 2019


https://hg.python.org/jython/rev/262428b2c51a
changeset:   8237:262428b2c51a
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Fri Apr 19 17:43:45 2019 +0100
summary:
  Comments and minor re-write of PyJavaType.handleMroError

This is work in aid of bjo #2445, to make the logic clearer, without (I think)
changing it. It adds (but skips) a failing test that reproduces the continuing
problem with IBM classes related to MQ.

files:
  Lib/test/test_java_integration.py                        |   11 +-
  src/org/python/core/Py.java                              |   39 +-
  src/org/python/core/PyJavaType.java                      |  141 +++++----
  src/org/python/core/PyType.java                          |  140 +++++++--
  tests/java/org/python/tests/mro/EclipseChallengeMRO.java |   29 +-
  tests/java/org/python/tests/mro/IBMMQChallengeMRO.java   |   93 ++++++
  6 files changed, 309 insertions(+), 144 deletions(-)


diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py
--- a/Lib/test/test_java_integration.py
+++ b/Lib/test/test_java_integration.py
@@ -45,6 +45,7 @@
 from javatests.ProxyTests import NullToString, Person
 
 from clamp import SerializableProxies
+from unittest.case import skip
 
 
 
@@ -575,9 +576,15 @@
         self.assertEqual(set(m), set(["abc", "xyz"]))
         self.assertEqual(m["abc"], 42)
 
-    def test_diamond_lattice_inheritance(self):
+    def test_mro_eclipse(self):
         # http://bugs.jython.org/issue2445
-        from org.python.tests.mro import TortureMRO
+        from org.python.tests.mro import EclipseChallengeMRO
+
+    @unittest.skip("FIXME: see http://bugs.jython.org/issue2445")
+    def test_mro_ibmmq(self):
+        # http://bugs.jython.org/issue2445
+        from org.python.tests.mro import IBMMQChallengeMRO
+        t = IBMMQChallengeMRO.mq.jms.MQQueue
 
 
 def roundtrip_serialization(obj):
diff --git a/src/org/python/core/Py.java b/src/org/python/core/Py.java
--- a/src/org/python/core/Py.java
+++ b/src/org/python/core/Py.java
@@ -69,8 +69,7 @@
     /** A zero-length array of Strings to pass to functions that
     don't have any keyword arguments **/
     public final static String[] NoKeywords = new String[0];
-    /** A zero-length array of PyObject's to pass to functions that
-    expect zero-arguments **/
+    /** A zero-length array of PyObject's to pass to functions when we have no arguments **/
     public final static PyObject[] EmptyObjects = new PyObject[0];
     /** A frozenset with zero elements **/
     public final static PyFrozenSet EmptyFrozenSet = new PyFrozenSet();
@@ -2566,29 +2565,29 @@
         }
     }
 
+    /**
+     * Turn any Python iterable into an array of its elements.
+     *
+     * @param iterable to evaluate
+     * @return array of elements from iterable
+     */
     static PyObject[] make_array(PyObject iterable) {
         // Special-case the common tuple and list cases, for efficiency
         if (iterable instanceof PySequenceList) {
             return ((PySequenceList) iterable).getArray();
+        } else {
+            int n = 10;
+            if (!(iterable instanceof PyGenerator)) {
+                try {
+                    n = iterable.__len__(); // may be available, otherwise ...
+                } catch (PyException pye) { /* ... leave n at 0 */ }
+            }
+            List<PyObject> objs = new ArrayList<PyObject>(n);
+            for (PyObject item : iterable.asIterable()) {
+                objs.add(item);
+            }
+            return objs.toArray(Py.EmptyObjects);
         }
-
-        // Guess result size and allocate space. The typical make_array arg supports
-        // __len__, with one exception being generators, so avoid the overhead of an
-        // exception from __len__ in their case
-        int n = 10;
-        if (!(iterable instanceof PyGenerator)) {
-            try {
-                n = iterable.__len__();
-            } catch (PyException pye) {
-                // ok
-            }
-        }
-
-        List<PyObject> objs = new ArrayList<PyObject>(n);
-        for (PyObject item : iterable.asIterable()) {
-            objs.add(item);
-        }
-        return objs.toArray(Py.EmptyObjects);
     }
 
 //------------------------constructor-section---------------------------
diff --git a/src/org/python/core/PyJavaType.java b/src/org/python/core/PyJavaType.java
--- a/src/org/python/core/PyJavaType.java
+++ b/src/org/python/core/PyJavaType.java
@@ -1,5 +1,8 @@
 package org.python.core;
 
+import org.python.core.util.StringUtil;
+import org.python.util.Generic;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -27,7 +30,9 @@
 import java.util.Enumeration;
 import java.util.EventListener;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -35,9 +40,6 @@
 import java.util.Set;
 import java.util.Stack;
 
-import org.python.core.util.StringUtil;
-import org.python.util.Generic;
-
 public class PyJavaType extends PyType {
 
     private final static Class<?>[] OO = {PyObject.class, PyObject.class};
@@ -218,76 +220,88 @@
         postDelattr(name);
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * An override specifically for Java classes (that are not {@link PyObject}) has the possibility
+     * of completing the MRO in {@code mro}, by additional steps affecting the {@code mro} and
+     * {@code toMerge} passed in. This divergence from the Python rules is acceptable for Java.
+     */
     @Override
     void handleMroError(MROMergeState[] toMerge, List<PyObject> mro) {
+
         if (underlying_class != null) {
-            // If this descends from PyObject, don't do the Java mro cleanup
+            // This descends from PyObject (but is not exposed): don't attempt recovery.
             super.handleMroError(toMerge, mro);
         }
-        Set<PyJavaType> inConflict = Generic.set();
-        PyJavaType winner = null;
+
+        // Make a set of all the PyJavaTypes still in the lists to merge.
+        Set<PyJavaType> inConflict = new LinkedHashSet<>();
         for (MROMergeState mergee : toMerge) {
             for (int i = mergee.next; i < mergee.mro.length; i++) {
-                if (mergee.mro[i] == PyObject.TYPE
-                        || mergee.mro[i] == PyType.fromClass(Object.class)) {
-                    continue;
+                PyObject m = mergee.mro[i];
+                if (m instanceof PyJavaType && m != Constant.OBJECT) {
+                    inConflict.add((PyJavaType) m);
                 }
-                if (winner == null) {
-                    /*
-                     * Pick an arbitrary class to be added to the mro next and break the conflict.
-                     * If method name conflicts were allowed between methods added to Java types, it
-                     * would go first, but that's prevented, so being a winner doesn't actually get
-                     * it anything.
-                     */
-                    winner = (PyJavaType) mergee.mro[i];
-                }
-                inConflict.add((PyJavaType) mergee.mro[i]);
             }
         }
 
-        Set<String> allModified = Generic.set();
-        PyJavaType[] conflictedAttributes = inConflict.toArray(new PyJavaType[inConflict.size()]);
-        for (PyJavaType type : conflictedAttributes) {
-            if (type.modified == null) {
-                continue;
-            }
-            for (String method : type.modified) {
-                if (!allModified.add(method)) {
-                    // Another type in conflict has this method, possibly fail
-                    PyList types = new PyList();
-                    Set<Class<?>> proxySet = Generic.set();
-                    for (PyJavaType othertype : conflictedAttributes) {
+        /*
+         * Collect the names of all the methods added to any of these types (with certain
+         * exclusions) that occur in more than one of these residual types. If a name is found in
+         * more than one of these types, raise an error.
+         */
+        Set<String> allModified = new HashSet<>();
+        for (PyJavaType type : inConflict) {
+            if (type.modified != null) {
+                // For every method name modified in type ...
+                for (String method : type.modified) {
+                    if (!allModified.add(method)) {
                         /*
-                         * Ignore any pairings of types that are in a superclass/superinterface
-                         * relationship with each other. This problem is a false positive that
-                         * happens because of the automatic addition of methods so that Java classes
-                         * behave more like their corresponding Python types, such as adding sort or
-                         * remove. See http://bugs.jython.org/issue2445
+                         * The method name was already in the set, so has appeared already. Work out
+                         * which one that was by rescanning.
+                         */// XXX Why didn't we keep a map?
+                        List<PyJavaType> types = new ArrayList<>();
+                        Set<Class<?>> proxySet = new HashSet<>();
+                        Class<?> proxyType = type.getProxyType();
+                        for (PyJavaType othertype : inConflict) {
+                            /*
+                             * Ignore any pairings of types that are in a superclass/superinterface
+                             * relationship with each other. This problem is a false positive that
+                             * happens because of the automatic addition of methods so that Java
+                             * classes behave more like their corresponding Python types, such as
+                             * adding sort or remove. See http://bugs.jython.org/issue2445
+                             */
+                            if (othertype.modified != null && othertype.modified.contains(method)) {
+                                Class<?> otherProxyType = othertype.getProxyType();
+                                if (otherProxyType.isAssignableFrom(proxyType)) {
+                                    continue;
+                                } else if (proxyType.isAssignableFrom(otherProxyType)) {
+                                    continue;
+                                } else {
+                                    types.add(othertype);
+                                    proxySet.add(otherProxyType);
+                                }
+                            }
+                        }
+
+                        /*
+                         * Need to special case collections that implement both Iterable and Map.
+                         * Ignore the conflict in having duplicate __iter__ added (see
+                         * getCollectionProxies), while still allowing each path on the inheritance
+                         * hierarchy to get an __iter__. Annoying but necessary logic. See
+                         * http://bugs.jython.org/issue1878
                          */
-                        if (othertype.modified != null && othertype.modified.contains(method)
-                                && !(othertype.getProxyType().isAssignableFrom(type.getProxyType())
-                                        || type.getProxyType()
-                                                .isAssignableFrom(othertype.getProxyType()))) {
-                            types.add(othertype);
-                            proxySet.add(othertype.getProxyType());
+                        if (method.equals("__iter__")
+                                && Generic.set(Iterable.class, Map.class).containsAll(proxySet)) {
+                            continue;
                         }
-                    }
-                    /*
-                     * Need to special case collections that implement both Iterable and Map. Ignore
-                     * the conflict in having duplicate __iter__ added (see getCollectionProxies),
-                     * while still allowing each path on the inheritance hierarchy to get an
-                     * __iter__. Annoying but necessary logic. See http://bugs.jython.org/issue1878
-                     */
-                    if (method.equals("__iter__")
-                            && Generic.set(Iterable.class, Map.class).containsAll(proxySet)) {
-                        continue;
-                    }
 
-                    if (types.size() > 0) {
-                        throw Py.TypeError(String.format(
-                                "Supertypes that share a modified attribute "
-                                        + "have an MRO conflict[attribute=%s, supertypes=%s, type=%s]",
-                                method, types, this.getName()));
+                        String fmt = "Supertypes that share a modified attribute "
+                                + "have an MRO conflict[attribute=%s, supertypes=%s, type=%s]";
+                        if (types.size() > 0) {
+                            throw Py.TypeError(String.format(fmt, method, types, this.getName()));
+                        }
                     }
                 }
             }
@@ -297,7 +311,7 @@
          * We can keep trucking, there aren't any existing method name conflicts. Mark the conflicts
          * in all the classes so further method additions can check for trouble.
          */
-        for (PyJavaType type : conflictedAttributes) {
+        for (PyJavaType type : inConflict) {
             for (PyJavaType otherType : inConflict) {
                 if (otherType != type) {
                     if (type.conflicted == null) {
@@ -308,11 +322,18 @@
             }
         }
 
-        // Add our winner to the mro, clear the clog, and try to finish the rest
+        /*
+         * Emit the first conflicting type we encountered to the MRO, and remove it from the working
+         * lists. Forcing a step like this is ok for classes compiled from Java as the order of
+         * bases is not significant as long as hierarchy is preserved.
+         */
+        PyJavaType winner = inConflict.iterator().next();
         mro.add(winner);
         for (MROMergeState mergee : toMerge) {
             mergee.removeFromUnmerged(winner);
         }
+
+        // Restart the MRO generation algorithm from the current state.
         computeMro(toMerge, mro);
     }
 
diff --git a/src/org/python/core/PyType.java b/src/org/python/core/PyType.java
--- a/src/org/python/core/PyType.java
+++ b/src/org/python/core/PyType.java
@@ -1441,30 +1441,39 @@
         return (tp_flags & Py.TPFLAGS_IS_ABSTRACT) != 0;
     }
 
+    /**
+     * Set the {@link #mro} field from the Python {@code mro()} method which uses
+     * ({@link #computeMro()} by default. We must repeat this whenever the bases of this type
+     * change, which they may in general for classes defined in Python.
+     */
     private void mro_internal() {
+
         if (getType() == TYPE) {
-            mro = computeMro();
+            mro = computeMro(); // Shortcut
+
         } else {
+            // Use the mro() method, which may have been redefined to find the MRO as an array.
             PyObject mroDescr = getType().lookup("mro");
             if (mroDescr == null) {
                 throw Py.AttributeError("mro");
             }
             PyObject[] result = Py.make_array(mroDescr.__get__(null, getType()).__call__(this));
 
+            // Verify that Python types in the MRO have a "solid base" in common with this type.
             PyType solid = solid_base(this);
+
             for (PyObject cls : result) {
                 if (cls instanceof PyClass) {
                     continue;
-                }
-                if (!(cls instanceof PyType)) {
-                    throw Py.TypeError(String.format("mro() returned a non-class ('%.500s')",
-                            cls.getType().fastGetName()));
-                }
-                PyType t = (PyType) cls;
-                if (!solid.isSubType(solid_base(t))) {
-                    throw Py.TypeError(String.format(
-                            "mro() returned base with unsuitable layout " + "('%.500s')",
-                            t.fastGetName()));
+                } else if (cls instanceof PyType) {
+                    PyType t = (PyType) cls;
+                    if (!solid.isSubType(solid_base(t))) {
+                        String fmt = "mro() returned base with unsuitable layout ('%.500s')";
+                        throw Py.TypeError(String.format(fmt, t.fastGetName()));
+                    }
+                } else {
+                    String fmt = "mro() returned a non-class ('%.500s')";
+                    throw Py.TypeError(String.format(fmt, cls.getType().fastGetName()));
                 }
             }
             mro = result;
@@ -1605,14 +1614,20 @@
     }
 
     @ExposedMethod(defaults = "null", doc = BuiltinDocs.type_mro_doc)
-    final PyList type_mro(PyObject o) {
-        if (o == null) {
-            return new PyList(computeMro());
-        }
-        return new PyList(((PyType) o).computeMro());
+    final PyList type_mro(PyObject X) {
+        // This is either X.mro (where X is a type object) or type.mro(X)
+        PyObject[] res = (X == null) ? computeMro() : ((PyType) X).computeMro();
+        return new PyList(res);
     }
 
+    /**
+     * Examine the bases (which must contain no repetition) and the MROs of these bases and return
+     * the MRO of this class.
+     *
+     * @return the MRO of this class
+     */
     PyObject[] computeMro() {
+        // First check that there are no duplicates amongst the bases of this class.
         for (int i = 0; i < bases.length; i++) {
             PyObject cur = bases[i];
             for (int j = i + 1; j < bases.length; j++) {
@@ -1624,6 +1639,7 @@
             }
         }
 
+        // Build a table of the MROs of the bases as MROMergeState objects.
         MROMergeState[] toMerge = new MROMergeState[bases.length + 1];
         for (int i = 0; i < bases.length; i++) {
             toMerge[i] = new MROMergeState();
@@ -1633,14 +1649,27 @@
                 toMerge[i].mro = classic_mro((PyClass) bases[i]);
             }
         }
+
+        // Append to this table the list of bases of this class
         toMerge[bases.length] = new MROMergeState();
         toMerge[bases.length].mro = bases;
 
+        // The head of the output MRO is the current class itself.
         List<PyObject> mro = Generic.list();
         mro.add(this);
+
+        // Now execute the core of the MRO generation algorithm.
         return computeMro(toMerge, mro);
     }
 
+    /**
+     * Core algorithm for computing the MRO for "new-style" (although it's been a while) Python
+     * classes.
+     *
+     * @param toMerge data structure representing the (partly processed) MROs of bases.
+     * @param mro partial MRO (initially only this class)
+     * @return the MRO of this class
+     */
     PyObject[] computeMro(MROMergeState[] toMerge, List<PyObject> mro) {
         boolean addedProxy = false;
         Class<?> thisProxyAttr = this.getProxyType();
@@ -1693,12 +1722,17 @@
     }
 
     /**
-     * Must either throw an exception, or bring the merges in <code>toMerge</code> to completion by
-     * finishing filling in <code>mro</code>.
+     * This method is called when the {@link #computeMro(MROMergeState[], List)} reaches an impasse
+     * as far as its official algorithm is concerned, with the partial MRO and current state of the
+     * working lists at the point the problem is detected. The base implementation raises a Python
+     * {@code TypeError}, diagnosing the problem.
+     *
+     * @param toMerge partially processed algorithm state
+     * @param mro output MRO (incomplete)
      */
     void handleMroError(MROMergeState[] toMerge, List<PyObject> mro) {
         StringBuilder msg = new StringBuilder(
-                "Cannot create a consistent method resolution\n" + "order (MRO) for bases ");
+                "Cannot create a consistent method resolution\norder (MRO) for bases ");
         Set<PyObject> set = Generic.set();
         for (MROMergeState mergee : toMerge) {
             if (!mergee.isMerged()) {
@@ -1720,7 +1754,9 @@
     }
 
     /**
-     * Finds the parent of type with an underlying_class or with slots sans a __dict__ slot.
+     * Finds the first super-type of the given {@code type} that is a "solid base" of the type, that
+     * is, the returned type has an {@link #underlying_class}, or it defines {@code __slots__} and
+     * no instance level dictionary ({@code __dict__} attribute).
      */
     private static PyType solid_base(PyType type) {
         do {
@@ -1732,39 +1768,49 @@
         return PyObject.TYPE;
     }
 
+    /**
+     * A "solid base" is a type that has an {@link #underlying_class}, or defines {@code __slots__}
+     * and no instance level dictionary (no {@code __dict__} attribute).
+     */
     private static boolean isSolidBase(PyType type) {
         return type.underlying_class != null || (type.ownSlots != 0 && !type.needs_userdict);
     }
 
     /**
-     * Finds the base in bases with the most derived solid_base, ie the most base type
+     * Find the base selected from a {@link #bases} array that has the "most derived solid base".
+     * This will become the {@link #base} attribute of a class under construction, or in which the
+     * {@link #bases} tuple is being updated. On a successful return, the return value is one of the
+     * elements of the argument {@code bases} and the solid base of that return is a sub-type of the
+     * solid bases of all other (non-classic) elements of the argument.
      *
      * @throws Py.TypeError if the bases don't all derive from the same solid_base
      * @throws Py.TypeError if at least one of the bases isn't a new-style class
      */
     private static PyType best_base(PyObject[] bases) {
-        PyType winner = null;
-        PyType candidate = null;
-        PyType best = null;
-        for (PyObject base : bases) {
-            if (base instanceof PyClass) {
+        PyType best = null;         // The best base found so far
+        PyType bestSolid = null;    // The solid base of the best base so far
+        for (PyObject b : bases) {
+            if (b instanceof PyType) {
+                PyType base = (PyType) b, solid = solid_base(base);
+                if (bestSolid == null) {
+                    // First (non-classic) base we find becomes the best base so far
+                    best = base;
+                    bestSolid = solid;
+                } else if (bestSolid.isSubType(solid)) {
+                    // Current best is still the best so far
+                } else if (solid.isSubType(bestSolid)) {
+                    // base is better than the previous best since its solid base is more derived.
+                    best = base;
+                    bestSolid = solid;
+                } else {
+                    throw Py.TypeError("multiple bases have instance lay-out conflict");
+                }
+            } else if (b instanceof PyClass) {
+                // Skip over classic bases
                 continue;
-            }
-            if (!(base instanceof PyType)) {
+            } else {
                 throw Py.TypeError("bases must be types");
             }
-            candidate = solid_base((PyType) base);
-            if (winner == null) {
-                winner = candidate;
-                best = (PyType) base;
-            } else if (winner.isSubType(candidate)) {
-                ;
-            } else if (candidate.isSubType(winner)) {
-                winner = candidate;
-                best = (PyType) base;
-            } else {
-                throw Py.TypeError("multiple bases have instance lay-out conflict");
-            }
         }
         if (best == null) {
             throw Py.TypeError("a new-style class can't have only classic bases");
@@ -2603,6 +2649,20 @@
             }
             mro = newMro.toArray(new PyObject[newMro.size()]);
         }
+
+        @Override
+        public String toString() {
+            List<String> names = Generic.list();
+            for (int i = next; i < mro.length; i++) {
+                PyObject t = mro[i];
+                if (t instanceof PyType) {
+                    names.add(((PyType) t).name);
+                } else {
+                    names.add(t.toString());
+                }
+            }
+            return names.toString();
+        }
     }
 
     /**
diff --git a/tests/java/org/python/tests/mro/TortureMRO.java b/tests/java/org/python/tests/mro/EclipseChallengeMRO.java
rename from tests/java/org/python/tests/mro/TortureMRO.java
rename to tests/java/org/python/tests/mro/EclipseChallengeMRO.java
--- a/tests/java/org/python/tests/mro/TortureMRO.java
+++ b/tests/java/org/python/tests/mro/EclipseChallengeMRO.java
@@ -1,4 +1,4 @@
-// Copyright (c) Jython Developers.
+// Copyright (c)2019 Jython Developers.
 // Licensed to the Python Software Foundation under a Contributor Agreement.
 
 package org.python.tests.mro;
@@ -6,46 +6,31 @@
 /**
  * A class providing interface and abstract class relationships that approximate the structure of
  * org.eclipse.emf.ecore.util.DelegatingFeatureMap, in order to exercise b.j.o issue 2445. The
- * complex inheritance (more complex than diamond inheritance) confused PyJavaType handling of the
- * MRO. This class is imported by
- * {@code test_java_integration.JavaMROTest.test_diamond_lattice_inheritance} as a test.
+ * complex inheritance confused PyJavaType handling of the MRO. This class is imported by
+ * {@code test_java_integration.JavaMROTest.test_mro_eclipse} as a test.
  * <p>
- * An invocation at the prompt (for debugging use), and output before the fix, is:
- *
- * <pre>
- * PS > dist\bin\jython -S -c"from org.python.tests.mro import TortureMRO"
+ * An invocation at the prompt (for debugging use), and output before the fix, is: <pre>
+ * PS > dist\bin\jython -S -c"from org.python.tests.mro import EclipseChallengeMRO"
  * Traceback (most recent call last):
  *   File "<string>", line 1, in <module>
  * TypeError: Supertypes that share a modified attribute have an MRO
  * conflict[attribute=sort, supertypes=[<type 'java.util.List'>,
- * <type 'java.util.AbstractList'>], type=TortureMRO$Target]
+ * <type 'java.util.AbstractList'>], type=EclipseChallengeMRO$Target]
  * </pre>
  */
-public class TortureMRO {
+public class EclipseChallengeMRO {
 
     interface Entry {} // Was FeatureMap.Entry
-
     interface Thing {} // Was EStructuralFeature.Setting
-
     interface EList<E> extends java.util.List<E> {}
-
     interface IEList<E> extends EList<E> {}
-
     interface FList extends EList<Entry> {} // Was FeatureMap
-
     interface FIEListThing extends FList, IEList<Entry>, Thing {}  // Was 2 FeatureMap.Internal
-
     abstract static class AbstractEList<E> extends java.util.AbstractList<E> implements EList<E> {}
-
     abstract static class DEList<E> extends AbstractEList<E> {}
-
     interface NList<E> extends EList<E> {}
-
     abstract static class DNListImpl<E> extends DEList<E> implements NList<E> {}
-
     abstract static class DNIEListImpl<E> extends DNListImpl<E> implements IEList<E> {}
-
     abstract static class DNIEListThing<E> extends DNIEListImpl<E> implements Thing {}
-
     public abstract static class Target extends DNIEListThing<Entry> implements FIEListThing {}
 }
diff --git a/tests/java/org/python/tests/mro/IBMMQChallengeMRO.java b/tests/java/org/python/tests/mro/IBMMQChallengeMRO.java
new file mode 100644
--- /dev/null
+++ b/tests/java/org/python/tests/mro/IBMMQChallengeMRO.java
@@ -0,0 +1,93 @@
+// Copyright (c)2019 Jython Developers.
+// Licensed to the Python Software Foundation under a Contributor Agreement.
+
+package org.python.tests.mro;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * A class providing interface and abstract class relationships that approximate the structure of
+ * com.ibm classes related to MQ, in order to exercise b.j.o issue 2445. The complex inheritance
+ * confused PyJavaType handling of the MRO. This class is imported by
+ * {@code test_java_integration.JavaMROTest.test_mro_ibmmq}.
+ * <p>
+ * An invocation at the prompt (for debugging use), and output before the fix, is: <pre>
+ * PS > dist\bin\jython -S -c "from org.python.tests.mro import IBMMQChallengeMRO; t=m.mq.jms.MQQueue"
+ * Traceback (most recent call last):
+ *   File "<string>", line 1, in <module>
+ * TypeError: Supertypes that share a modified attribute have an MRO conflict
+ * [attribute=get,supertypes=[<type
+ * 'org.python.tests.mro.IBMMQChallengeMRO$msg$client$jms$internal$JmsPropertyContextImpl'>],
+ * type=IBMMQChallengeMRO$mq$jms$MQQueue]
+ * </pre>
+ */
+public class IBMMQChallengeMRO {
+
+    static class javax_jms { // represents javax.jms
+
+        public interface Queue extends Destination {}
+        public interface Destination {}
+    }
+
+    static class javax_naming { // represents javax.naming
+
+        public interface Referenceable {}
+    }
+
+    static class msg { // represents com.ibm.msg
+
+        static class client { // represents com.ibm.msg.client
+
+            static class jms { // represents com.ibm.msg.client.jms
+
+                public interface JmsDestination extends JmsPropertyContext, javax_jms.Destination {}
+                public interface JmsPropertyContext
+                        extends JmsReadablePropertyContext, Map<String, Object> {}
+                public interface JmsReadablePropertyContext extends Serializable {}
+                public interface JmsQueue extends JmsDestination, javax_jms.Queue {}
+
+                static class admin { // represents com.ibm.msg.client.jms.admin
+
+                    public static abstract/* ? */ class JmsJndiDestinationImpl
+                            extends JmsDestinationImpl
+                            implements JmsDestination, javax_naming.Referenceable, Serializable {}
+                    public static abstract/* ? */ class JmsDestinationImpl
+                            extends internal.JmsPropertyContextImpl implements JmsDestination {}
+                }
+
+                static class internal { // represents com.ibm.msg.client.jms.internal
+
+                    public static abstract/* ? */ class JmsPropertyContextImpl
+                            extends JmsReadablePropertyContextImpl implements JmsPropertyContext,
+                            provider.ProviderPropertyContextCallback {}
+                    public static abstract class JmsReadablePropertyContextImpl
+                            implements JmsReadablePropertyContext {}
+                }
+            }
+
+            static class provider { // represents com.ibm.msg.client.provider
+
+                public interface ProviderPropertyContextCallback {}
+            }
+        }
+    }
+
+    static class jms { // represents com.ibm.jms
+
+        public interface JMSDestination extends javax_jms.Destination {}
+    }
+
+    public static class mq { // represents com.ibm.mq
+
+        public static class jms { // represents com.ibm.mq.jms
+
+            public abstract/* ? */ class MQDestination
+                    extends msg.client.jms.admin.JmsJndiDestinationImpl implements
+                    javax_jms.Destination, IBMMQChallengeMRO.jms.JMSDestination, Serializable {}
+            /** Target class in the test **/
+            public abstract/* ? */ class MQQueue extends MQDestination implements javax_jms.Queue,
+                    msg.client.jms.JmsQueue, javax_naming.Referenceable, Serializable {}
+        }
+    }
+}

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list