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

Reply via email to