Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class DebugScreenExample {
// Subclass the handler:
class MyDebugScreen extends DebugScreen {
@Override
protected void installTables(LinkedHashMap<String, Map<String, ? extends Object>> tables, Request request, Exception exception) {
protected void installTables(LinkedHashMap<String, Map<String, ? extends Object>> tables, Request request, Throwable exception) {
super.installTables(tables, request, exception);
Map<String, Object> myTable = new LinkedHashMap<>();
tables.put("My Table", myTable);
Expand All @@ -62,11 +62,11 @@ By default DebugScreen looks within the folders `src/main/java` and `src/test/ja

```java
Spark.exception(Exception.class, new DebugScreen(
ImmutableList.of(new FileSearchSourceLocator(new File("/path/to/source/code")))
ImmutableList.of(new LocalSourceLocator(new File("/path/to/source/code")))
));
```

You can specify multiple locators in the list (later ones are used as fallbacks if earlier ones cannot find a file). If this is still not specific enough for you, you can implement your own `SourceLocator` to find the files and provide that to the handler.
You can specify multiple locators in the list (later ones are used as fallbacks if earlier ones cannot find a file). If this is still not specific enough for you, you can implement your own `SourceLocator` to find the files and provide that to the handler. This could even fetch files over the network if needed.

## Notes:

Expand Down
477 changes: 243 additions & 234 deletions src/main/java/spark/debug/DebugScreen.java

Large diffs are not rendered by default.

96 changes: 0 additions & 96 deletions src/main/java/spark/debug/FileSearchSourceLocator.java

This file was deleted.

61 changes: 61 additions & 0 deletions src/main/java/spark/debug/LocalSourceFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package spark.debug;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Map;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;

/**
* A source file located on a local file system.
*/
public class LocalSourceFile implements SourceFile {
private final File file;

public LocalSourceFile(File file) {
this.file = file;
}

@Override
public String getPath() {
return file.getPath();
}

@Override
public Optional<Map<Integer, String>> getLines(StackTraceElement frame) {
// If the frame has no line number attached, we can't fetch anything.
if (frame.getLineNumber() < 0) {
return Optional.absent();
}

// Otherwise, fetch 20 lines centered on the number provided in the trace.
ImmutableMap.Builder<Integer, String> lines = ImmutableMap.builder();
int start = Math.max(frame.getLineNumber() - 10, 0);
int end = start + 20;
int current = 0;

try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
if (current < start) {
current++;
continue;
}

if (current > end) {
break;
}

lines.put(current, line);
current++;
}

return Optional.of(lines.build());
} catch (Exception e) {
// Ignore and move on (the frame just won't have a code snippet).
return Optional.absent();
}
}
}
109 changes: 109 additions & 0 deletions src/main/java/spark/debug/LocalSourceLocator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package spark.debug;

import java.io.File;
import java.io.IOException;
import java.util.Collection;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;

import com.google.common.base.Optional;

/**
* Locates source files by searching exhaustively within a base directory.
* The base directory should contain all of the code for your app (e.g. src/main/java).
* Assumes conventions are followed (e.g. path.to.Class -> src/main/java/path/to/Class.java).
*/
public class LocalSourceLocator implements SourceLocator {
private final File basePathFile;

/**
* @param basePath The path of the directory to search for source files within.
*/
public LocalSourceLocator(String basePath) {
this.basePathFile = new File(basePath);
}

/**
* @param basePath The directory to search for source files within.
*/
public LocalSourceLocator(File basePath) {
this.basePathFile = basePath;
}

@Override
public String toString() {
try {
return this.basePathFile.getCanonicalPath();
} catch (Exception e) {
return this.basePathFile.getPath();
}
}

@Override
public Optional<SourceFile> findFileForFrame(StackTraceElement frame) {
// Cannot find a file if the frame does not have one.
if (frame.getFileName() == null) {
return Optional.absent();
}

// Ignore cases where the directory doesn't exist/cannot be read.
// No point in returning an error here since this is for a debug screen.
if (!basePathFile.exists() || !basePathFile.isDirectory() || !basePathFile.canRead()) {
return Optional.absent();
}

// This is where things get a little bit tricky.
// The stack frame only contains the file name (e.g. "File.java"), but not the full path.
// The compiled byte code also does not contain the full path.
// Our strategy here is to find all files with a matching file name and pare them down.
// We may not always find a file (e.g. sometimes they won't be available like in a JAR).

Collection<File> possibilities = FileUtils.listFiles(basePathFile, new IOFileFilter() {
@Override
public boolean accept(File file) {
return file.getName().equals(frame.getFileName());
}

@Override
public boolean accept(File dir, String name) {
return name.equals(frame.getFileName());
}
}, TrueFileFilter.INSTANCE);

if (possibilities.size() == 1) {
// Assume the matched file is the source file (we may be wrong, but we can't know).
return Optional.of(new LocalSourceFile(Iterables.first(possibilities)));
}

if (possibilities.size() > 1) {
// Use the canonical class name to identify the correct file.
// Assumes the directory structure matches the package and name of the class.
// e.g. edu.rice.mschurr.Hello -> src/main/java/edu/rice/mschurr/Hello.java
String className = frame.getClassName();

// Remove anything after the $ in the class name to get the declaring class name.
// Effectively, this finds the top-level class name for lambdas and nested classes.
if (className.indexOf('$') != -1) {
className = className.substring(0, className.lastIndexOf('$'));
}

// Build the expected path of the file.
String path = File.separatorChar + className.replace('.', File.separatorChar) + ".java";

// Check each possibility against the expected path.
try {
for (File file : possibilities) {
if (file.getCanonicalPath().endsWith(path)) {
return Optional.of(new LocalSourceFile(file));
}
}
} catch (IOException e) {
// Ignore (the provided frame just won't have a code snippet).
}
}

return Optional.absent();
}
}
19 changes: 19 additions & 0 deletions src/main/java/spark/debug/SourceFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package spark.debug;

import java.util.Map;

import com.google.common.base.Optional;

public interface SourceFile {
/**
* @return The path of the file (to display)
*/
public String getPath();

/**
* @param frame A stack frame.
* @return Returns +- 10 lines of the file's content (centered on the line in the stack frame). A
* map of line numbers in the file to the contents of that line.
*/
public Optional<Map<Integer, String>> getLines(StackTraceElement frame);
}
14 changes: 5 additions & 9 deletions src/main/java/spark/debug/SourceLocator.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package spark.debug;

import java.io.File;

import com.google.common.base.Optional;

/**
* Defines an interface for locating source files.
*
* @author mschurr
*/
public interface SourceLocator {
/**
* @param frame A stack trace frame.
* @return A file containing the code referenced by the StackTraceElement.
*/
public Optional<File> findFileForFrame(StackTraceElement frame);
/**
* @param frame A stack trace frame.
* @return A file containing the code referenced by the StackTraceElement.
*/
public Optional<SourceFile> findFileForFrame(StackTraceElement frame);
}
Loading