/*
* Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation and proper error handling, might not be present in
* this sample code.
*/
/*
* This script creates a simple Notepad-like interface, which
* serves as a simple script editor, runner.
*
* File dependency:
*
* gui.js -> for basic GUI functions
*/
/*
* globalThis is used for actionHelpGlobals() and showFrame().
*/
var globalThis = this;
/*
* JavaImporter helps in avoiding pollution of JavaScript
* global namespace. We can import multiple Java packages
* with this and use the JavaImporter object with "with"
* statement.
*/
var guiPkgs = new JavaImporter(java.awt, java.awt.event,
javax.swing, javax.swing.undo,
javax.swing.event, javax.swing.text);
// main entry point of the scriptpad application
var main = function() {
function createEditor() {
var c = new guiPkgs.JTextArea();
c.setDragEnabled(true);
c.setFont(new guiPkgs.Font("monospaced", guiPkgs.Font.PLAIN, 12));
return c;
}
/*const*/ var titleSuffix = "- Scriptpad";
/*const*/ var defaultTitle = "Untitled" + titleSuffix;
// Scriptpad's main frame
var frame;
// Scriptpad's main editor
var editor;
// To track the current file name
var curFileName = null;
// To track whether the current document
// has been modified or not
var docChanged = false;
// check and alert user for unsaved
// but modified document
function checkDocChanged() {
if (docChanged) {
// ignore zero-content untitled document
if (curFileName == null &&
editor.document.length == 0) {
return;
}
if (confirm("Do you want to save the changes?",
"The document has changed")) {
actionSave();
}
}
}
// set a document listener to track
// whether that is modified or not
function setDocListener() {
var doc = editor.getDocument();
docChanged = false;
doc.addDocumentListener( new guiPkgs.DocumentListener() {
equals: function(o) {
return this === o; },
toString: function() {
return "doc listener"; },
changeUpdate: function() {
docChanged = true; },
insertUpdate: function() {
docChanged = true; },
removeUpdate: function() {
docChanged = true; }
});
}
// menu action functions
// "File" menu
// create a "new" document
function actionNew() {
checkDocChanged();
curFileName = null;
editor.setDocument(new guiPkgs.PlainDocument());
setDocListener();
frame.setTitle(defaultTitle);
editor.revalidate();
}
// open an existing file
function actionOpen() {
checkDocChanged();
var f = fileDialog();
if (f == null) {
return;
}
if (f.isFile() && f.canRead()) {
frame.setTitle(f.getName() + titleSuffix);
editor.setDocument(new guiPkgs.PlainDocument());
var progress = new guiPkgs.JProgressBar();
progress.setMinimum(0);
progress.setMaximum(f.length());
var doc = editor.getDocument();
var inp = new java.io.FileReader(f);
var buff = java.lang.reflect.Array.newInstance(
java.lang.Character.TYPE, 4096);
var nch;
while ((nch = inp.read(buff, 0, buff.length)) != -1) {
doc.insertString(doc.getLength(),
new java.lang.String(buff, 0, nch), null);
progress.setValue(progress.getValue() + nch);
}
inp.close();
curFileName = f.getAbsolutePath();
setDocListener();
} else {
error("Can not open file: " + f,
"Error opening file: " + f);
}
}
// open script from a URL
function actionOpenURL() {
checkDocChanged();
var url = prompt("Address:");
if (url == null) {
return;
}
try {
var u = new java.net.URL(url);
editor.setDocument(new guiPkgs.PlainDocument());
frame.setTitle(url + titleSuffix);
var progress = new guiPkgs.JProgressBar();
progress.setMinimum(0);
progress.setIndeterminate(true);
var doc = editor.getDocument();
var inp = new java.io.InputStreamReader(u.openStream());
var buff = java.lang.reflect.Array.newInstance(
java.lang.Character.TYPE, 4096);
var nch;
while ((nch = inp.read(buff, 0, buff.length)) != -1) {
doc.insertString(doc.getLength(),
new java.lang.String(buff, 0, nch), null);
progress.setValue(progress.getValue() + nch);
}
curFileName = null;
setDocListener();
} catch (e) {
error("Error opening URL: " + e,
"Can not open URL: " + url);
}
}
// factored out "save" function used by
// save, save as menu actions
function save(file) {
var doc = editor.getDocument();
frame.setTitle(file.getName() + titleSuffix);
curFileName = file;
var progress = new guiPkgs.JProgressBar();
progress.setMinimum(0);
progress.setMaximum(file.length());
var out = new java.io.FileWriter(file);
var text = new guiPkgs.Segment();
text.setPartialReturn(true);
var charsLeft = doc.getLength();
var offset = 0;
var min;
while (charsLeft > 0) {
doc.getText(offset, Math.min(4096, charsLeft), text);
out.write(text.array, text.offset, text.count);
charsLeft -= text.count;
offset += text.count;
progress.setValue(offset);
java.lang.Thread.sleep(10);
}
out.flush();
out.close();
docChanged = false;
}
// file-save as menu
function actionSaveAs() {
var ret = fileDialog(null, true);
if (ret == null) {
return;
}
save(ret);
}
// file-save menu
function actionSave() {
if (curFileName) {
save(new java.io.File(curFileName));
} else {
actionSaveAs();
}
}
// exit from scriptpad
function actionExit() {
checkDocChanged();
java.lang.System.exit(0);
}
// "Edit" menu
// cut the currently selected text
function actionCut() {
editor.cut();
}
// copy the currently selected text to clipboard
function actionCopy() {
editor.copy();
}
// paste clipboard content to document
function actionPaste() {
editor.paste();
}
// select all the text in editor
function actionSelectAll() {
editor.selectAll();
}
// "Tools" menu
// run the current document as JavaScript
function actionRun() {
var doc = editor.getDocument();
var script = doc.getText(0, doc.getLength());
var oldFile = engine.get(javax.script.ScriptEngine.FILENAME);
try {
if (engine == undefined) {
var m = new javax.script.ScriptEngineManager();
engine = m.getEngineByName("nashorn");
}
engine.put(javax.script.ScriptEngine.FILENAME, frame.title);
engine.eval(script, context);
} catch (e) {
error(e, "Script Error");
e.printStackTrace();
} finally {
engine.put(javax.script.ScriptEngine.FILENAME, oldFile);
}
}
// "Examples" menu
// show given script as new document
function showScript(title, str) {
actionNew();
frame.setTitle("Example - " + title + titleSuffix);
var doc = editor.document;
doc.insertString(0, str, null);
}
// "hello world"
function actionHello() {
showScript(actionEval.title,
"alert('Hello, world');");
}
actionHello.title = "Hello, World";
// eval the "hello world"!
function actionEval() {
showScript(actionEval.title,
"eval(\"alert('Hello, world')\");");
}
actionEval.title = "Eval";
// show how to access Java static methods
function actionJavaStatic() {
showScript(arguments.callee.title,
"// Just use Java syntax\n" +
"var props = java.lang.System.getProperties();\n" +
"alert(props.get('os.name'));");
}
actionJavaStatic.title = "Java Static Calls";
// show how to access Java classes, methods
function actionJavaAccess() {
showScript(arguments.callee.title,
"// just use new JavaClass();\n" +
"var fr = new javax.swing.JFrame();\n" +
"// call all public methods as in Java\n" +
"fr.setTitle('hello');\n" +
"fr.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);\n" +
"fr.setSize(200, 200);\n" +
"fr.setVisible(true);");
}
actionJavaAccess.title = "Java Object Access";
// show how to use Java bean conventions
function actionJavaBean() {
showScript(arguments.callee.title,
"var fr = new javax.swing.JFrame();\n" +
"fr.setSize(200, 200);\n" +
"// access public get/set methods as fields\n" +
"fr.defaultCloseOperation = javax.swing.WindowConstants.DISPOSE_ON_CLOSE;\n" +
"fr.title = 'hello';\n" +
"fr.visible = true;");
}
actionJavaBean.title = "Java Beans";
// show how to implement Java interface
function actionJavaInterface() {
showScript(arguments.callee.title,
"// use Java anonymizer class-like syntax!\n" +
"var r = new java.lang.Runnable() {\n" +
" run: function() {\n" +
" alert('hello');\n" +
" }\n" +
" };\n" +
"// use the above Runnable to create a Thread\n" +
"new java.lang.Thread(r).start();\n" +
"// For simple one method interfaces, just pass script function\n" +
"new java.lang.Thread(function() { alert('world'); }).start();");
}
actionJavaInterface.title = "Java Interfaces";
// show how to import Java classes, packages
function actionJavaImport() {
showScript(arguments.callee.title,
"// use Java-like import *...\n" +
"// importPackage(java.io);\n" +
"// or import a specific class\n" +
"// importClass(java.io.File);\n" +
"// or better - import just within a scope!\n" +
"var ioPkgs = JavaImporter(java.io);\n" +
"with (ioPkgs) { alert(new File('.').absolutePath); }");
}
actionJavaImport.title = "Java Import";
// "Help" menu
/*
* Shows a one liner help message for each
* global function. Note that this function
* depends on docString meta-data for each
* function.
*/
function actionHelpGlobals() {
var names = new java.util.ArrayList();
for (var i in globalThis) {
var func = globalThis[i];
if (typeof(func) == "function" &&
("docString" in func)) {
names.add(i);
}
}
java.util.Collections.sort(names);
var helpDoc = new java.lang.StringBuffer();
helpDoc.append("<table border='1'>");
var itr = names.iterator();
while (itr.hasNext()) {
var name = itr.next();
helpDoc.append("<tr><td>");
helpDoc.append(name);
helpDoc.append("</td><td>");
helpDoc.append(globalThis[name].docString);
helpDoc.append("</td></tr>");
}
helpDoc.append("</table>");
var helpEditor = new guiPkgs.JEditorPane();
helpEditor.setContentType("text/html");
helpEditor.setEditable(false);
helpEditor.setText(helpDoc.toString());
var scroller = new guiPkgs.JScrollPane();
var port = scroller.getViewport();
port.add(helpEditor);
var helpFrame = new guiPkgs.JFrame("Help - Global Functions");
helpFrame.getContentPane().add("Center", scroller);
helpFrame.setDefaultCloseOperation(guiPkgs.WindowConstants.DISPOSE_ON_CLOSE);
helpFrame.pack();
helpFrame.setSize(500, 600);
helpFrame.setVisible(true);
}
// show a simple about message for scriptpad
function actionAbout() {
alert("Scriptpad\nVersion 1.1", "Scriptpad");
}
/*
* This data is used to construct menu bar.
* This way adding a menu is easier. Just add
* top level menu or add an item to an existing
* menu. "action" should be a function that is
* called back on clicking the correponding menu.
*/
var menuData = [
{
menu: "File",
items: [
{ name: "New", action: actionNew , accel: guiPkgs.KeyEvent.VK_N },
{ name: "Open...", action: actionOpen, accel: guiPkgs.KeyEvent.VK_O },
{ name: "Open URL...", action: actionOpenURL, accel: guiPkgs.KeyEvent.VK_U },
{ name: "Save", action: actionSave, accel: guiPkgs.KeyEvent.VK_S },
{ name: "Save As...", action: actionSaveAs },
{ name: "-" },
{ name: "Exit", action: actionExit, accel: guiPkgs.KeyEvent.VK_Q }
]
},
{
menu: "Edit",
items: [
{ name: "Cut", action: actionCut, accel: guiPkgs.KeyEvent.VK_X },
{ name: "Copy", action: actionCopy, accel: guiPkgs.KeyEvent.VK_C },
{ name: "Paste", action: actionPaste, accel: guiPkgs.KeyEvent.VK_V },
{ name: "-" },
{ name: "Select All", action: actionSelectAll, accel: guiPkgs.KeyEvent.VK_A }
]
},
{
menu: "Tools",
items: [
{ name: "Run", action: actionRun, accel: guiPkgs.KeyEvent.VK_R }
]
},
{
menu: "Examples",
items: [
{ name: actionHello.title, action: actionHello },
{ name: actionEval.title, action: actionEval },
{ name: actionJavaStatic.title, action: actionJavaStatic },
{ name: actionJavaAccess.title, action: actionJavaAccess },
{ name: actionJavaBean.title, action: actionJavaBean },
{ name: actionJavaInterface.title, action: actionJavaInterface },
{ name: actionJavaImport.title, action: actionJavaImport }
]
},
{
menu: "Help",
items: [
{ name: "Global Functions", action: actionHelpGlobals },
{ name: "-" },
{ name: "About Scriptpad", action: actionAbout }
]
}
];
function setMenuAccelerator(mi, accel) {
var keyStroke = guiPkgs.KeyStroke.getKeyStroke(accel,
guiPkgs.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
mi.setAccelerator(keyStroke);
}
// create a menubar using the above menu data
function createMenubar() {
var mb = new guiPkgs.JMenuBar();
for (var m in menuData) {
var items = menuData[m].items;
var menu = new guiPkgs.JMenu(menuData[m].menu);
for (var i in items) {
if (items[i].name.equals("-")) {
menu.addSeparator();
} else {
var mi = new guiPkgs.JMenuItem(items[i].name);
var action = items[i].action;
mi.addActionListener(action);
var accel = items[i].accel;
if (accel) {
setMenuAccelerator(mi, accel);
}
menu.add(mi);
}
}
mb.add(menu);
}
return mb;
}
// function to add a new menu item under "Tools" menu
function addTool(menuItem, action, accel) {
if (typeof(action) != "function") {
return;
}
var toolsIndex = -1;
// find the index of the "Tools" menu
for (var i in menuData) {
if (menuData[i].menu.equals("Tools")) {
toolsIndex = i;
break;
}
}
if (toolsIndex == -1) {
return;
}
var toolsMenu = frame.getJMenuBar().getMenu(toolsIndex);
var mi = new guiPkgs.JMenuItem(menuItem);
mi.addActionListener(action);
if (accel) {
setMenuAccelerator(mi, accel);
}
toolsMenu.add(mi);
}
// create Scriptpad frame
function createFrame() {
frame = new guiPkgs.JFrame();
frame.setTitle(defaultTitle);
frame.setBackground(guiPkgs.Color.lightGray);
frame.getContentPane().setLayout(new guiPkgs.BorderLayout());
// create notepad panel
var notepad = new guiPkgs.JPanel();
notepad.setBorder(guiPkgs.BorderFactory.createEtchedBorder());
notepad.setLayout(new guiPkgs.BorderLayout());
// create editor
editor = createEditor();
var scroller = new guiPkgs.JScrollPane();
var port = scroller.getViewport();
port.add(editor);
// add editor to notepad panel
var panel = new guiPkgs.JPanel();
panel.setLayout(new guiPkgs.BorderLayout());
panel.add("Center", scroller);
notepad.add("Center", panel);
// add notepad panel to frame
frame.getContentPane().add("Center", notepad);
// set menu bar to frame and show the frame
frame.setJMenuBar(createMenubar());
frame.setDefaultCloseOperation(guiPkgs.JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setSize(500, 600);
}
// show Scriptpad frame
function showFrame() {
// set global variable by the name "window"
globalThis.window = frame;
// open new document
actionNew();
frame.setVisible(true);
}
// create and show Scriptpad frame
createFrame();
showFrame();
/*
* Application object has two fields "frame", "editor"
* which are current JFrame and editor and a method
* called "addTool" to add new menu item to "Tools" menu.
*/
return {
frame: frame,
editor: editor,
addTool: addTool
};
};
/*
* Call the main and store Application object
* in a global variable named "application".
*/
var application = main();
if (this.load == undefined) {
function load(file) {
var ioPkgs = new JavaImporter(java.io);
with (ioPkgs) {
var stream = new FileInputStream(file);
var bstream = new BufferedInputStream(stream);
var reader = new BufferedReader(new InputStreamReader(bstream));
var oldFilename = engine.get(engine.FILENAME);
engine.put(engine.FILENAME, file);
try {
engine.eval(reader, context);
} finally {
engine.put(engine.FILENAME, oldFilename);
}
stream.close();
}
}
load.docString = "loads the given script file";
}
/*
* Load user specific init file under home dir, if found.
*/
function loadUserInit() {
var home = java.lang.System.getProperty("user.home");
var f = new java.io.File(home, "scriptpad.js");
if (f.exists()) {
engine.eval(new java.io.FileReader(f));
}
}
loadUserInit();