source: trunk/src/org/expeditee/actions/Javascript2.java@ 951

Last change on this file since 951 was 951, checked in by davidb, 9 years ago

Change in code to cope with commit #936, that broke the Javascript running within Expeditee

File size: 9.7 KB
Line 
1/**
2 * Javascript2.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.actions;
20
21import java.awt.Color;
22import java.util.Collection;
23import java.util.LinkedList;
24import java.util.List;
25
26import javax.script.Bindings;
27import javax.script.Invocable;
28import javax.script.ScriptContext;
29import javax.script.ScriptEngine;
30import javax.script.ScriptEngineManager;
31import javax.script.ScriptException;
32import javax.script.SimpleScriptContext;
33
34import org.expeditee.gui.Frame;
35import org.expeditee.gui.FrameIO;
36import org.expeditee.gui.FrameUtils;
37import org.expeditee.gui.MessageBay;
38import org.expeditee.io.flowlayout.XGroupItem;
39import org.expeditee.items.Item;
40import org.expeditee.items.ItemUtils;
41import org.expeditee.items.Text;
42
43/**
44 * Javascript parser.
45 * Works differently to the other Javascript class in that it
46 * parses a frame as a whole rather than parsing individual text items as separate statements
47 *
48 * @author jts21
49 */
50public class Javascript2 {
51
52 public static final String ERROR_FRAMESET = "JavascriptErrors";
53
54 public static final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
55 public static final ScriptEngine scriptEngine = scriptEngineManager.getEngineByMimeType("application/javascript");
56 static {
57 scriptEngine.put("invocable", (Invocable) scriptEngine);
58 }
59
60 public static void printJSFrame(Item item) {
61 if(item.getChild() == null) {
62 // if the user clicked on the action without an item on their cursor
63 if(item.hasAction()) {
64 boolean isThis = false;
65 for(String s : item.getAction()) {
66 if(s.equalsIgnoreCase("runJSFrame")) {
67 isThis = true;
68 break;
69 }
70 }
71 if(isThis) {
72 System.out.println(new Javascript2(item.getParentOrCurrentFrame(), true));
73 return;
74 }
75 }
76 MessageBay.warningMessage("Requires either an item with a link to a frame, or no item (will run the current frame)");
77 } else {
78 System.out.println(new Javascript2(item.getChild(), true));
79 }
80 }
81
82 public static void runJSFrame(Item item) throws Exception {
83 if(item.getChild() == null) {
84 // if the user clicked on the action without an item on their cursor
85 if(item.hasAction()) {
86 boolean isThis = false;
87 for(String s : item.getAction()) {
88 if(s.equalsIgnoreCase("runJSFrame")) {
89 isThis = true;
90 break;
91 }
92 }
93 if(isThis) {
94 Javascript2.runFrame(item.getParentOrCurrentFrame(), true);
95 return;
96 }
97 }
98 MessageBay.warningMessage("Requires either an item with a link to a frame, or no item (will run the current frame)");
99 } else {
100 Javascript2.runFrame(item.getChild(), true);
101 }
102 }
103
104 public static Object eval(String code) {
105 try {
106 return scriptEngine.eval(code);
107 } catch (ScriptException e) {
108 e.printStackTrace();
109 }
110 return null;
111 }
112
113 private static synchronized void runFrame(Frame frame, boolean followLinks) throws Exception {
114 Javascript2 js = new Javascript2(frame, followLinks);
115 try {
116 try {
117 scriptEngine.eval(js.toString());
118 } catch (ScriptException e) {
119 js.handleError(e.getMessage(), e.getLineNumber());
120 } catch (RuntimeException e) {
121 // there doesn't seem to be a way to safely get the lineNumber on which the error occurred
122 // so as a workaround we just parse the exception
123 if(e.getCause() == null) {
124 throw e;
125 }
126 String detail = e.getCause().getStackTrace()[1].toString();
127 int lastColon = detail.lastIndexOf(':');
128 int lastBracket = detail.lastIndexOf(')');
129 int lineNumber;
130 if(lastColon == -1 || lastBracket == -1) {
131 lineNumber = -1;
132 } else {
133 lineNumber = Integer.parseInt(detail.substring(lastColon + 1, lastBracket));
134 }
135 js.handleError(e.getMessage(), lineNumber);
136 }
137 } catch(Exception e) {
138 js.handleError(null, -1);
139 System.out.println(js.toString());
140 e.printStackTrace();
141 }
142 }
143
144 private static final class CodeLine {
145 public Text item;
146 public int line;
147 public String source;
148
149 public CodeLine(Text item, int line, String source) {
150 this.item = item;
151 this.line = line;
152 this.source = source;
153 }
154
155 public String toString() {
156 return line + ": " + source;
157 }
158 }
159
160 private List<Frame> seen = new LinkedList<Frame>();
161 private List<CodeLine> lines = new LinkedList<CodeLine>();
162 private StringBuffer sb = new StringBuffer();
163 private Javascript2(Frame frame, boolean followLinks) {
164 this.parseFrame(frame, followLinks);
165 }
166
167 private void parseFrame(Frame frame, boolean followLinks) {
168 if(frame == null) {
169 return;
170 }
171
172 // make sure we don't get into an infinite loop
173 // TODO: find a smarter way to do this that allows reusing frames but still stops infinite loops?
174 seen.add(frame);
175
176 // get all items on the frame
177 List<Item> y_ordered_items = (List<Item>)frame.getItems();
178 // remove the title item
179 y_ordered_items.remove(frame.getTitleItem());
180
181 XGroupItem toplevel_xgroup = new XGroupItem(frame,y_ordered_items);
182 // ... following on from Steps 1 and 2 in the Constructor in XGroupItem ...
183
184 // Step 3: Reposition any 'out-of-flow' XGroupItems
185 toplevel_xgroup.repositionOutOfFlowGroups(toplevel_xgroup);
186
187 // Step 4: Now add in the remaining (nested) XGroupItems
188 List<XGroupItem> grouped_item_list = toplevel_xgroup.getGroupedItemList();
189 toplevel_xgroup.mapInXGroupItemsRecursive(grouped_item_list);
190
191 // Finally, retrieve linear list of all Items, (ordered, Y by X, allowing for overlap, nested-boxing, and arrow flow)
192 List<Item> overlapping_y_ordered_items = toplevel_xgroup.getYXOverlappingItemList(true);
193
194 // Loop through the items looking for code and links to new frames
195 for(Item i : overlapping_y_ordered_items) {
196 if(followLinks && i.hasLink()) {
197 Frame child = i.getChild();
198 if(child != null && !seen.contains(child)) {
199 this.parseFrame(child, true);
200 }
201 }
202 if(i instanceof Text && !i.isAnnotation()) {
203 String text = ((Text)i).getText();
204 if (i == org.expeditee.io.flowlayout.XGroupItem.GROUPSEP_START) {
205 text = "{";
206 }
207 else if (i == org.expeditee.io.flowlayout.XGroupItem.GROUPSEP_END) {
208 text = "}";
209 }
210
211 int lineNumber = 0;
212 for(String line : text.trim().split("[\\n\\r]+")) {
213 sb.append(line).append("\n");
214 lines.add(new CodeLine((Text)i, lineNumber++, line));
215 }
216 }
217 }
218 }
219
220 private void handleError(String message, int lineNumber) throws Exception {
221 // negative line number bad
222 if(lineNumber < 0) {
223 MessageBay.errorMessage("Failed to determine the line on which the error occurred");
224 return;
225 }
226 // if for some reason the error is after the end of the code, assume it should be the last line
227 if(lineNumber > this.lines.size()) {
228 lineNumber = this.lines.size();
229 }
230 CodeLine cl = this.lines.get(lineNumber - 1);
231 Frame errorSourceFrame = cl.item.getParent();
232 if(errorSourceFrame == null) {
233 MessageBay.errorMessage("Failed to find frame on which the error occurred");
234 return;
235 }
236 Frame errorFrame;
237 String title = "Error parsing \"" + errorSourceFrame.getTitle() + "\" (" + errorSourceFrame.getName() + ")";
238 if(FrameIO.canAccessFrameset(ERROR_FRAMESET)) {
239 errorFrame = FrameIO.CreateFrame(ERROR_FRAMESET, title, null);
240 } else {
241 errorFrame = FrameIO.CreateFrameset(ERROR_FRAMESET, FrameIO.FRAME_PATH);
242 errorFrame.setTitle(title);
243 }
244 Collection<Item> toAdd = errorSourceFrame.getAllItems();
245 toAdd.remove(errorSourceFrame.getTitleItem());
246 toAdd.remove(cl.item);
247 errorFrame.addAllItems(ItemUtils.CopyItems(toAdd));
248 String errorItemText = cl.item.getText().trim();
249 String[] errorItemLines = errorItemText.split("[\\n\\r]+");
250 int errorLinePos = 0;
251 int x = cl.item.getX();
252 int y = cl.item.getY();
253 if(cl.line != 0) {
254 for(int i = 0; i < cl.line; i++) {
255 errorLinePos += errorItemLines[i].length();
256 }
257 Text beforeErrorItem = errorFrame.addText(x, y,
258 errorItemText.substring(0, errorLinePos), null);
259 y = beforeErrorItem.getY() + beforeErrorItem.getBoundsHeight();
260 }
261 Text errorItem;
262 errorItem = errorFrame.addText(x, y, errorItemLines[cl.line], null);
263 errorItem.setBackgroundColor(Color.RED);
264 for(String line : message.split("[\\n\\r]+")) {
265 errorItem.setTooltip("text: " + line);
266 }
267 errorItem.setTooltip("font: " + Text.MONOSPACED_FONT);
268 errorItem.setTooltip("width: " + 80 * 12);
269 errorLinePos += errorItemLines[cl.line].length();
270 if(++errorLinePos < errorItemText.length()) {
271 errorFrame.addText(cl.item.getX(), errorItem.getY() + errorItem.getBoundsHeight(),
272 errorItemText.substring(errorLinePos + 1), null);
273 }
274 errorFrame.change();
275 FrameIO.SaveFrame(errorFrame);
276 MessageBay.displayMessage("Script failed at line " + lineNumber + " - `" + cl.source + "`",
277 errorFrame.getName(), MessageBay.ERROR_COLOR, true, null);
278 FrameUtils.DisplayFrame(errorFrame, true, true);
279 }
280
281 public String toString() {
282 return this.sb.toString();
283 }
284
285}
Note: See TracBrowser for help on using the repository browser.