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}