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