From: Oti <oh...@ya...> - 2004-12-09 21:35:20
|
[ katre ] > I am writing an application that needs a scripting language, and I > think > jython looks very useful. Indeed! > I am only having one problem with my > implementation. I have a dedicated data object class that is going > to > be manipulated by jython scripts. This class is a Java Bean and has > a > number of properties defined the usual way (getFoo/setFoo, etc). > However, I also want to be able to let users dynamically create new > properties, because the same objects will be processed by many > different > scripts. I have the java code to handle the dynamic properties, but > I > am not sure how to make them visible to Jython. > > Example: > > java side > public class Data { > public String getFoo() ... > public void setFoo(String foo) ... > public void addDynProp(String name) ... > public Object getDynProp(String name) ... > public void setDynProp(String name, Object value) ... > } > > jython side > d = Data() > d.foo = "bob" > print d.foo > > d.bar = "thing" # error > d.addDynProp("bar") > d.bar = "thing" # works > print d.bar # works > > My initial thought was to use a BeanInfo, but each instance of the > Data > class needs its own properties. I then thought to use th apache > commons > beanutils DynaBean class, but jython doesn't work with those. Then I > hoped to somehow override the __getattr__ and __setattr__ methods, > but > those are final in PyObject. > > Does anyone have any advice? At a last resort I can just make people > pretend like dynProp is a Map (or dictionary), but my version also > handles type checking, etc and it would be more convenient to be able > to > access the dynamic properties directly. Hi Katre, maybe I have a trick for you. But this might be incompatible with future versions of Jython, so carefully consider the possible tradeoffs. If you apply the following changes to the 2.1 codebase: File /core/PyJavaInstance.java: ------------------------------- Old method: protected void noField(String name, PyObject value) { throw Py.TypeError("can't set arbitrary attribute in java instance: "+ name); } replaced with: protected void noField(String name, PyObject value) { PyObject method = __class__.lookup("__setattr__", false); if ( method == null ) { throw Py.TypeError("can't set arbitrary attribute in java instance: " + name); } else { method.__call__(this, new PyString(name), value); } } File /core/PyInstance.java: --------------------------- Old method: protected PyObject ifindfunction(String name) { PyObject getter = __class__.__getattr__; if (getter == null) return null; try { return getter.__call__(this, new PyString(name)); } catch (PyException exc) { if (Py.matchException(exc, Py.AttributeError)) return null; throw exc; } } replaced with: protected PyObject ifindfunction(String name) { PyObject getter = __class__.__getattr__; if ( getter == null && __class__.getProxyClass() != null ) { getter = __class__.lookup("__getattr__", false); } if ( getter == null ) { return null; } try { return getter.__call__(this, new PyString(name)); } catch (PyException exc) { if (Py.matchException(exc, Py.AttributeError)) return null; throw exc; } } then you can design your java class as follows (just a sketch): package test; import java.util.HashMap; import java.util.Map; public class DynamicProperties { private Map _properties = new HashMap(); private String _foo; public String getFoo() { return _foo; } public void setFoo(String foo) { _foo = foo; } public Object getDynProp(String name) { return _properties.get(name); } public void setDynProp(String name, Object value) { _properties.put(name, value); } public Object __getattr__(String name) { if (_properties.containsKey(name)) { return getDynProp(name); } else { return null; } } public void __setattr__(String name, Object value) { setDynProp(name, value); } } and write: Jython 2.1 on java1.4.2_05 (JIT: null) Type "copyright", "credits" or "license" for more information. >>> from test import DynamicProperties >>> dp = DynamicProperties() >>> dp.dynamic1 = "dynamic1" >>> dp.dynamic1 'dynamic1' >>> dp.dynamic2 = "dynamic2" >>> dp.dynamic2 'dynamic2' >>> dp.foo = "hey foo" >>> dp.foo 'hey foo' >>> dp.getFoo() 'hey foo' >>> dp.setFoo("foo2") >>> dp.foo 'foo2' >>> Because I heavily depend on this feature - "script-enable" my own java classes - you can be sure that I will try to make it happen in Jython 2.2, too, but of course I can't guarantee anything. And a last hint: in java it is completely legal to have a method named like the lowered part of the set/get bean property method: public String foo() { return "this was method foo()"; } with this method added to DynamicProperties.java, you get: >>> dp = DynamicProperties() >>> dp.foo <method test.DynamicProperties.foo of test.DynamicProperties instance at 18724539> >>> dp.foo = "some foo property" Traceback (innermost last): File "<console>", line 1, in ? TypeError: can't assign to this attribute in java instance: foo >>> dp.foo() 'this was method foo()' >>> Best wishes, Oti. |