blob: 27b2c8716a39240a3e31e0280df53d7d0f67f6ab [file] [log] [blame]
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.leakcanary;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.squareup.leakcanary.LeakTraceElement.Holder.ARRAY;
import static com.squareup.leakcanary.LeakTraceElement.Holder.CLASS;
import static com.squareup.leakcanary.LeakTraceElement.Holder.THREAD;
import static com.squareup.leakcanary.LeakTraceElement.Type.STATIC_FIELD;
import static java.util.Collections.unmodifiableList;
import static java.util.Locale.US;
/** Represents one reference in the chain of references that holds a leaking object in memory. */
public final class LeakTraceElement implements Serializable {
public enum Type {
INSTANCE_FIELD, STATIC_FIELD, LOCAL, ARRAY_ENTRY
}
public enum Holder {
OBJECT, CLASS, THREAD, ARRAY
}
/**
* Information about the reference that points to the next {@link LeakTraceElement} in the leak
* chain. Null if this is the last element in the leak trace, ie the leaking object.
*/
public final LeakReference reference;
/**
* @deprecated Use {@link #reference} and {@link LeakReference#getDisplayName()} instead.
* Null if this is the last element in the leak trace, ie the leaking object.
*/
@Deprecated
public final String referenceName;
/**
* @deprecated Use {@link #reference} and {@link LeakReference#type} instead.
* Null if this is the last element in the leak trace, ie the leaking object.
*/
@Deprecated
public final Type type;
public final Holder holder;
/**
* Class hierarchy for that object. The first element is {@link #className}. {@link Object}
* is excluded. There is always at least one element.
*/
public final List<String> classHierarchy;
public final String className;
/** Additional information, may be null. */
public final String extra;
/** If not null, there was no path that could exclude this element. */
public final Exclusion exclusion;
/** List of all fields (member and static) for that object. */
public final List<LeakReference> fieldReferences;
/**
* @deprecated Use {@link #fieldReferences} instead.
*/
@Deprecated
public final List<String> fields;
LeakTraceElement(LeakReference reference, Holder holder, List<String> classHierarchy,
String extra, Exclusion exclusion, List<LeakReference> leakReferences) {
this.reference = reference;
this.referenceName = reference == null ? null : reference.getDisplayName();
this.type = reference == null ? null : reference.type;
this.holder = holder;
this.classHierarchy = Collections.unmodifiableList(new ArrayList<>(classHierarchy));
this.className = classHierarchy.get(0);
this.extra = extra;
this.exclusion = exclusion;
this.fieldReferences = unmodifiableList(new ArrayList<>(leakReferences));
List<String> stringFields = new ArrayList<>();
for (LeakReference leakReference : leakReferences) {
stringFields.add(leakReference.toString());
}
fields = Collections.unmodifiableList(stringFields);
}
/**
* Returns the string value of the first field reference that has the provided referenceName, or
* null if no field reference with that name was found.
*/
public String getFieldReferenceValue(String referenceName) {
for (LeakReference fieldReference : fieldReferences) {
if (fieldReference.name.equals(referenceName)) {
return fieldReference.value;
}
}
return null;
}
/** @see #isInstanceOf(String) */
public boolean isInstanceOf(Class<?> expectedClass) {
return isInstanceOf(expectedClass.getName());
}
/**
* Returns true if this element is an instance of the provided class name, false otherwise.
*/
public boolean isInstanceOf(String expectedClassName) {
for (String className : classHierarchy) {
if (className.equals(expectedClassName)) {
return true;
}
}
return false;
}
/**
* Returns {@link #className} without the package.
*/
public String getSimpleClassName() {
int separator = className.lastIndexOf('.');
if (separator == -1) {
return className;
} else {
return className.substring(separator + 1);
}
}
@Override public String toString() {
return toString(false);
}
public String toString(boolean maybeLeakCause) {
String string = "";
int refNameLen = 0;
if (reference != null && reference.type == STATIC_FIELD) {
string += "static ";
}
if (holder == ARRAY || holder == THREAD) {
string += holder.name().toLowerCase(US) + " ";
}
string += getSimpleClassName();
int requiredSpaces = string.length();
if (reference != null) {
String referenceName = reference.getDisplayName();
string += "." + referenceName;
refNameLen = referenceName.length() + 1;
}
if (extra != null) {
string += " " + extra;
}
if (exclusion != null) {
string += " , matching exclusion " + exclusion.matching;
}
if (maybeLeakCause) {
string += "\n│ ";
StringBuilder builder = new StringBuilder();
for (int i = 0; i < requiredSpaces; i++) {
builder.append(" ");
}
string += builder.toString();
builder.setLength(0);
for (int i = 0; i < refNameLen; i++) {
builder.append("~");
}
string += builder.toString();
}
return string;
}
public String toDetailedString() {
String string = "* ";
if (holder == ARRAY) {
string += "Array of";
} else if (holder == CLASS) {
string += "Class";
} else {
string += "Instance of";
}
string += " " + className + "\n";
for (LeakReference leakReference : fieldReferences) {
string += "| " + leakReference + "\n";
}
return string;
}
}