blob: 6ac369ebe4a3929e753b93056c4bff9eed898b28 [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.LinkedHashMap;
import java.util.Map;
import static com.squareup.leakcanary.Preconditions.checkNotNull;
import static java.util.Collections.unmodifiableMap;
/**
* Prevents specific references from being taken into account when computing the shortest strong
* reference path from a suspected leaking instance to the GC roots.
*
* This class lets you ignore known memory leaks that you known about. If the shortest path
* matches {@link ExcludedRefs}, than the heap analyzer should look for a longer path with nothing
* matching in {@link ExcludedRefs}.
*/
public final class ExcludedRefs implements Serializable {
public static Builder builder() {
return new BuilderWithParams();
}
public final Map<String, Map<String, Exclusion>> fieldNameByClassName;
public final Map<String, Map<String, Exclusion>> staticFieldNameByClassName;
public final Map<String, Exclusion> threadNames;
public final Map<String, Exclusion> classNames;
ExcludedRefs(BuilderWithParams builder) {
this.fieldNameByClassName = unmodifiableRefStringMap(builder.fieldNameByClassName);
this.staticFieldNameByClassName = unmodifiableRefStringMap(builder.staticFieldNameByClassName);
this.threadNames = unmodifiableRefMap(builder.threadNames);
this.classNames = unmodifiableRefMap(builder.classNames);
}
private Map<String, Map<String, Exclusion>> unmodifiableRefStringMap(
Map<String, Map<String, ParamsBuilder>> mapmap) {
LinkedHashMap<String, Map<String, Exclusion>> fieldNameByClassName = new LinkedHashMap<>();
for (Map.Entry<String, Map<String, ParamsBuilder>> entry : mapmap.entrySet()) {
fieldNameByClassName.put(entry.getKey(), unmodifiableRefMap(entry.getValue()));
}
return unmodifiableMap(fieldNameByClassName);
}
private Map<String, Exclusion> unmodifiableRefMap(Map<String, ParamsBuilder> fieldBuilderMap) {
Map<String, Exclusion> fieldMap = new LinkedHashMap<>();
for (Map.Entry<String, ParamsBuilder> fieldEntry : fieldBuilderMap.entrySet()) {
fieldMap.put(fieldEntry.getKey(), new Exclusion(fieldEntry.getValue()));
}
return unmodifiableMap(fieldMap);
}
@Override public String toString() {
String string = "";
for (Map.Entry<String, Map<String, Exclusion>> classes : fieldNameByClassName.entrySet()) {
String clazz = classes.getKey();
for (Map.Entry<String, Exclusion> field : classes.getValue().entrySet()) {
String always = field.getValue().alwaysExclude ? " (always)" : "";
string += "| Field: " + clazz + "." + field.getKey() + always + "\n";
}
}
for (Map.Entry<String, Map<String, Exclusion>> classes : staticFieldNameByClassName.entrySet()) {
String clazz = classes.getKey();
for (Map.Entry<String, Exclusion> field : classes.getValue().entrySet()) {
String always = field.getValue().alwaysExclude ? " (always)" : "";
string += "| Static field: " + clazz + "." + field.getKey() + always + "\n";
}
}
for (Map.Entry<String, Exclusion> thread : threadNames.entrySet()) {
String always = thread.getValue().alwaysExclude ? " (always)" : "";
string += "| Thread:" + thread.getKey() + always + "\n";
}
for (Map.Entry<String, Exclusion> clazz : classNames.entrySet()) {
String always = clazz.getValue().alwaysExclude ? " (always)" : "";
string += "| Class:" + clazz.getKey() + always + "\n";
}
return string;
}
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);
ExcludedRefs build();
}
public static final class BuilderWithParams implements Builder {
private final Map<String, Map<String, ParamsBuilder>> fieldNameByClassName =
new LinkedHashMap<>();
private final Map<String, Map<String, ParamsBuilder>> staticFieldNameByClassName =
new LinkedHashMap<>();
private final Map<String, ParamsBuilder> threadNames = new LinkedHashMap<>();
private final Map<String, ParamsBuilder> classNames = new LinkedHashMap<>();
private ParamsBuilder lastParams;
BuilderWithParams() {
}
@Override public BuilderWithParams instanceField(String className, String fieldName) {
checkNotNull(className, "className");
checkNotNull(fieldName, "fieldName");
Map<String, ParamsBuilder> excludedFields = fieldNameByClassName.get(className);
if (excludedFields == null) {
excludedFields = new LinkedHashMap<>();
fieldNameByClassName.put(className, excludedFields);
}
lastParams = new ParamsBuilder("field " + className + "#" + fieldName);
excludedFields.put(fieldName, lastParams);
return this;
}
@Override public BuilderWithParams staticField(String className, String fieldName) {
checkNotNull(className, "className");
checkNotNull(fieldName, "fieldName");
Map<String, ParamsBuilder> excludedFields = staticFieldNameByClassName.get(className);
if (excludedFields == null) {
excludedFields = new LinkedHashMap<>();
staticFieldNameByClassName.put(className, excludedFields);
}
lastParams = new ParamsBuilder("static field " + className + "#" + fieldName);
excludedFields.put(fieldName, lastParams);
return this;
}
@Override public BuilderWithParams thread(String threadName) {
checkNotNull(threadName, "threadName");
lastParams = new ParamsBuilder("any threads named " + threadName);
threadNames.put(threadName, lastParams);
return this;
}
/** 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 BuilderWithParams named(String name) {
lastParams.name = name;
return this;
}
public BuilderWithParams reason(String reason) {
lastParams.reason = reason;
return this;
}
public BuilderWithParams alwaysExclude() {
lastParams.alwaysExclude = true;
return this;
}
@Override public ExcludedRefs build() {
return new ExcludedRefs(this);
}
}
}