From: <cg...@us...> - 2009-01-11 06:04:17
|
Revision: 5916 http://jython.svn.sourceforge.net/jython/?rev=5916&view=rev Author: cgroves Date: 2009-01-11 06:04:11 +0000 (Sun, 11 Jan 2009) Log Message: ----------- Convert to PyObject going into and Object coming out of java.util.Set methods on PySet like PyDictionary does for Map methods and PyList does for List methods. Without this, Java code can add non-PyObjects to the underlying set causing the Python access to it to throw ClassCastExceptions. Modified Paths: -------------- trunk/jython/src/org/python/core/BaseSet.java trunk/jython/src/org/python/core/PyFrozenSet.java trunk/jython/src/org/python/core/PyIterator.java trunk/jython/src/org/python/core/PyJavaType.java trunk/jython/src/org/python/core/PySet.java trunk/jython/src/org/python/core/PyType.java trunk/jython/src/org/python/util/Generic.java trunk/jython/tests/java/javatests/ListTest.java Added Paths: ----------- trunk/jython/Lib/test/test_set_jy.py trunk/jython/tests/java/javatests/PySetInJavaTest.java Removed Paths: ------------- trunk/jython/src/org/python/core/PySetIterator.java Added: trunk/jython/Lib/test/test_set_jy.py =================================================================== --- trunk/jython/Lib/test/test_set_jy.py (rev 0) +++ trunk/jython/Lib/test/test_set_jy.py 2009-01-11 06:04:11 UTC (rev 5916) @@ -0,0 +1,35 @@ +from test import test_support +import unittest + +from java.util import Random +from javatests import PySetInJavaTest + +class SetInJavaTest(unittest.TestCase): + "Tests for derived dict behaviour" + def test_using_PySet_as_Java_Set(self): + PySetInJavaTest.testPySetAsJavaSet() + + def test_accessing_items_added_in_java(self): + s = PySetInJavaTest.createPySetContainingJavaObjects() + for v in s: + self.assert_(v in s) + if isinstance(v, unicode): + self.assertEquals("value", v) + else: + v.nextInt()#Should be a java.util.Random; ensure we can call it + + def test_java_accessing_items_added_in_python(self): + # Test a type that should be coerced into a Java type, a Java instance + # that should be wrapped, and a Python instance that should pass + # through as itself with str, Random and tuple respectively. + s = set(["value", Random(), ("tuple", "of", "stuff")]) + PySetInJavaTest.accessAndRemovePySetItems(s) + self.assertEquals(0, len(s))# Check that the Java removal affected the underlying set + + + +def test_main(): + test_support.run_unittest(SetInJavaTest) + +if __name__ == '__main__': + test_main() Modified: trunk/jython/src/org/python/core/BaseSet.java =================================================================== --- trunk/jython/src/org/python/core/BaseSet.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/src/org/python/core/BaseSet.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -1,41 +1,35 @@ package org.python.core; +import java.lang.reflect.Array; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.Set; public abstract class BaseSet extends PyObject implements Set { /** The underlying Set. */ - protected Set _set; + protected Set<PyObject> _set; /** * Create a new Python set instance from the specified Set object. - * - * @param set An Set object. */ - protected BaseSet(Set set) { + protected BaseSet(Set<PyObject> set) { _set = set; } - protected BaseSet(PyType type, Set set) { + protected BaseSet(PyType type, Set<PyObject> set) { super(type); _set = set; } - protected void _update(PyObject data) throws PyIgnoreMethodTag { + protected void _update(PyObject data) { _update(_set, data); } /** * Update the underlying set with the contents of the iterable. - * - * @param data An iterable instance. - * @throws PyIgnoreMethodTag Ignore. */ - protected static Set _update(Set set, PyObject data) throws PyIgnoreMethodTag { + protected static Set<PyObject> _update(Set<PyObject> set, PyObject data) { if (data == null) { return set; } @@ -53,7 +47,7 @@ /** * The union of <code>this</code> with <code>other</code>. <p/> <br/> (I.e. all elements * that are in either set) - * + * * @param other * A <code>BaseSet</code> instance. * @return The union of the two sets as a new set. @@ -115,9 +109,9 @@ final PyObject baseset_difference(PyObject other) { BaseSet bs = (other instanceof BaseSet) ? (BaseSet)other : new PySet(other); - Set set = bs._set; + Set<PyObject> set = bs._set; BaseSet o = BaseSet.makeNewSet(getType()); - for (Object p : _set) { + for (PyObject p : _set) { if (!set.contains(p)) { o._set.add(p); } @@ -153,12 +147,12 @@ final PyObject baseset_symmetric_difference(PyObject other) { BaseSet bs = (other instanceof BaseSet) ? (BaseSet)other : new PySet(other); BaseSet o = BaseSet.makeNewSet(getType()); - for (Object p : _set) { + for (PyObject p : _set) { if (!bs._set.contains(p)) { o._set.add(p); } } - for (Object p : bs._set) { + for (PyObject p : bs._set) { if (!_set.contains(p)) { o._set.add(p); } @@ -206,7 +200,22 @@ } final PyObject baseset___iter__() { - return new PySetIterator(_set); + return new PyIterator() { + private int size = _set.size(); + + private Iterator<PyObject> iterator = _set.iterator(); + + @Override + public PyObject __iternext__() { + if (_set.size() != size) { + throw Py.RuntimeError("set changed size during iteration"); + } + if (iterator.hasNext()) { + return iterator.next(); + } + return null; + } + }; } public boolean __contains__(PyObject other) { @@ -257,7 +266,7 @@ } final PyObject baseset___le__(PyObject other) { - BaseSet bs = _binary_sanity_check(other); + _binary_sanity_check(other); return baseset_issubset(other); } @@ -266,7 +275,7 @@ } final PyObject baseset___ge__(PyObject other) { - BaseSet bs = _binary_sanity_check(other); + _binary_sanity_check(other); return baseset_issuperset(other); } @@ -299,7 +308,7 @@ public PyObject __reduce__() { return baseset___reduce__(); } - + final PyObject baseset___reduce__(){ PyObject args = new PyTuple(new PyList((PyObject)this)); PyObject dict = __findattr__("__dict__"); @@ -309,19 +318,6 @@ return new PyTuple(getType(), args, dict); } - /** - * Return this instance as a Java object. Only coerces to Collection and subinterfaces. - * - * @param c The Class to coerce to. - * @return the underlying HashSet (not a copy) - */ - public Object __tojava__(Class c) { - if (Collection.class.isAssignableFrom(c)) { - return Collections.unmodifiableSet(_set); - } - return super.__tojava__(c); - } - final PyObject baseset_union(PyObject other) { BaseSet result = BaseSet.makeNewSet(getType(), this); result._update(other); @@ -388,8 +384,8 @@ return name + "(...)"; } StringBuilder buf = new StringBuilder(name).append("(["); - for (Iterator i = _set.iterator(); i.hasNext();) { - buf.append(((PyObject)i.next()).__repr__().toString()); + for (Iterator<PyObject> i = _set.iterator(); i.hasNext();) { + buf.append((i.next()).__repr__().toString()); if (i.hasNext()) { buf.append(", "); } @@ -408,20 +404,20 @@ } /** - * Return a PyFrozenSet whose contents are shared with value when - * value is a BaseSet and pye is a TypeError. + * Return a PyFrozenSet whose contents are shared with value when value is a BaseSet and pye is + * a TypeError. * - * WARNING: The PyFrozenSet returned is only intended to be used - * temporarily (and internally); since its contents are shared - * with value, it could be mutated! - * - * This is better than special-casing behavior based on - * isinstance, because a Python subclass can override, say, - * __hash__ and all of a sudden you can't assume that a - * non-PyFrozenSet is unhashable anymore. + * WARNING: The PyFrozenSet returned is only intended to be used temporarily (and internally); + * since its contents are shared with value, it could be mutated! * - * @param pye The exception thrown from a hashable operation. - * @param value The object which was unhashable. + * This is better than special-casing behavior based on isinstance, because a Python subclass + * can override, say, __hash__ and all of a sudden you can't assume that a non-PyFrozenSet is + * unhashable anymore. + * + * @param pye + * The exception thrown from a hashable operation. + * @param value + * The object which was unhashable. * @return A PyFrozenSet if appropriate, otherwise the pye is rethrown */ protected final PyFrozenSet asFrozen(PyException pye, PyObject value) { @@ -442,7 +438,7 @@ protected static BaseSet makeNewSet(PyType type) { return makeNewSet(type, null); } - + /** * Create a new <et of type from iterable. * @@ -477,43 +473,89 @@ return _set.isEmpty(); } - public Object[] toArray() { - return _set.toArray(); - } - public boolean add(Object o) { - return _set.add(o); + return _set.add(Py.java2py(o)); } public boolean contains(Object o) { - return _set.contains(o); + return _set.contains(Py.java2py(o)); } public boolean remove(Object o) { - return _set.remove(o); + return _set.remove(Py.java2py(o)); } public boolean addAll(Collection c) { - return _set.addAll(c); + boolean added = false; + for (Object object : c) { + added |= add(object); + } + return added; } public boolean containsAll(Collection c) { - return _set.containsAll(c); + for (Object object : c) { + if (!_set.contains(Py.java2py(object))) { + return false; + } + } + return true; } public boolean removeAll(Collection c) { - return _set.removeAll(c); + boolean removed = false; + for (Object object : c) { + removed |= _set.remove(Py.java2py(object)); + } + return removed; } public boolean retainAll(Collection c) { - return _set.retainAll(c); + boolean modified = false; + Iterator e = iterator(); + while (e.hasNext()) { + if (!c.contains(e.next())) { + e.remove(); + modified = true; + } + } + return modified; } public Iterator iterator() { - return _set.iterator(); + return new Iterator() { + Iterator<PyObject> real = _set.iterator(); + + public boolean hasNext() { + return real.hasNext(); + } + + public Object next() { + return Py.tojava(real.next(), Object.class); + } + + public void remove() { + real.remove(); + } + }; } + public Object[] toArray() { + return toArray(new Object[size()]); + } + public Object[] toArray(Object a[]) { - return _set.toArray(a); + int size = size(); + if (a.length < size) { + a = (Object[])Array.newInstance(a.getClass().getComponentType(), size); + } + Iterator<PyObject> it = iterator(); + for (int i = 0; i < size; i++) { + a[i] = it.next(); + } + if (a.length > size) { + a[size] = null; + } + return a; } } Modified: trunk/jython/src/org/python/core/PyFrozenSet.java =================================================================== --- trunk/jython/src/org/python/core/PyFrozenSet.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/src/org/python/core/PyFrozenSet.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -11,9 +11,9 @@ @ExposedType(name = "frozenset", base = PyObject.class) public class PyFrozenSet extends BaseSet { - + public static final PyType TYPE = PyType.fromClass(PyFrozenSet.class); - + public PyFrozenSet() { super(new HashSet<PyObject>()); } @@ -204,8 +204,8 @@ } public Iterator iterator() { + final Iterator i = super.iterator(); return new Iterator() { - Iterator i = _set.iterator(); public boolean hasNext() { return i.hasNext(); Modified: trunk/jython/src/org/python/core/PyIterator.java =================================================================== --- trunk/jython/src/org/python/core/PyIterator.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/src/org/python/core/PyIterator.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -2,21 +2,18 @@ package org.python.core; /** - * An abstract helper class usefull when implementing an iterator object. This - * implementation supply a correct __iter__() and a next() method based on the - * __iternext__() implementation. The __iternext__() method must be supplied by - * the subclass. + * An abstract helper class useful when implementing an iterator object. This implementation supply + * a correct __iter__() and a next() method based on the __iternext__() implementation. The + * __iternext__() method must be supplied by the subclass. * - * If the implementation raises a StopIteration exception, it should be stored - * in stopException so the correct exception can be thrown to preserve the line - * numbers in the traceback. + * If the implementation raises a StopIteration exception, it should be stored in stopException so + * the correct exception can be thrown to preserve the line numbers in the traceback. */ public abstract class PyIterator extends PyObject { protected PyException stopException; - public PyIterator() { - } + public PyIterator() {} public PyIterator(PyType subType) { super(subType); Modified: trunk/jython/src/org/python/core/PyJavaType.java =================================================================== --- trunk/jython/src/org/python/core/PyJavaType.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/src/org/python/core/PyJavaType.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -87,8 +87,7 @@ bases[i] = PyType.fromClass(underlying_class.getInterfaces()[i - 1]); } Set<PyObject> seen = Generic.set(); - List<PyObject> mros = Generic.list(); - mros.add(this); + List<PyObject> mros = Generic.list(this); for (PyObject obj : bases) { for (PyObject mroObj : ((PyType)obj).mro) { if (seen.add(mroObj)) { Modified: trunk/jython/src/org/python/core/PySet.java =================================================================== --- trunk/jython/src/org/python/core/PySet.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/src/org/python/core/PySet.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -13,7 +13,7 @@ public class PySet extends BaseSet { public static final PyType TYPE = PyType.fromClass(PySet.class); - + public PySet() { super(new ConcurrentHashSet<PyObject>()); } @@ -25,7 +25,7 @@ public PySet(PyObject data) { super(_update(new ConcurrentHashSet<PyObject>(), data)); } - + @ExposedNew @ExposedMethod final void set___init__(PyObject[] args, String[] kwds) { @@ -40,7 +40,7 @@ _set.clear(); _update(args[0]); } - + @ExposedMethod(type = MethodType.BINARY) final PyObject set___cmp__(PyObject o) { return new PyInteger(baseset___cmp__(o)); @@ -181,7 +181,7 @@ return set___iand__(other); } - @ExposedMethod(type = MethodType.BINARY) + @ExposedMethod(type = MethodType.BINARY) final PyObject set___iand__(PyObject other) { if (!(other instanceof BaseSet)) { return null; @@ -281,8 +281,7 @@ } BaseSet bs = (other instanceof BaseSet) ? (BaseSet)other : new PySet(other); - for (Iterator iterator = bs._set.iterator(); iterator.hasNext();) { - Object o = iterator.next(); + for (PyObject o : bs._set) { if (_set.contains(o)) { _set.remove(o); } else { Deleted: trunk/jython/src/org/python/core/PySetIterator.java =================================================================== --- trunk/jython/src/org/python/core/PySetIterator.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/src/org/python/core/PySetIterator.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -1,52 +0,0 @@ -package org.python.core; - -import java.util.Iterator; -import java.util.Set; - -public class PySetIterator extends PyObject { - - private Set set; - - private int size; - - private Iterator<PyObject> iterator; - - public PySetIterator(Set set) { - super(); - this.set = set; - size = set.size(); - iterator = set.iterator(); - } - - public PyObject __iter__() { - return this; - } - - /** - * Returns the next item in the iteration or raises a StopIteration. - * - * @return the next item in the iteration - */ - public PyObject next() { - PyObject o = this.__iternext__(); - if (o == null) { - throw Py.StopIteration(""); - } - return o; - } - - /** - * Returns the next item in the iteration. - * - * @return the next item in the iteration or null to signal the end of the iteration - */ - public PyObject __iternext__() { - if (set.size() != size) { - throw Py.RuntimeError("set changed size during iteration"); - } - if (iterator.hasNext()) { - return iterator.next(); - } - return null; - } -} Modified: trunk/jython/src/org/python/core/PyType.java =================================================================== --- trunk/jython/src/org/python/core/PyType.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/src/org/python/core/PyType.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -722,8 +722,7 @@ to_merge[n] = bases; remain[n] = 0; - List<PyObject> acc = Generic.list(); - acc.add(this); + List<PyObject> acc = Generic.list(this); int empty_cnt = 0; Modified: trunk/jython/src/org/python/util/Generic.java =================================================================== --- trunk/jython/src/org/python/util/Generic.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/src/org/python/util/Generic.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -15,11 +15,22 @@ */ public class Generic { /** - * Makes a List with its generic type inferred from whatever its being assigned to. + * Makes a List with its generic type inferred from whatever it's being assigned to. */ public static <T> List<T> list() { return new ArrayList<T>(); } + /** + * Makes a List with its generic type inferred from whatever it's being assigned to filled with + * the items in <code>contents</code>. + */ + public static <T, U extends T> List<T> list(U...contents) { + List<T> l = new ArrayList<T>(contents.length); + for (T t : contents) { + l.add(t); + } + return l; + } /** * Makes a Map using generic types inferred from whatever this is being assigned to. Modified: trunk/jython/tests/java/javatests/ListTest.java =================================================================== --- trunk/jython/tests/java/javatests/ListTest.java 2009-01-11 03:21:25 UTC (rev 5915) +++ trunk/jython/tests/java/javatests/ListTest.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -472,8 +472,7 @@ int prevIndex = li.previousIndex(); TestSupport.assertThat(prevIndex == -1, "ListIterator.previousIndex() on empty List did not return -1"); - List<Object> l = Generic.list(); - l.add(1); + List<Object> l = Generic.list(1); li = newInstance(l).listIterator(); TestSupport.assertThat(!li.hasPrevious(), "ListIterator.hasPrevious() is true with nothing previous"); @@ -494,10 +493,7 @@ TestSupport.fail("expected IllegalStateException"); } catch (IllegalStateException e) {} } - l = Generic.list(); - l.add(0); - l.add(1); - l.add(2); + l = Generic.list(0, 1, 2); li = newInstance(l).listIterator(); for (int i = 0, n = l.size(); i < n; i++) { TestSupport.assertThat(li.next().equals(i), Added: trunk/jython/tests/java/javatests/PySetInJavaTest.java =================================================================== --- trunk/jython/tests/java/javatests/PySetInJavaTest.java (rev 0) +++ trunk/jython/tests/java/javatests/PySetInJavaTest.java 2009-01-11 06:04:11 UTC (rev 5916) @@ -0,0 +1,92 @@ +package javatests; + +import java.util.Iterator; +import java.util.Random; +import java.util.Set; + +import org.python.core.PySet; +import org.python.core.PyTuple; +import org.python.util.Generic; + +public class PySetInJavaTest { + + public static Set<Object> createPySetContainingJavaObjects() { + PySet s = new PySet(); + s.add("value"); + s.add(new Random()); + return s; + } + + public static void testPySetAsJavaSet() { + PySet s = new PySet(); + String v = "value"; + check(s.add(v));// Add a String as it should be wrapped in PyString + check(!s.add(v)); + String[] asArray = (String[])s.toArray(new String[0]);// The array type should be the same + // and it should be resized properly + check(asArray.length == 1); + check(asArray[0] == v); + Object[] naiveArray = s.toArray(); + check(naiveArray.length == 1); + check(naiveArray[0] == v); + // Add a Random as it should be wrapped in a generic PyObject; go through addAll to give it + // a little exercise + Random rand = new Random(); + check(s.addAll(Generic.list(rand))); + check(!s.addAll(Generic.list(rand, v))); + naiveArray = s.toArray(); + check(naiveArray.length == 2); + for (Object object : naiveArray) { + if (object instanceof String) { + check(object == v); + } else { + check(object == rand, "Should be 'value' or rand, not " + object); + } + } + check(!s.remove(new Random()), "The Random in the set shouldn't match a new Random"); + check(s.remove(rand)); + check(s.removeAll(Generic.list(rand, v)), + "The set should contain v and indicate it removed it"); + check(s.isEmpty()); + check(s.addAll(Generic.list(rand, v))); + check(2 == s.size(), "There should be 2 items, not " + s.size()); + check(s.containsAll(Generic.list(rand, v))); + check(!s.containsAll(Generic.list(rand, v, "other"))); + check(s.retainAll(Generic.list(rand))); + check(!s.retainAll(Generic.list(rand))); + check(s.addAll(Generic.list(rand, v))); + check(2 == s.size(), "There should be 2 items, not " + s.size()); + check(!s.addAll(Generic.list(rand, v))); + check(2 == s.size(), "There should be 2 items, not " + s.size()); + } + + public static void accessAndRemovePySetItems(Set<Object> items) { + check(items instanceof PySet, "The set shouldn't be __tojava'd into " + items.getClass()); + check(items.size() == 3, "Should be 3 items, not " + items.size()); + check(items.contains("value"), "The set from Python should contain 'value'"); + check(!items.contains(new Random()), "The set contains a particular Random"); + Iterator<Object> it = items.iterator(); + while (it.hasNext()) { + Object object = it.next(); + check(items.contains(object), "The set should contain all items from its iterator"); + if (object instanceof String) { + check(object.equals("value"), "The string should be 'value', not '" + object + "'"); + } else { + check(object instanceof Random || object instanceof PyTuple, + "The objects in the set should be a String, a Random or a PyTuple, not a " + + object.getClass()); + } + it.remove(); // Tests that removing on the iterator works + } + } + + private static void check(boolean testVal) { + check(testVal, ""); + } + + private static void check(boolean testVal, String failMsg) { + if (!testVal) { + throw new RuntimeException(failMsg); + } + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |