FTR: Interface-centric persistent class declaration
John Rose
john.r.rose at oracle.com
Wed Aug 2 22:29:44 UTC 2017
FTR, here is an earlier conversation about using the binder pattern to work with persistent memory.
See also http://cr.openjdk.java.net/~jrose/panama/using-interfaces.html (will post a copy of that FTR).
Begin forwarded message:
From: John Rose <john.r.rose at oracle.com>
Subject: Re: Interface-centric persistent class declaration
Date: May 18, 2017 at 6:05:41 PM PDT
To: "Dohrmann, Steve" <steve.dohrmann at intel.com>
Cc: Paul Sandoz <paul.sandoz at oracle.com>, Mikael Vidstedt <mikael.vidstedt at oracle.com>, "Viswanathan, Sandhya" <sandhya.viswanathan at intel.com>
On May 18, 2017, at 4:28 PM, Dohrmann, Steve <steve.dohrmann at intel.com> wrote:
>
> Hi John,
>
> I appreciate your feedback and ideas yesterday. It was a great meeting.
>
> Based on your suggestions I'm looking again at an experiment we did early-on generating factories from interfaces. I've included a couple of example interfaces below; not sure if they are useful starting points for discussion of what you had in mind.
>
> Best regards,
> Steve
>
> interface Employee {
> public interface Factory {
> Employee create(long id, String name);
> }
> long id();
> void id(long id);
> String name();
> void name(String name);
> }
>
> interface Engineer extends Employee {
> public interface Factory {
> Engineer create(long id, String name, int projectId);
> }
> int projectId();
> void projectId(int id);
> }
Yes, that's about it. I would add a @Field annotation to all six of the id, name, and projectId methods.
And default methods would make good holders for standard (representation-independent) logic.
Finally, you need a special "heads up" notation for binding Object methods since they don't mix with default methods.
Like this:
interface Employee {
public interface Statics {
@Constructor Employee create(long id, String name);
default String[] reportFieldNames() { return new String[] { "id", "name" } } // random logic
default int getAPIPriority() { return 99; }
}
@Static Statics statics(); // hook for getting back to my statics
// fields (don't need @Getter/@Setter distinction, probably)
@Field long id();
@Field void id(long id);
@Field String name();
@Field void name(String name);
// random logic methods are just default methods
default Object[] reportFields() { return new Object[]{ id(), name(); } }
default int getInstanceSecretNumber() { return 99 ^ id(); }
// object method overrides require a different name
@ForObject default String toString_() { return name()+"("+id()+")"; }
@ForObject default boolean equals_(Employee that) { return this.name().equals(that.name()) && this.id() == that.id(); }
@ForObject default int hashCode_() { return Objects.hashCode(name(), id()); }
}
The biggest problem with this convention is that all interfaces are fully public, so you can't directly
declare non-public members in the interface you want to model. In some cases, making the interface
itself non-public will help.
A possible problem related to accessibility is the fact that any public interface can be implemented
by anybody, including untrusted code. This means that you have fewer guarantees than you think
when you are holding an Employee in your hand; it may have been implemented by an attacker.
This can be partially addressed by a future feature we call "sealed interfaces", but there is an
uneasy truce between open polymorphism and opening the door to hostile spoofing. We see it
all the time with the collection API.
A big issue with this setup (which occurs in Panama too) is that one interface cannot easily represent
both the static and non-static "slices" of a class (either Java or C++). Note that constructors are really
more like static methods, from the outside (though they look non-static from the inside).
There's a degree of freedom which always comes up here, of how to "stack" the static and non-static stuff.
1. Non-static first:
interface Employee { long id(); … interface Static { Employee make(long id, String name); … } @Static Statics statics(); }
static final Employee.Statics es = MyBinder.bind(Employee.Static.class);
Employee e = es.make(42, "Ix");
e.name("Zaphod");
Then also:
es.staticMethod(); // same as...
e.statics().staticMethod(); // … this call
e.statics().make(007, "Bond"); // "make another one like e"
2. Static first:
interface Employee { interface Instance { long id(); … } Instance make(long id, String name); … }
static final Employee es = MyBinder.bind(Employee.class);
Employee.Instance e = es.make(42, "Ix");
e.name("Zaphod");
3. All in one:
interface Employee { long id(); … Employee make(long id, String name); …; @NullTest boolean isNull(); }
static final Employee e0 = MyBinder.bind(Employee.class);
Employee e = e0.make(42, "Ix");
e.name("Zaphod");
Employee b = e.make(007, "Bond"); // statics uniformly mixed with instance methods
assert(!e.isNull() && e0.isNull()); // but there is a dynamic difference
e0.name("Robbie"); => throws an exception; this is the null value of Employee good only for statics
4. None at top level:
interface Employee { interface Instance { long id(); … Statics statics(); }
interface Statics { Instance make(long id, String name); … Instance instance(); } }
static final Employee.Statics es = MyBinder.bind(Employee.class);
Employee.Instance e = es.make(42, "Ix");
e.name("Zaphod");
(For Panama there is a special temptation to do #3 because it gives typed null pointers "for free".
But we mostly gravitate towards #1. I think #2 is the most principled, but use cases read funny.
I threw in #4 for the sake of brainstorming.)
Note that the binder is much more complex than it looks. Often a binder will have its own
configuration parameters. You can have different binders for different classes of storage,
such as persistent, off-heap, on-heap, etc., and for different levels of invariant checking,
such as unsafe-but-fast or safe-and-slow. A binder can mix in additional interfaces
under the covers, such as ones for doing shallow and deep copies and/or freezing
(to make immutables).
In Panama a binder can extract statically stored information (from annotations) about
the library that implements an API, and be sure to load that library dynamically, then
bind its entry points to the methods of the type it is implementing.
For dealing with private fields and methods, there are a few choices, none of them
good. First, wait for a future version of Java to add non-public members to interfaces.
(That's a can of worms.) Second, add a Lookup parameter to all non-public methods
at the same time as raising them to public status. Have the binder insert appropriate
authentication logic into the the methods. Third, support temporary private (and
package-private) *views* of your classes, separate interfaces (nested like Statics)
that contain a public method or field accessor for each private method or field.
Then, use a Lookup object (as before) to "unlock" access to the private view of
a public object. Be careful not to pass around the view objects, since that's
equivalent to opening up the internals of your object, by delegation via the view,
to the delegation recipient. As a variation of the third tactic, the interfaces
could be made non-public. (All this requires the binder to break access control
at times, to hook up the private bits.)
public class Employee {
public long id;
private String password;
}
public interface Employee {
public @Static interface Statics { @Constructor Employee make(long id, String secret); }
@Static Statics statics();
@Field long id;
// and to view the password there's a separate view:
private @Private interface Privates {
@Field String password();
}
// deriving the view goes like this:
@Private Privates privates(Lookup token);
}
Employee.Statics es = MyBinder.bind(Employee.Statics.class);
Employee e = es.make(1, "shazam");
String s = es.privates(lookup()).secret();
Except for some sharp edges, such a bindable interface can model a fair amount of the capabilities of the class file, with relatively little annotation overhead and boilerplate overhead, and complete representational abstraction, including the ability to handle multiple representations simultaneously.
— John
More information about the panama-spec-experts
mailing list