diff --git a/src/main/java/com/squareup/leakcanary/ExcludedRefs.java b/src/main/java/com/squareup/leakcanary/ExcludedRefs.java index 9660eda278..47662445ec 100644 --- a/src/main/java/com/squareup/leakcanary/ExcludedRefs.java +++ b/src/main/java/com/squareup/leakcanary/ExcludedRefs.java @@ -32,130 +32,178 @@ */ public final class ExcludedRefs implements Serializable { - public final Map> fieldNameByClassName; - public final Map> staticFieldNameByClassName; - public final Map threadNames; - public final Map classNames; - public final Map rootSuperClassNames; - - ExcludedRefs(Map> fieldNameByClassName, - Map> staticFieldNameByClassName, - Map threadNames, Map classNames, - Map rootSuperClassNames) { - // Copy + unmodifiable. - this.fieldNameByClassName = unmodifiableMap(new LinkedHashMap<>(fieldNameByClassName)); - this.staticFieldNameByClassName = - unmodifiableMap(new LinkedHashMap<>(staticFieldNameByClassName)); - this.threadNames = unmodifiableMap(new LinkedHashMap<>(threadNames)); - this.classNames = unmodifiableMap(new LinkedHashMap<>(classNames)); - this.rootSuperClassNames = unmodifiableMap(new LinkedHashMap<>(rootSuperClassNames)); + public static Builder builder() { + return new BuilderWithParams(); + } + + public final Map> fieldNameByClassName; + public final Map> staticFieldNameByClassName; + public final Map threadNames; + public final Map classNames; + public final Map rootClassNames; + + ExcludedRefs(BuilderWithParams builder) { + this.fieldNameByClassName = unmodifiableRefStringMap(builder.fieldNameByClassName); + this.staticFieldNameByClassName = unmodifiableRefStringMap(builder.staticFieldNameByClassName); + this.threadNames = unmodifiableRefMap(builder.threadNames); + this.classNames = unmodifiableRefMap(builder.classNames); + this.rootClassNames = unmodifiableRefMap(builder.rootClassNames); + } + + private Map> unmodifiableRefStringMap( + Map> mapmap) { + LinkedHashMap> fieldNameByClassName = new LinkedHashMap<>(); + for (Map.Entry> entry : mapmap.entrySet()) { + fieldNameByClassName.put(entry.getKey(), unmodifiableRefMap(entry.getValue())); + } + return unmodifiableMap(fieldNameByClassName); + } + + private Map unmodifiableRefMap(Map fieldBuilderMap) { + Map fieldMap = new LinkedHashMap<>(); + for (Map.Entry fieldEntry : fieldBuilderMap.entrySet()) { + fieldMap.put(fieldEntry.getKey(), new Exclusion(fieldEntry.getValue())); + } + return unmodifiableMap(fieldMap); } @Override public String toString() { String string = ""; - for (Map.Entry> classes : fieldNameByClassName.entrySet()) { + for (Map.Entry> classes : fieldNameByClassName.entrySet()) { String clazz = classes.getKey(); - for (Map.Entry field : classes.getValue().entrySet()) { - String always = field.getValue() ? " (always)" : ""; + for (Map.Entry field : classes.getValue().entrySet()) { + String always = field.getValue().alwaysExclude ? " (always)" : ""; string += "| Field: " + clazz + "." + field.getKey() + always + "\n"; } } - for (Map.Entry> classes : staticFieldNameByClassName.entrySet()) { + for (Map.Entry> classes : staticFieldNameByClassName.entrySet()) { String clazz = classes.getKey(); - for (Map.Entry field : classes.getValue().entrySet()) { - String always = field.getValue() ? " (always)" : ""; + for (Map.Entry field : classes.getValue().entrySet()) { + String always = field.getValue().alwaysExclude ? " (always)" : ""; string += "| Static field: " + clazz + "." + field.getKey() + always + "\n"; } } - for (Map.Entry thread : threadNames.entrySet()) { - String always = thread.getValue() ? " (always)" : ""; + for (Map.Entry thread : threadNames.entrySet()) { + String always = thread.getValue().alwaysExclude ? " (always)" : ""; string += "| Thread:" + thread.getKey() + always + "\n"; } - for (Map.Entry clazz : classNames.entrySet()) { - String always = clazz.getValue() ? " (always)" : ""; + for (Map.Entry clazz : classNames.entrySet()) { + String always = clazz.getValue().alwaysExclude ? " (always)" : ""; string += "| Class:" + clazz.getKey() + always + "\n"; } - for (Map.Entry clazz : rootSuperClassNames.entrySet()) { - String always = clazz.getValue() ? " (always)" : ""; + for (Map.Entry clazz : rootClassNames.entrySet()) { + String always = clazz.getValue().alwaysExclude ? " (always)" : ""; string += "| Root Class:" + clazz.getKey() + always + "\n"; } return string; } - public static final class Builder { - private final Map> fieldNameByClassName = new LinkedHashMap<>(); - private final Map> staticFieldNameByClassName = + static final class ParamsBuilder { + String name; + String reason; + boolean alwaysExclude; + final String matching; + + ParamsBuilder(String matching) { + this.matching = matching; + } + } + + public interface Builder { + BuilderWithParams instanceField(String className, String fieldName); + + BuilderWithParams staticField(String className, String fieldName); + + BuilderWithParams thread(String threadName); + + BuilderWithParams clazz(String className); + + BuilderWithParams rootClass(String rootSuperClassName); + + ExcludedRefs build(); + } + + public static final class BuilderWithParams implements Builder { + + private final Map> fieldNameByClassName = new LinkedHashMap<>(); - private final Map threadNames = new LinkedHashMap<>(); - private final Map classNames = new LinkedHashMap<>(); - private final Map rootSuperClassNames = new LinkedHashMap<>(); + private final Map> staticFieldNameByClassName = + new LinkedHashMap<>(); + private final Map threadNames = new LinkedHashMap<>(); + private final Map classNames = new LinkedHashMap<>(); + private final Map rootClassNames = new LinkedHashMap<>(); + + private ParamsBuilder lastParams; - public Builder instanceField(String className, String fieldName) { - return instanceField(className, fieldName, false); + BuilderWithParams() { } - public Builder instanceField(String className, String fieldName, boolean always) { + @Override public BuilderWithParams instanceField(String className, String fieldName) { checkNotNull(className, "className"); checkNotNull(fieldName, "fieldName"); - Map excludedFields = fieldNameByClassName.get(className); + Map excludedFields = fieldNameByClassName.get(className); if (excludedFields == null) { excludedFields = new LinkedHashMap<>(); fieldNameByClassName.put(className, excludedFields); } - excludedFields.put(fieldName, always); + lastParams = new ParamsBuilder("field " + className + "#" + fieldName); + excludedFields.put(fieldName, lastParams); return this; } - public Builder staticField(String className, String fieldName) { - return staticField(className, fieldName, false); - } - - public Builder staticField(String className, String fieldName, boolean always) { + @Override public BuilderWithParams staticField(String className, String fieldName) { checkNotNull(className, "className"); checkNotNull(fieldName, "fieldName"); - Map excludedFields = staticFieldNameByClassName.get(className); + Map excludedFields = staticFieldNameByClassName.get(className); if (excludedFields == null) { excludedFields = new LinkedHashMap<>(); staticFieldNameByClassName.put(className, excludedFields); } - excludedFields.put(fieldName, always); + lastParams = new ParamsBuilder("static field " + className + "#" + fieldName); + excludedFields.put(fieldName, lastParams); return this; } - public Builder thread(String threadName) { - return thread(threadName, false); + @Override public BuilderWithParams thread(String threadName) { + checkNotNull(threadName, "threadName"); + lastParams = new ParamsBuilder("any threads named " + threadName); + threadNames.put(threadName, lastParams); + return this; } - public Builder thread(String threadName, boolean always) { - checkNotNull(threadName, "threadName"); - threadNames.put(threadName, always); + /** Ignores all fields and static fields of all subclasses of the provided class name. */ + @Override public BuilderWithParams clazz(String className) { + checkNotNull(className, "className"); + lastParams = new ParamsBuilder("any subclass of " + className); + classNames.put(className, lastParams); return this; } - public Builder clazz(String className) { - return thread(className, false); + /** Ignores any GC root that belongs to a subclass of the provided class name. */ + @Override public BuilderWithParams rootClass(String rootClassName) { + checkNotNull(rootClassName, "rootClassName"); + lastParams = new ParamsBuilder("any GC root subclass of " + rootClassName); + rootClassNames.put(rootClassName, lastParams); + return this; } - public Builder clazz(String className, boolean always) { - checkNotNull(className, "className"); - classNames.put(className, always); + public BuilderWithParams named(String name) { + lastParams.name = name; return this; } - public Builder rootSuperClass(String rootSuperClassName) { - return rootSuperClass(rootSuperClassName, false); + public BuilderWithParams reason(String reason) { + lastParams.reason = reason; + return this; } - /** Ignores any GC root that is a subclass of the provided class name. */ - public Builder rootSuperClass(String rootSuperClassName, boolean always) { - checkNotNull(rootSuperClassName, "rootSuperClassName"); - rootSuperClassNames.put(rootSuperClassName, always); + public BuilderWithParams alwaysExclude() { + lastParams.alwaysExclude = true; return this; } public ExcludedRefs build() { - return new ExcludedRefs(fieldNameByClassName, staticFieldNameByClassName, threadNames, - classNames, rootSuperClassNames); + return new ExcludedRefs(this); } } } diff --git a/src/main/java/com/squareup/leakcanary/Exclusion.java b/src/main/java/com/squareup/leakcanary/Exclusion.java new file mode 100644 index 0000000000..4d87d05e6c --- /dev/null +++ b/src/main/java/com/squareup/leakcanary/Exclusion.java @@ -0,0 +1,17 @@ +package com.squareup.leakcanary; + +import java.io.Serializable; + +public final class Exclusion implements Serializable { + public final String name; + public final String reason; + public final boolean alwaysExclude; + public final String matching; + + Exclusion(ExcludedRefs.ParamsBuilder builder) { + this.name = builder.name; + this.reason = builder.reason; + this.alwaysExclude = builder.alwaysExclude; + this.matching = builder.matching; + } +} diff --git a/src/main/java/com/squareup/leakcanary/RefWatcher.java b/src/main/java/com/squareup/leakcanary/RefWatcher.java index 7d2b9fc43f..5d124535ee 100644 --- a/src/main/java/com/squareup/leakcanary/RefWatcher.java +++ b/src/main/java/com/squareup/leakcanary/RefWatcher.java @@ -48,7 +48,7 @@ public final class RefWatcher { }, new HeapDump.Listener() { @Override public void analyze(HeapDump heapDump) { } - }, new ExcludedRefs.Builder().build()); + }, new ExcludedRefs.BuilderWithParams().build()); private final Executor watchExecutor; private final DebuggerControl debuggerControl; diff --git a/src/test/java/com/squareup/leakcanary/RefWatcherTest.java b/src/test/java/com/squareup/leakcanary/RefWatcherTest.java index 31e6531700..15b44def50 100644 --- a/src/test/java/com/squareup/leakcanary/RefWatcherTest.java +++ b/src/test/java/com/squareup/leakcanary/RefWatcherTest.java @@ -24,7 +24,7 @@ public class RefWatcherTest { - static final ExcludedRefs NO_REF = new ExcludedRefs.Builder().build(); + static final ExcludedRefs NO_REF = new ExcludedRefs.BuilderWithParams().build(); static class TestDumper implements HeapDumper { boolean called;