ECS + ASTGen: offset mapping and error check overhaul

This commit is contained in:
Jakub Valtar
2016-04-27 15:52:17 +02:00
parent e8856e9d6d
commit 7c76e30543
3 changed files with 214 additions and 369 deletions

View File

@@ -113,6 +113,7 @@ import processing.app.Sketch;
import processing.app.ui.EditorStatus;
import processing.app.ui.Toolkit;
import processing.mode.java.JavaEditor;
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
import com.google.classpath.ClassPath;
import com.google.classpath.RegExpResourceFilter;
@@ -966,18 +967,6 @@ public class ASTGenerator {
}
/**
* Given a word(identifier) in pde code, finds its location in the ASTNode
* @param lineNumber
* @param name
* @param offset - line start nonwhitespace offset
* @param scrollOnly
* @return
*/
public SimpleName getSimpleNameAt(int javaOffset) {
return getSimpleNameAt(javaOffset, javaOffset);
}
public SimpleName getSimpleNameAt(int startJavaOffset, int stopJavaOffset) {
Messages.log("* getSimpleNameAt");
@@ -1168,10 +1157,9 @@ public class ASTGenerator {
.forEach(occurrences::add);
}
// TODO: extract mapping from ShowUsageTreeNode to use here
Map<Integer, List<ShowUsageTreeNode>> mappedNodes = occurrences.stream()
.map(node -> ShowUsageTreeNode.fromSimpleName(ps, node))
.collect(Collectors.groupingBy(node -> node.tabIndex));
Map<Integer, List<SketchInterval>> mappedNodes = occurrences.stream()
.map(ps::mapJavaToSketch)
.collect(Collectors.groupingBy(interval -> interval.tabIndex));
Sketch sketch = ps.sketch;
@@ -1183,9 +1171,10 @@ public class ASTGenerator {
int tabIndex = entry.getKey();
sketch.setCurrentCode(tabIndex);
List<ShowUsageTreeNode> nodes = entry.getValue();
List<SketchInterval> nodes = entry.getValue();
nodes.stream()
.sorted(Comparator.comparing((ShowUsageTreeNode n) -> n.startTabOffset).reversed())
// Replace from the end so all unprocess offsets stay valid
.sorted(Comparator.comparing((SketchInterval n) -> n.startTabOffset).reversed())
.forEach(n -> {
// Make sure offsets are in bounds
int length = editor.getTextArea().getDocumentLength();
@@ -1200,16 +1189,21 @@ public class ASTGenerator {
sketch.setModified(true);
});
int precedingNodes = (int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList()).stream()
.filter(node -> node.stopTabOffset < currentOffset)
.count();
int offsetDiff = precedingNodes * (newName.length() - (binding.getName().length()));
int precedingIntervals =
(int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList())
.stream()
.filter(interval -> interval.stopTabOffset < currentOffset)
.count();
int intervalLengthDiff = newName.length() - binding.getName().length();
int offsetDiff = precedingIntervals * intervalLengthDiff;
sketch.setCurrentCode(currentTabIndex);
editor.getTextArea().setCaretPosition(currentOffset + offsetDiff);
editor.stopCompoundEdit();
// TODO: update Show Usage window if shown
errorCheckerService.request();
}
@@ -1246,8 +1240,12 @@ public class ASTGenerator {
List<SimpleName> occurrences = findAllOccurrences(ps.compilationUnit, bindingKey);
if (occurrences == null) return;
List<SketchInterval> occurrenceIntervals = occurrences.stream()
.map(ps::mapJavaToSketch)
.collect(Collectors.toList());
// Send to gui
EventQueue.invokeLater(() -> gui.handleShowUsage(binding, occurrences));
EventQueue.invokeLater(() -> gui.handleShowUsage(binding, occurrenceIntervals));
}
@@ -2147,7 +2145,8 @@ public class ASTGenerator {
// Adjust line number for tabbed sketches
int codeIndex = editor.getSketch().getCodeIndex(editor.getCurrentTab());
int lineNumber = ps.tabLineToJavaLine(codeIndex, line);
int lineStartOffset = editor.getTextArea().getLineStartOffset(line);
int lineNumber = ps.tabOffsetToJavaLine(codeIndex, lineStartOffset);
// Ensure that we're not inside a comment. TODO: Binary search
@@ -2572,7 +2571,7 @@ public class ASTGenerator {
int javaOffset = ps.tabOffsetToJavaOffset(tabIndex, offset);
SimpleName simpleName = getSimpleNameAt(javaOffset);
SimpleName simpleName = getSimpleNameAt(javaOffset, javaOffset);
if (simpleName == null) {
Messages.log("nothing found");
@@ -2608,7 +2607,7 @@ public class ASTGenerator {
handleShowUsage(binding);
} else {
Messages.log("found declaration, offset " + decl.getStartPosition() + ", name: " + declName);
errorCheckerService.highlightJavaRange(declName.getStartPosition(), declName.getLength());
errorCheckerService.highlightNode(declName);
}
}
@@ -2796,7 +2795,7 @@ public class ASTGenerator {
}
public void handleShowUsage(IBinding binding, List<SimpleName> occurrences) {
public void handleShowUsage(IBinding binding, List<SketchInterval> occurrences) {
showUsageBinding = binding;
PreprocessedSketch ps = astGen.errorCheckerService.latestResult;
@@ -2822,11 +2821,12 @@ public class ASTGenerator {
new DefaultMutableTreeNode(bindingType + ": " + binding.getName());
Map<Integer, List<ShowUsageTreeNode>> tabGroupedTreeNodes = occurrences.stream()
// Convert to TreeNodes
.map(name -> ShowUsageTreeNode.fromSimpleName(ps, name))
// TODO: this has to be fixed with better token mapping
// remove occurrences which fall into generated header
.filter(node -> node.tabIndex != 0 || (node.startTabOffset >= 0 && node.stopTabOffset > 0))
.filter(in -> in.tabIndex != 0 ||
(in.startTabOffset >= 0 && in.stopTabOffset > 0))
// Convert to TreeNodes
.map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in))
// Group by tab
.collect(Collectors.groupingBy(node -> node.tabIndex));
@@ -2971,9 +2971,7 @@ public class ASTGenerator {
(DefaultMutableTreeNode) debugTree.getLastSelectedPathComponent();
if (tnode.getUserObject() instanceof ASTNode) {
ASTNode node = (ASTNode) tnode.getUserObject();
int startOffset = node.getStartPosition();
int length = node.getLength();
astGen.errorCheckerService.highlightJavaRange(startOffset, length);
astGen.errorCheckerService.highlightNode(node);
}
}
@@ -2985,31 +2983,19 @@ public class ASTGenerator {
protected static class ShowUsageTreeNode {
int tabIndex;
int tabLine;
int startTabOffset;
int stopTabOffset;
String text;
public static ShowUsageTreeNode fromSimpleName(PreprocessedSketch ps, SimpleName name) {
int startJavaOffset = name.getStartPosition();
int stopJavaOffset = startJavaOffset + name.getLength();
public static ShowUsageTreeNode fromSketchInterval(PreprocessedSketch ps, SketchInterval in) {
int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', in.startPdeOffset) + 1;
int lineStopPdeOffset = ps.pdeCode.indexOf('\n', in.stopPdeOffset);
int startPdeOffset = ps.javaOffsetToPdeOffset(startJavaOffset);
int stopPdeOffset = ps.javaOffsetToPdeOffset(stopJavaOffset);
int highlightStartOffset = in.startPdeOffset - lineStartPdeOffset;
int highlightStopOffset = in.stopPdeOffset - lineStartPdeOffset;
int tabIndex = ps.pdeOffsetToTabIndex(startPdeOffset);
int startTabOffset = ps.pdeOffsetToTabOffset(tabIndex, startPdeOffset);
int stopTabOffset = ps.pdeOffsetToTabOffset(tabIndex, stopPdeOffset);
int tabLine = ps.tabOffsetToTabLine(tabIndex, startTabOffset);
int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', startPdeOffset) + 1;
int lineStopPdeOffset = ps.pdeCode.indexOf('\n', stopPdeOffset);
int highlightStartOffset = startPdeOffset - lineStartPdeOffset;
int highlightStopOffset = stopPdeOffset - lineStartPdeOffset;
int tabLine = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
// TODO: what a mess
String line = ps.pdeCode.substring(lineStartPdeOffset, lineStopPdeOffset);
@@ -3023,10 +3009,9 @@ public class ASTGenerator {
line = line.trim();
ShowUsageTreeNode node = new ShowUsageTreeNode();
node.tabIndex = tabIndex;
node.tabLine = tabLine;
node.startTabOffset = startTabOffset;
node.stopTabOffset = stopTabOffset;
node.tabIndex = in.tabIndex;
node.startTabOffset = in.startTabOffset;
node.stopTabOffset = in.stopTabOffset;
node.text = "<html><font color=#bbbbbb>" +
(tabLine + 1) + "</font> <font color=#777777>" + line + "</font></html>";

View File

@@ -25,10 +25,7 @@ import com.google.classpath.ClassPathFactory;
import com.google.classpath.RegExpResourceFilter;
import java.awt.EventQueue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
@@ -38,7 +35,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -56,21 +52,11 @@ import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import processing.app.Library;
import processing.app.Messages;
@@ -86,6 +72,7 @@ import processing.data.IntList;
import processing.data.StringList;
import processing.mode.java.JavaMode;
import processing.mode.java.JavaEditor;
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
import processing.mode.java.pdex.TextTransform.OffsetMapper;
import processing.mode.java.preproc.PdePreprocessor;
import processing.mode.java.preproc.PdePreprocessor.Mode;
@@ -324,10 +311,10 @@ public class ErrorCheckerService {
workBuffer.append('\n');
}
}
result.tabStarts = tabStartsList.array();
result.tabStartOffsets = tabStartsList.array();
String pdeStage = result.pdeCode = workBuffer.toString();
char[] javaStageChars;
char[] compilableStageChars;
{ // Prepare core and default imports
// TODO: do this only once
@@ -344,6 +331,7 @@ public class ErrorCheckerService {
}
// Prepare code folder imports
// TODO: do this only when code folder changes
if (sketch.hasCodeFolder()) {
File codeFolder = sketch.getCodeFolder();
String codeFolderClassPath = Util.contentsToClassPath(codeFolder);
@@ -355,21 +343,22 @@ public class ErrorCheckerService {
// TODO: convert unicode escapes to chars
{{ // SYNTAX CHECK
List<IProblem> problems = new ArrayList<>();
SourceUtils.scrubCommentsAndStrings(workBuffer);
SourceUtils.scrubCommentsAndStrings(workBuffer);
Mode mode = PdePreprocessor.parseMode(workBuffer);
Mode mode = PdePreprocessor.parseMode(workBuffer);
// Prepare transforms
TextTransform toParsable = new TextTransform(pdeStage);
toParsable.addAll(SourceUtils.insertImports(coreAndDefaultImports));
toParsable.addAll(SourceUtils.insertImports(codeFolderImports));
toParsable.addAll(SourceUtils.parseProgramImports(workBuffer, programImports));
toParsable.addAll(SourceUtils.replaceTypeConstructors(workBuffer));
toParsable.addAll(SourceUtils.replaceHexLiterals(workBuffer));
toParsable.addAll(SourceUtils.wrapSketch(mode, className, workBuffer.length()));
// Prepare transforms to convert pde code into parsable code
TextTransform toParsable = new TextTransform(pdeStage);
toParsable.addAll(SourceUtils.insertImports(coreAndDefaultImports));
toParsable.addAll(SourceUtils.insertImports(codeFolderImports));
toParsable.addAll(SourceUtils.parseProgramImports(workBuffer, programImports));
toParsable.addAll(SourceUtils.replaceTypeConstructors(workBuffer));
toParsable.addAll(SourceUtils.replaceHexLiterals(workBuffer));
toParsable.addAll(SourceUtils.wrapSketch(mode, className, workBuffer.length()));
{ // Refresh sketch classloader and classpath if imports changed
boolean importsChanged = prevResult == null ||
prevResult.classPath == null || prevResult.classLoader == null ||
checkIfImportsChanged(programImports, prevResult.programImports) ||
@@ -396,52 +385,79 @@ public class ErrorCheckerService {
result.classPath = prevResult.classPath;
result.classPathArray = prevResult.classPathArray;
}
}
// Transform code
String parsableStage = toParsable.apply();
OffsetMapper parsableMapper = toParsable.getMapper();
// Transform code to parsable state
String parsableStage = toParsable.apply();
OffsetMapper parsableMapper = toParsable.getMapper();
// Create AST
CompilationUnit parsableCU =
makeAST(parser, parsableStage.toCharArray(), COMPILER_OPTIONS);
// Create intermediate AST for advanced preprocessing
CompilationUnit parsableCU =
makeAST(parser, parsableStage.toCharArray(), COMPILER_OPTIONS);
// Prepare transforms
TextTransform toCompilable = new TextTransform(parsableStage);
toCompilable.addAll(SourceUtils.addPublicToTopLevelMethods(parsableCU));
toCompilable.addAll(SourceUtils.replaceColorAndFixFloats(parsableCU));
// Prepare advanced transforms which operate on AST
TextTransform toCompilable = new TextTransform(parsableStage);
toCompilable.addAll(SourceUtils.addPublicToTopLevelMethods(parsableCU));
toCompilable.addAll(SourceUtils.replaceColorAndFixFloats(parsableCU));
// Transform code
String compilableStage = toCompilable.apply();
OffsetMapper compilableMapper = toCompilable.getMapper();
javaStageChars = compilableStage.toCharArray();
// Transform code to compilable state
String compilableStage = toCompilable.apply();
OffsetMapper compilableMapper = toCompilable.getMapper();
compilableStageChars = compilableStage.toCharArray();
// Create AST
CompilationUnit compilableCU =
makeASTWithBindings(parser, javaStageChars, COMPILER_OPTIONS,
className, result.classPathArray);
// Create compilable AST to get syntax problems
CompilationUnit compilableCU =
makeAST(parser, compilableStageChars, COMPILER_OPTIONS);
// Update result
result.offsetMapper = parsableMapper.thenMapping(compilableMapper);
result.javaCode = compilableStage;
result.compilationUnit = compilableCU;
// Get syntax problems from compilable AST
List<IProblem> syntaxProblems = Arrays.asList(compilableCU.getProblems());
problems.addAll(syntaxProblems);
result.hasSyntaxErrors = syntaxProblems.stream().anyMatch(IProblem::isError);
// Get syntax problems
List<IProblem> syntaxProblems = Arrays.asList(compilableCU.getProblems());
// Generate bindings after getting problems - avoids
// 'missing type' errors when there are syntax problems
CompilationUnit bindingsCU =
makeASTWithBindings(parser, compilableStageChars, COMPILER_OPTIONS,
className, result.classPathArray);
result.hasSyntaxErrors = syntaxProblems.stream().anyMatch(IProblem::isError);
}}
// Show compilation problems only when there are no syntax problems
if (!result.hasSyntaxErrors) {
problems.clear(); // clear warnings, they will be generated again
List<IProblem> bindingsProblems = Arrays.asList(bindingsCU.getProblems());
problems.addAll(bindingsProblems);
result.hasCompilationErrors = bindingsProblems.stream().anyMatch(IProblem::isError);
}
{{ // COMPILATION CHECK
// Compile it
List<IProblem> compilationProblems =
compileAndReturnProblems(className, javaStageChars,
COMPILER_OPTIONS, result.classLoader,
result.classPath);
// Update builder
result.offsetMapper = parsableMapper.thenMapping(compilableMapper);
result.javaCode = compilableStage;
result.compilationUnit = bindingsCU;
// TODO: handle error stuff *after* building PreprocessedSketch
List<Problem> mappedCompilationProblems =
mapProblems(compilationProblems, result.tabStarts, result.pdeCode,
result.offsetMapper);
// Build it
PreprocessedSketch ps = result.build();
{ // Process problems
List<Problem> mappedCompilationProblems = problems.stream()
// Filter Warnings if they are not enabled
.filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled))
// Hide a useless error which is produced when a line ends with
// an identifier without a semicolon. "Missing a semicolon" is
// also produced and is preferred over this one.
// (Syntax error, insert ":: IdentifierOrNew" to complete Expression)
// See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405780
.filter(iproblem -> !iproblem.getMessage()
.contains("Syntax error, insert \":: IdentifierOrNew\""))
// Transform into our Problems
.map(iproblem -> {
int start = iproblem.getSourceStart();
int stop = iproblem.getSourceEnd() + 1; // make it exclusive
SketchInterval in = ps.mapJavaToSketch(start, stop);
int line = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
Problem p = new Problem(iproblem, in.tabIndex, line);
p.setPDEOffsets(in.startTabOffset, in.stopTabOffset);
return p;
})
.collect(Collectors.toList());
if (Preferences.getBoolean(JavaMode.SUGGEST_IMPORTS_PREF)) {
Map<String, List<Problem>> undefinedTypeProblems = mappedCompilationProblems.stream()
@@ -462,19 +478,16 @@ public class ErrorCheckerService {
undefinedTypeProblems.entrySet().stream()
.forEach(entry -> {
String missingClass = entry.getKey();
List<Problem> problems = entry.getValue();
List<Problem> affectedProblems = entry.getValue();
String[] suggestions = getImportSuggestions(cp, missingClass);
problems.forEach(p -> p.setImportSuggestions(suggestions));
affectedProblems.forEach(p -> p.setImportSuggestions(suggestions));
});
}
result.problems.addAll(mappedCompilationProblems);
ps.problems.addAll(mappedCompilationProblems);
}
result.hasCompilationErrors = mappedCompilationProblems.stream()
.anyMatch(Problem::isError);
}}
return result.build();
return ps;
}
@@ -556,57 +569,6 @@ public class ErrorCheckerService {
}
protected static List<Problem> mapProblems(List<IProblem> problems,
int[] tabStarts, String pdeCode,
OffsetMapper mapper) {
return problems.stream()
// Filter Warnings if they are not enabled
.filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled))
// Hide a useless error which is produced when a line ends with
// an identifier without a semicolon. "Missing a semicolon" is
// also produced and is preferred over this one.
// (Syntax error, insert ":: IdentifierOrNew" to complete Expression)
// See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405780
.filter(iproblem -> !iproblem.getMessage()
.contains("Syntax error, insert \":: IdentifierOrNew\""))
// Transform into our Problems
.map(iproblem -> {
int start = iproblem.getSourceStart();
int stop = iproblem.getSourceEnd(); // inclusive
// Apply mapping
start = mapper.getInputOffset(start);
stop = mapper.getInputOffset(stop);
if (stop < start) {
// Should not happen, just to be sure
int temp = start;
start = stop;
stop = temp;
}
int pdeStart = PApplet.constrain(start, 0, pdeCode.length()-1);
int pdeStop = PApplet.constrain(stop + 1, 1, pdeCode.length()); // +1 for exclusive end
int tab = Arrays.binarySearch(tabStarts, pdeStart);
if (tab < 0) {
tab = -(tab + 1) - 1;
}
int tabStart = tabStarts[tab];
// TODO: quick hack; make it smart, fast & beautiful later
int line = Util.countLines(pdeCode.substring(tabStart, pdeStart)) - 1;
Problem problem = new Problem(iproblem, tab, line);
problem.setPDEOffsets(pdeStart - tabStart, pdeStop - tabStart);
return problem;
})
.collect(Collectors.toList());
}
protected static CompilationUnit makeAST(ASTParser parser,
char[] source,
Map<String, String> options) {
@@ -635,50 +597,6 @@ public class ErrorCheckerService {
}
/**
* Performs compiler error check.
* @param sourceName - name of the class
* @param source - source code
* @param options - compiler options
* @param classLoader - custom classloader which can load all dependencies
* @return list of compiler errors and warnings
*/
static public List<IProblem> compileAndReturnProblems(String sourceName,
char[] source,
Map<String, String> options,
URLClassLoader classLoader,
ClassPath classPath) {
final List<IProblem> problems = new ArrayList<>();
ICompilerRequestor requestor = cr -> {
if (cr.hasProblems()) Collections.addAll(problems, cr.getProblems());
};
final char[] contents = source;
final char[][] packageName = new char[][]{};
final char[] mainTypeName = sourceName.toCharArray();
final char[] fileName = (sourceName + ".java").toCharArray();
ICompilationUnit unit = new ICompilationUnit() {
@Override public char[] getContents() { return contents; }
@Override public char[][] getPackageName() { return packageName; }
@Override public char[] getMainTypeName() { return mainTypeName; }
@Override public char[] getFileName() { return fileName; }
@Override public boolean ignoreOptionalProblems() { return false; }
};
org.eclipse.jdt.internal.compiler.Compiler compiler =
new Compiler(new NameEnvironmentImpl(classLoader, classPath),
DefaultErrorHandlingPolicies.proceedWithAllProblems(),
new CompilerOptions(options),
requestor,
new DefaultProblemFactory(Locale.getDefault()));
compiler.compile(new ICompilationUnit[]{unit});
return problems;
}
public CompilationUnit getLatestCU() {
return latestResult.compilationUnit;
}
@@ -932,32 +850,12 @@ public class ErrorCheckerService {
}
public void highlightJavaRange(int startJavaOffset, int javaLength) {
public void highlightNode(ASTNode node) {
PreprocessedSketch ps = latestResult;
int stopJavaOffset = startJavaOffset + javaLength;
int startPdeOffset = ps.javaOffsetToPdeOffset(startJavaOffset);
int stopPdeOffset;
if (javaLength == 0) {
stopPdeOffset = startPdeOffset;
} else {
// Subtract one for inclusive end
stopPdeOffset = ps.javaOffsetToPdeOffset(stopJavaOffset - 1);
if (javaLength == 1 || stopPdeOffset > startPdeOffset) {
// Add one back for exclusive end
stopPdeOffset += 1;
}
}
int tabIndex = ps.pdeOffsetToTabIndex(startPdeOffset);
int startTabOffset = ps.pdeOffsetToTabOffset(tabIndex, startPdeOffset);
int stopTabOffset = ps.pdeOffsetToTabOffset(tabIndex, stopPdeOffset);
EventQueue.invokeLater(() -> highlightTabRange(tabIndex, startTabOffset, stopTabOffset));
SketchInterval si = ps.mapJavaToSketch(node);
EventQueue.invokeLater(() -> {
highlightTabRange(si.tabIndex, si.startTabOffset, si.stopTabOffset);
});
}
@@ -996,70 +894,4 @@ public class ErrorCheckerService {
}
}
private static class NameEnvironmentImpl implements INameEnvironment {
private final ClassLoader classLoader;
private final ClassPath classPath;
NameEnvironmentImpl(ClassLoader classLoader, ClassPath classPath) {
this.classLoader = classLoader;
this.classPath = classPath;
}
@Override
public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
return readClassFile(CharOperation.toString(compoundTypeName), classLoader);
}
@Override
public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) {
String fullName = CharOperation.toString(packageName);
if (typeName != null) {
if (fullName.length() > 0) fullName += ".";
fullName += new String(typeName);
}
return readClassFile(fullName, classLoader);
}
@Override
public boolean isPackage(char[][] parentPackageName, char[] packageName) {
String fullName = CharOperation.toString(parentPackageName);
if (packageName != null) {
if (fullName.length() > 0) fullName += ".";
fullName += new String(packageName);
}
return classPath.isPackage(fullName.replace('.', '/'));
}
@Override
public void cleanup() { }
private static NameEnvironmentAnswer readClassFile(String fullName, ClassLoader classLoader) {
String classFileName = fullName.replace('.', '/') + ".class";
InputStream is = classLoader.getResourceAsStream(classFileName);
if (is == null) return null;
byte[] buffer = new byte[8192];
ByteArrayOutputStream os = new ByteArrayOutputStream(buffer.length);
try {
int bytes;
while ((bytes = is.read(buffer, 0, buffer.length)) > 0) {
os.write(buffer, 0, bytes);
}
os.flush();
ClassFileReader classFileReader =
new ClassFileReader(os.toByteArray(), fullName.toCharArray(), true);
return new NameEnvironmentAnswer(classFileReader, null);
} catch (IOException | ClassFormatException e) {
return null;
}
}
}
}

View File

@@ -2,6 +2,7 @@ package processing.mode.java.pdex;
import com.google.classpath.ClassPath;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import java.net.URLClassLoader;
@@ -23,7 +24,7 @@ public class PreprocessedSketch {
public final ClassPath classPath;
public final URLClassLoader classLoader;
public final int[] tabStarts;
public final int[] tabStartOffsets;
public final String pdeCode;
public final String javaCode;
@@ -40,67 +41,58 @@ public class PreprocessedSketch {
public final List<ImportStatement> codeFolderImports;
// TODO: optimize
public static int lineToOffset(String text, int line) {
int lineOffset = 0;
for (int i = 0; i < line && lineOffset >= 0; i++) {
lineOffset = text.indexOf('\n', lineOffset) + 1;
/// JAVA -> SKETCH -----------------------------------------------------------
public static class SketchInterval {
private SketchInterval(int tabIndex,
int startTabOffset, int stopTabOffset,
int startPdeOffset, int stopPdeOffset) {
this.tabIndex = tabIndex;
this.startTabOffset = startTabOffset;
this.stopTabOffset = stopTabOffset;
this.startPdeOffset = startPdeOffset;
this.stopPdeOffset = stopPdeOffset;
}
return lineOffset;
final int tabIndex;
final int startTabOffset;
final int stopTabOffset;
final int startPdeOffset;
final int stopPdeOffset;
}
// TODO: optimize
public static int offsetToLine(String text, int start, int offset) {
int line = 0;
while (offset >= start) {
offset = text.lastIndexOf('\n', offset-1);
line++;
}
return line - 1;
public SketchInterval mapJavaToSketch(ASTNode node) {
return mapJavaToSketch(node.getStartPosition(),
node.getStartPosition() + node.getLength());
}
// TODO: optimize
public static int offsetToLine(String text, int offset) {
return offsetToLine(text, 0, offset);
public SketchInterval mapJavaToSketch(int startJavaOffset, int stopJavaOffset) {
boolean zeroLength = stopJavaOffset == startJavaOffset;
int startPdeOffset = javaOffsetToPdeOffset(startJavaOffset);
int stopPdeOffset = zeroLength ?
javaOffsetToPdeOffset(stopJavaOffset) :
javaOffsetToPdeOffset(stopJavaOffset-1)+1;
int tabIndex = pdeOffsetToTabIndex(startPdeOffset);
return new SketchInterval(tabIndex,
pdeOffsetToTabOffset(tabIndex, startPdeOffset),
pdeOffsetToTabOffset(tabIndex, stopPdeOffset),
startPdeOffset, stopPdeOffset);
}
// TODO: optimize, build lookup together with tabStarts
public int tabIndexToTabStartLine(int tabIndex) {
int pdeLineNumber = 0;
for (int i = 0; i < tabIndex; i++) {
pdeLineNumber += sketch.getCode(i).getLineCount();
}
return pdeLineNumber;
}
public int tabLineToJavaLine(int tabIndex, int tabLine) {
int tabStartLine = tabIndexToTabStartLine(tabIndex);
int pdeLine = tabStartLine + tabLine;
int pdeLineOffset = lineToOffset(pdeCode, pdeLine);
int javaLineOffset = offsetMapper.getOutputOffset(pdeLineOffset);
return offsetToLine(javaCode, javaLineOffset);
}
public int tabOffsetToJavaOffset(int tabIndex, int tabOffset) {
int tabStartLine = tabIndexToTabStartLine(tabIndex);
int tabStartOffset = lineToOffset(pdeCode, tabStartLine);
int pdeOffset = tabStartOffset + tabOffset;
return offsetMapper.getOutputOffset(pdeOffset);
}
public int javaOffsetToPdeOffset(int javaOffset) {
private int javaOffsetToPdeOffset(int javaOffset) {
return offsetMapper.getInputOffset(javaOffset);
}
public int pdeOffsetToTabIndex(int pdeOffset) {
int tab = Arrays.binarySearch(tabStarts, pdeOffset);
private int pdeOffsetToTabIndex(int pdeOffset) {
int tab = Arrays.binarySearch(tabStartOffsets, pdeOffset);
if (tab < 0) {
tab = -(tab + 1) - 1;
}
@@ -108,18 +100,56 @@ public class PreprocessedSketch {
}
public int pdeOffsetToTabOffset(int tabIndex, int pdeOffset) {
int tabStartOffset = tabStarts[tabIndex];
private int pdeOffsetToTabOffset(int tabIndex, int pdeOffset) {
int tabStartOffset = tabStartOffsets[tabIndex];
return pdeOffset - tabStartOffset;
}
/// SKETCH -> JAVA -----------------------------------------------------------
public int tabOffsetToJavaOffset(int tabIndex, int tabOffset) {
int tabStartOffset = tabStartOffsets[tabIndex];
int pdeOffset = tabStartOffset + tabOffset;
return offsetMapper.getOutputOffset(pdeOffset);
}
/// LINE NUMBERS -------------------------------------------------------------
public int tabOffsetToJavaLine(int tabIndex, int tabOffset) {
int javaOffset = tabOffsetToJavaOffset(tabIndex, tabOffset);
return offsetToLine(javaCode, javaOffset);
}
public int tabOffsetToTabLine(int tabIndex, int tabOffset) {
int tabStartOffset = tabStarts[tabIndex];
int tabStartOffset = tabStartOffsets[tabIndex];
return offsetToLine(pdeCode, tabStartOffset, tabStartOffset + tabOffset);
}
// TODO: optimize
private static int offsetToLine(String text, int offset) {
return offsetToLine(text, 0, offset);
}
// TODO: optimize
private static int offsetToLine(String text, int start, int offset) {
int line = 0;
while (offset >= start) {
offset = text.lastIndexOf('\n', offset-1);
line++;
}
return line - 1;
}
/// BUILDER BUSINESS /////////////////////////////////////////////////////////
@@ -138,7 +168,7 @@ public class PreprocessedSketch {
public ClassPath classPath;
public URLClassLoader classLoader;
public int[] tabStarts = new int[0];
public int[] tabStartOffsets = new int[0];
public String pdeCode;
public String javaCode;
@@ -148,8 +178,6 @@ public class PreprocessedSketch {
public boolean hasSyntaxErrors;
public boolean hasCompilationErrors;
public final List<Problem> problems = new ArrayList<>();
public final List<ImportStatement> programImports = new ArrayList<>();
public final List<ImportStatement> coreAndDefaultImports = new ArrayList<>();
public final List<ImportStatement> codeFolderImports = new ArrayList<>();
@@ -172,7 +200,7 @@ public class PreprocessedSketch {
classPath = b.classPath;
classLoader = b.classLoader;
tabStarts = b.tabStarts;
tabStartOffsets = b.tabStartOffsets;
pdeCode = b.pdeCode;
javaCode = b.javaCode;
@@ -182,7 +210,7 @@ public class PreprocessedSketch {
hasSyntaxErrors = b.hasSyntaxErrors;
hasCompilationErrors = b.hasCompilationErrors;
problems = Collections.unmodifiableList(b.problems);
problems = new ArrayList<>();
programImports = Collections.unmodifiableList(b.programImports);
coreAndDefaultImports = Collections.unmodifiableList(b.coreAndDefaultImports);