1/* 2 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * - Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * - Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * - Neither the name of Oracle nor the names of its 16 * contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32/* 33 * This source code is provided to illustrate the usage of a given feature 34 * or technique and has been deliberately simplified. Additional steps 35 * required for a production-quality application, such as security checks, 36 * input validation and proper error handling, might not be present in 37 * this sample code. 38 */ 39 40/* 41 * This script creates a simple Notepad-like interface, which 42 * serves as a simple script editor, runner. 43 * 44 * File dependency: 45 * 46 * gui.js -> for basic GUI functions 47 */ 48 49/* 50 * globalThis is used for actionHelpGlobals() and showFrame(). 51 */ 52var globalThis = this; 53 54/* 55 * JavaImporter helps in avoiding pollution of JavaScript 56 * global namespace. We can import multiple Java packages 57 * with this and use the JavaImporter object with "with" 58 * statement. 59 */ 60var guiPkgs = new JavaImporter(java.awt, java.awt.event, 61 javax.swing, javax.swing.undo, 62 javax.swing.event, javax.swing.text); 63 64// main entry point of the scriptpad application 65var main = function() { 66 function createEditor() { 67 var c = new guiPkgs.JTextArea(); 68 c.setDragEnabled(true); 69 c.setFont(new guiPkgs.Font("monospaced", guiPkgs.Font.PLAIN, 12)); 70 return c; 71 } 72 73 /*const*/ var titleSuffix = "- Scriptpad"; 74 /*const*/ var defaultTitle = "Untitled" + titleSuffix; 75 76 // Scriptpad's main frame 77 var frame; 78 // Scriptpad's main editor 79 var editor; 80 81 // To track the current file name 82 var curFileName = null; 83 84 // To track whether the current document 85 // has been modified or not 86 var docChanged = false; 87 88 // check and alert user for unsaved 89 // but modified document 90 function checkDocChanged() { 91 if (docChanged) { 92 // ignore zero-content untitled document 93 if (curFileName == null && 94 editor.document.length == 0) { 95 return; 96 } 97 98 if (confirm("Do you want to save the changes?", 99 "The document has changed")) { 100 actionSave(); 101 } 102 } 103 } 104 105 // set a document listener to track 106 // whether that is modified or not 107 function setDocListener() { 108 var doc = editor.getDocument(); 109 docChanged = false; 110 doc.addDocumentListener( new guiPkgs.DocumentListener() { 111 equals: function(o) { 112 return this === o; }, 113 toString: function() { 114 return "doc listener"; }, 115 changeUpdate: function() { 116 docChanged = true; }, 117 insertUpdate: function() { 118 docChanged = true; }, 119 removeUpdate: function() { 120 docChanged = true; } 121 }); 122 } 123 124 // menu action functions 125 126 // "File" menu 127 128 // create a "new" document 129 function actionNew() { 130 checkDocChanged(); 131 curFileName = null; 132 editor.setDocument(new guiPkgs.PlainDocument()); 133 setDocListener(); 134 frame.setTitle(defaultTitle); 135 editor.revalidate(); 136 } 137 138 // open an existing file 139 function actionOpen() { 140 checkDocChanged(); 141 var f = fileDialog(); 142 if (f == null) { 143 return; 144 } 145 146 if (f.isFile() && f.canRead()) { 147 frame.setTitle(f.getName() + titleSuffix); 148 editor.setDocument(new guiPkgs.PlainDocument()); 149 var progress = new guiPkgs.JProgressBar(); 150 progress.setMinimum(0); 151 progress.setMaximum(f.length()); 152 var doc = editor.getDocument(); 153 var inp = new java.io.FileReader(f); 154 var buff = java.lang.reflect.Array.newInstance( 155 java.lang.Character.TYPE, 4096); 156 var nch; 157 while ((nch = inp.read(buff, 0, buff.length)) != -1) { 158 doc.insertString(doc.getLength(), 159 new java.lang.String(buff, 0, nch), null); 160 progress.setValue(progress.getValue() + nch); 161 } 162 inp.close(); 163 curFileName = f.getAbsolutePath(); 164 setDocListener(); 165 } else { 166 error("Can not open file: " + f, 167 "Error opening file: " + f); 168 } 169 } 170 171 // open script from a URL 172 function actionOpenURL() { 173 checkDocChanged(); 174 var url = prompt("Address:"); 175 if (url == null) { 176 return; 177 } 178 179 try { 180 var u = new java.net.URL(url); 181 editor.setDocument(new guiPkgs.PlainDocument()); 182 frame.setTitle(url + titleSuffix); 183 var progress = new guiPkgs.JProgressBar(); 184 progress.setMinimum(0); 185 progress.setIndeterminate(true); 186 var doc = editor.getDocument(); 187 var inp = new java.io.InputStreamReader(u.openStream()); 188 var buff = java.lang.reflect.Array.newInstance( 189 java.lang.Character.TYPE, 4096); 190 var nch; 191 while ((nch = inp.read(buff, 0, buff.length)) != -1) { 192 doc.insertString(doc.getLength(), 193 new java.lang.String(buff, 0, nch), null); 194 progress.setValue(progress.getValue() + nch); 195 } 196 curFileName = null; 197 setDocListener(); 198 } catch (e) { 199 error("Error opening URL: " + e, 200 "Can not open URL: " + url); 201 } 202 } 203 204 // factored out "save" function used by 205 // save, save as menu actions 206 function save(file) { 207 var doc = editor.getDocument(); 208 frame.setTitle(file.getName() + titleSuffix); 209 curFileName = file; 210 var progress = new guiPkgs.JProgressBar(); 211 progress.setMinimum(0); 212 progress.setMaximum(file.length()); 213 var out = new java.io.FileWriter(file); 214 var text = new guiPkgs.Segment(); 215 text.setPartialReturn(true); 216 var charsLeft = doc.getLength(); 217 var offset = 0; 218 var min; 219 220 while (charsLeft > 0) { 221 doc.getText(offset, Math.min(4096, charsLeft), text); 222 out.write(text.array, text.offset, text.count); 223 charsLeft -= text.count; 224 offset += text.count; 225 progress.setValue(offset); 226 java.lang.Thread.sleep(10); 227 } 228 229 out.flush(); 230 out.close(); 231 docChanged = false; 232 } 233 234 // file-save as menu 235 function actionSaveAs() { 236 var ret = fileDialog(null, true); 237 if (ret == null) { 238 return; 239 } 240 save(ret); 241 } 242 243 // file-save menu 244 function actionSave() { 245 if (curFileName) { 246 save(new java.io.File(curFileName)); 247 } else { 248 actionSaveAs(); 249 } 250 } 251 252 // exit from scriptpad 253 function actionExit() { 254 checkDocChanged(); 255 java.lang.System.exit(0); 256 } 257 258 // "Edit" menu 259 260 // cut the currently selected text 261 function actionCut() { 262 editor.cut(); 263 } 264 265 // copy the currently selected text to clipboard 266 function actionCopy() { 267 editor.copy(); 268 } 269 270 // paste clipboard content to document 271 function actionPaste() { 272 editor.paste(); 273 } 274 275 // select all the text in editor 276 function actionSelectAll() { 277 editor.selectAll(); 278 } 279 280 // "Tools" menu 281 282 // run the current document as JavaScript 283 function actionRun() { 284 var doc = editor.getDocument(); 285 var script = doc.getText(0, doc.getLength()); 286 var oldFile = engine.get(javax.script.ScriptEngine.FILENAME); 287 try { 288 if (engine == undefined) { 289 var m = new javax.script.ScriptEngineManager(); 290 engine = m.getEngineByName("nashorn"); 291 } 292 engine.put(javax.script.ScriptEngine.FILENAME, frame.title); 293 engine.eval(script, context); 294 } catch (e) { 295 error(e, "Script Error"); 296 e.printStackTrace(); 297 } finally { 298 engine.put(javax.script.ScriptEngine.FILENAME, oldFile); 299 } 300 } 301 302 // "Examples" menu 303 304 // show given script as new document 305 function showScript(title, str) { 306 actionNew(); 307 frame.setTitle("Example - " + title + titleSuffix); 308 var doc = editor.document; 309 doc.insertString(0, str, null); 310 } 311 312 // "hello world" 313 function actionHello() { 314 showScript(actionEval.title, 315 "alert('Hello, world');"); 316 } 317 actionHello.title = "Hello, World"; 318 319 // eval the "hello world"! 320 function actionEval() { 321 showScript(actionEval.title, 322 "eval(\"alert('Hello, world')\");"); 323 } 324 actionEval.title = "Eval"; 325 326 // show how to access Java static methods 327 function actionJavaStatic() { 328 showScript(arguments.callee.title, 329 "// Just use Java syntax\n" + 330 "var props = java.lang.System.getProperties();\n" + 331 "alert(props.get('os.name'));"); 332 } 333 actionJavaStatic.title = "Java Static Calls"; 334 335 // show how to access Java classes, methods 336 function actionJavaAccess() { 337 showScript(arguments.callee.title, 338 "// just use new JavaClass();\n" + 339 "var fr = new javax.swing.JFrame();\n" + 340 "// call all public methods as in Java\n" + 341 "fr.setTitle('hello');\n" + 342 "fr.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);\n" + 343 "fr.setSize(200, 200);\n" + 344 "fr.setVisible(true);"); 345 } 346 actionJavaAccess.title = "Java Object Access"; 347 348 // show how to use Java bean conventions 349 function actionJavaBean() { 350 showScript(arguments.callee.title, 351 "var fr = new javax.swing.JFrame();\n" + 352 "fr.setSize(200, 200);\n" + 353 "// access public get/set methods as fields\n" + 354 "fr.defaultCloseOperation = javax.swing.WindowConstants.DISPOSE_ON_CLOSE;\n" + 355 "fr.title = 'hello';\n" + 356 "fr.visible = true;"); 357 } 358 actionJavaBean.title = "Java Beans"; 359 360 // show how to implement Java interface 361 function actionJavaInterface() { 362 showScript(arguments.callee.title, 363 "// use Java anonymizer class-like syntax!\n" + 364 "var r = new java.lang.Runnable() {\n" + 365 " run: function() {\n" + 366 " alert('hello');\n" + 367 " }\n" + 368 " };\n" + 369 "// use the above Runnable to create a Thread\n" + 370 "new java.lang.Thread(r).start();\n" + 371 "// For simple one method interfaces, just pass script function\n" + 372 "new java.lang.Thread(function() { alert('world'); }).start();"); 373 } 374 actionJavaInterface.title = "Java Interfaces"; 375 376 // show how to import Java classes, packages 377 function actionJavaImport() { 378 showScript(arguments.callee.title, 379 "// use Java-like import *...\n" + 380 "// importPackage(java.io);\n" + 381 "// or import a specific class\n" + 382 "// importClass(java.io.File);\n" + 383 "// or better - import just within a scope!\n" + 384 "var ioPkgs = JavaImporter(java.io);\n" + 385 "with (ioPkgs) { alert(new File('.').absolutePath); }"); 386 } 387 actionJavaImport.title = "Java Import"; 388 389 // "Help" menu 390 391 /* 392 * Shows a one liner help message for each 393 * global function. Note that this function 394 * depends on docString meta-data for each 395 * function. 396 */ 397 function actionHelpGlobals() { 398 var names = new java.util.ArrayList(); 399 for (var i in globalThis) { 400 var func = globalThis[i]; 401 if (typeof(func) == "function" && 402 ("docString" in func)) { 403 names.add(i); 404 } 405 } 406 java.util.Collections.sort(names); 407 var helpDoc = new java.lang.StringBuffer(); 408 helpDoc.append("<table border='1'>"); 409 var itr = names.iterator(); 410 while (itr.hasNext()) { 411 var name = itr.next(); 412 helpDoc.append("<tr><td>"); 413 helpDoc.append(name); 414 helpDoc.append("</td><td>"); 415 helpDoc.append(globalThis[name].docString); 416 helpDoc.append("</td></tr>"); 417 } 418 helpDoc.append("</table>"); 419 420 var helpEditor = new guiPkgs.JEditorPane(); 421 helpEditor.setContentType("text/html"); 422 helpEditor.setEditable(false); 423 helpEditor.setText(helpDoc.toString()); 424 425 var scroller = new guiPkgs.JScrollPane(); 426 var port = scroller.getViewport(); 427 port.add(helpEditor); 428 429 var helpFrame = new guiPkgs.JFrame("Help - Global Functions"); 430 helpFrame.getContentPane().add("Center", scroller); 431 helpFrame.setDefaultCloseOperation(guiPkgs.WindowConstants.DISPOSE_ON_CLOSE); 432 helpFrame.pack(); 433 helpFrame.setSize(500, 600); 434 helpFrame.setVisible(true); 435 } 436 437 // show a simple about message for scriptpad 438 function actionAbout() { 439 alert("Scriptpad\nVersion 1.1", "Scriptpad"); 440 } 441 442 /* 443 * This data is used to construct menu bar. 444 * This way adding a menu is easier. Just add 445 * top level menu or add an item to an existing 446 * menu. "action" should be a function that is 447 * called back on clicking the correponding menu. 448 */ 449 var menuData = [ 450 { 451 menu: "File", 452 items: [ 453 { name: "New", action: actionNew , accel: guiPkgs.KeyEvent.VK_N }, 454 { name: "Open...", action: actionOpen, accel: guiPkgs.KeyEvent.VK_O }, 455 { name: "Open URL...", action: actionOpenURL, accel: guiPkgs.KeyEvent.VK_U }, 456 { name: "Save", action: actionSave, accel: guiPkgs.KeyEvent.VK_S }, 457 { name: "Save As...", action: actionSaveAs }, 458 { name: "-" }, 459 { name: "Exit", action: actionExit, accel: guiPkgs.KeyEvent.VK_Q } 460 ] 461 }, 462 463 { 464 menu: "Edit", 465 items: [ 466 { name: "Cut", action: actionCut, accel: guiPkgs.KeyEvent.VK_X }, 467 { name: "Copy", action: actionCopy, accel: guiPkgs.KeyEvent.VK_C }, 468 { name: "Paste", action: actionPaste, accel: guiPkgs.KeyEvent.VK_V }, 469 { name: "-" }, 470 { name: "Select All", action: actionSelectAll, accel: guiPkgs.KeyEvent.VK_A } 471 ] 472 }, 473 474 { 475 menu: "Tools", 476 items: [ 477 { name: "Run", action: actionRun, accel: guiPkgs.KeyEvent.VK_R } 478 ] 479 }, 480 481 { 482 menu: "Examples", 483 items: [ 484 { name: actionHello.title, action: actionHello }, 485 { name: actionEval.title, action: actionEval }, 486 { name: actionJavaStatic.title, action: actionJavaStatic }, 487 { name: actionJavaAccess.title, action: actionJavaAccess }, 488 { name: actionJavaBean.title, action: actionJavaBean }, 489 { name: actionJavaInterface.title, action: actionJavaInterface }, 490 { name: actionJavaImport.title, action: actionJavaImport } 491 ] 492 }, 493 494 { 495 menu: "Help", 496 items: [ 497 { name: "Global Functions", action: actionHelpGlobals }, 498 { name: "-" }, 499 { name: "About Scriptpad", action: actionAbout } 500 ] 501 } 502 ]; 503 504 function setMenuAccelerator(mi, accel) { 505 var keyStroke = guiPkgs.KeyStroke.getKeyStroke(accel, 506 guiPkgs.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false); 507 mi.setAccelerator(keyStroke); 508 } 509 510 // create a menubar using the above menu data 511 function createMenubar() { 512 var mb = new guiPkgs.JMenuBar(); 513 for (var m in menuData) { 514 var items = menuData[m].items; 515 var menu = new guiPkgs.JMenu(menuData[m].menu); 516 517 for (var i in items) { 518 if (items[i].name.equals("-")) { 519 menu.addSeparator(); 520 } else { 521 var mi = new guiPkgs.JMenuItem(items[i].name); 522 var action = items[i].action; 523 mi.addActionListener(action); 524 var accel = items[i].accel; 525 if (accel) { 526 setMenuAccelerator(mi, accel); 527 } 528 menu.add(mi); 529 } 530 } 531 532 mb.add(menu); 533 } 534 535 return mb; 536 } 537 538 // function to add a new menu item under "Tools" menu 539 function addTool(menuItem, action, accel) { 540 if (typeof(action) != "function") { 541 return; 542 } 543 544 var toolsIndex = -1; 545 // find the index of the "Tools" menu 546 for (var i in menuData) { 547 if (menuData[i].menu.equals("Tools")) { 548 toolsIndex = i; 549 break; 550 } 551 } 552 if (toolsIndex == -1) { 553 return; 554 } 555 var toolsMenu = frame.getJMenuBar().getMenu(toolsIndex); 556 var mi = new guiPkgs.JMenuItem(menuItem); 557 mi.addActionListener(action); 558 if (accel) { 559 setMenuAccelerator(mi, accel); 560 } 561 toolsMenu.add(mi); 562 } 563 564 // create Scriptpad frame 565 function createFrame() { 566 frame = new guiPkgs.JFrame(); 567 frame.setTitle(defaultTitle); 568 frame.setBackground(guiPkgs.Color.lightGray); 569 frame.getContentPane().setLayout(new guiPkgs.BorderLayout()); 570 571 // create notepad panel 572 var notepad = new guiPkgs.JPanel(); 573 notepad.setBorder(guiPkgs.BorderFactory.createEtchedBorder()); 574 notepad.setLayout(new guiPkgs.BorderLayout()); 575 576 // create editor 577 editor = createEditor(); 578 var scroller = new guiPkgs.JScrollPane(); 579 var port = scroller.getViewport(); 580 port.add(editor); 581 582 // add editor to notepad panel 583 var panel = new guiPkgs.JPanel(); 584 panel.setLayout(new guiPkgs.BorderLayout()); 585 panel.add("Center", scroller); 586 notepad.add("Center", panel); 587 588 // add notepad panel to frame 589 frame.getContentPane().add("Center", notepad); 590 591 // set menu bar to frame and show the frame 592 frame.setJMenuBar(createMenubar()); 593 frame.setDefaultCloseOperation(guiPkgs.JFrame.EXIT_ON_CLOSE); 594 frame.pack(); 595 frame.setSize(500, 600); 596 } 597 598 // show Scriptpad frame 599 function showFrame() { 600 // set global variable by the name "window" 601 globalThis.window = frame; 602 603 // open new document 604 actionNew(); 605 606 frame.setVisible(true); 607 } 608 609 // create and show Scriptpad frame 610 createFrame(); 611 showFrame(); 612 613 /* 614 * Application object has two fields "frame", "editor" 615 * which are current JFrame and editor and a method 616 * called "addTool" to add new menu item to "Tools" menu. 617 */ 618 return { 619 frame: frame, 620 editor: editor, 621 addTool: addTool 622 }; 623}; 624 625/* 626 * Call the main and store Application object 627 * in a global variable named "application". 628 */ 629var application = main(); 630 631if (this.load == undefined) { 632 function load(file) { 633 var ioPkgs = new JavaImporter(java.io); 634 with (ioPkgs) { 635 var stream = new FileInputStream(file); 636 var bstream = new BufferedInputStream(stream); 637 var reader = new BufferedReader(new InputStreamReader(bstream)); 638 var oldFilename = engine.get(engine.FILENAME); 639 engine.put(engine.FILENAME, file); 640 try { 641 engine.eval(reader, context); 642 } finally { 643 engine.put(engine.FILENAME, oldFilename); 644 } 645 stream.close(); 646 } 647 } 648 load.docString = "loads the given script file"; 649} 650 651/* 652 * Load user specific init file under home dir, if found. 653 */ 654function loadUserInit() { 655 var home = java.lang.System.getProperty("user.home"); 656 var f = new java.io.File(home, "scriptpad.js"); 657 if (f.exists()) { 658 engine.eval(new java.io.FileReader(f)); 659 } 660} 661 662loadUserInit(); 663