package org.expeditee.actions; import java.awt.Color; import java.util.Collection; import java.util.LinkedList; import java.util.List; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameIO; import org.expeditee.gui.FrameUtils; import org.expeditee.gui.MessageBay; import org.expeditee.io.flowlayout.XGroupItem; import org.expeditee.items.Item; import org.expeditee.items.ItemUtils; import org.expeditee.items.Text; /** * Javascript parser. * Works differently to the other Javascript class in that it * parses a frame as a whole rather than parsing individual text items as separate statements * * @author jts21 */ public class Javascript2 { public static final String ERROR_FRAMESET = "JavascriptErrors"; private static ScriptEngineManager sem = new ScriptEngineManager(); private static ScriptEngine se = sem.getEngineByMimeType("application/javascript"); public static synchronized void runFrame(Frame frame, boolean followLinks) throws Exception { Javascript2 js = new Javascript2(frame, followLinks); try { se.eval(js.toString()); } catch (ScriptException e) { int lineNumber = e.getLineNumber(); if(lineNumber != -1) { CodeLine cl = js.lines.get(lineNumber - 1); Frame errorSourceFrame = cl.item.getParent(); if(errorSourceFrame == null) { throw new Exception("Failed to find frame on which error occurred"); } Frame errorFrame; String title = "Error parsing \"" + errorSourceFrame.getTitle() + "\" (" + errorSourceFrame.getName() + ")"; if(FrameIO.canAccessFrameset(ERROR_FRAMESET)) { errorFrame = FrameIO.CreateFrame(ERROR_FRAMESET, title, null); } else { errorFrame = FrameIO.CreateFrameset(ERROR_FRAMESET, FrameIO.FRAME_PATH); errorFrame.setTitle(title); } Collection toAdd = errorSourceFrame.getAllItems(); toAdd.remove(errorSourceFrame.getTitleItem()); toAdd.remove(cl.item); errorFrame.addAllItems(ItemUtils.CopyItems(toAdd)); String errorItemText = cl.item.getText().trim(); String[] errorItemLines = errorItemText.split("[\\n|\\r]+"); int errorLinePos = 0; for(int i = 0; i < cl.line; i++) { errorLinePos += errorItemLines[i].length(); } Text beforeErrorItem = errorFrame.addText(cl.item.getX(), cl.item.getY(), errorItemText.substring(0, errorLinePos), null); Text errorItem; errorItem = errorFrame.addText(cl.item.getX(), beforeErrorItem.getY() + beforeErrorItem.getBoundsHeight(), errorItemLines[cl.line], null); errorItem.setBackgroundColor(Color.RED); // todo set width to 80 characters worth for(String line : e.getMessage().split("[\\n|\\r]+")) { errorItem.setTooltip("text: " + line); } errorItem.setTooltip("font: " + Text.MONOSPACED_FONT); errorItem.setTooltip("width: " + 80 * 12); errorLinePos += errorItemLines[cl.line].length(); if(++errorLinePos < errorItemText.length()) { errorFrame.addText(cl.item.getX(), errorItem.getY() + errorItem.getBoundsHeight(), errorItemText.substring(errorLinePos + 1), null); } errorFrame.change(); FrameIO.SaveFrame(errorFrame); MessageBay.displayMessage("Script failed at line " + lineNumber + " - `" + cl.source + "`", errorFrame.getName(), MessageBay.ERROR_COLOR, true, null); FrameUtils.DisplayFrame(errorFrame, true, true); } } } private static final class CodeLine { public Text item; public int line; public String source; public CodeLine(Text item, int line, String source) { this.item = item; this.line = line; this.source = source; } } private List seen = new LinkedList(); private List lines = new LinkedList(); private StringBuffer sb = new StringBuffer(); private Javascript2(Frame frame, boolean followLinks) { this.parseFrame(frame, followLinks); } private void parseFrame(Frame frame, boolean followLinks) { if(frame == null) { return; } // make sure we don't get into an infinite loop // TODO: find a smarter way to do this that allows reusing frames but still stops infinite loops? seen.add(frame); // get all items on the frame List y_ordered_items = (List)frame.getItems(); // remove the title item y_ordered_items.remove(frame.getTitleItem()); XGroupItem toplevel_xgroup = new XGroupItem(frame,y_ordered_items); // ... following on from Steps 1 and 2 in the Constructor in XGroupItem ... // Step 3: Reposition any 'out-of-flow' XGroupItems toplevel_xgroup.repositionOutOfFlowGroups(toplevel_xgroup); // Step 4: Now add in the remaining (nested) XGroupItems List grouped_item_list = toplevel_xgroup.getGroupedItemList(); toplevel_xgroup.mapInXGroupItemsRecursive(grouped_item_list); // Finally, retrieve linear list of all Items, (ordered, Y by X, allowing for overlap, nested-boxing, and arrow flow) List overlapping_y_ordered_items = toplevel_xgroup.getYXOverlappingItemList(); // Loop through the items looking for code and links to new frames for(Item i : overlapping_y_ordered_items) { if(followLinks && i.hasLink()) { Frame child = i.getChild(); if(child != null && !seen.contains(child)) { this.parseFrame(child, true); } } if(i instanceof Text && !i.isAnnotation()) { String text = ((Text)i).getText(); int lineNumber = 0; for(String line : text.trim().split("[\\n|\\r]+")) { sb.append(line).append("\n"); lines.add(new CodeLine((Text)i, lineNumber++, line)); } } } } public String toString() { return this.sb.toString(); } }