quarkninja.mode.xqmode.ErrorCheckerService.java Source code

Java tutorial

Introduction

Here is the source code for quarkninja.mode.xqmode.ErrorCheckerService.java

Source

/*
  Part of the XQMode project - https://github.com/Manindra29/XQMode
      
  Under Google Summer of Code 2012 - 
  http://www.google-melange.com/gsoc/homepage/google/gsoc2012
      
  Copyright (C) 2012 Manindra Moharana
       
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License version 2
  as published by the Free Software Foundation.
    
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
    
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package quarkninja.mode.xqmode;

import java.awt.EventQueue;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.table.DefaultTableModel;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;

import processing.app.Base;
import processing.app.Library;
import processing.app.SketchCode;
import processing.core.PApplet;

/**
 * Error Checking Service for XQMode.<br>
 * <br>
 * 
 * Program Flow:<br>
 * <li>Fetches code from editor every few seconds, converts it into pure java
 * and runs it through the Eclipse AST parser. <li>Parser detects the syntax
 * errors. Errors are passed on to Error Window, ErrorBar, etc. to be displayed
 * to the user. <li>
 * If no syntax errors are detected, code is further processed into compilable
 * form by the XQPreprocessor. <li>Contributed libraries' jars are added to
 * classpath and Compiler is loaded by URLCLassLoader with the extra jars in its
 * classpath. <li>The code is compiled and finally, ErrorWindow, ErrorBar, etc
 * are updated with new errors. <br>
 * All this happens in a separate thread, so that PDE keeps running without any
 * hiccups.
 * 
 * @author Manindra Moharana &lt;mkmoharana29@gmail.com&gt;
 */
public class ErrorCheckerService implements Runnable {

    static private String PATH = "D:/TestStuff/HelloPeasy.java";
    /**
     * Error check happens every sleepTime milliseconds
     */
    public static final int sleepTime = 1000;

    /**
     * The amazing eclipse ast parser
     */
    private ASTParser parser;
    protected XQEditor editor;
    /**
     * Used to indirectly stop the Error Checker Thread
     */
    public boolean stopThread = false;

    /**
     * If true, Error Checking is paused. Calls to checkCode() become useless.
     */
    private boolean pauseThread = false;

    protected ErrorWindow errorWindow;
    protected ErrorBar errorBar;
    /**
     * IProblem[] returned by parser stored in here
     */
    private IProblem[] problems;

    /**
     * Class name of current sketch
     */
    protected String className;

    /**
     * Source code of current sketch
     */
    protected String sourceCode;

    /**
     * URLs of extra imports jar files stored here.
     */
    protected URL[] classpath;

    /**
     * Code preprocessed by the custom preprocessor
     */
    private StringBuffer rawCode;

    /**
     * P5 Preproc offset
     */
    private int scPreProcOffset = 0;

    /**
     * Stores all Problems in the sketch
     */
    public ArrayList<Problem> problemsList;

    /**
     * How many lines are present till the initial class declaration? In static
     * mode, this would include imports, class declaration and setup
     * declaration. In nomral mode, this would include imports, class
     * declaration only. It's fate is decided inside preprocessCode()
     */
    public int mainClassOffset;

    /**
     * Is the sketch running in static mode or active mode?
     */
    public boolean staticMode = false;

    /**
     * Compilation Unit for current sketch
     */
    protected CompilationUnit cu;

    /**
     * If true, compilation checker will be reloaded with updated classpath
     * items.
     */
    private boolean loadCompClass = true;

    /**
     * Compiler Checker class. Note that methods for compilation checking are
     * called from the compilationChecker object, not from this
     */
    protected Class<?> checkerClass;

    /**
     * Compilation Checker object.
     */
    protected Object compilationChecker;

    /**
     * Compiler Checker used by compCheck class
     */
    protected CompilationCheckerInterface compCheck;

    /**
     * List of jar files to be present in compilation checker's classpath
     */
    protected ArrayList<URL> classpathJars;

    /**
     * Timestamp - for measuring total overhead
     */
    private long lastTimeStamp = System.currentTimeMillis();

    /**
     * Used for displaying the rotating slash on the Problem Window title bar
     */
    private String[] slashAnimation = { "|", "/", "--", "\\", "|", "/", "--", "\\" };
    private int slashAnimationIndex = 0;

    /**
     * Used to detect if the current tab index has changed and thus repaint the
     * textarea.
     */
    private int currentTab = 0, lastTab = 0;

    /**
     * Stores the current import statements in the program. Used to compare for
     * changed import statements and update classpath if needed.
     */
    protected ArrayList<ImportStatement> programImports;

    /**
     * List of imports when sketch was last checked. Used for checking for
     * changed imports
     */
    protected ArrayList<ImportStatement> previousImports = new ArrayList<ImportStatement>();

    /**
     * Teh Preprocessor
     */
    protected XQPreprocessor xqpreproc;

    /**
     * Regexp for import statements. (Used from Processing source)
     */
    final public String importRegexp = "(?:^|;)\\s*(import\\s+)((?:static\\s+)?\\S+)(\\s*;)";

    public static void main(String[] args) {
        // This is mostly for testing stuff on my system. Won't remove coz
        // might be needed in future
        try {
            ErrorCheckerService syncheck = new ErrorCheckerService();
            ErrorCheckerService.showClassPath();
            // syncheck.checkCode();
            // syncheck.preprocessCode();
            File f = new File("D:/WorkSpaces/Eclipse Workspace 2/AST Test 2/bin");
            // File f = new File("resources/CompilationCheckerClasses");
            System.out.println(f.toURI().toURL().toString());
            File f2 = new File("peasycam.jar");

            URL[] classpath;
            classpath = new URL[] { f2.toURI().toURL(), f.toURI().toURL() };
            URLClassLoader classLoader = new URLClassLoader(classpath);
            // quarkninja.mode.xqmode.CompilationChecker
            Class<?> checkerClass = Class.forName("CompilationChecker", true, classLoader);
            CompilationCheckerInterface compCheck = (CompilationCheckerInterface) checkerClass.newInstance();
            System.out.println(compCheck.getErrors("HelloPeasy", syncheck.preprocessCode())[0]);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public ErrorCheckerService() {
        initParser();
        initializeErrorWindow();
    }

    public ErrorCheckerService(String path) {
        PATH = path;
        initParser();
        initializeErrorWindow();
    }

    /**
     * Constructor for Error Chekcer Service
     * 
     * @param editor
     *            - XQEditor instance
     * @param erb
     *            - ErrorBar instance
     */
    public ErrorCheckerService(XQEditor editor, ErrorBar erb) {
        initParser();
        this.editor = editor;
        this.errorBar = erb;

    }

    /**
     * Initializes ASTParser
     */
    private void initParser() {
        try {
            parser = ASTParser.newParser(AST.JLS4);
        } catch (Exception e) {
            System.err.println(
                    "XQMode initialization failed. " + "Are you running the right version of Processing? ");
            pauseThread();
        } catch (Error e) {
            System.err.println("XQMode initialization failed. ");
            e.printStackTrace();
            pauseThread();
        }
    }

    /**
     * Initialiazes the Error Window
     */
    public void initializeErrorWindow() {
        if (editor == null)
            return;
        if (errorWindow != null)
            return;

        final ErrorCheckerService thisService = this;
        final XQEditor thisEditor = editor;
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    errorWindow = new ErrorWindow(thisEditor, thisService);
                    // errorWindow.setVisible(true);
                    editor.toFront();
                    errorWindow.errorTable.setFocusable(false);
                    editor.setSelection(0, 0);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Prints out classpath elements per line. Used for debugging only.
     */
    public static void showClassPath() {
        System.out.println("------Classpath------");
        String cps[] = PApplet.split(System.getProperty("java.class.path"), Base.isWindows() ? ';' : ':');
        for (int i = 0; i < cps.length; i++) {
            System.out.println(cps[i]);
        }
        System.out.println("---------------------");
    }

    // String[] tempPath = {
    // // "file:/D:/WorkSpaces/Eclipse Workspace 2/AST Test 2/bin",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.core.contenttype_3.4.100.v20110423-0524.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.core.jobs_3.5.100.v20110404.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.core.resources_3.7.100.v20110510-0712.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.core.runtime_3.7.0.v20110110.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.equinox.common_3.6.0.v20110523.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.equinox.preferences_3.4.1.R37x_v20110725.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.jdt.core_3.7.1.v_B76_R37x.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/org.eclipse.text_3.5.101.r371_v20110810-0800.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/XQMode.jar",
    // "file:/C:/Users/QuarkNinja.XQ/Documents/Processing/modes/XQMode/mode/CompilationChecker.jar"
    // };

    /**
     * Perform error check
     * 
     * @return true - if checking was completed succesfully.
     */
    public boolean checkCode() {
        // Reset stuff here, maybe make reset()?
        sourceCode = "";
        lastTimeStamp = System.currentTimeMillis();

        try {
            if (editor != null)
                sourceCode = preprocessCode();
            else {
                // sourceCode = readFile(PATH);
                return false; // Bummer
            }

            // Begin Stage 1!
            syntaxCheck();

            // No syntax errors, proceed for compilation check, Stage 2.
            if (problems.length == 0 && editor.compilationCheckEnabled) {
                sourceCode = xqpreproc.doYourThing(sourceCode, programImports);
                prepareCompilerClasspath();
                mainClassOffset = xqpreproc.mainClassOffset; // tiny, but
                // significant
                if (staticMode)
                    mainClassOffset++; // Extra line for setup() decl.
                // System.out.println("--------------------------");
                // System.out.println(sourceCode);
                // System.out.println("--------------------------");
                compileCheck();
            }

            // Notify user of the mess he's done.
            updateErrorTable();
            errorBar.updateErrorPoints(problemsList);
            updateTextAreaPainter();
            updateEditorStatus();
            return true;

        } catch (Exception e) {
            System.out.println("Oops! [ErrorCheckerService.checkCode]: " + e);
            e.printStackTrace();
        }
        return false;
    }

    /**
     * Updates editor status bar, depending on whether the caret is on an error
     * line or not
     */
    public void updateEditorStatus() {
        // editor.statusNotice("Position: " +
        // editor.getTextArea().getCaretLine());
        boolean notFound = true;
        for (ErrorMarker emarker : errorBar.errorPoints) {
            if (emarker.problem.lineNumber == editor.getTextArea().getCaretLine() + 1) {
                if (emarker.type == ErrorMarker.Warning)
                    editor.statusNotice(emarker.problem.message);
                else
                    editor.statusError(emarker.problem.message);
                return;
            }
        }
        if (notFound)
            editor.statusEmpty();
    }

    /**
     * Performs syntax check.
     */
    private void syntaxCheck() {
        parser.setSource(sourceCode.toCharArray());
        parser.setKind(ASTParser.K_COMPILATION_UNIT);

        @SuppressWarnings("unchecked")
        Map<String, String> options = JavaCore.getOptions();

        // Ben has decided to move on to 1.6. Yay!
        JavaCore.setComplianceOptions(JavaCore.VERSION_1_6, options);
        options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_6);
        parser.setCompilerOptions(options);
        cu = (CompilationUnit) parser.createAST(null);

        // Store errors returned by the ast parser
        problems = cu.getProblems();

        // Populate the probList
        problemsList = new ArrayList<Problem>();
        for (int i = 0; i < problems.length; i++) {
            int a[] = calculateTabIndexAndLineNumber(problems[i]);
            problemsList.add(new Problem(problems[i], a[0], a[1]));
        }
    }

    /**
     * The name cannot get any simpler, can it?
     */
    private void compileCheck() {

        // Currently (Sept, 2012) I'm using Java's reflection api to load the
        // CompilationChecker class(from CompilationChecker.jar) that houses the
        // Eclispe JDT compiler and call its getErrorsAsObj method to obtain
        // errors. This way, I'm able to add the paths of contributed libraries
        // to the classpath of CompilationChecker, dynamically.

        try {

            // NOTE TO SELF: If classpath contains null Strings
            // URLClassLoader gets angry. Drops NPE bombs.

            // If imports have changed, reload classes with new classpath.
            if (loadCompClass) {

                if (classpathJars.size() > 0)
                    System.out.println("XQMode: Loading contributed libraries referenced by import statements.");
                File f = new File(editor.getBase().getSketchbookFolder().getAbsolutePath() + File.separator
                        + "modes" + File.separator + "XQMode" + File.separator + "mode");

                FileFilter fileFilter = new FileFilter() {
                    public boolean accept(File file) {
                        return (file.getName().endsWith(".jar") && !file.getName().startsWith("XQMode"));
                    }
                };

                File[] jarFiles = f.listFiles(fileFilter);
                for (File jarFile : jarFiles) {
                    classpathJars.add(jarFile.toURI().toURL());
                }

                // for (int i = 0; i < tempPath.length; i++) {
                // classpathJars.add(new URL(tempPath[i]));
                // }

                classpath = new URL[classpathJars.size()]; // + 1 for
                // Compilation
                // Checker class
                for (int i = 0; i < classpathJars.size(); i++) {
                    classpath[i] = classpathJars.get(i);
                }

                // System.out.println("-- " + classpath.length);
                URLClassLoader classLoader = new URLClassLoader(classpath);
                // System.out.println("1.");
                checkerClass = Class.forName("CompilationChecker", true, classLoader);
                // System.out.println("2.");
                compilationChecker = checkerClass.newInstance();
                loadCompClass = false;
            }

            if (compilerSettings == null)
                prepareCompilerSetting();
            Method getErrors = checkerClass.getMethod("getErrorsAsObjArr",
                    new Class[] { String.class, String.class, Map.class });
            // Method disp = checkerClass.getMethod("test", (Class<?>[])null);

            Object[][] errorList = (Object[][]) getErrors.invoke(compilationChecker, className, sourceCode,
                    compilerSettings);

            if (errorList == null)
                return;

            problems = new DefaultProblem[errorList.length];

            for (int i = 0; i < errorList.length; i++) {

                // for (int j = 0; j < errorList[i].length; j++)
                // System.out.print(errorList[i][j] + ", ");

                problems[i] = new DefaultProblem((char[]) errorList[i][0], (String) errorList[i][1],
                        ((Integer) errorList[i][2]).intValue(), (String[]) errorList[i][3],
                        ((Integer) errorList[i][4]).intValue(), ((Integer) errorList[i][5]).intValue(),
                        ((Integer) errorList[i][6]).intValue(), ((Integer) errorList[i][7]).intValue(), 0);

                // System.out
                // .println("ECS: " + problems[i].getMessage() + ","
                // + problems[i].isError() + ","
                // + problems[i].isWarning());

                IProblem problem = problems[i];

                int a[] = calculateTabIndexAndLineNumber(problem);
                Problem p = new Problem(problem, a[0], a[1]);
                if ((Boolean) errorList[i][8])
                    p.setType(Problem.ERROR);
                if ((Boolean) errorList[i][9])
                    p.setType(Problem.WARNING);

                if (p.isWarning() && !warningsEnabled)
                    continue;
                problemsList.add(p);
            }

        } catch (ClassNotFoundException e) {
            System.err.println("Compiltation Checker files couldn't be found! " + e + " compileCheck() problem.");
            stopThread();
        } catch (MalformedURLException e) {
            System.err.println("Compiltation Checker files couldn't be found! " + e + " compileCheck() problem.");
            stopThread();
        } catch (Exception e) {
            System.err.println("compileCheck() problem." + e);
            e.printStackTrace();
            stopThread();
        } catch (NoClassDefFoundError e) {
            System.err.println(e + " compileCheck() problem. Somebody tried to mess with XQMode files.");
            stopThread();
        }
        // System.out.println("Compilecheck, Done.");
    }

    /**
     * Various option for JDT Compiler
     */
    @SuppressWarnings("rawtypes")
    protected Map compilerSettings;

    /**
     * Enable/Disable warnings from being shown
     */
    public boolean warningsEnabled = true;

    /**
     * Sets compiler options for JDT Compiler
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected void prepareCompilerSetting() {
        compilerSettings = new HashMap();

        compilerSettings.put(CompilerOptions.OPTION_LineNumberAttribute, CompilerOptions.GENERATE);
        compilerSettings.put(CompilerOptions.OPTION_SourceFileAttribute, CompilerOptions.GENERATE);
        compilerSettings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_6);
        compilerSettings.put(CompilerOptions.OPTION_ReportUnusedImport, CompilerOptions.IGNORE);
        compilerSettings.put(CompilerOptions.OPTION_ReportMissingSerialVersion, CompilerOptions.IGNORE);
        compilerSettings.put(CompilerOptions.OPTION_ReportRawTypeReference, CompilerOptions.IGNORE);
        compilerSettings.put(CompilerOptions.OPTION_ReportUncheckedTypeOperation, CompilerOptions.IGNORE);
    }

    /**
     * Calculates the tab number and line number of the error in that particular
     * tab. Provides mapping between pure java and pde code.
     * 
     * @param problem
     *            - IProblem
     * @return int[0] - tab number, int[1] - line number
     */
    public int[] calculateTabIndexAndLineNumber(IProblem problem) {
        // String[] lines = {};// = PApplet.split(sourceString, '\n');
        int codeIndex = 0;
        int bigCount = 0;

        int x = problem.getSourceLineNumber() - mainClassOffset;
        if (x < 0) {
            // System.out.println("Negative line number "
            // + problem.getSourceLineNumber() + " , offset "
            // + mainClassOffset);
            x = problem.getSourceLineNumber() - 2; // Another -1 for 0 index
            if (x < programImports.size() && x >= 0) {
                ImportStatement is = programImports.get(x);
                // System.out.println(is.importName + ", " + is.tab + ", "
                // + is.lineNumber);
                return new int[] { is.tab, is.lineNumber };
            } else {

                // Some seriously ugly stray error, just can't find the source
                // line! Simply return first line for first tab.
                return new int[] { 0, 1 };
            }

        }

        try {
            for (SketchCode sc : editor.getSketch().getCode()) {
                if (sc.isExtension("pde")) {
                    sc.setPreprocOffset(bigCount);
                    int len = 0;
                    if (editor.getSketch().getCurrentCode().equals(sc)) {
                        len = Base.countLines(sc.getDocument().getText(0, sc.getDocument().getLength())) + 1;
                    } else {
                        len = Base.countLines(sc.getProgram()) + 1;
                    }

                    // System.out.println("x,len, CI: " + x + "," + len + ","
                    // + codeIndex);

                    if (x >= len) {

                        // We're in the last tab and the line count is greater
                        // than the no.
                        // of lines in the tab,
                        if (codeIndex >= editor.getSketch().getCodeCount() - 1) {
                            // System.out.println("Exceeds lc " + x + "," + len
                            // + problem.toString());
                            // x = len
                            x = editor.getSketch().getCode(codeIndex).getLineCount();
                            // TODO: Obtain line having last non-white space
                            // character in the code.
                            break;
                        } else {
                            x -= len;
                            codeIndex++;
                        }
                    } else {

                        if (codeIndex >= editor.getSketch().getCodeCount())
                            codeIndex = editor.getSketch().getCodeCount() - 1;
                        break;
                    }

                }
                bigCount += sc.getLineCount();
            }
        } catch (Exception e) {
            System.err.println("Things got messed up in ErrorCheckerService.calculateTabIndexAndLineNumber()");
        }

        return new int[] { codeIndex, x };
    }

    /**
     * Pretty silly this one. Shows the XQMode version number after 3 seconds of
     * running.
     */
    private int runCount = 0;

    /**
     * Starts the Error Checker Service thread
     */
    @Override
    public void run() {
        lastTab = editor.getSketch().getCodeIndex(editor.getSketch().getCurrentCode());
        initializeErrorWindow();
        xqpreproc = new XQPreprocessor();
        // Run check code just once before entering into loop.
        // Makes sure everything is initialized and set.
        checkCode();
        editor.getTextArea().repaint();
        stopThread = false;

        while (!stopThread) {
            try {
                // Take a nap.
                Thread.sleep(sleepTime);
            } catch (Exception e) {
                System.out.println("Oops! [ErrorCheckerThreaded]: " + e);
                // e.printStackTrace();
            }

            if (pauseThread)
                continue;

            // Check every x seconds
            checkCode();
            if (runCount < 5) {
                runCount++;
            }

            if (runCount == 3) {
                Package p = XQMode.class.getPackage();
                System.out.println(p.getImplementationTitle() + " v" + p.getImplementationVersion());
            }
        }
    }

    /**
     * Stops the Error Checker Service thread
     */
    public void stopThread() {
        stopThread = true;
        System.out.println(editor.getSketch().getName() + " - Error Checker stopped.");
    }

    /**
     * Pauses the Error Checker Service thread
     */
    public void pauseThread() {
        pauseThread = true;
    }

    /**
     * Resumes the Error Checker Service thread
     */
    public void resumeThread() {
        pauseThread = false;
    }

    /**
     * Gets if Error Checker Service thread is paused
     * 
     * @return ErrorCheckerService.pauseThread
     */
    public boolean isThreadPaused() {
        return pauseThread;
    }

    /**
     * Fetches code from the editor tabs and pre-processes it into parsable pure
     * java source. And there's a difference between parsable and compilable.
     * XQPrerocessor.java makes this code compilable. <br>
     * Handles: <li>Removal of import statements <li>Conversion of int(),
     * char(), etc to (int)(), (char)(), etc. <li>Replacing '#' with 0xff for
     * color representation<li>Converts all 'color' datatypes to int
     * (experimental) <li>Appends class declaration statement after determining
     * the mode the sketch is in - ACTIVE or STATIC
     * 
     * @return String - Pure java representation of PDE code. Note that this
     *         code is not yet compile ready.
     */
    private String preprocessCode() {

        String sourceAlt = "";
        programImports = new ArrayList<ImportStatement>();
        if (editor == null) {
            try {
                sourceAlt = readFile(PATH);
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println(sourceAlt);
            System.out.println("-----------PreProcessed----------");
            return sourceAlt;
        }
        // Super wicked regular expressions! (Used from Processing source)
        final Pattern FUNCTION_DECL = Pattern.compile(
                "(^|;)\\s*((public|private|protected|final|static)\\s+)*"
                        + "(void|int|float|double|String|char|byte)" + "(\\s*\\[\\s*\\])?\\s+[a-zA-Z0-9]+\\s*\\(",
                Pattern.MULTILINE);

        // Handle code input from editor/java file
        try {
            if (editor == null) {
                System.out.println("Reading .java file: " + PATH);
            } else {
                rawCode = new StringBuffer();

                for (SketchCode sc : editor.getSketch().getCode()) {
                    if (sc.isExtension("pde")) {
                        sc.setPreprocOffset(scPreProcOffset);

                        try {

                            if (editor.getSketch().getCurrentCode().equals(sc)) {

                                // rawCode.append(sc.getDocument().getText(0,
                                // sc.getDocument().getLength()));
                                rawCode.append(scrapImportStatements(
                                        sc.getDocument().getText(0, sc.getDocument().getLength()),
                                        editor.getSketch().getCodeIndex(sc)));
                            } else {

                                // rawCode.append(sc.getProgram());
                                rawCode.append(scrapImportStatements(sc.getProgram(),
                                        editor.getSketch().getCodeIndex(sc)));

                            }
                            rawCode.append('\n');
                        } catch (Exception e) {
                            System.err.println("Exception in preprocessCode() - bigCode " + e.toString());
                        }
                        rawCode.append('\n');
                        scPreProcOffset += sc.getLineCount();
                    }
                }

                sourceAlt = rawCode.toString();
                // System.out.println("Obtaining source from editor.");
            }
        } catch (Exception e) {

            System.out.println("Exception in preprocessCode()");

        }

        // Replace comments with whitespaces
        // sourceAlt = scrubComments(sourceAlt);

        // Find all int(*), replace with PApplet.parseInt(*)

        // \bint\s*\(\s*\b , i.e all exclusive "int("

        String dataTypeFunc[] = { "int", "char", "float", "boolean", "byte" };
        for (String dataType : dataTypeFunc) {
            String dataTypeRegexp = "\\b" + dataType + "\\s*\\(";
            Pattern pattern = Pattern.compile(dataTypeRegexp);
            Matcher matcher = pattern.matcher(sourceAlt);

            // while (matcher.find()) {
            // System.out.print("Start index: " + matcher.start());
            // System.out.println(" End index: " + matcher.end() + " ");
            // System.out.println("-->" + matcher.group() + "<--");
            // }
            sourceAlt = matcher.replaceAll(
                    "PApplet.parse" + Character.toUpperCase(dataType.charAt(0)) + dataType.substring(1) + "(");

        }

        // Find all #[web color] and replace with 0xff[webcolor]
        // Should be 6 digits only.
        final String webColorRegexp = "#{1}[A-F|a-f|0-9]{6}\\W";
        Pattern webPattern = Pattern.compile(webColorRegexp);
        Matcher webMatcher = webPattern.matcher(sourceAlt);
        while (webMatcher.find()) {
            // System.out.println("Found at: " + webMatcher.start());
            String found = sourceAlt.substring(webMatcher.start(), webMatcher.end());
            // System.out.println("-> " + found);
            sourceAlt = webMatcher.replaceFirst("0xff" + found.substring(1));
            webMatcher = webPattern.matcher(sourceAlt);
        }

        // TODO: Experimental.
        // Replace all color data types with int
        // Regex, Y U SO powerful?
        final String colorTypeRegex = "color(?![a-zA-Z0-9_])(?=\\[*)(?!(\\s*\\())";
        Pattern colorPattern = Pattern.compile(colorTypeRegex);
        Matcher colorMatcher = colorPattern.matcher(sourceAlt);
        sourceAlt = colorMatcher.replaceAll("int");

        checkForChangedImports();

        className = (editor == null) ? "DefaultClass" : editor.getSketch().getName();

        // Check whether the code is being written in STATIC mode(no function
        // declarations) - append class declaration and void setup() declaration
        Matcher matcher = FUNCTION_DECL.matcher(sourceAlt);
        if (!matcher.find()) {
            sourceAlt = "public class " + className + " extends PApplet {\n" + "public void setup() {\n" + sourceAlt
                    + "\nnoLoop();\n}\n" + "\n}\n";
            staticMode = true;
            mainClassOffset = 2;

        } else {
            sourceAlt = "public class " + className + " extends PApplet {\n" + sourceAlt + "\n}";
            staticMode = false;
            mainClassOffset = 1;
        }

        // Handle unicode characters
        sourceAlt = substituteUnicode(sourceAlt);

        // System.out.println("-->\n" + sourceAlt + "\n<--");
        // System.out.println("PDE code processed - "
        // + editor.getSketch().getName());
        sourceCode = sourceAlt;
        return sourceAlt;
    }

    /**
     * Removes import statements from tabSource, replaces each with white spaces
     * and adds the import to the list of program imports
     * 
     * @param tabProgram
     *            - Code in a tab
     * @param tabNumber
     *            - index of the tab
     * @return String - Tab code with imports replaced with white spaces
     */
    private String scrapImportStatements(String tabProgram, int tabNumber) {

        String tabSource = new String(tabProgram);
        do {
            // System.out.println("-->\n" + sourceAlt + "\n<--");
            String[] pieces = PApplet.match(tabSource, importRegexp);

            // Stop the loop if we've removed all the import lines
            if (pieces == null)
                break;

            String piece = pieces[1] + pieces[2] + pieces[3];
            int len = piece.length(); // how much to trim out

            // programImports.add(piece); // the package name

            // find index of this import in the program
            int idx = tabSource.indexOf(piece);
            // System.out.print("Import -> " + piece);
            // System.out.println(" - "
            // + Base.countLines(tabSource.substring(0, idx)) + " tab "
            // + tabNumber);
            programImports.add(new ImportStatement(piece, tabNumber, Base.countLines(tabSource.substring(0, idx))));
            // Remove the import from the main program
            // Substitue with white spaces
            String whiteSpace = "";
            for (int j = 0; j < piece.length(); j++) {
                whiteSpace += " ";
            }
            tabSource = tabSource.substring(0, idx) + whiteSpace + tabSource.substring(idx + len);

        } while (true);
        // System.out.println(tabSource);
        return tabSource;
    }

    /**
     * Checks if import statements in the sketch have changed. If they have,
     * compiler classpath needs to be updated.
     */
    private void checkForChangedImports() {
        // System.out.println("Imports: " + programImports.size() +
        // " Prev Imp: "
        // + previousImports.size());
        if (programImports.size() != previousImports.size()) {
            // System.out.println(1);
            loadCompClass = true;
            previousImports = programImports;
        } else {
            for (int i = 0; i < programImports.size(); i++) {
                if (!programImports.get(i).importName.equals(previousImports.get(i).importName)) {
                    // System.out.println(2);
                    loadCompClass = true;
                    previousImports = programImports;
                    break;
                }
            }
        }
        // System.out.println("load..? " + loadCompClass);
    }

    /**
     * Processes import statements to obtain classpaths of contributed
     * libraries. This would be needed for compilation check. Also, adds
     * stuff(jar files, class files, candy) from the code folder. And it looks
     * messed up.
     * 
     */
    private void prepareCompilerClasspath() {
        if (!loadCompClass)
            return;
        // System.out.println("1..");
        classpathJars = new ArrayList<URL>();
        String entry = "";
        boolean codeFolderChecked = false;
        for (ImportStatement impstat : programImports) {
            String item = impstat.importName;
            int dot = item.lastIndexOf('.');
            entry = (dot == -1) ? item : item.substring(0, dot);

            entry = entry.substring(6).trim();
            // System.out.println("Entry--" + entry);
            if (ignorableImport(entry)) {
                // System.out.println("Ignoring: " + entry);
                continue;
            }
            Library library = null;

            // Try to get the library classpath and add it to the list
            try {
                library = editor.getMode().getLibrary(entry);
                // System.out.println("lib->" + library.getClassPath() + "<-");
                String libraryPath[] = PApplet.split(library.getClassPath().substring(1).trim(),
                        File.pathSeparatorChar);
                for (int i = 0; i < libraryPath.length; i++) {
                    // System.out.println(entry + " ::"
                    // + new File(libraryPath[i]).toURI().toURL());
                    classpathJars.add(new File(libraryPath[i]).toURI().toURL());
                }
                // System.out.println("-- ");
                // classpath[count] = (new File(library.getClassPath()
                // .substring(1))).toURI().toURL();
                // System.out.println("  found ");
                // System.out.println(library.getClassPath().substring(1));
            } catch (Exception e) {
                if (library == null && !codeFolderChecked) {
                    // System.out.println(1);
                    // Look around in the code folder for jar files
                    if (editor.getSketch().hasCodeFolder()) {
                        File codeFolder = editor.getSketch().getCodeFolder();

                        // get a list of .jar files in the "code" folder
                        // (class files in subfolders should also be picked up)
                        String codeFolderClassPath = Base.contentsToClassPath(codeFolder);
                        codeFolderChecked = true;
                        if (codeFolderClassPath.equalsIgnoreCase("")) {
                            System.err.println("XQMODE: Yikes! Can't find \"" + entry + "\" library! Line: "
                                    + impstat.lineNumber + " in tab: "
                                    + editor.getSketch().getCode(impstat.tab).getPrettyName());
                            System.out.println("Please make sure that the library is present in <sketchbook "
                                    + "folder>/libraries folder or in the code folder of your sketch");

                        }
                        String codeFolderPath[] = PApplet.split(codeFolderClassPath.substring(1).trim(),
                                File.pathSeparatorChar);
                        try {
                            for (int i = 0; i < codeFolderPath.length; i++) {
                                classpathJars.add(new File(codeFolderPath[i]).toURI().toURL());
                            }

                        } catch (Exception e2) {
                            System.out.println("Yikes! codefolder, prepareImports(): " + e2);
                        }
                    } else {
                        System.err.println(
                                "XQMODE: Yikes! Can't find \"" + entry + "\" library! Line: " + impstat.lineNumber
                                        + " in tab: " + editor.getSketch().getCode(impstat.tab).getPrettyName());
                        System.out.println("Please make sure that the library is present in <sketchbook "
                                + "folder>/libraries folder or in the code folder of your sketch");
                    }

                } else {
                    System.err.println("Yikes! There was some problem in prepareImports(): " + e);
                    System.err.println("I was processing: " + entry);

                    // e.printStackTrace();
                }
            }

        }

    }

    /**
     * Ignore processing packages, java.*.*. etc.
     * 
     * @param packageName
     * @return boolean
     */
    protected boolean ignorableImport(String packageName) {
        // packageName.startsWith("processing.")
        // ||
        if (packageName.startsWith("java.") || packageName.startsWith("javax.")) {
            return true;
        }
        return false;
    }

    /**
     * Updates the error table in the Error Window.
     */
    synchronized public void updateErrorTable() {

        try {
            String[][] errorData = new String[problemsList.size()][3];
            for (int i = 0; i < problemsList.size(); i++) {
                errorData[i][0] = problemsList.get(i).message;
                errorData[i][1] = editor.getSketch().getCode(problemsList.get(i).tabIndex).getPrettyName();
                errorData[i][2] = problemsList.get(i).lineNumber + "";
            }

            // initializeErrorWindow();

            if (errorWindow != null) {
                DefaultTableModel tm = new DefaultTableModel(errorData, XQErrorTable.columnNames);
                if (errorWindow.isVisible())
                    errorWindow.updateTable(tm);
                ((XQEditor) editor).updateTable(tm);

                // A rotating slash animation on the title bar to show
                // that error checker thread is running

                slashAnimationIndex++;
                if (slashAnimationIndex == slashAnimation.length)
                    slashAnimationIndex = 0;
                if (editor != null) {
                    String info = slashAnimation[slashAnimationIndex] + " T:"
                            + (System.currentTimeMillis() - lastTimeStamp) + "ms";
                    errorWindow.setTitle("Problems - " + editor.getSketch().getName() + " " + info);
                }
            }

        } catch (Exception e) {
            System.out.println("Exception at updateErrorTable() " + e);
            e.printStackTrace();
            stopThread();
        }

    }

    /**
     * Repaints the textarea if required
     */
    public void updateTextAreaPainter() {
        currentTab = editor.getSketch().getCodeIndex(editor.getSketch().getCurrentCode());
        if (currentTab != lastTab) {
            lastTab = currentTab;
            editor.getTextArea().repaint();
            // System.out.println("1 Repaint " + System.currentTimeMillis());
            return;
        }

        if (errorBar.errorPointsChanged())
            editor.getTextArea().repaint();

    }

    /**
     * Scrolls to the error source in code. And selects the line text. Used by
     * XQErrorTable and ErrorBar
     * 
     * @param errorIndex
     *            - index of error
     */
    public void scrollToErrorLine(int errorIndex) {
        if (editor == null)
            return;
        if (errorIndex < problemsList.size() && errorIndex >= 0) {
            Problem p = problemsList.get(errorIndex);
            try {
                editor.toFront();
                editor.getSketch().setCurrentCode(p.tabIndex);
                editor.getTextArea().scrollTo(p.lineNumber - 1, 0);
                editor.setSelection(
                        editor.getTextArea().getLineStartNonWhiteSpaceOffset(p.lineNumber - 1)
                                + editor.getTextArea().getLineText(p.lineNumber - 1).trim().length(),
                        editor.getTextArea().getLineStartNonWhiteSpaceOffset(p.lineNumber - 1));
                editor.repaint();
            } catch (Exception e) {
                System.err.println(e + " : Error while selecting text in scrollToErrorLine()");
                // e.printStackTrace();
            }
            // System.out.println("---");

        }
    }

    /**
     * Replaces non-ascii characters with their unicode escape sequences and
     * stuff. Used as it is from
     * processing.src.processing.mode.java.preproc.PdePreprocessor
     * 
     * @param program
     *            - Input String containing non ascii characters
     * @return String - Converted String
     */
    public static String substituteUnicode(String program) {
        // check for non-ascii chars (these will be/must be in unicode format)
        char p[] = program.toCharArray();
        int unicodeCount = 0;
        for (int i = 0; i < p.length; i++) {
            if (p[i] > 127)
                unicodeCount++;
        }
        if (unicodeCount == 0)
            return program;
        // if non-ascii chars are in there, convert to unicode escapes
        // add unicodeCount * 5.. replacing each unicode char
        // with six digit uXXXX sequence (xxxx is in hex)
        // (except for nbsp chars which will be a replaced with a space)
        int index = 0;
        char p2[] = new char[p.length + unicodeCount * 5];
        for (int i = 0; i < p.length; i++) {
            if (p[i] < 128) {
                p2[index++] = p[i];
            } else if (p[i] == 160) { // unicode for non-breaking space
                p2[index++] = ' ';
            } else {
                int c = p[i];
                p2[index++] = '\\';
                p2[index++] = 'u';
                char str[] = Integer.toHexString(c).toCharArray();
                // add leading zeros, so that the length is 4
                // for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0';
                for (int m = 0; m < 4 - str.length; m++)
                    p2[index++] = '0';
                System.arraycopy(str, 0, p2, index, str.length);
                index += str.length;
            }
        }
        return new String(p2, 0, index);
    }

    /**
     * File I/O
     * 
     * @param file
     * @return String - Contents of the file
     * @throws IOException
     */
    public static String readFile(File file) throws IOException {
        System.out.println("File: " + file.getAbsolutePath());
        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        try {
            StringBuilder ret = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                ret.append(line);
                ret.append("\n");
            }
            return ret.toString();
        } finally {
            reader.close();
        }
    }

    /**
     * File I/O
     * 
     * @param path
     * @return String - Contents of the file
     * @throws IOException
     */
    public static String readFile(String path) throws IOException {
        return readFile(new File(path));
    }

}