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();
}
}
});
}
}