diff --git a/src/main/java/org/mockito/internal/debugging/Java8LocationImpl.java b/src/main/java/org/mockito/internal/debugging/Java8LocationImpl.java new file mode 100644 index 0000000000..e8ee387c0a --- /dev/null +++ b/src/main/java/org/mockito/internal/debugging/Java8LocationImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.debugging; + +import java.io.Serializable; + +import org.mockito.internal.exceptions.stacktrace.StackTraceFilter; +import org.mockito.invocation.Location; + +class Java8LocationImpl implements Location, Serializable { + + private static final long serialVersionUID = -9054861157390980624L; + // Limit the amount of objects being created, as this class is heavily instantiated: + private static final StackTraceFilter stackTraceFilter = new StackTraceFilter(); + + private String stackTraceLine; + private String sourceFile; + + public Java8LocationImpl(Throwable stackTraceHolder, boolean isInline) { + this(stackTraceFilter, stackTraceHolder, isInline); + } + + private Java8LocationImpl( + StackTraceFilter stackTraceFilter, Throwable stackTraceHolder, boolean isInline) { + computeStackTraceInformation(stackTraceFilter, stackTraceHolder, isInline); + } + + @Override + public String toString() { + return stackTraceLine; + } + + /** + * Eagerly compute the stacktrace line from the stackTraceHolder. Storing the Throwable is + * memory-intensive for tests that have large stacktraces and have a lot of invocations on + * mocks. + */ + private void computeStackTraceInformation( + StackTraceFilter stackTraceFilter, Throwable stackTraceHolder, boolean isInline) { + StackTraceElement filtered = stackTraceFilter.filterFirst(stackTraceHolder, isInline); + + // there are corner cases where exception can have a null or empty stack trace + // for example, a custom exception can override getStackTrace() method + if (filtered == null) { + this.stackTraceLine = "-> at <>"; + this.sourceFile = ""; + } else { + this.stackTraceLine = "-> at " + filtered; + this.sourceFile = filtered.getFileName(); + } + } + + @Override + public String getSourceFile() { + return sourceFile; + } +} diff --git a/src/main/java/org/mockito/internal/debugging/LocationFactory.java b/src/main/java/org/mockito/internal/debugging/LocationFactory.java index 08e441e87a..3d936ae761 100644 --- a/src/main/java/org/mockito/internal/debugging/LocationFactory.java +++ b/src/main/java/org/mockito/internal/debugging/LocationFactory.java @@ -7,6 +7,8 @@ import org.mockito.invocation.Location; public final class LocationFactory { + private static final Factory factory = createLocationFactory(); + private LocationFactory() {} public static Location create() { @@ -14,6 +16,36 @@ public static Location create() { } public static Location create(boolean inline) { - return new LocationImpl(inline); + return factory.create(inline); + } + + private interface Factory { + Location create(boolean inline); + } + + private static Factory createLocationFactory() { + try { + // On some platforms, like Android, the StackWalker APIs may not be + // available, in this case we have to fallback to Java 8 style of stack + // traversing. + Class.forName("java.lang.StackWalker"); + return new DefaultLocationFactory(); + } catch (ClassNotFoundException e) { + return new Java8LocationFactory(); + } + } + + private static final class Java8LocationFactory implements Factory { + @Override + public Location create(boolean inline) { + return new Java8LocationImpl(new Throwable(), inline); + } + } + + private static final class DefaultLocationFactory implements Factory { + @Override + public Location create(boolean inline) { + return new LocationImpl(inline); + } } }