1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.builder;
33
34import com.google.common.collect.ImmutableList;
35import org.jf.dexlib2.builder.debug.*;
36import org.jf.dexlib2.iface.instruction.Instruction;
37import org.jf.dexlib2.iface.reference.StringReference;
38import org.jf.dexlib2.iface.reference.TypeReference;
39
40import javax.annotation.Nonnull;
41import javax.annotation.Nullable;
42import java.util.*;
43
44public class MethodLocation {
45    @Nullable BuilderInstruction instruction;
46    int codeAddress;
47    int index;
48
49    // We end up creating and keeping around a *lot* of MethodLocation objects
50    // when building a new dex file, so it's worth the trouble of lazily creating
51    // the labels and debugItems lists only when they are needed
52
53    @Nullable
54    private List<Label> labels = null;
55    @Nullable
56    private List<BuilderDebugItem> debugItems = null;
57
58    MethodLocation(@Nullable BuilderInstruction instruction, int codeAddress, int index) {
59        this.instruction = instruction;
60        this.codeAddress = codeAddress;
61        this.index = index;
62    }
63
64    @Nullable
65    public Instruction getInstruction() {
66        return instruction;
67    }
68
69    public int getCodeAddress() {
70        return codeAddress;
71    }
72
73    public int getIndex() {
74        return index;
75    }
76
77    @Nonnull
78    private List<Label> getLabels(boolean mutable) {
79        if (labels == null) {
80            if (mutable) {
81                labels = new ArrayList<Label>(1);
82                return labels;
83            }
84            return ImmutableList.of();
85        }
86        return labels;
87    }
88
89    @Nonnull
90    private List<BuilderDebugItem> getDebugItems(boolean mutable) {
91        if (debugItems == null) {
92            if (mutable) {
93                debugItems = new ArrayList<BuilderDebugItem>(1);
94                return debugItems;
95            }
96            return ImmutableList.of();
97        }
98        return debugItems;
99    }
100
101    void mergeInto(@Nonnull MethodLocation other) {
102        if (this.labels != null || other.labels != null) {
103            List<Label> otherLabels = other.getLabels(true);
104            for (Label label: this.getLabels(false)) {
105                label.location = other;
106                otherLabels.add(label);
107            }
108            this.labels = null;
109        }
110
111        if (this.debugItems != null || other.labels != null) {
112            // We need to keep the debug items in the same order. We add the other debug items to this list, then reassign
113            // the list.
114            List<BuilderDebugItem> debugItems = getDebugItems(true);
115            for (BuilderDebugItem debugItem: debugItems) {
116                debugItem.location = other;
117            }
118            debugItems.addAll(other.getDebugItems(false));
119            other.debugItems = debugItems;
120            this.debugItems = null;
121        }
122    }
123
124    @Nonnull
125    public Set<Label> getLabels() {
126        return new AbstractSet<Label>() {
127            @Nonnull
128            @Override public Iterator<Label> iterator() {
129                final Iterator<Label> it = getLabels(false).iterator();
130
131                return new Iterator<Label>() {
132                    private @Nullable Label currentLabel = null;
133
134                    @Override public boolean hasNext() {
135                        return it.hasNext();
136                    }
137
138                    @Override public Label next() {
139                        currentLabel = it.next();
140                        return currentLabel;
141                    }
142
143                    @Override public void remove() {
144                        if (currentLabel != null) {
145                            currentLabel.location = null;
146                        }
147                        it.remove();
148                    }
149                };
150            }
151
152            @Override public int size() {
153                return getLabels(false).size();
154            }
155
156            @Override public boolean add(@Nonnull Label label) {
157                if (label.isPlaced()) {
158                    throw new IllegalArgumentException("Cannot add a label that is already placed. You must remove " +
159                            "it from its current location first.");
160                }
161                label.location = MethodLocation.this;
162                getLabels(true).add(label);
163                return true;
164            }
165        };
166    }
167
168    @Nonnull
169    public Label addNewLabel() {
170        Label label = new Label(this);
171        getLabels(true).add(label);
172        return label;
173    }
174
175    @Nonnull
176    public Set<BuilderDebugItem> getDebugItems() {
177        return new AbstractSet<BuilderDebugItem>() {
178            @Nonnull
179            @Override public Iterator<BuilderDebugItem> iterator() {
180                final Iterator<BuilderDebugItem> it = getDebugItems(false).iterator();
181
182                return new Iterator<BuilderDebugItem>() {
183                    private @Nullable BuilderDebugItem currentDebugItem = null;
184
185                    @Override public boolean hasNext() {
186                        return it.hasNext();
187                    }
188
189                    @Override public BuilderDebugItem next() {
190                        currentDebugItem = it.next();
191                        return currentDebugItem;
192                    }
193
194                    @Override public void remove() {
195                        if (currentDebugItem != null) {
196                            currentDebugItem.location = null;
197                        }
198                        it.remove();
199                    }
200                };
201            }
202
203            @Override public int size() {
204                return getDebugItems(false).size();
205            }
206
207            @Override public boolean add(@Nonnull BuilderDebugItem debugItem) {
208                if (debugItem.location != null) {
209                    throw new IllegalArgumentException("Cannot add a debug item that has already been added to a " +
210                            "method. You must remove it from its current location first.");
211                }
212                debugItem.location = MethodLocation.this;
213                getDebugItems(true).add(debugItem);
214                return true;
215            }
216        };
217    }
218
219    public void addLineNumber(int lineNumber) {
220        getDebugItems().add(new BuilderLineNumber(lineNumber));
221    }
222
223    public void addStartLocal(int registerNumber, @Nullable StringReference name, @Nullable TypeReference type,
224                              @Nullable StringReference signature) {
225        getDebugItems().add(new BuilderStartLocal(registerNumber, name, type, signature));
226    }
227
228    public void addEndLocal(int registerNumber) {
229        getDebugItems().add(new BuilderEndLocal(registerNumber));
230    }
231
232    public void addRestartLocal(int registerNumber) {
233        getDebugItems().add(new BuilderRestartLocal(registerNumber));
234    }
235
236    public void addPrologue() {
237        getDebugItems().add(new BuilderPrologueEnd());
238    }
239
240    public void addEpilogue() {
241        getDebugItems().add(new BuilderEpilogueBegin());
242    }
243
244    public void addSetSourceFile(@Nullable StringReference sourceFile) {
245        getDebugItems().add(new BuilderSetSourceFile(sourceFile));
246    }
247}
248