Reputation: 16430
I just saw that EBean does bytecode transformation of record class files in a way that feels odd to me and I seek an answer about whether this is legal from a JVM point of view.
Apparently, it is possible to have a class file, where the class extends java.lang.Record
and defines record component attributes (so it's a "record" like javac would create it), but with the following additional "features" which javac would not allow:
To me, this seems illegal and I would have expected a JVM verification error. I would like to know if this is something that is "supported", which I can build upon, or if the lack of verification is a JVM bug. Are records just a Java language feature without JVM support?! I read that final fields of records are "truly final" and can't be changed even through reflection and assumed there must be special JVM support that makes sure records match the Java language semantics...
Upvotes: 10
Views: 1264
Reputation: 4031
For a background to this question with respect to ORM modelling of concatenated primary keys.
So it's been stated: a bytecode transformer can't remove the final modifier of a record field. So ends the story.
For what its worth I'll add the thoughts that came out of our review.
This issue as we saw it really boiled down to the final modifier and the view record types are a language feature (not a jvm feature) and the issues around what that means.
In that way you can almost paraphrase the posted question as: Are records a language feature or a jvm feature? We could view the first part of the response as - Yes, records are a language feature (hence the requirements on javac with jdk support and semantic requirements of equals/hashCode etc).
All the various questions around breaking record semantics equals/hashCode, accessors, constructors, customisation of those etc - this all reinforced the view that record types are indeed a language feature. We were super happy to get those [false] claims because we could prove via tests that nothing was broken and we could explain the details on why that was.
Q: But it's dodgy removing the final modifier and we broke records right? Well, it went to effectively final / effectively immutable. Another way of looking at that is to play devils advocate and see how to stuff it up - e.g. If we were to create a record instance, partially populate it and hand it off in that partially populated state that would stuff up equals/hashCode. Obviously you don't hand off a partially loaded record / partially initialised instance. Where we ended was more the question around whether record type could go from being a language feature to a jvm feature (would a future jvm assume a final) and thoughts around that.
To be clear, we don't have a failing test or jvm error or anything like that we can point to - there is no case where the ebean bytecode transformer is applied to records and that breaks anything. What we do have is the question around the assumption that record types are a language feature vs jvm feature, and that question of effectively final/effectively immutable vs actual final/immutable [a question of semantics like equals/hashCode vs bytecode & Java memory model "proper construction" etc].
Ultimately I think there are 2 ways to look at this:
Language view: Record types are extremely important, they allow pattern matching are a kind of golden key that will unlock lots of cool language features going forward. The details don't matter and the message is simple - don't anyone **** this up !!
Details view: When we look at the bytecode, semantics, java memory model and we compare to how we would write shallowly immutable types without records we see exactly nothing new. No new bytecode, no different semantics etc. This is typified by ORM @EmbeddedId
being an exact match to record type. Similarly the changes ebean needed to make to support record type where exactly none.
Brian read 'mutable' and 'not final' and fired his bazooka and that is fair enough. What the question didn't say was 'effectively immutable', 'effectively final', 'late initialisation' - heck, its even a language feature in kotlin - lateinit
.
A bytecode transformation agent that does not even know about record types is lined up for some choice words. What is it actually getting wrong? Well, once you get into the details - nothing.
Q: But the semantics of Record::equals()
is new? Not to a bytecode transformer no. The only way to **** this up is for a dev to provide a customised equals/hashCode implementation and for that to not follow the semantics of Record::equals()
- but that is on the dev providing the equals/hashCode implementation and not on the bytecode transformer.
Also noting the semantics of Record::equals()
match the old and existing @EmbeddedId
. This actually isn't new from an ORM perspective.
Q: So ebean supported java records by doing nothing? Well yeah, ebean doesn't require a default constructor and hence we been supporting shallowly immutable types for years. Hence records presented as nothing new. Cool and useful but nothing new.
I'll write up all the details and we will have a review and go from there.
I will look to organise a session for people who are interested in the details around this issue. The questions Brian has posed. What record type bytecode looks like, what the enhancement does and why it does it. What actually occurs when we deal with customisation code in constructor, equals/hashCode, accessors, toString etc (it's actually pretty simple once you understand what its doing).
I'll happily take any test using @Embeddable
,record
,junit5, Java 16. If it fails under enhancement I will buy you a beer! We likely will ask permission to add the test to our test suite (Apache2).
Ebean being in the ORM business deals with interesting problems like interception, lazy loading, partial objects, dirty checking etc. Bytecode transformation is a commonly used tool in this space because it can greatly simplify how we handle some tough problems. Record type are nice, interesting and useful but they also don't present anything new to ebean bytecode transformation and in fact have the same semantics and needs of EmbeddedId
.
Next steps: Have the review session and determine how to proceed.
Record::equals()
. This concern is misplaced, there is nothing new, different or difficult here as far as the bytecode transformation is concerned. We are now really solidly in the comfort zone of what the ebean transformation does. The chance of a real problem here has significantly dropped. The chance of an issue specific to record type has now gone to almost zero. To explain that, we have no problem with the record supplied equals/hashCode implementations. If people provide customized equals/hashCode implementation then these of course must honor the semantics of record but that aspect is on the author of those implementations - as far as ebean bytecode transformation is concerned it just needs to support a provided implementation (in interception terms) that but this no different to the non-record normal class case. There is nothing new or different here in terms of what the ebean transformation has been doing for 16 years.@Entity
and @Embeddable
classes). These cases is what ebean has been dealing with for 16 years now and I'd suggest is highly battle tested.As the author of the bytecode transformation in question I'll just add some details.
TLDR: The intention and my expectation is that the semantics of record (as I understand those semantics) is still 100% honored. At this stage I need to get more clarity on the specific semantics that Brian is unhappy about.
The effective change of this bytecode transformation is that the bytecode is going from 'strictly shallowly immutable at construction' to 'effectively shallowly immutable allowing for some late initialisation'.
The late initialisation that can occur is transparent to any code/bytecode that uses the transformed record - code using the transformed record will experience no difference in behaviour or result and I would suggest no difference in semantics.
Code using this transformed record will still think it is immutable and is not able to mutate it.
For folks familiar with Kotlin lateinit
it is a bit similar to that - still effectively immutable but allows for late initialisation of the record fields in question. [With 'late' meaning after construction]
Also noting that the transformation is adding some extra fields, methods and some interception on the accessors all for 'internal use only' and nothing is added to the record publicly in terms of fields or methods - none of this is visible to code using the transformed record. My expectation is that they have not changed the semantics of record but more clarity is required here.
The fields have the final modifier removed to allow late initialisation. This means from a Java memory model perspective we have indeed lost the nice 'final on construction JMM semantic' that we get with final fields in general. I'd be super surprised if this was the specific issue but ideally we get that clarified.
In reviewing the bytecode again and reading the comments above it isn't yet clear to me what the specific semantics of records Brian is especially unhappy about. As I see it the possible options could be:
Again, the semantics and results of all record methods (hashcode, equals, toString, constructor) are unchanged so it would be good to get good clarity on what the specific party foul is and hence what specific semantics are in question.
Edit:
Quick overview example
Before transformation
@Embeddable
public record UserRoleId(Integer userId, String roleId) {}
After transformsation (without synthetic fields and methods, IntelliJ decompile to source form)
@Embeddable
public record UserRoleId(Integer userId, String roleId) implements EntityBean {
private Integer userId;
private String roleId;
public UserRoleId(Integer userId, String roleId) {
this._ebean_intercept = new InterceptReadWrite(this);
this._ebean_set_userId(userId);
this._ebean_set_roleId(roleId);
}
public Integer userId() {
return this._ebean_get_userId();
}
public String roleId() {
return this._ebean_get_roleId();
}
}
Diff in bytecode:
I've put the before and after in bytecode form in a second answer as otherwise we exceed the character limit here.
Folks reviewing the bytecode, please have a look at that second posted answer.
Edit re customisation of record types
Brian has suggested that "but it only seems to work for records that do not customize the constructor, accessors, equals, or hashCode methods".
This isn't the case. Customisation of all those is expected, allowed, handled + multiple constructors. To explain that a bit more, these cases are not different to the non-record class cases that we have already been dealing with for many years. Ebean is 16 years old, we have been doing bytecode transformation for that time.
Q: Have we made mistakes in the past? Absolutely.
Q: Do we have a mistake in what the transformation is doing with @Embeddable
record
?
At the moment we have no evidence of a mistake. (ok, it's a bug that we have that _ebean_identity field there but I've just fixed that).
Although record type is new'ish (Java 16) the concept of shallow immutability isn't new and bytecode wise records are not too different from immutable types we have been able to code forever in java.
The JPA spec requires default constructor (btw: a restriction soon to be removed it seems) and getters/setters but ebean does not have those restrictions. This means that the customisations that Brian mentions are things that ebean transformation has had to deal with for a very long time - 16 years actually, as these are all the things with expect with non record entity classes. For the mutating entity class case there are other slightly interesting things that we need to deal with (around collections) that we do not for record types.
That is, there isn't any customisation of record types that would be new or different to what the ebean transformer has been dealing with.
Another detail to put in here is that the JVM didn't always enforce final. From vague memory from around Java 8 or so the JVM really did enforce final. This is the sort of little detail that might be concerning/nagging Brian.
Edit:
Absolutely we should not be taking things personally but lets put this in context. I've been in the Java community for 25 years, I'm the organiser of the local JUG, I have an open source project that is 16 years old that is taking a serious reputational hit right here.
Brian Geotz, a literal Java God has said "pretty serious party foul", "shamed out of the community", "ignorance", "poisoning the well" - to someone like me who is a Java fanboy these are literally hammer blows from a God. Being referred to as "the author" doesn't actually soften those blows in case you were wondering, in fact it hurts more because it suggests this isn't a really serious issue. In case it isn't obvious, I'm taking this issue really really really seriously and I'll be pushing to get right to the detail of this and confirm if there is indeed a problem with the bytecode transformation here.
24 hours in and I'm holding up. I might even foolishly be thinking I am starting to get to the heart of the matter. The current TLDR is probably that you should not take on bytecode transformation unless you really know what you are doing. For myself, I've got 16 years of taking bytecode transformation seriously. I'm not ignorant of the size of the challenge and the depth of knowledge you need to get this right. This is not tiddly winks.
At this stage we don't actually have evidence of wrong doing and it's more a suggestion that ebean might be incorrectly handling customisation of record types. This is actually really really good news to me because I've got 16 years of experience to fall back on that suggests that the bytecode transformation does indeed cover all the cases Brian is concerned about (plus other cases thrown in by Kotlin, Scala and Groovy compilers and other cases thrown in by mutable types).
Record types are actually the nice easy case as far as ebean transformation is concerned.
Next steps:
Can we get actual evidence of ebean transformation doing the wrong thing?
Brian might be able to give me a curly example to test out and report back the bytecode. I think this is where we are at.
Upvotes: 14
Reputation: 95466
Your question posits a false dichotomy. Records are a language feature, with some degree of JVM support (primarily for reflection), but that doesn't mean that the JVM will (or even can) enforce all the requirements on records that the language requires. (Gaps like this are inevitable, as the JVM is a more general computing substrate, and which services other languages besides Java; for example, the JVM permits methods to be overloaded on return type, but the language does not.)
That said, the behavior you describe is a pretty serious party foul, and those who engage in it should be shamed out of the community. (It is also possible they are doing so out of ignorance, in which case they might be educable.) Most likely, these people think they are being "clever" in subverting rules they don't like, but in the process, poisoning the well by promoting behaviors that users may find astonishing.
EDIT
The author of the transformer posted some further context here about what they were trying to accomplish. I'll give them credit for making a good faith effort to conform with the semantics of records, but it undermines the final field semantics, and only appears to work for records that do not customize the constructor, accessors, equals, or hashCode methods. This describes a lot of records, but not all. This is a good cautionary tale; even when trying to preserve the semantics of a class while transforming it, it is easy to make questionable assumptions about what the class does or does not do that can get in the way.
The author waves away the concern about the final field semantics as "not likely to cause a problem." But this is not the bar. The language provides certain semantics for records. This transformation undermines those semantics, and yet still tells the user they are records. Even if they are "minor" and "unlikely", you are breaking the semantics that the Java language promises. "99% compatible" rounds to zero in this case. So I stand by my assertion that this framework is taking inappropriate liberties with the language semantics. They may have been well-intentioned, they may have tried hard to not break things, but break things they did.
Upvotes: 9
Reputation: 4031
Ok, my comments are too big for comments so ...
More notes:
So the enhancement as it is has design aspects orientated to mutating entity beans and so there is bytecode here that isn't strictly required or effectively is a noop for the @Embedded
/ @EmbeddedId
record
case:
@DbJson
and the _ebean_intercept in employed in those 2 cases. Could we lose that for the record case? Maybe, hmmm.How did we get here?
Well, with ebean we have been dealing with entity and embedded beans that have final fields and non-default constructors for years. These cases go from 'partially' immutable (very common) to 'fully' immutable (no setters and look very similar to records but yes this is rare) and the fully immutable tend to be for the reporting cases (views, aggregations/group by etc). Other ORMs like DataNucleus have also been doing this so for some ORM folks relaxing the final modifier on fields isn't a new thing.
In this sense record
in terms of bytecode doesn't look too different to what we have been doing except that they come with hashCode/equals implementation but even this isn't actually any different to what we have been doing for many years - ebean must honor a hashCode/equals implementation if it's been provided etc.
In terms of JMM and removing the final modifier - I'm comfortable that we are not being a bad actor. Perhaps too comfortable but this isn't Valhalla.
Upvotes: 0
Reputation: 4031
Adding a second answer with the bytecode diff as adding the bytecode exceeds the character limit.
Edit2: Before and After in bytecode form.
Before
// class version 60.0 (60)
// RECORD
// access flags 0x10031
public final class org/example/records/UserRoleId extends java/lang/Record {
// compiled from: UserRoleId.java
@Ljavax/persistence/Embeddable;()
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
RECORDCOMPONENT Ljava/lang/Integer; userId
RECORDCOMPONENT Ljava/lang/String; roleId
// access flags 0x12
private final Ljava/lang/Integer; userId
// access flags 0x12
private final Ljava/lang/String; roleId
// access flags 0x1
public <init>(Ljava/lang/Integer;Ljava/lang/String;)V
// parameter userId
// parameter roleId
L0
LINENUMBER 6 L0
ALOAD 0
INVOKESPECIAL java/lang/Record.<init> ()V
ALOAD 0
ALOAD 1
PUTFIELD org/example/records/UserRoleId.userId : Ljava/lang/Integer;
ALOAD 0
ALOAD 2
PUTFIELD org/example/records/UserRoleId.roleId : Ljava/lang/String;
RETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
LOCALVARIABLE userId Ljava/lang/Integer; L0 L1 1
LOCALVARIABLE roleId Ljava/lang/String; L0 L1 2
MAXSTACK = 2
MAXLOCALS = 3
// access flags 0x11
public final toString()Ljava/lang/String;
L0
LINENUMBER 5 L0
ALOAD 0
INVOKEDYNAMIC toString(Lorg/example/records/UserRoleId;)Ljava/lang/String; [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
org.example.records.UserRoleId.class,
"userId;roleId",
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.userId(Ljava/lang/Integer;),
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.roleId(Ljava/lang/String;)
]
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final hashCode()I
L0
LINENUMBER 5 L0
ALOAD 0
INVOKEDYNAMIC hashCode(Lorg/example/records/UserRoleId;)I [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
org.example.records.UserRoleId.class,
"userId;roleId",
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.userId(Ljava/lang/Integer;),
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.roleId(Ljava/lang/String;)
]
IRETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final equals(Ljava/lang/Object;)Z
L0
LINENUMBER 5 L0
ALOAD 0
ALOAD 1
INVOKEDYNAMIC equals(Lorg/example/records/UserRoleId;Ljava/lang/Object;)Z [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
org.example.records.UserRoleId.class,
"userId;roleId",
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.userId(Ljava/lang/Integer;),
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.roleId(Ljava/lang/String;)
]
IRETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
LOCALVARIABLE o Ljava/lang/Object; L0 L1 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public userId()Ljava/lang/Integer;
L0
LINENUMBER 5 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId.userId : Ljava/lang/Integer;
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public roleId()Ljava/lang/String;
L0
LINENUMBER 5 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId.roleId : Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
After
// class version 60.0 (60)
// RECORD
// access flags 0x10031
public final class org/example/records/UserRoleId extends java/lang/Record implements io/ebean/bean/EntityBean {
// compiled from: UserRoleId.java
@Ljavax/persistence/Embeddable;()
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
RECORDCOMPONENT Ljava/lang/Integer; userId
RECORDCOMPONENT Ljava/lang/String; roleId
// access flags 0x2
private Ljava/lang/Integer; userId
// access flags 0x2
private Ljava/lang/String; roleId
// access flags 0x1009
public static synthetic [Ljava/lang/String; _ebean_props
// access flags 0x1004
protected synthetic Lio/ebean/bean/EntityBeanIntercept; _ebean_intercept
// access flags 0x1084
protected transient synthetic Ljava/lang/Object; _ebean_identity
// access flags 0x1
public <init>(Ljava/lang/Integer;Ljava/lang/String;)V
// parameter userId
// parameter roleId
L0
LINENUMBER 6 L0
ALOAD 0
INVOKESPECIAL java/lang/Record.<init> ()V
ALOAD 0
NEW io/ebean/bean/InterceptReadWrite
DUP
ALOAD 0
INVOKESPECIAL io/ebean/bean/InterceptReadWrite.<init> (Ljava/lang/Object;)V
PUTFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ALOAD 0
ALOAD 1
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_set_userId (Ljava/lang/Integer;)V
ALOAD 0
ALOAD 2
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_set_roleId (Ljava/lang/String;)V
RETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
LOCALVARIABLE userId Ljava/lang/Integer; L0 L1 1
LOCALVARIABLE roleId Ljava/lang/String; L0 L1 2
MAXSTACK = 4
MAXLOCALS = 3
// access flags 0x11
public final toString()Ljava/lang/String;
L0
LINENUMBER 5 L0
ALOAD 0
INVOKEDYNAMIC toString(Lorg/example/records/UserRoleId;)Ljava/lang/String; [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
org.example.records.UserRoleId.class,
"userId;roleId",
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.userId(Ljava/lang/Integer;),
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.roleId(Ljava/lang/String;)
]
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final hashCode()I
L0
LINENUMBER 5 L0
ALOAD 0
INVOKEDYNAMIC hashCode(Lorg/example/records/UserRoleId;)I [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
org.example.records.UserRoleId.class,
"userId;roleId",
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.userId(Ljava/lang/Integer;),
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.roleId(Ljava/lang/String;)
]
IRETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x11
public final equals(Ljava/lang/Object;)Z
L0
LINENUMBER 5 L0
ALOAD 0
ALOAD 1
INVOKEDYNAMIC equals(Lorg/example/records/UserRoleId;Ljava/lang/Object;)Z [
// handle kind 0x6 : INVOKESTATIC
java/lang/runtime/ObjectMethods.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
// arguments:
org.example.records.UserRoleId.class,
"userId;roleId",
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.userId(Ljava/lang/Integer;),
// handle kind 0x1 : GETFIELD
org/example/records/UserRoleId.roleId(Ljava/lang/String;)
]
IRETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
LOCALVARIABLE o Ljava/lang/Object; L0 L1 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public userId()Ljava/lang/Integer;
L0
LINENUMBER 5 L0
ALOAD 0
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_get_userId ()Ljava/lang/Integer;
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public roleId()Ljava/lang/String;
L0
LINENUMBER 5 L0
ALOAD 0
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_get_roleId ()Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 1 L0
ICONST_2
ANEWARRAY java/lang/String
DUP
ICONST_0
LDC "userId"
AASTORE
DUP
ICONST_1
LDC "roleId"
AASTORE
PUTSTATIC org/example/records/UserRoleId._ebean_props : [Ljava/lang/String;
L1
LINENUMBER 1 L1
RETURN
MAXSTACK = 4
MAXLOCALS = 0
// access flags 0x1001
public synthetic <init>()V
L0
LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL java/lang/Record.<init> ()V
ALOAD 0
NEW io/ebean/bean/InterceptReadWrite
DUP
ALOAD 0
INVOKESPECIAL io/ebean/bean/InterceptReadWrite.<init> (Ljava/lang/Object;)V
PUTFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
L1
LINENUMBER 2 L1
RETURN
L2
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L2 0
MAXSTACK = 4
MAXLOCALS = 1
// access flags 0x1001
public synthetic _ebean_getPropertyNames()[Ljava/lang/String;
L0
LINENUMBER 13 L0
GETSTATIC org/example/records/UserRoleId._ebean_props : [Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1001
public synthetic _ebean_getPropertyName(I)Ljava/lang/String;
L0
LINENUMBER 16 L0
GETSTATIC org/example/records/UserRoleId._ebean_props : [Ljava/lang/String;
ILOAD 1
AALOAD
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
LOCALVARIABLE pos I L0 L1 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1001
public synthetic _ebean_getIntercept()Lio/ebean/bean/EntityBeanIntercept;
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1001
public synthetic _ebean_intercept()Lio/ebean/bean/EntityBeanIntercept;
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
IFNONNULL L1
L2
LINENUMBER 2 L2
ALOAD 0
NEW io/ebean/bean/InterceptReadWrite
DUP
ALOAD 0
INVOKESPECIAL io/ebean/bean/InterceptReadWrite.<init> (Ljava/lang/Object;)V
PUTFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
L1
LINENUMBER 3 L1
FRAME SAME
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ARETURN
L3
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L3 0
MAXSTACK = 4
MAXLOCALS = 1
// access flags 0x1004
protected synthetic _ebean_get_userId()Ljava/lang/Integer;
L0
LINENUMBER 6 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ICONST_0
INVOKEINTERFACE io/ebean/bean/EntityBeanIntercept.preGetter (I)V (itf)
L1
LINENUMBER 7 L1
ALOAD 0
GETFIELD org/example/records/UserRoleId.userId : Ljava/lang/Integer;
ARETURN
L2
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x1004
protected synthetic _ebean_set_userId(Ljava/lang/Integer;)V
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ICONST_1
ICONST_0
ALOAD 0
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_get_userId ()Ljava/lang/Integer;
ALOAD 1
INVOKEINTERFACE io/ebean/bean/EntityBeanIntercept.preSetter (ZILjava/lang/Object;Ljava/lang/Object;)V (itf)
L1
LINENUMBER 2 L1
ALOAD 0
ALOAD 1
PUTFIELD org/example/records/UserRoleId.userId : Ljava/lang/Integer;
L2
LINENUMBER 4 L2
RETURN
L3
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L3 0
LOCALVARIABLE newValue Ljava/lang/Integer; L0 L3 1
MAXSTACK = 5
MAXLOCALS = 2
// access flags 0x1004
protected synthetic _ebean_getni_userId()Ljava/lang/Integer;
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId.userId : Ljava/lang/Integer;
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1004
protected synthetic _ebean_setni_userId(Ljava/lang/Integer;)V
L0
LINENUMBER 1 L0
ALOAD 0
ALOAD 1
PUTFIELD org/example/records/UserRoleId.userId : Ljava/lang/Integer;
L1
LINENUMBER 2 L1
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ICONST_0
INVOKEINTERFACE io/ebean/bean/EntityBeanIntercept.setLoadedProperty (I)V (itf)
L2
LINENUMBER 1 L2
RETURN
L3
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L3 0
LOCALVARIABLE _newValue Ljava/lang/Integer; L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1004
protected synthetic _ebean_get_roleId()Ljava/lang/String;
L0
LINENUMBER 6 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ICONST_1
INVOKEINTERFACE io/ebean/bean/EntityBeanIntercept.preGetter (I)V (itf)
L1
LINENUMBER 7 L1
ALOAD 0
GETFIELD org/example/records/UserRoleId.roleId : Ljava/lang/String;
ARETURN
L2
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x1004
protected synthetic _ebean_set_roleId(Ljava/lang/String;)V
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ICONST_1
ICONST_1
ALOAD 0
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_get_roleId ()Ljava/lang/String;
ALOAD 1
INVOKEINTERFACE io/ebean/bean/EntityBeanIntercept.preSetter (ZILjava/lang/Object;Ljava/lang/Object;)V (itf)
L1
LINENUMBER 2 L1
ALOAD 0
ALOAD 1
PUTFIELD org/example/records/UserRoleId.roleId : Ljava/lang/String;
L2
LINENUMBER 4 L2
RETURN
L3
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L3 0
LOCALVARIABLE newValue Ljava/lang/String; L0 L3 1
MAXSTACK = 5
MAXLOCALS = 2
// access flags 0x1004
protected synthetic _ebean_getni_roleId()Ljava/lang/String;
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD org/example/records/UserRoleId.roleId : Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1004
protected synthetic _ebean_setni_roleId(Ljava/lang/String;)V
L0
LINENUMBER 1 L0
ALOAD 0
ALOAD 1
PUTFIELD org/example/records/UserRoleId.roleId : Ljava/lang/String;
L1
LINENUMBER 2 L1
ALOAD 0
GETFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
ICONST_1
INVOKEINTERFACE io/ebean/bean/EntityBeanIntercept.setLoadedProperty (I)V (itf)
L2
LINENUMBER 1 L2
RETURN
L3
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L3 0
LOCALVARIABLE _newValue Ljava/lang/String; L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1001
public synthetic _ebean_getField(I)Ljava/lang/Object;
L0
LINENUMBER 1 L0
ILOAD 1
TABLESWITCH
0: L1
1: L2
default: L3
L1
LINENUMBER 1 L1
FRAME SAME
ALOAD 0
GETFIELD org/example/records/UserRoleId.userId : Ljava/lang/Integer;
ARETURN
L2
LINENUMBER 1 L2
FRAME SAME
ALOAD 0
GETFIELD org/example/records/UserRoleId.roleId : Ljava/lang/String;
ARETURN
L3
LINENUMBER 1 L3
FRAME SAME
NEW java/lang/RuntimeException
DUP
NEW java/lang/StringBuilder
DUP
LDC "Invalid index "
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ILOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V
ATHROW
L4
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L4 0
LOCALVARIABLE index I L0 L4 1
MAXSTACK = 5
MAXLOCALS = 2
// access flags 0x1001
public synthetic _ebean_getFieldIntercept(I)Ljava/lang/Object;
L0
LINENUMBER 1 L0
ILOAD 1
TABLESWITCH
0: L1
1: L2
default: L3
L1
LINENUMBER 1 L1
FRAME SAME
ALOAD 0
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_get_userId ()Ljava/lang/Integer;
ARETURN
L2
LINENUMBER 1 L2
FRAME SAME
ALOAD 0
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_get_roleId ()Ljava/lang/String;
ARETURN
L3
LINENUMBER 1 L3
FRAME SAME
NEW java/lang/RuntimeException
DUP
NEW java/lang/StringBuilder
DUP
LDC "Invalid index "
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ILOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V
ATHROW
L4
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L4 0
LOCALVARIABLE index I L0 L4 1
MAXSTACK = 5
MAXLOCALS = 2
// access flags 0x1001
public synthetic _ebean_setField(ILjava/lang/Object;)V
L0
LINENUMBER 1 L0
LINENUMBER 1 L0
ILOAD 1
TABLESWITCH
0: L1
1: L2
default: L3
L1
LINENUMBER 1 L1
FRAME SAME
ALOAD 0
ALOAD 2
CHECKCAST java/lang/Integer
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_setni_userId (Ljava/lang/Integer;)V
L4
LINENUMBER 1 L4
RETURN
L2
LINENUMBER 1 L2
FRAME SAME
ALOAD 0
ALOAD 2
CHECKCAST java/lang/String
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_setni_roleId (Ljava/lang/String;)V
L5
LINENUMBER 1 L5
RETURN
L3
LINENUMBER 1 L3
FRAME SAME
NEW java/lang/RuntimeException
DUP
NEW java/lang/StringBuilder
DUP
LDC "Invalid index "
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ILOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V
ATHROW
L6
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L6 0
LOCALVARIABLE index I L0 L6 1
LOCALVARIABLE o Ljava/lang/Object; L0 L6 2
LOCALVARIABLE arg Ljava/lang/Object; L0 L6 3
LOCALVARIABLE p Lorg/example/records/UserRoleId; L0 L6 4
MAXSTACK = 5
MAXLOCALS = 5
// access flags 0x1001
public synthetic _ebean_setFieldIntercept(ILjava/lang/Object;)V
L0
LINENUMBER 1 L0
LINENUMBER 1 L0
ILOAD 1
TABLESWITCH
0: L1
1: L2
default: L3
L1
LINENUMBER 1 L1
FRAME SAME
ALOAD 0
ALOAD 2
CHECKCAST java/lang/Integer
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_set_userId (Ljava/lang/Integer;)V
L4
LINENUMBER 1 L4
RETURN
L2
LINENUMBER 1 L2
FRAME SAME
ALOAD 0
ALOAD 2
CHECKCAST java/lang/String
INVOKEVIRTUAL org/example/records/UserRoleId._ebean_set_roleId (Ljava/lang/String;)V
L5
LINENUMBER 1 L5
RETURN
L3
LINENUMBER 1 L3
FRAME SAME
NEW java/lang/RuntimeException
DUP
NEW java/lang/StringBuilder
DUP
LDC "Invalid index "
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ILOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V
ATHROW
L6
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L6 0
LOCALVARIABLE index I L0 L6 1
LOCALVARIABLE o Ljava/lang/Object; L0 L6 2
LOCALVARIABLE arg Ljava/lang/Object; L0 L6 3
LOCALVARIABLE p Lorg/example/records/UserRoleId; L0 L6 4
MAXSTACK = 5
MAXLOCALS = 5
// access flags 0x1001
public synthetic _ebean_setEmbeddedLoaded()V
L0
LINENUMBER 1 L0
RETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 0
MAXLOCALS = 1
// access flags 0x1001
public synthetic _ebean_isEmbeddedNewOrDirty()Z
L0
LINENUMBER 1 L0
ICONST_0
IRETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1001
public synthetic _ebean_newInstance()Ljava/lang/Object;
L0
LINENUMBER 10 L0
NEW org/example/records/UserRoleId
DUP
INVOKESPECIAL org/example/records/UserRoleId.<init> ()V
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x4
protected synthetic <init>(Lio/ebean/bean/EntityBean;)V
L0
LINENUMBER 2 L0
ALOAD 0
INVOKESPECIAL java/lang/Record.<init> ()V
L1
LINENUMBER 3 L1
ALOAD 0
NEW io/ebean/bean/InterceptReadOnly
DUP
ALOAD 0
INVOKESPECIAL io/ebean/bean/InterceptReadOnly.<init> (Ljava/lang/Object;)V
PUTFIELD org/example/records/UserRoleId._ebean_intercept : Lio/ebean/bean/EntityBeanIntercept;
L2
LINENUMBER 25 L2
RETURN
L3
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L3 0
LOCALVARIABLE ignore Lio/ebean/bean/EntityBean; L0 L3 1
MAXSTACK = 4
MAXLOCALS = 2
// access flags 0x1
public synthetic _ebean_newInstanceReadOnly()Ljava/lang/Object;
L0
LINENUMBER 4 L0
NEW org/example/records/UserRoleId
DUP
ACONST_NULL
INVOKESPECIAL org/example/records/UserRoleId.<init> (Lio/ebean/bean/EntityBean;)V
ARETURN
L1
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L1 0
MAXSTACK = 3
MAXLOCALS = 1
// access flags 0x1001
public synthetic toString(Lio/ebean/bean/ToStringBuilder;)V
L0
LINENUMBER 2 L0
ALOAD 1
ALOAD 0
INVOKEVIRTUAL io/ebean/bean/ToStringBuilder.start (Ljava/lang/Object;)V
L1
LINENUMBER 3 L1
ALOAD 1
LDC "userId"
ALOAD 0
GETFIELD org/example/records/UserRoleId.userId : Ljava/lang/Integer;
INVOKEVIRTUAL io/ebean/bean/ToStringBuilder.add (Ljava/lang/String;Ljava/lang/Object;)V
L2
LINENUMBER 3 L2
ALOAD 1
LDC "roleId"
ALOAD 0
GETFIELD org/example/records/UserRoleId.roleId : Ljava/lang/String;
INVOKEVIRTUAL io/ebean/bean/ToStringBuilder.add (Ljava/lang/String;Ljava/lang/Object;)V
L3
LINENUMBER 4 L3
ALOAD 1
INVOKEVIRTUAL io/ebean/bean/ToStringBuilder.end ()V
L4
LINENUMBER 5 L4
RETURN
L5
LOCALVARIABLE this Lorg/example/records/UserRoleId; L0 L5 0
LOCALVARIABLE sb Lio/ebean/bean/ToStringBuilder; L0 L5 1
MAXSTACK = 3
MAXLOCALS = 2
}
Notes:
Upvotes: 0