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}