How does one handle a java method which returns a non-public sub-type via reflection?
Peter Levart
peter.levart at gmail.com
Fri Jan 12 19:21:04 UTC 2018
Hi Jeffrey,
See my answer inline...
On 01/12/18 19:26, jeffrey kutcher wrote:
>
> This is a good discussion but none of the suggestions satisfy the need I'm looking for.https://stackoverflow.com/questions/450807/how-do-i-make-the-method-return-type-generic
>
> The example code below shows button0 being added to a VBox. It also shows button1 being added to the same VBox only instead of being added directly to the list returned by vbox.getChildren(), that is vbox.getChildren().add(button1), buttton1 is added via reflection/introspection. The end goal is the same in both cases. Add a button. The methodology is different. Regardless, this should be trivial. It's not. Why?
> In this example, adding button0 produces no compile time warnings, Attempt-1. As for the code adding button1, Attempt-3 too produces no compile time warning, yet when run produces the WARNINGS shown at the bottom of the code. Commenting out Attempt-3 and uncommenting Attempt-2, produces compile time warnings.
> The bottom of the code example shows how to compile and run the example.
> As is, this code references no illegal reference code and yet still produces run time WARNINGS.
> Is there a way to access the list returned by getChildren() via reflection so a widget can be added without producing the run time WARNINGS? It's not the WARNINGS that I'm concerned about. I'm concerned that when future Java releases become available this code will fail to work. I would like to find a solution so that doesn't happen and this code continues to work.
> Maps have similar behavior. Try and use reflection to iterate over a Map, a trivial exercise, and it quickly becomes an impossible task. The internal class of the Map isn't public making it impossible to iterate through the list. I'm assuming that is what we are seeing here as well.
> Personally I believe internal classes should never be used. They seem to produce more problems than they solve. They are a quick, lazy fix that usually results in painful long term support. Besides, internal classes defeat write once, run anywhere. If it's internal, it's not being used anywhere else; which means the code has no useful value elsewhere; obviously false. That implies the code will be duplicated elsewhere to leverage it's functionality, meaning the code is now written twice, defeating the montra. Therefore, internal classes should never be used. In the same spirit, classes should never be private for the same reason. The nice thing is, convention easily satisfies this suggestion. Just because you can, doesn't mean you should.
> Java has been out for 23 years. Does a solution exist? I would like to see this example code work for future releases of Java.
> Is there a solution? Would you please point me at an example.
> Thanks
> ---------- BEGIN SOURCE ----------
>
> public class ReflectAccessBug extends javafx.application.Application {
>
> public static void main(String[] args) {
> launch(args);
> }
>
> public void start(javafx.stage.Stage stage) {
> javafx.scene.control.Button button0 = new javafx.scene.control.Button();
> javafx.scene.control.Button button1 = new javafx.scene.control.Button();
> javafx.scene.layout.VBox vbox = new javafx.scene.layout.VBox();
>
> javafx.event.EventHandler<javafx.event.ActionEvent> event = e -> {
> javafx.scene.control.Button b =
> (javafx.scene.control.Button)e.getSource();
> System.err.println(b.getText() + " " + e);
> };
>
> button0.setText("button0");
> button0.setOnAction(event);
>
> button1.setText("button1");
> button1.setOnAction(event);
>
> vbox.getChildren().add(button0);
>
> // Attempt-1
> // This works as expected.
> //vbox.getChildren().add(button1);
>
> try {
> // Now let's try adding button1 using introspection ...
> java.lang.reflect.Method m =
> vbox.getClass().getMethod("getChildren", new Class[0]);
> Object o = m.invoke(vbox, new Object[0]);
> /*
> // Attempt-2
> // This causes the same runtime warning as Attempt-3
> // and I don't believe it should.
> // Attempt-2 produces a compile time warning.
> // Attempt-3 eliminates the compile time warning.
> javafx.collections.ObservableList<javafx.scene.Node> l =
> (javafx.collections.ObservableList<javafx.scene.Node>)o;
> m = l.getClass().getMethod("add", new Class[] { Object.class, });
Try this instead of above line:
m = javafx.collections.ObservableList.class.getMethod("add", new Class[]
{ Object.class, });
m.invoke(o, ....);
The problem is that when you say: o.getClass().getMethod(....), you
start searching for a method in the runtime implementation class of the
object 'o' (it doesn't matter if you assign variable 'o' to 'l' with a
cast - 'l' will point to the same runtime object as 'o'). The method
might be found in some class/interface that is not public or not
exported. Class#getMethod always returns the Method object describing
the most specific method - the method declared in the most specific type
(see the javadoc of getMethod() for more accurate description). What
you may get back from such call is a Method object describing the
declaration of the method in the implementation class. Although such
method is public, the declaring class (implementation class) might not
be accessible (either not public or not exported).
You better start searching for a method in some well known public type
(ObservableList interface for example) that you know declares the method
that is implemented or overridden in the runtime implementation class.
You will get a different Method object, describing the abstract
interface method, but when you invoke the method via this Method object,
the invocation will be dispatched to the most specific implementation of
that method because it obeys the virtual dispatch semantics. Reflection
access checks are unfortunately performed with the declaring type/method
described in the Method object and not with the method that the
invocation actually dispatches to.
Hope this helps.
Regards, Peter
> o = m.invoke(l, new Object[] { button1, });
> System.err.println("o="+o);
> */
> // Attempt-3
> // This results with the same runtime warning as Attempt-2.
> // I don't believe it should produce this warning and is the
> // point of this exercise.
> // Attempt-3 produces *NO* compile time warning as in Attempt-2.
> // It's not the compile time warning I'm concerned about. It's the
> // runtime warning that is the issue.
> // This code references no illegal reference code and yet still
> // produces a runtime warning. It references
> // "illegal reflective access operations". It's not this code
> // that is illegally accessing reflective code. Lower level code
> // accesses illegal reflective code on behalf of this code.
> m = o.getClass().getMethod("add", new Class[] { Object.class, });
> o = m.invoke(o, new Object[] { button1, });
> System.err.println("o="+o);
> } catch (Exception x) {
> x.printStackTrace();
> }
>
> javafx.scene.Scene scene0 = new javafx.scene.Scene(vbox);
> stage.setScene(scene0);
> stage.show();
> }
>
> }
>
> /*
> # compile:
> javac ReflectAccessBug.java
>
> # run:
> java ReflectAccessBug
> WARNING: An illegal reflective access operation has occurred
> WARNING: Illegal reflective access by ReflectAccessBug (...) to method com.sun.javafx.collections.VetoableListDecorator.add(java.lang.Object)
> WARNING: Please consider reporting this to the maintainers of ReflectAccessBug
> WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
> WARNING: All illegal access operations will be denied in a future release
> */
>
> ---------- END SOURCE ----------
>
More information about the jigsaw-dev
mailing list