001package io.ebean.enhance.entity; 002 003import io.ebean.enhance.asm.MethodVisitor; 004import io.ebean.enhance.asm.Opcodes; 005import io.ebean.enhance.common.ClassMeta; 006 007import java.util.ArrayList; 008import java.util.List; 009 010/** 011 * This is a class that 'defers' bytecode instructions in the default constructor initialisation 012 * such that code that initialises persistent many properties (Lists, Sets and Maps) is removed. 013 * <p> 014 * The purpose is to consume unwanted initialisation of Lists, Sets and Maps for OneToMany 015 * and ManyToMany properties. 016 * </p> 017 * <pre> 018 * 019 * mv.visitVarInsn(ALOAD, 0); 020 * mv.visitTypeInsn(NEW, "java/util/ArrayList"); 021 * mv.visitInsn(DUP); 022 * mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false); 023 * mv.visitTypeInsn(CHECKCAST, "java/util/List"); // OPTIONAL 024 * mv.visitFieldInsn(PUTFIELD, "test/model/WithInitialisedCollections", "contacts", "Ljava/util/List;"); 025 * 026 * </pre> 027 */ 028public class ConstructorDeferredCode implements Opcodes { 029 030 private static final ALoad ALOAD_INSTRUCTION = new ALoad(); 031 032 private static final Dup DUP_INSTRUCTION = new Dup(); 033 034 private final ClassMeta meta; 035 private final MethodVisitor mv; 036 037 private final List<DeferredCode> codes = new ArrayList<DeferredCode>(); 038 039 ConstructorDeferredCode(ClassMeta meta, MethodVisitor mv) { 040 this.meta = meta; 041 this.mv = mv; 042 } 043 044 /** 045 * Return true if this is an ALOAD 0 which we defer. 046 */ 047 public boolean deferVisitVarInsn(int opcode, int var) { 048 flush(); 049 if (opcode == ALOAD && var == 0) { 050 codes.add(ALOAD_INSTRUCTION); 051 return true; 052 } 053 return false; 054 } 055 056 /** 057 * Return true if we defer this based on it being a NEW or CHECKCAST on persistent many 058 * and was proceeded by a deferred ALOAD (for NEW) or Collection init (for CHECKCAST). 059 */ 060 public boolean deferVisitTypeInsn(int opcode, String type) { 061 if (opcode == NEW && isCollection(type) && stateAload()) { 062 codes.add(new NewCollection(type)); 063 return true; 064 } 065 if (opcode == CHECKCAST && stateCollectionInit()) { 066 codes.add(new CheckCastCollection(type)); 067 return true; 068 } 069 flush(); 070 return false; 071 } 072 073 /** 074 * Return true if we defer this based on it being a DUP and was proceeded 075 * by a deferred ALOAD and NEW. 076 */ 077 public boolean deferVisitInsn(int opcode) { 078 if (opcode == DUP && stateNewCollection()) { 079 codes.add(DUP_INSTRUCTION); 080 return true; 081 } 082 flush(); 083 return false; 084 } 085 086 /** 087 * Return true if we defer this based on it being an init of a collection 088 * and was proceeded by a deferred DUP. 089 */ 090 public boolean deferVisitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 091 092 if (opcode == INVOKESPECIAL && stateDup() && isCollectionInit(owner, name, desc)) { 093 codes.add(new CollectionInit(opcode, owner, name, desc, itf)); 094 return true; 095 } 096 flush(); 097 return false; 098 } 099 100 /** 101 * Return true if this is an init of a ArrayList, HashSet, LinkedHashSet. 102 */ 103 private boolean isCollectionInit(String owner, String name, String desc) { 104 return name.equals("<init>") && desc.equals("()V") && isCollection(owner); 105 } 106 107 /** 108 * Return true if we have consumed all the deferred code that initialises a persistent collection. 109 */ 110 public boolean consumeVisitFieldInsn(int opcode, String owner, String name, String desc) { 111 if (opcode == PUTFIELD && stateConsumeDeferred() && meta.isFieldPersistentMany(name)) { 112 if (meta.isLog(2)) { 113 meta.log("... consumed init of many: " + name); 114 } 115 codes.clear(); 116 return true; 117 } 118 flush(); 119 return false; 120 } 121 122 123 /** 124 * Flush all deferred instructions. 125 */ 126 protected void flush() { 127 if (!codes.isEmpty()) { 128 for (DeferredCode code : codes) { 129 if (meta.isLog(4)) { 130 meta.log("... flush deferred: " + code); 131 } 132 code.write(mv); 133 } 134 codes.clear(); 135 } 136 } 137 138 private boolean stateAload() { 139 // ALOAD always first deferred instruction 140 return (codes.size() == 1); 141 } 142 143 private boolean stateNewCollection() { 144 // New Collection always second deferred instruction 145 return (codes.size() == 2); 146 } 147 148 private boolean stateDup() { 149 // DUP always third deferred instruction 150 return (codes.size() == 3); 151 } 152 153 private boolean stateCollectionInit() { 154 // Checkcast proceeded by CollectionInit 155 return (codes.size() == 4); 156 } 157 158 private boolean stateConsumeDeferred() { 159 // Proceeded by CollectionInit with optional Checkcast 160 int size = codes.size(); 161 return (size == 4 || size == 5); 162 } 163 164 /** 165 * Return true if this is a collection type used to initialise persistent collections. 166 */ 167 private boolean isCollection(String type) { 168 return ("java/util/ArrayList".equals(type) 169 || "java/util/LinkedHashSet".equals(type) 170 || "java/util/HashSet".equals(type)); 171 } 172 173 /** 174 * ALOAD 0 175 */ 176 static class ALoad implements DeferredCode { 177 @Override 178 public void write(MethodVisitor mv) { 179 mv.visitVarInsn(ALOAD, 0); 180 } 181 182 public String toString() { 183 return "ALOAD 0"; 184 } 185 } 186 187 /** 188 * DUP 189 */ 190 static class Dup implements DeferredCode { 191 @Override 192 public void write(MethodVisitor mv) { 193 mv.visitInsn(DUP); 194 } 195 196 public String toString() { 197 return "DUP"; 198 } 199 } 200 201 /** 202 * Typically NEW java/util/ArrayList 203 */ 204 static class NewCollection implements DeferredCode { 205 206 final String type; 207 208 NewCollection(String type) { 209 this.type = type; 210 } 211 212 @Override 213 public void write(MethodVisitor mv) { 214 mv.visitTypeInsn(NEW, type); 215 } 216 217 public String toString() { 218 return "NEW " + type; 219 } 220 } 221 222 /** 223 * Typically CHECKCAST java/util/List 224 */ 225 static class CheckCastCollection implements DeferredCode { 226 227 final String type; 228 229 CheckCastCollection(String type) { 230 this.type = type; 231 } 232 233 @Override 234 public void write(MethodVisitor mv) { 235 mv.visitTypeInsn(CHECKCAST, type); 236 } 237 238 public String toString() { 239 return "CHECKCAST " + type; 240 } 241 } 242 243 /** 244 * Typically INVOKESPECIAL java/util/ArrayList.<init> ()V 245 */ 246 static class CollectionInit implements DeferredCode { 247 248 final int opcode; 249 final String owner; 250 final String name; 251 final String desc; 252 final boolean itf; 253 254 public CollectionInit(int opcode, String owner, String name, String desc, boolean itf) { 255 this.opcode = opcode; 256 this.owner = owner; 257 this.name = name; 258 this.desc = desc; 259 this.itf = itf; 260 } 261 262 @Override 263 public void write(MethodVisitor mv) { 264 mv.visitMethodInsn(opcode, owner, name, desc, itf); 265 } 266 267 public String toString() { 268 return "INVOKESPECIAL " + owner + ".<init> ()V"; 269 } 270 } 271}