package com.drawbridge.vl; import java.awt.Component; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Stack; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.SwingUtilities; import com.drawbridge.correspondance.RadialSpotlight; import com.drawbridge.jsengine.JsEngine; import com.drawbridge.jsengine.ast.DBParser; import com.drawbridge.jsengine.ast.Evaluator; import com.drawbridge.jsengine.ast.ParserListener; import com.drawbridge.text.DocumentError; import com.drawbridge.text.TextPanel; import com.drawbridge.utils.Utils; import com.drawbridge.vl.blocks.Block; import com.drawbridge.vl.blocks.BlockFunctionCall; import com.drawbridge.vl.blocks.BlockFunctionDefinition; import com.drawbridge.vl.blocks.BlockIdentifier; import com.drawbridge.vl.blocks.BlockPrimitive; public class VLModel implements ParserListener { private VLCanvas mParent; private LinkedList<String> mLastValidComments = null; Stack<GenerationMode> mStack = new Stack<GenerationMode>(); private int mIndentation = 0; private static final String INDENT_UNIT = " "; private Thread mParserChangeThread = null; private Runnable mParserChangeRunnable; private int mContinueParserChangeRequests = 0; private int mNumberProcessed = 0; private int mNumberRequested = 0; /** * Holds the underlying information for the Grid used in the VLJPanel * * @param parent */ public VLModel(VLCanvas parent) { mParent = parent; mParserChangeRunnable = new Runnable() { @Override public void run() { while (mContinueParserChangeRequests > 0) { mContinueParserChangeRequests = 0; Utils.out.println(VLModel.class, "**onParseChange!"); mLastValidComments = DBParser.getInstance().getComments(); mParent.mDocumentError = null; // The old way // replaceGrid(); // Do some processing injectNewGrid(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } mNumberProcessed++; } mParent.repaint(); Utils.out.println(VLModel.class, "Finished processing parser change!"); } }; } enum GenerationMode { FUNCTION_PARAM_MODE, FUNCTION_CALL_MODE, FUNCTION_DEFINITION_MODE, NORMAL_MODE }; /** * Generates code for each block line. Treats functions specially. * * @param line * @return */ private String generateCodeForLine(LinkedList<Block> line) { String result = ""; if(line == null) return result; int paramCount = 0; if (line.size() == 0) { if (mStack.size() > 0 && mStack.lastElement() == GenerationMode.FUNCTION_DEFINITION_MODE) { for (int i = 0; i < mIndentation - 1; i++) result += INDENT_UNIT; result += "}"; mStack.pop(); mIndentation--; } else if (mStack.size() > 1) { mStack.pop(); } } else if (line.size() > 0) { for (int i = 0; i < mIndentation; i++) result += INDENT_UNIT; for (int i = 0; i < line.size(); i++) { Block b = line.get(i); if (b instanceof BlockFunctionCall) { if (mStack.size() > 0 && mStack.lastElement() == GenerationMode.FUNCTION_PARAM_MODE) { result += "."; } else { mStack.push(GenerationMode.FUNCTION_CALL_MODE); mStack.push(GenerationMode.FUNCTION_PARAM_MODE); paramCount = 0; } } else if (b instanceof BlockFunctionDefinition) { mStack.push(GenerationMode.FUNCTION_DEFINITION_MODE); mStack.push(GenerationMode.FUNCTION_PARAM_MODE); mIndentation++; } else if (!mStack.empty() && mStack.lastElement() == GenerationMode.FUNCTION_PARAM_MODE) { GenerationMode oldMode = mStack.pop(); if ((isFunctionDefinitionParam(b) && mStack.lastElement() == GenerationMode.FUNCTION_DEFINITION_MODE) || ((isFunctionCallParam(b)) && mStack.lastElement() == GenerationMode.FUNCTION_CALL_MODE)) { if (paramCount > 0) result += ", "; paramCount++; } mStack.push(oldMode); } else if (mStack.empty() || (!mStack.empty() && mStack.lastElement() != GenerationMode.NORMAL_MODE)) { mStack.push(GenerationMode.NORMAL_MODE); } result += b.generateCode(); boolean notInNormalMode = mStack.lastElement() != GenerationMode.NORMAL_MODE; boolean stackNotEmpty = !mStack.empty(); boolean atLeastOneBlockLeft = i < line.size() - 1; boolean thisIsLastElement = (i == line.size() - 1); // Look ahead if (stackNotEmpty && atLeastOneBlockLeft && (notInNormalMode && line.get(i) instanceof BlockFunctionCall && !(line.get(i + 1) instanceof BlockFunctionCall))) { result += "("; } else if (stackNotEmpty && atLeastOneBlockLeft && (notInNormalMode)) { if (mStack.lastElement() == GenerationMode.FUNCTION_PARAM_MODE) { GenerationMode oldMode = mStack.pop(); if ((!isFunctionCallParam(line.get(i + 1)) && !(line.get(i + 1) instanceof BlockFunctionCall) && mStack.lastElement() == GenerationMode.FUNCTION_CALL_MODE)) { if (paramCount == 0) result += "("; result += ")"; } else if ((!isFunctionDefinitionParam(line.get(i + 1)) && mStack.lastElement() == GenerationMode.FUNCTION_DEFINITION_MODE)) { result += "){"; } else if (line.get(i + 1) instanceof BlockFunctionCall) { } mStack.push(oldMode); } } else if (atLeastOneBlockLeft && b instanceof BlockIdentifier && line.get(i + 1) instanceof BlockFunctionCall) { result += "."; } else if (stackNotEmpty && thisIsLastElement && (mStack.lastElement() == GenerationMode.FUNCTION_PARAM_MODE)) { mStack.pop(); if (mStack.lastElement() == GenerationMode.FUNCTION_CALL_MODE) { if (paramCount == 0) result += "("; result += ");"; mStack.pop(); } else if (mStack.lastElement() == GenerationMode.FUNCTION_DEFINITION_MODE) { result += "){"; } } else if (!mStack.empty() && thisIsLastElement && mStack.lastElement() == GenerationMode.NORMAL_MODE) { mStack.pop(); result += ";"; } } } return result; } private boolean isFunctionCallParam(Block block) { if (block instanceof BlockPrimitive || block instanceof BlockIdentifier) return true; else return false; } private boolean isFunctionDefinitionParam(Block block) { if (block instanceof BlockIdentifier) return true; else return false; } /** * This method generates code to send to the parser. The code maintains whitespace that may have existed within a * previous version of * * @param mViewGrid */ public synchronized String onGridUpdate(Grid mViewGrid) { Utils.out.println(getClass(), "OnGridUpdate"); String generatedCode = generateCodeFromBlocks(mViewGrid); // Reset Invalid Lines mParent.mDocumentError = null; if(TextPanel.hasInstance()) TextPanel.getInstance().setText(VLModel.class, generatedCode); JsEngine.getInstance().executeCode(this, generatedCode, false); mParent.validateGridLayout(); return generatedCode; } // This method generates text from all the blocks in a mViewGrid public synchronized String generateCodeFromBlocks(Grid mViewGrid) { // Generate Code from Each Block int lineNo = 0; mIndentation = 0; String generatedCode = ""; for (int i = 0; i < mViewGrid.size(); i++) { LinkedList<Block> line = mViewGrid.get(i); generatedCode += generateCodeForLine(line); if (DBParser.getInstance().getComments().size() > lineNo) { LinkedList<String> comments = DBParser.getInstance().getComments(); generatedCode += comments.get(lineNo); generatedCode += "\n"; } else if (mLastValidComments != null && mLastValidComments.size() > lineNo) { LinkedList<String> comments = mLastValidComments; generatedCode += comments.get(lineNo); generatedCode += "\n"; } else { generatedCode += "\n"; } lineNo++; } return generatedCode; } private void extendGrid(Grid grid, int index) { for (int i = grid.size() - 1; i < index; i++) { grid.add(new LinkedList<Block>()); } } @Override public void onParserChange() { mContinueParserChangeRequests++; mNumberRequested++; // Utils.out.println("Requested:" + mNumberRequested + ", processed:" + mNumberProcessed); // If thread isn't working, start it if (mParserChangeThread != null && mParserChangeThread.isAlive()) { // it will process if it has time } else { mParserChangeThread = new Thread(mParserChangeRunnable); mParserChangeThread.setPriority(Thread.MIN_PRIORITY); mParserChangeThread.start(); } } /** * Does a straight grid replacement, including every block. Is very slow. Reads directly from parser results. * * @param parser */ public void replaceGrid() { synchronized (mParent.mViewGrid) { mParent.mViewGrid.clear(); Component[] cs = mParent.getComponents(); for (Component c : cs) { if (!(c instanceof PopupDiallerPrimitive || c instanceof PopupSelectorOperator)) mParent.remove(c); } int noOfFunctions = 0; for (Evaluator eval : DBParser.getInstance().getEvaluators()) { LinkedList<Block> result = eval.getBlocks(); noOfFunctions += (Utils.containsClassType(result, BlockFunctionDefinition.class) ? 1 : 0); // Add results to the grid if (result != null) { for (Block block : result) { int index = 0; try { index = block.getFilePosition().startLineNo() - 1; } catch (NullPointerException e) { Utils.out.println("NullPointerException:" + block.getClass().getSimpleName()); } while (mParent.mViewGrid.size() <= index) { mParent.mViewGrid.add(new LinkedList<Block>()); } mParent.mViewGrid.get(index).add(block); } } // Add results to the panel if (result != null) { for (Block resultBlock : result) { mParent.add(resultBlock); try { resultBlock.validate(); } catch (Exception e) { e.printStackTrace(); Utils.out.println(this.getClass(), "Block validation exception:" + resultBlock.getClass().getSimpleName()); } } } } // Add 10 blanks at the end just in case for (int i = 0; i < noOfFunctions + 2; i++) { mParent.mViewGrid.add(new LinkedList<Block>()); } } mParent.validate(); mParent.validateGridLayout(); mParent.repaint(); } /** * Adds new grid components as efficiently as possible using LCS. Faster but more prone to errors. * * @param newGrid */ public void injectNewGrid() { try{ Utils.out.println(getClass(), "Replacing grid using LCS"); System.currentTimeMillis(); synchronized (mParent.mViewGrid) { Grid newGrid = new Grid(); LinkedList<Evaluator> evals = DBParser.getInstance().getEvaluators(); if (evals.size() > 0) { int lastFilePos = evals.getLast().getFilePosition().startLineNo() - 1; extendGrid(newGrid, lastFilePos); CopyOnWriteArrayList<Evaluator> cowal = new CopyOnWriteArrayList<Evaluator>(); cowal.addAll(evals); for (Evaluator eval : cowal) { LinkedList<Block> result = new LinkedList<Block>(); synchronized (eval) { result.addAll(eval.getBlocks()); } // Add results to the new grid if (result != null) { for (Block block : result) { int index = 0; try { index = block.getFilePosition().startLineNo() - 1; } catch (NullPointerException e) { Utils.out.println("NullPointerException:" + block.getClass().getSimpleName()); } while (newGrid.size() <= index) { newGrid.add(new LinkedList<Block>()); } newGrid.get(index).add(block); } } } } int highestIndex = Math.max(mParent.mViewGrid.size(), newGrid.size()); for (int i = 0; i < highestIndex; i++) { extendGrid(mParent.mViewGrid, i); LinkedList<Block> oldLine = mParent.mViewGrid.get(i); LinkedList<Block> newLine = (newGrid.size() > i) ? newGrid.get(i) : new LinkedList<Block>(); if (!(oldLine.isEmpty() && newLine.isEmpty())) { VLModel.LongestCommonSubsequence(mParent, oldLine, newLine); } } } } catch(Exception e){ Utils.err.println("VLModel exception:" + e); e.printStackTrace(); } } public static boolean checkBlockEquality(Block a, Block b) { if (a.getClass().equals(b.getClass())) { if (a instanceof Block) { if (((Block) a).mRoundedText.getText().equals((((Block) b).mRoundedText.getText()))) { return true; } } else return true; } return false; } @Override public void onParserException(int lineNumber, String exception) { Utils.out.println("VLJ parser exception"); mParent.mDocumentError = new DocumentError(lineNumber, "VLJ parser exception", exception); mParent.repaint(); } /** * Uses the standard LCS algorithm to compare grids. Changes are propagated to oldLine * * @param oldLine * @param newLine */ public static void LongestCommonSubsequence(final VLCanvas parent, List<Block> oldLine, List<Block> newLine) { LinkedList<Block> spotlightBlocks = new LinkedList<Block>(); HashSet<RadialSpotlight> spotlightPoints = new HashSet<RadialSpotlight>(); LinkedList<Block> temp = new LinkedList<Block>(); int M = oldLine.size(); int N = newLine.size(); int[][] opt = new int[M + 1][N + 1]; for (int i = M - 1; i >= 0; i--) { for (int j = N - 1; j >= 0; j--) { if (checkBlockEquality(oldLine.get(i), newLine.get(j))) { opt[i][j] = opt[i + 1][j + 1] + 1; } else { opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]); } } } LinkedList<Block> oldLineBlocksToDelete = new LinkedList<Block>(); // recover LCS itself and print out non-matching lines int i = 0, j = 0; while (i < M && j < N) { if (checkBlockEquality(oldLine.get(i), newLine.get(j))) { // They are the same i++; j++; if (oldLine.get(i - 1) instanceof BlockPrimitive) { ((BlockPrimitive) oldLine.get(i - 1)).setEvaluatorNode(newLine.get(j - 1).getEvaluatorNode()); } temp.add(oldLine.get(i - 1)); } else if (opt[i + 1][j] >= opt[i][j + 1]) { i++; oldLineBlocksToDelete.add(oldLine.get(i - 1)); } else { j++; Block blockToAdd = newLine.get(j - 1); spotlightBlocks.add(blockToAdd); temp.add(blockToAdd); } } // dump out one remainder of one string if the other is exhausted while (i < M || j < N) { if (i == M) { j++; Block blockToAdd = newLine.get(j - 1); spotlightBlocks.add(blockToAdd); temp.add(blockToAdd); } else if (j == N) { i++; oldLineBlocksToDelete.add(oldLine.get(i - 1)); } } final LinkedList<Block> safeOldLineBlocksToDelete = oldLineBlocksToDelete; // Remove from the model for (int t = oldLineBlocksToDelete.size() - 1; t >= 0; t--) { oldLine.remove(oldLineBlocksToDelete.get(t)); } oldLine.clear(); oldLine.addAll(temp); final LinkedList<Block> safeTemp = temp; final LinkedList<Block> finalBlocks = (LinkedList<Block>) spotlightBlocks.clone(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { for (int t = safeOldLineBlocksToDelete.size() - 1; t >= 0; t--) { parent.remove(safeOldLineBlocksToDelete.get(t)); } for (Block b : safeTemp) { parent.add(b); } try{ parent.validate(); // Add spotlights to added blocks HashSet<RadialSpotlight> tmpSpotlights = new HashSet<RadialSpotlight>(); for (int s = 0; s < finalBlocks.size(); s++) { int x = finalBlocks.get(s).getX() + (finalBlocks.get(s).getWidth() / 2); int y = finalBlocks.get(s).getY() + (finalBlocks.get(s).getHeight() / 2); tmpSpotlights.add(new RadialSpotlight(x, y, (finalBlocks.get(s).getWidth() / 2), 40)); } if (tmpSpotlights.size() > 0 && parent != null) parent.setSpotlights(tmpSpotlights); parent.setBlocksToHighlight(finalBlocks); parent.validateGridLayout(); parent.repaint(); } catch(Exception e){ Utils.out.println(getClass(), "Exception when doing LCS:"); e.printStackTrace(); } } }); } }