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; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import processing.app.Problem; import processing.app.Sketch; import processing.core.PApplet; import processing.mode.java.pdex.TextTransform.OffsetMapper; public class PreprocessedSketch { public final Sketch sketch; public final CompilationUnit compilationUnit; public final String[] classPathArray; public final ClassPath classPath; public final URLClassLoader classLoader; public final String[] searchClassPathArray; public final int[] tabStartOffsets; public final String pdeCode; public final String javaCode; public final OffsetMapper offsetMapper; public final List<Problem> missingBraceProblems; public final boolean hasSyntaxErrors; public final boolean hasCompilationErrors; public final List<ImportStatement> programImports; public final List<ImportStatement> coreAndDefaultImports; public final List<ImportStatement> codeFolderImports; /// JAVA -> SKETCH ----------------------------------------------------------- public static class SketchInterval { public static final SketchInterval BEFORE_START = new SketchInterval(-1, -1, -1, -1, -1); 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; } final int tabIndex; final int startTabOffset; final int stopTabOffset; final int startPdeOffset; final int stopPdeOffset; } public boolean inRange(SketchInterval interval) { return interval != SketchInterval.BEFORE_START && interval.stopPdeOffset < pdeCode.length(); } public SketchInterval mapJavaToSketch(ASTNode node) { return mapJavaToSketch(node.getStartPosition(), node.getStartPosition() + node.getLength()); } public SketchInterval mapJavaToSketch(int startJavaOffset, int stopJavaOffset) { int length = stopJavaOffset - startJavaOffset; int startPdeOffset = javaOffsetToPdeOffset(startJavaOffset); int stopPdeOffset; if (length == 0) { stopPdeOffset = startPdeOffset; } else { stopPdeOffset = javaOffsetToPdeOffset(stopJavaOffset-1); if (stopPdeOffset >= 0 && (stopPdeOffset > startPdeOffset || length == 1)) { stopPdeOffset += 1; } } if (startPdeOffset < 0 || stopPdeOffset < 0) { return SketchInterval.BEFORE_START; } int tabIndex = pdeOffsetToTabIndex(startPdeOffset); if (startPdeOffset >= pdeCode.length()) { startPdeOffset = pdeCode.length() - 1; stopPdeOffset = startPdeOffset + 1; } return new SketchInterval(tabIndex, pdeOffsetToTabOffset(tabIndex, startPdeOffset), pdeOffsetToTabOffset(tabIndex, stopPdeOffset), startPdeOffset, stopPdeOffset); } private int javaOffsetToPdeOffset(int javaOffset) { return offsetMapper.getInputOffset(javaOffset); } private int pdeOffsetToTabIndex(int pdeOffset) { pdeOffset = Math.max(0, pdeOffset); int tab = Arrays.binarySearch(tabStartOffsets, pdeOffset); if (tab < 0) { tab = -(tab + 1) - 1; } return tab; } private int pdeOffsetToTabOffset(int tabIndex, int pdeOffset) { int tabStartOffset = tabStartOffsets[clipTabIndex(tabIndex)]; return pdeOffset - tabStartOffset; } /// SKETCH -> JAVA ----------------------------------------------------------- public int tabOffsetToJavaOffset(int tabIndex, int tabOffset) { int tabStartOffset = tabStartOffsets[clipTabIndex(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 = tabStartOffsets[clipTabIndex(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; } /// Util --------------------------------------------------------------------- private int clipTabIndex(int tabIndex) { return PApplet.constrain(tabIndex, 0, tabStartOffsets.length - 1); } /// BUILDER BUSINESS ///////////////////////////////////////////////////////// /** * There is a lot of fields and having constructor with this many parameters * is just not practical. Fill stuff into builder and then simply build it. * Builder also guards against calling methods in the middle of building process. */ public static class Builder { public Sketch sketch; public CompilationUnit compilationUnit; public String[] classPathArray; public ClassPath classPath; public URLClassLoader classLoader; public String[] searchClassPathArray; public int[] tabStartOffsets = new int[0]; public String pdeCode; public String javaCode; public OffsetMapper offsetMapper; public final List<Problem> missingBraceProblems = new ArrayList<>(0); public boolean hasSyntaxErrors; public boolean hasCompilationErrors; public final List<ImportStatement> programImports = new ArrayList<>(); public final List<ImportStatement> coreAndDefaultImports = new ArrayList<>(); public final List<ImportStatement> codeFolderImports = new ArrayList<>(); public PreprocessedSketch build() { return new PreprocessedSketch(this); } } public static PreprocessedSketch empty() { return new Builder().build(); } private PreprocessedSketch(Builder b) { sketch = b.sketch; compilationUnit = b.compilationUnit; classPathArray = b.classPathArray; classPath = b.classPath; classLoader = b.classLoader; searchClassPathArray = b.searchClassPathArray; tabStartOffsets = b.tabStartOffsets; pdeCode = b.pdeCode; javaCode = b.javaCode; offsetMapper = b.offsetMapper != null ? b.offsetMapper : OffsetMapper.EMPTY_MAPPER; missingBraceProblems = Collections.unmodifiableList(b.missingBraceProblems); hasSyntaxErrors = b.hasSyntaxErrors; hasCompilationErrors = b.hasCompilationErrors; programImports = Collections.unmodifiableList(b.programImports); coreAndDefaultImports = Collections.unmodifiableList(b.coreAndDefaultImports); codeFolderImports = Collections.unmodifiableList(b.codeFolderImports); } }