001/***
002 * ASM: a very small and fast Java bytecode manipulation framework
003 * Copyright (c) 2000-2011 INRIA, France Telecom
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions
008 * are met:
009 * 1. Redistributions of source code must retain the above copyright
010 *    notice, this list of conditions and the following disclaimer.
011 * 2. Redistributions in binary form must reproduce the above copyright
012 *    notice, this list of conditions and the following disclaimer in the
013 *    documentation and/or other materials provided with the distribution.
014 * 3. Neither the name of the copyright holders nor the names of its
015 *    contributors may be used to endorse or promote products derived from
016 *    this software without specific prior written permission.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
028 * THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package io.ebean.enhance.asm.tree;
031
032import io.ebean.enhance.asm.MethodVisitor;
033
034import java.util.ListIterator;
035import java.util.NoSuchElementException;
036
037/**
038 * A doubly linked list of {@link AbstractInsnNode} objects. <i>This
039 * implementation is not thread safe</i>.
040 */
041public class InsnList {
042
043    /**
044     * The number of instructions in this list.
045     */
046    private int size;
047
048    /**
049     * The first instruction in this list. May be <tt>null</tt>.
050     */
051    private AbstractInsnNode first;
052
053    /**
054     * The last instruction in this list. May be <tt>null</tt>.
055     */
056    private AbstractInsnNode last;
057
058    /**
059     * A cache of the instructions of this list. This cache is used to improve
060     * the performance of the {@link #get} method.
061     */
062    AbstractInsnNode[] cache;
063
064    /**
065     * Returns the number of instructions in this list.
066     * 
067     * @return the number of instructions in this list.
068     */
069    public int size() {
070        return size;
071    }
072
073    /**
074     * Returns the first instruction in this list.
075     * 
076     * @return the first instruction in this list, or <tt>null</tt> if the list
077     *         is empty.
078     */
079    public AbstractInsnNode getFirst() {
080        return first;
081    }
082
083    /**
084     * Returns the last instruction in this list.
085     * 
086     * @return the last instruction in this list, or <tt>null</tt> if the list
087     *         is empty.
088     */
089    public AbstractInsnNode getLast() {
090        return last;
091    }
092
093    /**
094     * Returns the instruction whose index is given. This method builds a cache
095     * of the instructions in this list to avoid scanning the whole list each
096     * time it is called. Once the cache is built, this method run in constant
097     * time. This cache is invalidated by all the methods that modify the list.
098     * 
099     * @param index
100     *            the index of the instruction that must be returned.
101     * @return the instruction whose index is given.
102     * @throws IndexOutOfBoundsException
103     *             if (index &lt; 0 || index &gt;= size()).
104     */
105    public AbstractInsnNode get(final int index) {
106        if (index < 0 || index >= size) {
107            throw new IndexOutOfBoundsException();
108        }
109        if (cache == null) {
110            cache = toArray();
111        }
112        return cache[index];
113    }
114
115    /**
116     * Returns <tt>true</tt> if the given instruction belongs to this list. This
117     * method always scans the instructions of this list until it finds the
118     * given instruction or reaches the end of the list.
119     * 
120     * @param insn
121     *            an instruction.
122     * @return <tt>true</tt> if the given instruction belongs to this list.
123     */
124    public boolean contains(final AbstractInsnNode insn) {
125        AbstractInsnNode i = first;
126        while (i != null && i != insn) {
127            i = i.next;
128        }
129        return i != null;
130    }
131
132    /**
133     * Returns the index of the given instruction in this list. This method
134     * builds a cache of the instruction indexes to avoid scanning the whole
135     * list each time it is called. Once the cache is built, this method run in
136     * constant time. The cache is invalidated by all the methods that modify
137     * the list.
138     * 
139     * @param insn
140     *            an instruction <i>of this list</i>.
141     * @return the index of the given instruction in this list. <i>The result of
142     *         this method is undefined if the given instruction does not belong
143     *         to this list</i>. Use {@link #contains contains} to test if an
144     *         instruction belongs to an instruction list or not.
145     */
146    public int indexOf(final AbstractInsnNode insn) {
147        if (cache == null) {
148            cache = toArray();
149        }
150        return insn.index;
151    }
152
153    /**
154     * Makes the given visitor visit all of the instructions in this list.
155     * 
156     * @param mv
157     *            the method visitor that must visit the instructions.
158     */
159    public void accept(final MethodVisitor mv) {
160        AbstractInsnNode insn = first;
161        while (insn != null) {
162            insn.accept(mv);
163            insn = insn.next;
164        }
165    }
166
167    /**
168     * Returns an iterator over the instructions in this list.
169     * 
170     * @return an iterator over the instructions in this list.
171     */
172    public ListIterator<AbstractInsnNode> iterator() {
173        return iterator(0);
174    }
175
176    /**
177     * Returns an iterator over the instructions in this list.
178     * 
179     * @param index
180     *            index of instruction for the iterator to start at
181     * 
182     * @return an iterator over the instructions in this list.
183     */
184    @SuppressWarnings("unchecked")
185    public ListIterator<AbstractInsnNode> iterator(int index) {
186        return new InsnListIterator(index);
187    }
188
189    /**
190     * Returns an array containing all of the instructions in this list.
191     * 
192     * @return an array containing all of the instructions in this list.
193     */
194    public AbstractInsnNode[] toArray() {
195        int i = 0;
196        AbstractInsnNode elem = first;
197        AbstractInsnNode[] insns = new AbstractInsnNode[size];
198        while (elem != null) {
199            insns[i] = elem;
200            elem.index = i++;
201            elem = elem.next;
202        }
203        return insns;
204    }
205
206    /**
207     * Replaces an instruction of this list with another instruction.
208     * 
209     * @param location
210     *            an instruction <i>of this list</i>.
211     * @param insn
212     *            another instruction, <i>which must not belong to any
213     *            {@link InsnList}</i>.
214     */
215    public void set(final AbstractInsnNode location, final AbstractInsnNode insn) {
216        AbstractInsnNode next = location.next;
217        insn.next = next;
218        if (next != null) {
219            next.prev = insn;
220        } else {
221            last = insn;
222        }
223        AbstractInsnNode prev = location.prev;
224        insn.prev = prev;
225        if (prev != null) {
226            prev.next = insn;
227        } else {
228            first = insn;
229        }
230        if (cache != null) {
231            int index = location.index;
232            cache[index] = insn;
233            insn.index = index;
234        } else {
235            insn.index = 0; // insn now belongs to an InsnList
236        }
237        location.index = -1; // i no longer belongs to an InsnList
238        location.prev = null;
239        location.next = null;
240    }
241
242    /**
243     * Adds the given instruction to the end of this list.
244     * 
245     * @param insn
246     *            an instruction, <i>which must not belong to any
247     *            {@link InsnList}</i>.
248     */
249    public void add(final AbstractInsnNode insn) {
250        ++size;
251        if (last == null) {
252            first = insn;
253            last = insn;
254        } else {
255            last.next = insn;
256            insn.prev = last;
257        }
258        last = insn;
259        cache = null;
260        insn.index = 0; // insn now belongs to an InsnList
261    }
262
263    /**
264     * Adds the given instructions to the end of this list.
265     * 
266     * @param insns
267     *            an instruction list, which is cleared during the process. This
268     *            list must be different from 'this'.
269     */
270    public void add(final InsnList insns) {
271        if (insns.size == 0) {
272            return;
273        }
274        size += insns.size;
275        if (last == null) {
276            first = insns.first;
277            last = insns.last;
278        } else {
279            AbstractInsnNode elem = insns.first;
280            last.next = elem;
281            elem.prev = last;
282            last = insns.last;
283        }
284        cache = null;
285        insns.removeAll(false);
286    }
287
288    /**
289     * Inserts the given instruction at the begining of this list.
290     * 
291     * @param insn
292     *            an instruction, <i>which must not belong to any
293     *            {@link InsnList}</i>.
294     */
295    public void insert(final AbstractInsnNode insn) {
296        ++size;
297        if (first == null) {
298            first = insn;
299            last = insn;
300        } else {
301            first.prev = insn;
302            insn.next = first;
303        }
304        first = insn;
305        cache = null;
306        insn.index = 0; // insn now belongs to an InsnList
307    }
308
309    /**
310     * Inserts the given instructions at the begining of this list.
311     * 
312     * @param insns
313     *            an instruction list, which is cleared during the process. This
314     *            list must be different from 'this'.
315     */
316    public void insert(final InsnList insns) {
317        if (insns.size == 0) {
318            return;
319        }
320        size += insns.size;
321        if (first == null) {
322            first = insns.first;
323            last = insns.last;
324        } else {
325            AbstractInsnNode elem = insns.last;
326            first.prev = elem;
327            elem.next = first;
328            first = insns.first;
329        }
330        cache = null;
331        insns.removeAll(false);
332    }
333
334    /**
335     * Inserts the given instruction after the specified instruction.
336     * 
337     * @param location
338     *            an instruction <i>of this list</i> after which insn must be
339     *            inserted.
340     * @param insn
341     *            the instruction to be inserted, <i>which must not belong to
342     *            any {@link InsnList}</i>.
343     */
344    public void insert(final AbstractInsnNode location,
345            final AbstractInsnNode insn) {
346        ++size;
347        AbstractInsnNode next = location.next;
348        if (next == null) {
349            last = insn;
350        } else {
351            next.prev = insn;
352        }
353        location.next = insn;
354        insn.next = next;
355        insn.prev = location;
356        cache = null;
357        insn.index = 0; // insn now belongs to an InsnList
358    }
359
360    /**
361     * Inserts the given instructions after the specified instruction.
362     * 
363     * @param location
364     *            an instruction <i>of this list</i> after which the
365     *            instructions must be inserted.
366     * @param insns
367     *            the instruction list to be inserted, which is cleared during
368     *            the process. This list must be different from 'this'.
369     */
370    public void insert(final AbstractInsnNode location, final InsnList insns) {
371        if (insns.size == 0) {
372            return;
373        }
374        size += insns.size;
375        AbstractInsnNode ifirst = insns.first;
376        AbstractInsnNode ilast = insns.last;
377        AbstractInsnNode next = location.next;
378        if (next == null) {
379            last = ilast;
380        } else {
381            next.prev = ilast;
382        }
383        location.next = ifirst;
384        ilast.next = next;
385        ifirst.prev = location;
386        cache = null;
387        insns.removeAll(false);
388    }
389
390    /**
391     * Inserts the given instruction before the specified instruction.
392     * 
393     * @param location
394     *            an instruction <i>of this list</i> before which insn must be
395     *            inserted.
396     * @param insn
397     *            the instruction to be inserted, <i>which must not belong to
398     *            any {@link InsnList}</i>.
399     */
400    public void insertBefore(final AbstractInsnNode location,
401            final AbstractInsnNode insn) {
402        ++size;
403        AbstractInsnNode prev = location.prev;
404        if (prev == null) {
405            first = insn;
406        } else {
407            prev.next = insn;
408        }
409        location.prev = insn;
410        insn.next = location;
411        insn.prev = prev;
412        cache = null;
413        insn.index = 0; // insn now belongs to an InsnList
414    }
415
416    /**
417     * Inserts the given instructions before the specified instruction.
418     * 
419     * @param location
420     *            an instruction <i>of this list</i> before which the
421     *            instructions must be inserted.
422     * @param insns
423     *            the instruction list to be inserted, which is cleared during
424     *            the process. This list must be different from 'this'.
425     */
426    public void insertBefore(final AbstractInsnNode location,
427            final InsnList insns) {
428        if (insns.size == 0) {
429            return;
430        }
431        size += insns.size;
432        AbstractInsnNode ifirst = insns.first;
433        AbstractInsnNode ilast = insns.last;
434        AbstractInsnNode prev = location.prev;
435        if (prev == null) {
436            first = ifirst;
437        } else {
438            prev.next = ifirst;
439        }
440        location.prev = ilast;
441        ilast.next = location;
442        ifirst.prev = prev;
443        cache = null;
444        insns.removeAll(false);
445    }
446
447    /**
448     * Removes the given instruction from this list.
449     * 
450     * @param insn
451     *            the instruction <i>of this list</i> that must be removed.
452     */
453    public void remove(final AbstractInsnNode insn) {
454        --size;
455        AbstractInsnNode next = insn.next;
456        AbstractInsnNode prev = insn.prev;
457        if (next == null) {
458            if (prev == null) {
459                first = null;
460                last = null;
461            } else {
462                prev.next = null;
463                last = prev;
464            }
465        } else {
466            if (prev == null) {
467                first = next;
468                next.prev = null;
469            } else {
470                prev.next = next;
471                next.prev = prev;
472            }
473        }
474        cache = null;
475        insn.index = -1; // insn no longer belongs to an InsnList
476        insn.prev = null;
477        insn.next = null;
478    }
479
480    /**
481     * Removes all of the instructions of this list.
482     * 
483     * @param mark
484     *            if the instructions must be marked as no longer belonging to
485     *            any {@link InsnList}.
486     */
487    void removeAll(final boolean mark) {
488        if (mark) {
489            AbstractInsnNode insn = first;
490            while (insn != null) {
491                AbstractInsnNode next = insn.next;
492                insn.index = -1; // insn no longer belongs to an InsnList
493                insn.prev = null;
494                insn.next = null;
495                insn = next;
496            }
497        }
498        size = 0;
499        first = null;
500        last = null;
501        cache = null;
502    }
503
504    /**
505     * Removes all of the instructions of this list.
506     */
507    public void clear() {
508        removeAll(false);
509    }
510
511    /**
512     * Reset all labels in the instruction list. This method should be called
513     * before reusing same instructions list between several
514     * <code>ClassWriter</code>s.
515     */
516    public void resetLabels() {
517        AbstractInsnNode insn = first;
518        while (insn != null) {
519            if (insn instanceof LabelNode) {
520                ((LabelNode) insn).resetLabel();
521            }
522            insn = insn.next;
523        }
524    }
525
526    // this class is not generified because it will create bridges
527    @SuppressWarnings("rawtypes")
528    private final class InsnListIterator implements ListIterator {
529
530        AbstractInsnNode next;
531
532        AbstractInsnNode prev;
533
534        AbstractInsnNode remove;
535
536        InsnListIterator(int index) {
537            if (index == size()) {
538                next = null;
539                prev = getLast();
540            } else {
541                next = get(index);
542                prev = next.prev;
543            }
544        }
545
546        public boolean hasNext() {
547            return next != null;
548        }
549
550        public Object next() {
551            if (next == null) {
552                throw new NoSuchElementException();
553            }
554            AbstractInsnNode result = next;
555            prev = result;
556            next = result.next;
557            remove = result;
558            return result;
559        }
560
561        public void remove() {
562            if (remove != null) {
563                if (remove == next) {
564                    next = next.next;
565                } else {
566                    prev = prev.prev;
567                }
568                InsnList.this.remove(remove);
569                remove = null;
570            } else {
571                throw new IllegalStateException();
572            }
573        }
574
575        public boolean hasPrevious() {
576            return prev != null;
577        }
578
579        public Object previous() {
580            AbstractInsnNode result = prev;
581            next = result;
582            prev = result.prev;
583            remove = result;
584            return result;
585        }
586
587        public int nextIndex() {
588            if (next == null) {
589                return size();
590            }
591            if (cache == null) {
592                cache = toArray();
593            }
594            return next.index;
595        }
596
597        public int previousIndex() {
598            if (prev == null) {
599                return -1;
600            }
601            if (cache == null) {
602                cache = toArray();
603            }
604            return prev.index;
605        }
606
607        public void add(Object o) {
608            if (next != null) {
609                InsnList.this.insertBefore(next, (AbstractInsnNode) o);
610            } else if (prev != null) {
611                InsnList.this.insert(prev, (AbstractInsnNode) o);
612            } else {
613                InsnList.this.add((AbstractInsnNode) o);
614            }
615            prev = (AbstractInsnNode) o;
616            remove = null;
617        }
618
619        public void set(Object o) {
620            if (remove != null) {
621                InsnList.this.set(remove, (AbstractInsnNode) o);
622                if (remove == prev) {
623                    prev = (AbstractInsnNode) o;
624                } else {
625                    next = (AbstractInsnNode) o;
626                }
627            } else {
628                throw new IllegalStateException();
629            }
630        }
631    }
632}