
io.ebean.enhance.entity.ConstructorDeferredCode Maven / Gradle / Ivy
package io.ebean.enhance.entity;
import io.ebean.enhance.asm.MethodVisitor;
import io.ebean.enhance.asm.Opcodes;
import io.ebean.enhance.common.ClassMeta;
import java.util.ArrayList;
import java.util.List;
/**
* This is a class that 'defers' bytecode instructions in the default constructor initialisation
* such that code that initialises persistent many properties (Lists, Sets and Maps) is removed.
*
* The purpose is to consume unwanted initialisation of Lists, Sets and Maps for OneToMany
* and ManyToMany properties.
*
*
*
* mv.visitVarInsn(ALOAD, 0);
* mv.visitTypeInsn(NEW, "java/util/ArrayList");
* mv.visitInsn(DUP);
* mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "", "()V", false);
* mv.visitTypeInsn(CHECKCAST, "java/util/List"); // OPTIONAL
* mv.visitFieldInsn(PUTFIELD, "test/model/WithInitialisedCollections", "contacts", "Ljava/util/List;");
*
*
*/
public class ConstructorDeferredCode implements Opcodes {
private static final ALoad ALOAD_INSTRUCTION = new ALoad();
private static final Dup DUP_INSTRUCTION = new Dup();
private final ClassMeta meta;
private final MethodVisitor mv;
private final List codes = new ArrayList();
ConstructorDeferredCode(ClassMeta meta, MethodVisitor mv) {
this.meta = meta;
this.mv = mv;
}
/**
* Return true if this is an ALOAD 0 which we defer.
*/
public boolean deferVisitVarInsn(int opcode, int var) {
flush();
if (opcode == ALOAD && var == 0) {
codes.add(ALOAD_INSTRUCTION);
return true;
}
return false;
}
/**
* Return true if we defer this based on it being a NEW or CHECKCAST on persistent many
* and was proceeded by a deferred ALOAD (for NEW) or Collection init (for CHECKCAST).
*/
public boolean deferVisitTypeInsn(int opcode, String type) {
if (opcode == NEW && isCollection(type) && stateAload()) {
codes.add(new NewCollection(type));
return true;
}
if (opcode == CHECKCAST && stateCollectionInit()) {
codes.add(new CheckCastCollection(type));
return true;
}
flush();
return false;
}
/**
* Return true if we defer this based on it being a DUP and was proceeded
* by a deferred ALOAD and NEW.
*/
public boolean deferVisitInsn(int opcode) {
if (opcode == DUP && stateNewCollection()) {
codes.add(DUP_INSTRUCTION);
return true;
}
flush();
return false;
}
/**
* Return true if we defer this based on it being an init of a collection
* and was proceeded by a deferred DUP.
*/
public boolean deferVisitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (opcode == INVOKESPECIAL && stateDup() && isCollectionInit(owner, name, desc)) {
codes.add(new CollectionInit(opcode, owner, name, desc, itf));
return true;
}
flush();
return false;
}
/**
* Return true if this is an init of a ArrayList, HashSet, LinkedHashSet.
*/
private boolean isCollectionInit(String owner, String name, String desc) {
return name.equals("") && desc.equals("()V") && isCollection(owner);
}
/**
* Return true if we have consumed all the deferred code that initialises a persistent collection.
*/
public boolean consumeVisitFieldInsn(int opcode, String owner, String name, String desc) {
if (opcode == PUTFIELD && stateConsumeDeferred() && meta.isFieldPersistentMany(name)) {
if (meta.isLog(2)) {
meta.log("... consumed init of many: " + name);
}
codes.clear();
return true;
}
flush();
return false;
}
/**
* Flush all deferred instructions.
*/
protected void flush() {
if (!codes.isEmpty()) {
for (DeferredCode code : codes) {
if (meta.isLog(4)) {
meta.log("... flush deferred: " + code);
}
code.write(mv);
}
codes.clear();
}
}
private boolean stateAload() {
// ALOAD always first deferred instruction
return (codes.size() == 1);
}
private boolean stateNewCollection() {
// New Collection always second deferred instruction
return (codes.size() == 2);
}
private boolean stateDup() {
// DUP always third deferred instruction
return (codes.size() == 3);
}
private boolean stateCollectionInit() {
// Checkcast proceeded by CollectionInit
return (codes.size() == 4);
}
private boolean stateConsumeDeferred() {
// Proceeded by CollectionInit with optional Checkcast
int size = codes.size();
return (size == 4 || size == 5);
}
/**
* Return true if this is a collection type used to initialise persistent collections.
*/
private boolean isCollection(String type) {
return ("java/util/ArrayList".equals(type)
|| "java/util/LinkedHashSet".equals(type)
|| "java/util/HashSet".equals(type));
}
/**
* ALOAD 0
*/
static class ALoad implements DeferredCode {
@Override
public void write(MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 0);
}
public String toString() {
return "ALOAD 0";
}
}
/**
* DUP
*/
static class Dup implements DeferredCode {
@Override
public void write(MethodVisitor mv) {
mv.visitInsn(DUP);
}
public String toString() {
return "DUP";
}
}
/**
* Typically NEW java/util/ArrayList
*/
static class NewCollection implements DeferredCode {
final String type;
NewCollection(String type) {
this.type = type;
}
@Override
public void write(MethodVisitor mv) {
mv.visitTypeInsn(NEW, type);
}
public String toString() {
return "NEW " + type;
}
}
/**
* Typically CHECKCAST java/util/List
*/
static class CheckCastCollection implements DeferredCode {
final String type;
CheckCastCollection(String type) {
this.type = type;
}
@Override
public void write(MethodVisitor mv) {
mv.visitTypeInsn(CHECKCAST, type);
}
public String toString() {
return "CHECKCAST " + type;
}
}
/**
* Typically INVOKESPECIAL java/util/ArrayList. ()V
*/
static class CollectionInit implements DeferredCode {
final int opcode;
final String owner;
final String name;
final String desc;
final boolean itf;
public CollectionInit(int opcode, String owner, String name, String desc, boolean itf) {
this.opcode = opcode;
this.owner = owner;
this.name = name;
this.desc = desc;
this.itf = itf;
}
@Override
public void write(MethodVisitor mv) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
public String toString() {
return "INVOKESPECIAL " + owner + ". ()V";
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy