On Sun, Jan 15, 2012 at 6:52 AM, Norman Gray <norman.x.g...@gmail.com> wrote: > > Greetings. > > I'm not understanding proxies. Can anyone advise me what I'm missing? > > I try the following: > > user=> (def m (proxy [java.util.HashMap] [] > (finalize [] > ;(proxy-super finalize) > (prn "finalizing...")) > (hashCode [] > 99))) > #'user/m > user=> (.hashCode m) > 99 > user=> (.finalize m) > IllegalArgumentException No matching field found: finalize for class > user.proxy$java.util.HashMap$0 clojure.lang.Reflector.getInstanceField > (Reflector.java:289) > user=> (ancestors m) > nil > user=> > > (the 'proxy-super' is commented out, just to reassure myself that its > presence is not somehow the root of the problem) > > I'm afraid that the documentation of the 'proxy' macro is too terse for me to > work out what's supposed to be happening here.
(TL;DR version: just skip to the end.) It's actually the case that most other clojure.core docstrings are even terser. :) > The documentation says that 'proxy' "creates a instance of a proxy class that > implements the named class/interface(s) by calling the supplied fns." That > suggests (without quite saying so) that 'm' should be an extension of class > java.util.HashMap, in this case, and this is corroborated by the remark that > "If a method fn is not provided for a class method, the _superclass_ methd > will be called" (my emphasis, note the docs' misspelling of 'methd'). > However, the null result of (ancestors m) suggests instead that 'm' extends > only Object, and that it handles _all_ of its method calling by delegation > through reflection on the class being proxied. Actually, ancestors expects a class rather than an instance, and returns nil for anything that's not a class. user=> (def x (proxy [Object][] (finalize [] (println "foo!")))) #'user/x user=> (def y (.getClass x)) #'user/y user=> (ancestors y) #{java.lang.Object clojure.lang.IProxy} Presto: two Class objects representing superclasses/interfaces inherited by the class y of the proxy object x. Furthermore: user=> (seq (.getMethods y)) (#<Method public void user.proxy$java.lang.Object$0.__initClojureFnMappings(clojure.lang.IPersistentMap)> #<Method public void user.proxy$java.lang.Object$0.__updateClojureFnMappings(clojure.lang.IPersistentMap)> #<Method public clojure.lang.IPersistentMap user.proxy$java.lang.Object$0.__getClojureFnMappings()> #<Method public boolean user.proxy$java.lang.Object$0.equals(java.lang.Object)> #<Method public java.lang.String user.proxy$java.lang.Object$0.toString()> #<Method public int user.proxy$java.lang.Object$0.hashCode()> #<Method public java.lang.Object user.proxy$java.lang.Object$0.clone()> #<Method public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException> #<Method public final void java.lang.Object.wait() throws java.lang.InterruptedException> #<Method public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException> #<Method public final native java.lang.Class java.lang.Object.getClass()> #<Method public final native void java.lang.Object.notify()> #<Method public final native void java.lang.Object.notifyAll()>) Note: no finalize. Javadocs for Object show finalize, so it's part of Object and should at least be inherited even if not overridden. The getMethods call obviously shows inherited, unoverridden methods as it shows several final methods of Object such as notifyAll. What is going on here? Javadocs for Class.getMethod show this: "Returns an array containing Method objects reflecting all the public member methods..." So, nonpublic methods aren't shown. Proxy seems to provide a public override of clone automatically, but finalize obviously remained protected, rather than the proxy override in x being public rather than protected. So, your problem is actually very simple: you tried to call a protected method from an unrelated class, which won't even work with reflection. At least, not normally. Indeed: user=> (.finalize (Object.)) #<IllegalArgumentException java.lang.IllegalArgumentException: No matching field found: finalize for class java.lang.Object> Same error, with a plain Object. A different way of calling the method produces a more accurate message: user=> (def z (.getDeclaredMethod Object "finalize" (into-array Class []))) #'user/z Now we have a method handle for Object's finalize method. user=> (.invoke z (Object.) (into-array Object [])) #<IllegalAccessException java.lang.IllegalAccessException: Class user$eval673 can not access a member of class java.lang.Object with modifiers "protected"> At the JVM level, your REPL evaluations become little classes with names like user$eval673. From a given class, such as user$eval673, you can normally only access protected members of an instance of that class. This includes inherited ones, unlike with private members, but the class of the object you're invoking a protected method of must be the same as or a subclass of the class containing the method invoking the protected method -- or else the class containing the protected method (not just a nonoverriding subclass) must be in the same package as the class containing the calling code. In this case, though user$eval673 is an Object, the (Object.) is *not* a user$eval673, and furthermore the package it's in (the default package, in this case) is not the package Object is in (java.lang). So the call fails. But ... user=> (.setAccessible z true) nil user=> (.invoke z (Object.) (into-array Object [])) nil Look, ma! No exception! But even this isn't the whole story with your proxy's finalize method. user=> (def w (.getDeclaredMethod y "finalize" (into-array Class []))) #<NoSuchMethodException java.lang.NoSuchMethodException: user.proxy$java.lang.Object$0.finalize()> What the hell? user=> (seq (.getDeclaredMethods Object)) (#<Method protected void java.lang.Object.finalize() throws java.lang.Throwable> #<Method public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException> #<Method public final void java.lang.Object.wait() throws java.lang.InterruptedException> #<Method public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException> #<Method public boolean java.lang.Object.equals(java.lang.Object)> #<Method public java.lang.String java.lang.Object.toString()> #<Method public native int java.lang.Object.hashCode()> #<Method public final native java.lang.Class java.lang.Object.getClass()> #<Method protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException> #<Method private static native void java.lang.Object.registerNatives()> #<Method public final native void java.lang.Object.notify()> #<Method public final native void java.lang.Object.notifyAll()>) Unlike getMethods, getDeclaredMethods shows non-public methods (while omitting unoverridden inherited ones). Object.finalize is shown, as well as Object.clone and the undocumented private method Object.registerNatives. user=> (seq (.getDeclaredMethods y)) (#<Method public void user.proxy$java.lang.Object$0.__initClojureFnMappings(clojure.lang.IPersistentMap)> #<Method public void user.proxy$java.lang.Object$0.__updateClojureFnMappings(clojure.lang.IPersistentMap)> #<Method public clojure.lang.IPersistentMap user.proxy$java.lang.Object$0.__getClojureFnMappings()> #<Method public boolean user.proxy$java.lang.Object$0.equals(java.lang.Object)> #<Method public java.lang.String user.proxy$java.lang.Object$0.toString()> #<Method public int user.proxy$java.lang.Object$0.hashCode()> #<Method public java.lang.Object user.proxy$java.lang.Object$0.clone()>) The proxy provided three methods that are clearly implementation details of Clojure proxies, as well as default implementations of equals, toString, hashCode, and clone (and made clone public). But it seems that the finalize method was *not* overridden, despite being in the proxy code. Yet the proxy docs clearly indicate it should be possible. Maybe finalize is a special case? A few more tests: user=> (def p (proxy [java.util.Observable] [] (clearChanged [] (println "foo!")))) #'user/p user=> (seq (.getDeclaredMethods (.getClass p))) (#<Method public void user.proxy$java.util.Observable$0.__initClojureFnMappings(clojure.lang.IPersistentMap)> #<Method public void user.proxy$java.util.Observable$0.__updateClojureFnMappings(clojure.lang.IPersistentMap)> #<Method public clojure.lang.IPersistentMap user.proxy$java.util.Observable$0.__getClojureFnMappings()> #<Method public void user.proxy$java.util.Observable$0.addObserver(java.util.Observer)> #<Method public void user.proxy$java.util.Observable$0.clearChanged()> #<Method public int user.proxy$java.util.Observable$0.countObservers()> #<Method public void user.proxy$java.util.Observable$0.deleteObserver(java.util.Observer)> #<Method public void user.proxy$java.util.Observable$0.deleteObservers()> #<Method public boolean user.proxy$java.util.Observable$0.hasChanged()> #<Method public void user.proxy$java.util.Observable$0.notifyObservers()> #<Method public void user.proxy$java.util.Observable$0.notifyObservers(java.lang.Object)> #<Method public void user.proxy$java.util.Observable$0.setChanged()> #<Method public boolean user.proxy$java.util.Observable$0.equals(java.lang.Object)> #<Method public java.lang.String user.proxy$java.util.Observable$0.toString()> #<Method public int user.proxy$java.util.Observable$0.hashCode()> #<Method public java.lang.Object user.proxy$java.util.Observable$0.clone()>) The protected clearChanged method of java.util.Observable can be overridden, it seems, and doing so makes it public in the proxy class. On top of that, however, the protected method setChanged was *also* made public in the proxy, though there was no explicit override of setChanged. Another test reveals some more: user=> (def q (proxy [javax.swing.Box] [1] (getAccessibleContext [] (println "foo!")))) #'user/q This ought to inherit JComponent's protected method fireVetoableChange, but it does not override it. user=> (seq (.getDeclaredMethods (.getClass q))) (#<Method public void user.proxy$javax.swing.Box$0.__initClojureFnMappings(clojure.lang.IPersistentMap)> #<Method public void user.proxy$javax.swing.Box$0.__updateClojureFnMappings(clojure.lang.IPersistentMap)> #<Method public clojure.lang.IPersistentMap user.proxy$javax.swing.Box$0.__getClojureFnMappings()> #<Method public java.awt.Component user.proxy$javax.swing.Box$0.add(java.awt.Component,int)> #<Method public void user.proxy$javax.swing.Box$0.add(java.awt.Component,java.lang.Object)> #<Method public java.awt.Component user.proxy$javax.swing.Box$0.add(java.lang.String,java.awt.Component)> #<Method public java.awt.Component user.proxy$javax.swing.Box$0.add(java.awt.Component)> #<Method public void user.proxy$javax.swing.Box$0.add(java.awt.PopupMenu)> #<Method public void user.proxy$javax.swing.Box$0.add(java.awt.Component,java.lang.Object,int)> ... #<Method public void user.proxy$javax.swing.Box$0.fireVetoableChange(java.lang.String,java.lang.Object,java.lang.Object)> ...) The proxy did create a public override of the method. So, a proxy WILL override protected methods that are directly in the class being proxied -- moreover it will make each of these public, whether or not the proxy specifically is told to include an overriding implementation of it. It WILL override most protected methods that are inherited from higher up the hierarchy, but not overridden in the class being proxied, again at the very least making each of them public even if the proxy isn't specifically given an implementation. This DOES include Object's clone method. But, it will NOT override Object's finalize method, EVEN IF explicitly told to do so and given an implementation for such an override. Apparently it's treating that as a special case, either as an undocumented but intentional limitation of the proxy facility of clojure or else as a bug perhaps caused by the JVM treating finalize specially. Any other hypothesis fails on the data: That it treats Object specially as a whole -- but clone is overridable. That it only allows overriding protected methods that have an implementation in the immediate superclass -- but Object.finalize isn't overridable even in a direct proxy of Object, and JComponent.fireVetoableChange is overridable in a proxy of Box, which does not override it. That it does not allow overriding protected methods at all, with a special-case exception for Object.clone -- but it allows overriding of JComponent.fireVetoableChange, Observer.clearChanged, and Observer.setChanged as well. Any further information is probably going to have to come, directly or indirectly, from someone who is knowledgeable about the intentions of the developers of the proxy macro, and not via the docs, since the docs, as you're aware, are mute on any special limitations on which protected methods can be overridden (they say only that they can be in general, but the user-supplied proxy code will not have access to protected fields of the proxied class). -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en