package bluej.stride.framedjava.ast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.stream.Stream;
import bluej.stride.framedjava.elements.CodeElement;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.editor.Editor;
import bluej.editor.EditorWatcher;
import bluej.stride.framedjava.ast.JavaFragment.Destination;
import bluej.stride.framedjava.ast.JavaFragment.ErrorRelation;
import bluej.stride.framedjava.frames.DebugInfo;
import bluej.stride.framedjava.slots.ExpressionSlot;
import bluej.stride.generic.Frame;
import bluej.utility.Debug;
| A piece of Java source code, generated from some Stride code.
|
public class JavaSource
{
| The list of source code lines in the Java.
|
private final List<SourceLine> lines = new ArrayList<>();
| A single line of Java source code.
|
private static class SourceLine
{
| Some string of spaces for the left indent of the line
|
private String indent;
| Just Java content, no preceding spaces, no trailing newlines. Will not be modified.
|
private final List<JavaFragment> content;
| A handler for exceptions and breakpoints on this line
|
private final JavaSingleLineDebugHandler debugHandler;
| Is this line a breakpoint?
|
private final boolean breakpoint;
public SourceLine(String indent, List<JavaFragment> content,
JavaSingleLineDebugHandler debugHandler,
boolean breakpoint) {
if (indent == null || content == null) {
throw new IllegalArgumentException("Null argument to SourceLine");
}
this.indent = indent;
this.content = content;
this.debugHandler = debugHandler;
this.breakpoint = breakpoint;
for (JavaFragment f : content)
{
if (f == null)
throw new IllegalArgumentException("Cannot have null Java fragment in sourceLine");
}
}
}
public JavaSource(JavaSource copyFrom)
{
lines.addAll(copyFrom.lines);
}
| Creates a single line of Java code from the list of fragments, with the given debug handler.
|
public JavaSource(JavaSingleLineDebugHandler debugHandler, JavaFragment... line)
{
this(debugHandler, List.of(line));
}
| Creates a single line of Java code from the list of fragments, with the given debug handler.
|
public JavaSource(JavaSingleLineDebugHandler debugHandler, List<JavaFragment> line)
{
appendLine(List.copyOf(line), debugHandler);
}
| Appends a single line of Java code from the list of fragments, with the given debug handler.
|
public void appendLine(List<JavaFragment> line, JavaSingleLineDebugHandler debugHandler)
{
addLine(lines.size(), line, debugHandler);
}
| Prepends a single line of Java code from the list of fragments, with the given debug handler.
|
public void prependLine(List<JavaFragment> line, JavaSingleLineDebugHandler debugHandler)
{
addLine(0, line, debugHandler);
}
| Adds the contents of the given JavaSource at the beginning of this one.
|
public void prepend(JavaSource src)
{
lines.addAll(0, src.lines);
}
| Adds the contents of the given JavaSource at the end of this one.
|
public void append(JavaSource javaCode)
{
lines.addAll(javaCode.lines);
}
| Helper method to add a new line of source code before
| the given position in the list of lines.
|
private void addLine(int position, List<JavaFragment> line, JavaSingleLineDebugHandler debugHandler)
{
lines.add(position, new SourceLine("", line, debugHandler, false));
}
| Adds the given Java source to the end of this one,
| but indents it by four spaces. Note that the given
| Java source is affected, so pass some JavaSource you
| don't mind being modified.
|
public void addIndented(JavaSource javaCode)
{
for (SourceLine line : javaCode.lines) {
line.indent += " ";
}
append(javaCode);
}
| A callback interface to help record the position in the Java
| source file of Stride code elements.
|
private static interface Recorder
{
| To be able to match back the elements to their position
| in each line of the Java code.
| @param fragment The Java fragment we are recording the position for
| @param posInSource Position across whole file, 0 being first char
| @param lineNumber Line in the file
| @param columnNumber Column in the file
| @param len The length of the code in the file
|
public void recordPosition(JavaFragment fragment, int posInSource, int lineNumber, int columnNumber, int len);
}
| Generate the complete String of the source for saving to
| the actual .java file on disk.
|
@OnThread(Tag.FXPlatform)
public String toDiskJavaCodeString()
{
return toJavaCodeString(Destination.JAVA_FILE_TO_COMPILE, null, (frag, pos, lineNumber, columnNumber, len) -> frag.recordDiskPosition(lineNumber, columnNumber, len));
}
| Generates the complete string of the source, purely for in-memory code analysis purposes.
| @param positions A map to be filled with source code positions,
| mapping JavaFragment to their character index
| in the source.
| @param completing If non-null, a slot that is currently doing code completion.
| This slot will have its exact code generated, even if there's a syntax error,
| unlike the usual case where syntax errors are replaced by dummy valid code.
|
@OnThread(Tag.FXPlatform)
public String toMemoryJavaCodeString(IdentityHashMap<JavaFragment, Integer> positions, ExpressionSlot<?> completing)
{
return toJavaCodeString(Destination.SOURCE_DOC_TO_ANALYSE, completing, (frag, pos, a, b, c) -> positions.put(frag, pos));
}
| Generates the complete Java source as a String, without
| recording any source code positions. Useful for incomplete
| pieces of Java source code, such as generated code being
| inserted during Greenfoot's save the world.
|
@OnThread(Tag.FXPlatform)
public String toTemporaryJavaCodeString()
{
return toJavaCodeString(Destination.TEMPORARY, null, (frag, pos, a, b, c) -> {
});
}
| Helper method for generating a Java source code String,
| unifying the implementation of the above to***String methods.
|
@OnThread(Tag.FXPlatform)
private String toJavaCodeString(Destination dest, ExpressionSlot<?> completing, Recorder recorder)
{
final Parser.DummyNameGenerator nameGen = new Parser.DummyNameGenerator();
StringBuilder sourceString = new StringBuilder();
int lineNumber = 1;
for (SourceLine line : lines) {
int sourceLength = sourceString.length();
StringBuilder oneLineString = new StringBuilder(100);
oneLineString.append(line.indent);
for (JavaFragment fragment : line.content) {
int lineLength = oneLineString.length();
String codeLine = fragment.getJavaCode(dest, completing, nameGen);
recorder.recordPosition(fragment, sourceLength + lineLength, lineNumber, lineLength + 1, codeLine.length());
if (codeLine.contains("\n") || codeLine.contains("\r")) {
throw new IllegalStateException("Source line contains \\n or \\r! Line: " + codeLine);
}
oneLineString.append(codeLine);
}
sourceString.append(oneLineString.toString()).append("\n");
lineNumber += 1;
}
return sourceString.toString();
}
| Handles a Java compile error.
|
| @param startLine Position of the compile error in the .java file.
| @param startColumn Position of the compile error in the .java file.
| @param endLine Position of the compile error in the .java file.
| @param endColumn Position of the compile error in the .java file.
| @param message Message of the compile error.
| @param identifier Error identifier for data recording purposes
|
@OnThread(Tag.FX)
public void handleError(int startLine, int startColumn,
int endLine, int endColumn, String message, int identifier)
{
JavaFragment fragment = findError(startLine, startColumn, endLine, endColumn, message);
if (fragment != null)
{
fragment.showCompileError(startLine, startColumn, endLine, endColumn, message, identifier);
}
}
| Finds the JavaFragment which best corresponds to the given position range
| in the .java file. (There may be multiple candidates as the error may span
| multiple fragments. We pick the first fragment in the range which can
| show errors; i.e. excluding boilerplate keywords like the class keyword
| in a class declaration which can't be focused and thus cannot show an error.)
|
| @param startLine Position in the .java file.
| @param startColumn Position in the .java file.
| @param endLine Position in the .java file.
| @param endColumn Position in the .java file.
| @param message Compiler message. Only used for debugging output if
| we can't find the position
| @return The best JavaFragment for displaying error, or null if we can't find one.
|
@OnThread(Tag.Any)
public JavaFragment findError(int startLine, int startColumn, int endLine, int endColumn, String message)
{
if (startLine == lines.size() + 1) {
startLine -= 1;
}
if (startLine >= lines.size() || startLine == -1)
{
for (int i = lines.size() - 1; i >= 0; i--)
{
List<JavaFragment> frags = lines.get(i).content;
for (int j = frags.size() - 1; j >= 0; j--)
{
JavaFragment f = frags.get(j);
if (f.checkCompileError(startLine, startColumn, endLine, endColumn) != ErrorRelation.CANNOT_SHOW)
{
return f;
}
}
}
Debug.message("No fragments found capable of showing error (shouldn't happen): " + message);
return null;
}
JavaFragment last = null;
for (JavaFragment f : lines.get(startLine - 1).content)
ErrorRelation r = f.checkCompileError(startLine, startColumn, endLine, endColumn);
if (r == ErrorRelation.CANNOT_SHOW)
continue;
if (r == ErrorRelation.BEFORE_FRAGMENT && last != null)
{
return last;
}
else if (r == ErrorRelation.OVERLAPS_FRAGMENT)
{
return f;
}
last = f;
}
if (last != null)
{
return last;
}
else
{
Debug.reportError("No slots found to show compile error: (" + startLine + "," + startColumn + ")->(" + endLine + "," + endColumn + "): " + message);
return null;
}
}
| Handle a stop event (hitting a breakpoint or ending a step request)
|
| @param line The line number (first line is 1) in the Java source
| @param debug The debug info to display.
| @return A breakpoint interface that be queried for further info
|
@OnThread(Tag.FXPlatform)
public HighlightedBreakpoint handleStop(int line, DebugInfo debug)
{
JavaSingleLineDebugHandler handler = lines.get(line - 1).debugHandler;
if (handler != null) {
return handler.showDebugBefore(debug);
}
else {
Debug.message("Cannot debug line: " + lines.get(line - 1).content);
return null;
}
}
| Handle an exception that occurred.
| @param lineNumber The line number (first line is 1) in the Java source
|
@OnThread(Tag.FXPlatform)
public void handleException(int lineNumber)
{
Debug.message("Handling " + lineNumber);
JavaSingleLineDebugHandler handler = lines.get(lineNumber - 1).debugHandler;
if (handler != null)
{
handler.showException();
}
else
{
Debug.message("Cannot show exception for line: " + lines.get(lineNumber - 1).content);
}
}
| Register the current list of breakpoints with the editor
| watcher, and return the list of line numbers (first line is 1)
| in a list.
|
@OnThread(Tag.FXPlatform)
public List registerBreakpoints(Editor editor, EditorWatcher watcher)
{
List<Integer> breakpoints = new ArrayList<>();
for (int i = 0;i < lines.size(); i++) {
if (lines.get(i).breakpoint) {
watcher.breakpointToggleEvent(i + 1, true);
breakpoints.add(i + 1);
}
}
return breakpoints;
}
public static JavaSource createMethod(Frame frame, CodeElement srcEl, JavaSingleLineDebugHandler debugHandler,
JavadocUnit documentation, List<JavaFragment> header, List<JavaSource> contents)
{
JavaSource parent = new JavaSource(debugHandler, header);
parent.prependJavadoc(documentation.getJavaCode());
parent.appendLine(Arrays.asList(new FrameFragment(frame, srcEl, "{")), null);
for (JavaSource src : contents) {
parent.addIndented(src);
}
parent.appendLine(Arrays.asList(new FrameFragment(frame, srcEl, "}")), debugHandler);
return parent;
}
public static JavaSource createCompoundStatement(Frame frame, CodeElement srcEl, JavaSingleLineDebugHandler headerDebugHandler,
JavaContainerDebugHandler endDebugHandler, List<JavaFragment> header, List<JavaSource> contents)
{
return createCompoundStatement(frame, srcEl, headerDebugHandler, endDebugHandler, header, contents, null);
}
public static JavaSource createCompoundStatement(Frame frame, CodeElement srcEl, JavaSingleLineDebugHandler headerDebugHandler,
final JavaContainerDebugHandler endDebugHandler, List<JavaFragment> header, List<JavaSource> contents, JavaFragment footer)
{
ArrayList<JavaFragment> headerAndBrace = new ArrayList<>(header);
headerAndBrace.add(new FrameFragment(frame, srcEl, " {"));
JavaSource parent = new JavaSource(headerDebugHandler, headerAndBrace);
for (JavaSource src : contents) {
parent.addIndented(src);
}
|
| Adding the extra dummy statement causes the breakboint to appear twice. In addition, it seems to be
| unneeded after the change that been made in the 'removeSpecialsAfter' method in the 'JavaCanvas' class
|
|// If, loops, etc cannot have breakpoint on last line so need extra code to break on,
|
|// except if the last statement is a return one.
|
|boolean hasReturn = parent.lines.get(parent.lines.size() -1).content.get(0).getJavaCode().contains("return");
if (endDebugHandler != null && !hasReturn) {} parent.appendLine(Arrays.asList(b("if (Object.class != null);")), errorHandler, new JavaSingleLineDebugHandler() {} @Override public HighlightedBreakpoint showDebugBefore(DebugInfo debug)
{}return endDebugHandler.showDebugAtEnd(debug);
|
|}
|
|});
|
|}
parent.appendLine(Arrays.asList(new FrameFragment(frame, srcEl, "}")), null);
if (footer != null) {
parent.addIndented(new JavaSource(headerDebugHandler, footer));
}
return parent;
}
public static JavaSource createBreakpoint(Frame frame, CodeElement srcEl, JavaSingleLineDebugHandler handler)
{
JavaSource r = new JavaSource(handler);
r.lines.add(new SourceLine("", Arrays.asList(new FrameFragment(frame, srcEl, "{ int org_greenfoot_debug_frame = 7; } /* dummy code for breakpoint */")), handler, true));
return r;
}
public JavaSingleLineDebugHandler internalGetDebugHandler(int i)
{
return lines.get(i).debugHandler;
}
public void prependJavadoc(List<String> javadocLines)
{
for (int i = javadocLines.size() - 1; i >= 0; i--) {
prependLine(Arrays.asList(new FrameFragment(null, null, javadocLines.get(i))), null);
}
}
public Stream getAllFragments()
{
return lines.stream().flatMap(l -> l.content.stream());
}
}
top,
use,
map,
class JavaSource
top,
use,
map,
class JavaSource . SourceLine
. SourceLine
. JavaSource
. JavaSource
. JavaSource
. appendLine
. prependLine
. prepend
. append
. addLine
. addIndented
top,
use,
map,
interface JavaSource . SourceLine . Recorder
. recordPosition
. toDiskJavaCodeString
. toMemoryJavaCodeString
. toTemporaryJavaCodeString
. toJavaCodeString
. handleError
. findError
. handleStop
. handleException
. registerBreakpoints
. createMethod
. createCompoundStatement
. createCompoundStatement
. createBreakpoint
. internalGetDebugHandler
. prependJavadoc
. getAllFragments
473 neLoCode
+ 80 LoComm