Lazily create the lists in MethodLocation

Millions of MethodLocations can be created and kept in memory when
using the Builder interface to build a large dex file. The arrays
backing these lists were taking up a large amount of memory.
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/builder/MethodLocation.java b/dexlib2/src/main/java/org/jf/dexlib2/builder/MethodLocation.java
index f7c0eff..5a707e9 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/builder/MethodLocation.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/builder/MethodLocation.java
@@ -31,7 +31,7 @@
 
 package org.jf.dexlib2.builder;
 
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableList;
 import org.jf.dexlib2.builder.debug.*;
 import org.jf.dexlib2.iface.instruction.Instruction;
 import org.jf.dexlib2.iface.reference.StringReference;
@@ -39,18 +39,21 @@
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import java.util.AbstractSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 
 public class MethodLocation {
     @Nullable BuilderInstruction instruction;
     int codeAddress;
     int index;
 
-    private List<Label> labels = Lists.newArrayList();
-    List<BuilderDebugItem> debugItems = Lists.newArrayList();
+    // We end up creating and keeping around a *lot* of MethodLocation objects
+    // when building a new dex file, so it's worth the trouble of lazily creating
+    // the labels and debugItems lists only when they are needed
+
+    @Nullable
+    private List<Label> labels = null;
+    @Nullable
+    private List<BuilderDebugItem> debugItems = null;
 
     MethodLocation(@Nullable BuilderInstruction instruction, int codeAddress, int index) {
         this.instruction = instruction;
@@ -71,19 +74,51 @@
         return index;
     }
 
+    @Nonnull
+    private List<Label> getLabels(boolean mutable) {
+        if (labels == null) {
+            if (mutable) {
+                labels = new ArrayList<Label>(1);
+                return labels;
+            }
+            return ImmutableList.of();
+        }
+        return labels;
+    }
+
+    @Nonnull
+    private List<BuilderDebugItem> getDebugItems(boolean mutable) {
+        if (debugItems == null) {
+            if (mutable) {
+                debugItems = new ArrayList<BuilderDebugItem>(1);
+                return debugItems;
+            }
+            return ImmutableList.of();
+        }
+        return debugItems;
+    }
+
     void mergeInto(@Nonnull MethodLocation other) {
-        for (Label label: labels) {
-            label.location = other;
-            other.labels.add(label);
+        if (this.labels != null || other.labels != null) {
+            List<Label> otherLabels = other.getLabels(true);
+            for (Label label: this.getLabels(false)) {
+                label.location = other;
+                otherLabels.add(label);
+            }
+            this.labels = null;
         }
 
-        // We need to keep the debug items in the same order. We add the other debug items to this list, then reassign
-        // the list.
-        for (BuilderDebugItem debugItem: debugItems) {
-            debugItem.location = other;
+        if (this.debugItems != null || other.labels != null) {
+            // We need to keep the debug items in the same order. We add the other debug items to this list, then reassign
+            // the list.
+            List<BuilderDebugItem> debugItems = getDebugItems(true);
+            for (BuilderDebugItem debugItem: debugItems) {
+                debugItem.location = other;
+            }
+            debugItems.addAll(other.getDebugItems(false));
+            other.debugItems = debugItems;
+            this.debugItems = null;
         }
-        debugItems.addAll(other.debugItems);
-        other.debugItems = debugItems;
     }
 
     @Nonnull
@@ -91,7 +126,7 @@
         return new AbstractSet<Label>() {
             @Nonnull
             @Override public Iterator<Label> iterator() {
-                final Iterator<Label> it = labels.iterator();
+                final Iterator<Label> it = getLabels(false).iterator();
 
                 return new Iterator<Label>() {
                     private @Nullable Label currentLabel = null;
@@ -115,7 +150,7 @@
             }
 
             @Override public int size() {
-                return labels.size();
+                return getLabels(false).size();
             }
 
             @Override public boolean add(@Nonnull Label label) {
@@ -124,7 +159,7 @@
                             "it from its current location first.");
                 }
                 label.location = MethodLocation.this;
-                labels.add(label);
+                getLabels(true).add(label);
                 return true;
             }
         };
@@ -133,7 +168,7 @@
     @Nonnull
     public Label addNewLabel() {
         Label label = new Label(this);
-        labels.add(label);
+        getLabels(true).add(label);
         return label;
     }
 
@@ -142,7 +177,7 @@
         return new AbstractSet<BuilderDebugItem>() {
             @Nonnull
             @Override public Iterator<BuilderDebugItem> iterator() {
-                final Iterator<BuilderDebugItem> it = debugItems.iterator();
+                final Iterator<BuilderDebugItem> it = getDebugItems(false).iterator();
 
                 return new Iterator<BuilderDebugItem>() {
                     private @Nullable BuilderDebugItem currentDebugItem = null;
@@ -166,7 +201,7 @@
             }
 
             @Override public int size() {
-                return labels.size();
+                return getDebugItems(false).size();
             }
 
             @Override public boolean add(@Nonnull BuilderDebugItem debugItem) {
@@ -175,7 +210,7 @@
                             "method. You must remove it from its current location first.");
                 }
                 debugItem.location = MethodLocation.this;
-                debugItems.add(debugItem);
+                getDebugItems(true).add(debugItem);
                 return true;
             }
         };