summaryrefslogtreecommitdiffstats
path: root/scripting/java/com/sun/star/script/framework/provider/beanshell
diff options
context:
space:
mode:
Diffstat (limited to 'scripting/java/com/sun/star/script/framework/provider/beanshell')
-rw-r--r--scripting/java/com/sun/star/script/framework/provider/beanshell/MANIFEST.MF2
-rw-r--r--scripting/java/com/sun/star/script/framework/provider/beanshell/PlainSourceView.java395
-rw-r--r--scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptEditorForBeanShell.java400
-rw-r--r--scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptProviderForBeanShell.java367
-rw-r--r--scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptSourceModel.java125
-rw-r--r--scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptSourceView.java29
-rw-r--r--scripting/java/com/sun/star/script/framework/provider/beanshell/UnsavedChangesListener.java13
-rw-r--r--scripting/java/com/sun/star/script/framework/provider/beanshell/template.bsh65
8 files changed, 1396 insertions, 0 deletions
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/MANIFEST.MF b/scripting/java/com/sun/star/script/framework/provider/beanshell/MANIFEST.MF
new file mode 100644
index 000000000..689b6fde9
--- /dev/null
+++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/MANIFEST.MF
@@ -0,0 +1,2 @@
+RegistrationClassName: com.sun.star.script.framework.provider.beanshell.ScriptProviderForBeanShell
+UNO-Type-Path:
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/PlainSourceView.java b/scripting/java/com/sun/star/script/framework/provider/beanshell/PlainSourceView.java
new file mode 100644
index 000000000..e519587ff
--- /dev/null
+++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/PlainSourceView.java
@@ -0,0 +1,395 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package com.sun.star.script.framework.provider.beanshell;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.UndoableEditEvent;
+import javax.swing.event.UndoableEditListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.undo.CompoundEdit;
+import javax.swing.undo.UndoManager;
+import java.util.List;
+import java.util.ArrayList;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PlainSourceView extends JScrollPane implements
+ ScriptSourceView, DocumentListener {
+
+ private final ScriptSourceModel model;
+ private JTextArea ta;
+ private GlyphGutter gg;
+ private int linecount;
+ private boolean isModified = false;
+ private static final String undoKey = "Undo";
+ private static final String redoKey = "Redo";
+ private CompoundEdit compoundEdit = null;
+ private static final int noLimit = -1;
+ UndoManager undoManager;
+ private List<UnsavedChangesListener> unsavedListener = new ArrayList<UnsavedChangesListener>();
+
+ private static final Pattern tabPattern = Pattern.compile("^ *(\\t)");
+ private static final Pattern indentationPattern = Pattern.compile("^([^\\S\\r\\n]*)(([^\\{])*\\{\\s*)*");
+
+ public PlainSourceView(ScriptSourceModel model) {
+ this.model = model;
+ initUI();
+ model.setView(this);
+ }
+
+ public void undo(){
+ if(compoundEdit!=null){
+ compoundEdit.end();
+ undoManager.addEdit(compoundEdit);
+ compoundEdit = null;
+ }
+ if(undoManager.canUndo()){
+ undoManager.undo();
+ }
+ // check if it's the last undoable change
+ if(undoManager.canUndo() == false){
+ setModified(false);
+ }
+ }
+ public void redo(){
+ if(undoManager.canRedo()){
+ undoManager.redo();
+ }
+ }
+ public void clear() {
+ ta.setText("");
+ }
+
+ public void update() {
+ /* Remove ourselves as a DocumentListener while loading the source
+ so we don't get a storm of DocumentEvents during loading */
+ ta.getDocument().removeDocumentListener(this);
+
+ if (!isModified) {
+ int pos = ta.getCaretPosition();
+ ta.setText(model.getText());
+
+ try {
+ ta.setCaretPosition(pos);
+ } catch (IllegalArgumentException iae) {
+ // do nothing and allow JTextArea to set its own position
+ }
+ }
+
+ // scroll to currentPosition of the model
+ try {
+ int line = ta.getLineStartOffset(model.getCurrentPosition());
+ Rectangle rect = ta.modelToView(line);
+ if (rect != null) {
+ ta.scrollRectToVisible(rect);
+ }
+ } catch (BadLocationException e) {
+ // couldn't scroll to line, do nothing
+ }
+
+ gg.repaint();
+
+ // Add back the listener
+ ta.getDocument().addDocumentListener(this);
+ }
+
+ public boolean isModified() {
+ return isModified;
+ }
+
+ private void notifyListeners (boolean isUnsaved) {
+ for (UnsavedChangesListener listener : unsavedListener) {
+ listener.onUnsavedChanges(isUnsaved);
+ }
+ }
+
+ public void setModified(boolean value) {
+ if(value != isModified) {
+ notifyListeners(value);
+ isModified = value;
+ }
+ }
+
+ private void initUI() {
+ try{
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ }
+ catch(Exception e){
+ // What to do here
+ }
+ ta = new JTextArea();
+ ta.setTabSize(4);
+ ta.setRows(15);
+ ta.setColumns(40);
+ ta.setLineWrap(false);
+ ta.insert(model.getText(), 0);
+ ta.setFont(new Font("Monospaced", ta.getFont().getStyle(), ta.getFont().getSize()));
+ undoManager = new UndoManager();
+ undoManager.setLimit(noLimit);
+ ta.getDocument().addUndoableEditListener(new UndoableEditListener(){
+ @Override
+ public void undoableEditHappened(UndoableEditEvent editEvent) {
+ if(compoundEdit == null){
+ compoundEdit = new CompoundEdit();
+ }
+ compoundEdit.addEdit(editEvent.getEdit());
+ }
+ });
+
+ ta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK), undoKey);
+ ta.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK), redoKey);
+
+ ta.addKeyListener(new KeyAdapter(){
+ @Override
+ public void keyPressed(KeyEvent ke) {
+ // if shift + tab was pressed, remove the first tab before any code begins
+ if (ke.isShiftDown() && ke.getKeyCode() == KeyEvent.VK_TAB) {
+ try {
+ int caretOffset = ta.getCaretPosition();
+ int lineOffset = ta.getLineOfOffset(caretOffset);
+ int startOffset = ta.getLineStartOffset(lineOffset);
+ int endOffset = ta.getLineEndOffset(lineOffset);
+
+ Matcher matcher = tabPattern.matcher(ta.getText(startOffset, endOffset - startOffset));
+ if (matcher.find()) {
+ ta.replaceRange(null, startOffset + matcher.start(1), startOffset + matcher.end(1));
+ }
+ } catch (BadLocationException e) {
+ // could not find correct location of the tab
+ }
+ }
+ // if the enter key was pressed, adjust indentation of the current line accordingly
+ if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
+ try {
+ int caretOffset = ta.getCaretPosition();
+ int lineOffset = ta.getLineOfOffset(caretOffset);
+ int startOffset = ta.getLineStartOffset(lineOffset);
+ int endOffset = ta.getLineEndOffset(lineOffset);
+
+ Matcher matcher = indentationPattern.matcher(ta.getText(startOffset, endOffset - startOffset));
+ // insert new line including indentation of the previous line
+ ta.insert("\n", caretOffset++);
+ if (matcher.find()) {
+ if (matcher.group(1).length() > 0) {
+ ta.insert(matcher.group(1), caretOffset++);
+ }
+ // if there is an open curly bracket in the current line, increase indentation level
+ if (matcher.group(3) != null) {
+ ta.insert("\t", caretOffset);
+ }
+ }
+ ke.consume();
+ } catch (BadLocationException e) {
+ // could not find correct location of the indentation
+ }
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent ke){
+ if(ke.getKeyCode() == KeyEvent.VK_SPACE || ke.getKeyCode() == KeyEvent.VK_ENTER){
+ compoundEdit.end();
+ undoManager.addEdit(compoundEdit);
+ compoundEdit = null;
+ }
+ }
+ });
+
+ ta.getActionMap().put(undoKey, new AbstractAction(undoKey){
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ undo();
+ }
+ });
+
+ ta.getActionMap().put(redoKey, new AbstractAction(redoKey){
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ redo();
+ }
+ });
+
+ linecount = ta.getLineCount();
+
+ gg = new GlyphGutter(this);
+
+ setViewportView(ta);
+ setRowHeaderView(gg);
+
+ ta.getDocument().addDocumentListener(this);
+ }
+
+ /* Implementation of DocumentListener interface */
+ public void insertUpdate(DocumentEvent e) {
+ doChanged();
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ doChanged();
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ doChanged();
+ }
+
+ /* If the number of lines in the JTextArea has changed then update the
+ GlyphGutter */
+ private void doChanged() {
+ setModified(true);
+
+ if (linecount != ta.getLineCount()) {
+ gg.update();
+ linecount = ta.getLineCount();
+ }
+ }
+
+ public String getText() {
+ return ta.getText();
+ }
+
+ public JTextArea getTextArea() {
+ return ta;
+ }
+
+ public int getCurrentPosition() {
+ return model.getCurrentPosition();
+ }
+
+ public void addListener(UnsavedChangesListener toAdd) {
+ unsavedListener.add(toAdd);
+ }
+}
+
+class GlyphGutter extends JComponent {
+
+ private final PlainSourceView view;
+ private static final String DUMMY_STRING = "99";
+
+ GlyphGutter(PlainSourceView view) {
+ this.view = view;
+ update();
+ }
+
+ public void update() {
+ JTextArea textArea = view.getTextArea();
+ Font font = textArea.getFont();
+ setFont(font);
+
+ FontMetrics metrics = getFontMetrics(font);
+ int h = metrics.getHeight();
+ int lineCount = textArea.getLineCount() + 1;
+
+ String dummy = Integer.toString(lineCount);
+
+ if (dummy.length() < 2) {
+ dummy = DUMMY_STRING;
+ }
+
+ Dimension d = new Dimension();
+ d.width = metrics.stringWidth(dummy) + 16;
+ d.height = lineCount * h + 100;
+ setPreferredSize(d);
+ setSize(d);
+ }
+
+ @Override
+ public void paintComponent(Graphics g) {
+ JTextArea textArea = view.getTextArea();
+
+ Font font = textArea.getFont();
+ g.setFont(font);
+
+ FontMetrics metrics = getFontMetrics(font);
+ Rectangle clip = g.getClipBounds();
+
+ g.setColor(getBackground());
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ int ascent = metrics.getMaxAscent();
+ int h = metrics.getHeight();
+ int lineCount = textArea.getLineCount() + 1;
+
+ int startLine = clip.y / h;
+ int endLine = (clip.y + clip.height) / h + 1;
+ int width = getWidth();
+
+ if (endLine > lineCount) {
+ endLine = lineCount;
+ }
+
+ for (int i = startLine; i < endLine; i++) {
+ String text;
+ text = Integer.toString(i + 1) + " ";
+ int y = i * h;
+ g.setColor(Color.blue);
+ g.drawString(text, 0, y + ascent);
+ int x = width - ascent;
+
+ // if currentPosition is not -1 then a red arrow will be drawn
+ if (i == view.getCurrentPosition()) {
+ drawArrow(g, ascent, x, y);
+ }
+ }
+ }
+
+ private void drawArrow(Graphics g, int ascent, int x, int y) {
+ Polygon arrow = new Polygon();
+ int dx = x;
+ y += ascent - 10;
+ int dy = y;
+ arrow.addPoint(dx, dy + 3);
+ arrow.addPoint(dx + 5, dy + 3);
+
+ for (x = dx + 5; x <= dx + 10; x++, y++) {
+ arrow.addPoint(x, y);
+ }
+
+ for (x = dx + 9; x >= dx + 5; x--, y++) {
+ arrow.addPoint(x, y);
+ }
+
+ arrow.addPoint(dx + 5, dy + 7);
+ arrow.addPoint(dx, dy + 7);
+
+ g.setColor(Color.red);
+ g.fillPolygon(arrow);
+ g.setColor(Color.black);
+ g.drawPolygon(arrow);
+ }
+}
+
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptEditorForBeanShell.java b/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptEditorForBeanShell.java
new file mode 100644
index 000000000..0ab265ecc
--- /dev/null
+++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptEditorForBeanShell.java
@@ -0,0 +1,400 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package com.sun.star.script.framework.provider.beanshell;
+
+import com.sun.star.script.framework.container.ScriptMetaData;
+import com.sun.star.script.framework.provider.ClassLoaderFactory;
+import com.sun.star.script.framework.provider.ScriptEditorBase;
+import com.sun.star.script.framework.provider.SwingInvocation;
+import com.sun.star.script.provider.XScriptContext;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.Dimension;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.net.URL;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import javax.swing.JToolBar;
+import javax.swing.BorderFactory;
+
+public class ScriptEditorForBeanShell extends ScriptEditorBase implements ActionListener {
+
+ private JFrame frame;
+ private String filename;
+
+ private ScriptSourceModel model;
+ private ScriptSourceView view;
+
+ private URL scriptURL = null;
+ private ClassLoader cl = null;
+ private JButton saveBtn;
+
+ // global ScriptEditorForBeanShell returned for getEditor() calls
+ private static ScriptEditorForBeanShell theScriptEditorForBeanShell;
+
+ // global list of ScriptEditors, key is [external form of URL] of file being edited
+ private static Map<String, ScriptEditorForBeanShell> BEING_EDITED =
+ new HashMap<String, ScriptEditorForBeanShell>();
+
+ // template for new BeanShell scripts
+ private static String BSHTEMPLATE;
+
+ // try to load the template for BeanShell scripts
+ static {
+ BSHTEMPLATE = "// BeanShell script";
+ try {
+ URL url = ScriptEditorForBeanShell.class.getResource("template.bsh");
+ if (url != null) {
+ InputStream in = url.openStream();
+ StringBuilder buf = new StringBuilder();
+ byte[] b = new byte[1024];
+ int len;
+
+ while ((len = in.read(b)) != -1) {
+ buf.append(new String(b, 0, len));
+ }
+
+ in.close();
+
+ BSHTEMPLATE = buf.toString();
+ }
+ } catch (IOException ioe) {
+ } catch (Exception e) {
+ }
+ }
+
+ /**
+ * Returns the global ScriptEditorForBeanShell instance.
+ */
+
+ public static synchronized ScriptEditorForBeanShell getEditor() {
+ if (theScriptEditorForBeanShell == null) {
+ theScriptEditorForBeanShell =
+ new ScriptEditorForBeanShell();
+ }
+
+ return theScriptEditorForBeanShell;
+ }
+
+ /**
+ * Get the ScriptEditorForBeanShell instance for this URL
+ *
+ * @param url The URL of the script source file
+ *
+ * @return The ScriptEditorForBeanShell associated with
+ * the given URL if one exists, otherwise null.
+ */
+ public static ScriptEditorForBeanShell getEditor(URL url) {
+ synchronized (BEING_EDITED) {
+ return BEING_EDITED.get(url.toExternalForm());
+ }
+ }
+
+ /**
+ * Returns whether or not the script source being edited in this
+ * ScriptEditorForBeanShell has been modified
+ */
+ public boolean isModified() {
+ return view.isModified();
+ }
+
+ /**
+ * Returns the text being displayed in this ScriptEditorForBeanShell
+ *
+ * @return The text displayed in this ScriptEditorForBeanShell
+ */
+ public String getText() {
+ return view.getText();
+ }
+
+ /**
+ * Returns the template text for BeanShell scripts
+ *
+ * @return The template text for BeanShell scripts
+ */
+ public String getTemplate() {
+ return BSHTEMPLATE;
+ }
+
+ /**
+ * Returns the default extension for BeanShell scripts
+ *
+ * @return The default extension for BeanShell scripts
+ */
+ public String getExtension() {
+ return "bsh";
+ }
+
+ /**
+ * Indicates the line where error occurred
+ *
+ */
+ public void indicateErrorLine(int lineNum) {
+ model.indicateErrorLine(lineNum);
+ }
+
+ /**
+ * Executes the script edited by the editor
+ *
+ */
+ public Object execute() throws Exception {
+ if (!isMacroExecutionEnabled()) {
+ showErrorMessage("Macro Execution has been disabled.");
+ return null;
+ }
+
+ frame.toFront();
+ return model.execute(context, cl);
+ }
+
+ /**
+ * Opens an editor window for the specified ScriptMetaData.
+ * If an editor window is already open for that data it will be
+ * moved to the front.
+ *
+ * @param context The context in which to execute the script
+ * @param entry The metadata describing the script
+ */
+ public void edit(final XScriptContext context, ScriptMetaData entry) {
+ if (entry != null) {
+ try {
+ ClassLoader cl = null;
+
+ try {
+ cl = ClassLoaderFactory.getURLClassLoader(entry);
+ } catch (Exception ignore) { // TODO re-examine error handling
+ }
+
+ final ClassLoader theCl = cl;
+ final URL url = entry.getSourceURL();
+ SwingInvocation.invoke(
+ new Runnable() {
+ public void run() {
+ ScriptEditorForBeanShell editor;
+
+ synchronized (BEING_EDITED) {
+ editor = BEING_EDITED.get(url.toExternalForm());
+
+ if (editor == null) {
+ editor = new ScriptEditorForBeanShell(
+ context, theCl, url);
+ BEING_EDITED.put(url.toExternalForm(), editor);
+ }
+ }
+
+ editor.frame.toFront();
+ }
+ });
+ } catch (IOException ioe) {
+ showErrorMessage("Error loading file: " + ioe.getMessage());
+ }
+ }
+ }
+
+ private ScriptEditorForBeanShell() {
+ }
+
+ private ScriptEditorForBeanShell(XScriptContext context, ClassLoader cl,
+ URL url) {
+ setContext(context);
+ this.scriptURL = url;
+ this.model = new ScriptSourceModel(url);
+ this.filename = ScriptMetaData.getFileName(url);
+ this.cl = cl;
+
+ try {
+
+ Class<?> c =
+ Class.forName("org.openoffice.netbeans.editor.NetBeansSourceView");
+
+ Class<?>[] types = new Class[] { ScriptSourceModel.class };
+
+ java.lang.reflect.Constructor<?> ctor = c.getConstructor(types);
+
+ if (ctor != null) {
+ Object[] args = new Object[] { this.model };
+ this.view = (ScriptSourceView) ctor.newInstance(args);
+ } else {
+ this.view = new PlainSourceView(model);
+ }
+ } catch (java.lang.Error err) {
+ this.view = new PlainSourceView(model);
+ } catch (Exception e) {
+ this.view = new PlainSourceView(model);
+ }
+
+ this.model.setView(this.view);
+ initUI();
+ this.view.addListener(new UnsavedChangesListener() {
+ @Override
+ public void onUnsavedChanges(boolean isUnsaved) {
+ if(filename != null) {
+ // enable or disable save button depending on unsaved changes
+ saveBtn.setEnabled(isUnsaved);
+ }
+ }
+ });
+ frame.setVisible(true);
+ }
+
+ private void initUI() {
+ frame = new JFrame("BeanShell Debug Window: " + filename);
+ frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+
+ frame.addWindowListener(
+ new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ doClose();
+ }
+ }
+ );
+
+ String[] labels = {"Run", "Clear", "Save","Undo","Redo"};
+ JToolBar toolbar = new JToolBar();
+ toolbar.setRollover(true);
+ for (String label : labels) {
+ JButton b = new JButton(label);
+ b.setToolTipText(label);
+ b.addActionListener(this);
+ toolbar.add(b);
+ toolbar.addSeparator();
+
+ // disable save button on start
+ if (label.equals("Save")) {
+ b.setEnabled(false);
+ saveBtn = b;
+ }
+ }
+
+ frame.getContentPane().add((JComponent)view, BorderLayout.CENTER);
+ frame.add(toolbar, BorderLayout.NORTH);
+ frame.pack();
+ frame.setSize(590, 480);
+ frame.setLocation(300, 200);
+ frame.setMinimumSize(new Dimension(500, 300));
+ }
+
+ private void doClose() {
+ if (view.isModified()) {
+
+ int result = JOptionPane.showConfirmDialog(frame,
+ "The script has been modified. Do you want to save the changes?");
+
+ if (result == JOptionPane.CANCEL_OPTION) {
+ // don't close the window, just return
+ return;
+ } else if (result == JOptionPane.YES_OPTION) {
+ boolean saveSuccess = saveTextArea();
+
+ if (!saveSuccess) {
+ return;
+ }
+ }
+ }
+
+ frame.dispose();
+ shutdown();
+ }
+
+ private boolean saveTextArea() {
+ boolean result = true;
+
+ if (!view.isModified()) {
+ return true;
+ }
+
+ OutputStream fos = null;
+
+ try {
+ String s = view.getText();
+ fos = scriptURL.openConnection().getOutputStream();
+
+ if (fos != null) {
+ fos.write(s.getBytes());
+ } else {
+ showErrorMessage(
+ "Error saving script: Could not open stream for file");
+ result = false;
+ }
+
+ view.setModified(false);
+ } catch (IOException ioe) {
+ showErrorMessage("Error saving script: " + ioe.getMessage());
+ result = false;
+ } catch (Exception e) {
+ showErrorMessage("Error saving script: " + e.getMessage());
+ result = false;
+ } finally {
+ if (fos != null) {
+ try {
+ fos.flush();
+ } catch (IOException ignore) {
+ }
+ try {
+ fos.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private void shutdown() {
+ synchronized (BEING_EDITED) {
+ BEING_EDITED.remove(scriptURL.toExternalForm());
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String actionCommand = e.getActionCommand();
+ if (actionCommand.equals("Run")) {
+ try {
+ execute();
+ } catch (Exception invokeException) {
+ showErrorMessage(invokeException.toString());
+ }
+ } else if (actionCommand.equals("Save")) {
+ saveTextArea();
+ } else if (actionCommand.equals("Clear")) {
+ view.clear();
+ } else if(actionCommand.equals("Undo")){
+ view.undo();
+ } else if(actionCommand.equals("Redo")){
+ view.redo();
+ }
+ }
+}
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptProviderForBeanShell.java b/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptProviderForBeanShell.java
new file mode 100644
index 000000000..2aabba712
--- /dev/null
+++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptProviderForBeanShell.java
@@ -0,0 +1,367 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package com.sun.star.script.framework.provider.beanshell;
+
+import bsh.Interpreter;
+
+import com.sun.star.comp.loader.FactoryHelper;
+
+import com.sun.star.document.XScriptInvocationContext;
+
+import com.sun.star.frame.XModel;
+
+import com.sun.star.lang.XMultiComponentFactory;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.lang.XSingleServiceFactory;
+
+import com.sun.star.reflection.InvocationTargetException;
+
+import com.sun.star.registry.XRegistryKey;
+
+import com.sun.star.script.framework.container.ScriptMetaData;
+import com.sun.star.script.framework.log.LogUtils;
+import com.sun.star.script.framework.provider.ClassLoaderFactory;
+import com.sun.star.script.framework.provider.ScriptContext;
+import com.sun.star.script.framework.provider.ScriptEditor;
+import com.sun.star.script.framework.provider.ScriptProvider;
+import com.sun.star.script.provider.ScriptErrorRaisedException;
+import com.sun.star.script.provider.ScriptExceptionRaisedException;
+import com.sun.star.script.provider.ScriptFrameworkErrorException;
+import com.sun.star.script.provider.ScriptFrameworkErrorType;
+import com.sun.star.script.provider.XScript;
+
+import com.sun.star.uno.Any;
+import com.sun.star.uno.Type;
+import com.sun.star.uno.XComponentContext;
+
+import java.net.URL;
+
+import java.util.StringTokenizer;
+
+public class ScriptProviderForBeanShell {
+
+ public static class ScriptProviderForBeanShell_2 extends ScriptProvider {
+
+ public ScriptProviderForBeanShell_2(XComponentContext ctx) {
+ super(ctx, "BeanShell");
+ }
+
+ @Override
+ public XScript getScript(/*IN*/String scriptURI) throws
+ com.sun.star.uno.RuntimeException, ScriptFrameworkErrorException {
+
+ ScriptMetaData scriptData = getScriptData(scriptURI);
+
+ try {
+
+ ScriptImpl script =
+ new ScriptImpl(m_xContext, scriptData, m_xModel, m_xInvocContext);
+
+ return script;
+ } catch (com.sun.star.uno.RuntimeException re) {
+ throw new ScriptFrameworkErrorException(
+ "Failed to create script object: " + re.getMessage(),
+ null, scriptData.getLanguageName(), language,
+ ScriptFrameworkErrorType.UNKNOWN);
+ }
+ }
+
+ @Override
+ public boolean hasScriptEditor() {
+ return true;
+ }
+
+ @Override
+ public ScriptEditor getScriptEditor() {
+ return ScriptEditorForBeanShell.getEditor();
+ }
+ }
+
+ /**
+ * Returns a factory for creating the service.
+ * This method is called by the <code>JavaLoader</code>
+ * <p>
+ *
+ * @param implName the name of the implementation for which a service is desired
+ * @param multiFactory the service manager to be used if needed
+ * @param regKey the registryKey
+ * @return returns a <code>XSingleServiceFactory</code> for creating
+ * the component
+ * @see com.sun.star.comp.loader.JavaLoader
+ */
+ public static XSingleServiceFactory __getServiceFactory(
+ String implName, XMultiServiceFactory multiFactory, XRegistryKey regKey) {
+
+ XSingleServiceFactory xSingleServiceFactory = null;
+
+ if (implName.equals(
+ ScriptProviderForBeanShell.ScriptProviderForBeanShell_2.class.getName())) {
+
+ xSingleServiceFactory =
+ FactoryHelper.getServiceFactory(
+ ScriptProviderForBeanShell.ScriptProviderForBeanShell_2.class,
+ "com.sun.star.script.provider.ScriptProviderForBeanShell",
+ multiFactory, regKey);
+
+ }
+
+ return xSingleServiceFactory;
+ }
+}
+
+class ScriptImpl implements XScript {
+
+ private final ScriptMetaData metaData;
+ private final XComponentContext m_xContext;
+ private XMultiComponentFactory m_xMultiComponentFactory;
+ private final XModel m_xModel;
+ private final XScriptInvocationContext m_xInvocContext;
+
+ ScriptImpl(XComponentContext ctx, ScriptMetaData metaData, XModel xModel,
+ XScriptInvocationContext xContext) throws
+ com.sun.star.uno.RuntimeException {
+
+ this.metaData = metaData;
+ this.m_xContext = ctx;
+ this.m_xModel = xModel;
+ this.m_xInvocContext = xContext;
+
+ try {
+ this.m_xMultiComponentFactory = m_xContext.getServiceManager();
+ } catch (Exception e) {
+ throw new com.sun.star.uno.RuntimeException(e);
+ }
+
+ LogUtils.DEBUG("ScriptImpl [beanshell] script data = " + metaData);
+ }
+
+ /**
+ * documentStorageID and document reference
+ * for use in script name resolving
+ *
+ * @param aParams All parameters; pure, out params are
+ * undefined in sequence, i.e., the value
+ * has to be ignored by the callee
+ *
+ * @param aOutParamIndex Out indices
+ *
+ * @param aOutParam Out parameters
+ *
+ * @return The value returned from the function
+ * being invoked
+ *
+ * @throws IllegalArgumentException If there is no matching script name
+ *
+ * @throws InvocationTargetException If the running script throws
+ * an exception this information
+ * is captured and rethrown as
+ * this exception type.
+ */
+
+ public Object invoke(/*IN*/Object[] aParams,
+ /*OUT*/short[][] aOutParamIndex,
+ /*OUT*/Object[][] aOutParam) throws
+ ScriptFrameworkErrorException, InvocationTargetException {
+
+ // Initialise the out parameters - not used at the moment
+ aOutParamIndex[0] = new short[0];
+ aOutParam[0] = new Object[0];
+
+ ClassLoader cl = null;
+ URL sourceUrl = null;
+
+ try {
+ cl = ClassLoaderFactory.getURLClassLoader(metaData);
+ sourceUrl = metaData.getSourceURL();
+ } catch (java.net.MalformedURLException mfu) {
+ // Framework error
+ throw new ScriptFrameworkErrorException(
+ mfu.getMessage(), null,
+ metaData.getLanguageName(), metaData.getLanguage(),
+ ScriptFrameworkErrorType.MALFORMED_URL);
+ }
+
+ // Set class loader to be used for class files
+ // and jar files
+ Thread.currentThread().setContextClassLoader(cl);
+ Interpreter interpreter = new Interpreter();
+
+ interpreter.getNameSpace().clear();
+ // Set class loader to be used by interpreter
+ // to look for classes by source e.g. interpreter
+ // will use this classloader to search classpath
+ // for source file ( bla.java ) on import or reference
+ interpreter.setClassLoader(cl);
+
+ try {
+
+ interpreter.set("XSCRIPTCONTEXT",
+ ScriptContext.createContext(m_xModel, m_xInvocContext,
+ m_xContext, m_xMultiComponentFactory));
+
+ interpreter.set("ARGUMENTS", aParams);
+ } catch (bsh.EvalError e) {
+ // Framework error setting up context
+ throw new ScriptFrameworkErrorException(
+ e.getMessage(), null,
+ metaData.getLanguageName(), metaData.getLanguage(),
+ ScriptFrameworkErrorType.UNKNOWN);
+ }
+
+ try {
+ Object result;
+
+ ScriptEditorForBeanShell editor =
+ ScriptEditorForBeanShell.getEditor(sourceUrl);
+
+ if (editor != null) {
+ result = editor.execute();
+
+ if (result == null) {
+ return new Any(new Type(), null);
+ }
+
+ return result;
+ }
+
+ metaData.loadSource();
+ String source = metaData.getSource();
+
+ if (source == null || source.length() == 0) {
+ throw new ScriptFrameworkErrorException(
+ "Failed to read script", null,
+ metaData.getLanguageName(), metaData.getLanguage(),
+ ScriptFrameworkErrorType.NO_SUCH_SCRIPT);
+ }
+
+ result = interpreter.eval(source);
+
+ if (result == null) {
+ return new Any(new Type(), null);
+ }
+
+ return result;
+ } catch (bsh.ParseException pe) {
+ throw new InvocationTargetException(
+ "Beanshell failed to parse " + metaData.getLanguageName(),
+ null, processBshException(pe, metaData.getLanguageName()));
+ } catch (bsh.TargetError te) {
+ throw new InvocationTargetException(
+ "Beanshell uncaught exception for " + metaData.getLanguageName(),
+ null, processBshException(te, metaData.getLanguageName()));
+ } catch (bsh.EvalError ex) {
+ throw new InvocationTargetException(
+ "Beanshell error for " + metaData.getLanguageName(),
+ null, processBshException(ex, metaData.getLanguageName()));
+ } catch (Exception e) {
+ throw new ScriptFrameworkErrorException(
+ "Failed to read script", null, metaData.getLanguageName(),
+ metaData.getLanguage(), ScriptFrameworkErrorType.UNKNOWN);
+ }
+ }
+ private void raiseEditor(int lineNum) {
+ try {
+ URL sourceUrl = metaData.getSourceURL();
+
+ ScriptEditorForBeanShell editor =
+ ScriptEditorForBeanShell.getEditor(sourceUrl);
+
+ if (editor == null) {
+ editor = ScriptEditorForBeanShell.getEditor();
+
+ editor.edit(
+ ScriptContext.createContext(m_xModel, m_xInvocContext, m_xContext,
+ m_xMultiComponentFactory), metaData);
+
+ editor = ScriptEditorForBeanShell.getEditor(sourceUrl);
+ }
+
+ if (editor != null) {
+ editor.indicateErrorLine(lineNum);
+ }
+ } catch (java.net.MalformedURLException ignore) {
+ }
+ }
+
+ private ScriptErrorRaisedException processBshException(
+ bsh.EvalError e, String script) {
+
+ LogUtils.DEBUG("Beanshell error RAW message " + e.getMessage());
+ String message = e.getMessage();
+ int usefulInfoIndex = message.lastIndexOf("\' :");
+ int lineNum = e.getErrorLineNumber();
+
+ raiseEditor(lineNum);
+
+ if (usefulInfoIndex > -1) {
+ message = message.substring(usefulInfoIndex + 2);
+ }
+
+ if (e instanceof bsh.TargetError) {
+ LogUtils.DEBUG("got instance of TargetError");
+
+ if (usefulInfoIndex == -1) {
+ message = ((bsh.TargetError)e).getTarget().getMessage();
+ }
+
+ String wrappedException = "";
+ String full = e.toString();
+ int index = full.indexOf("Target exception:");
+
+ if (index > -1) {
+ String toParse = full.substring(index);
+ LogUtils.DEBUG("About to parse " + toParse);
+
+ StringTokenizer tokenizer =
+ new StringTokenizer(full.substring(index), ":");
+
+ if (tokenizer.countTokens() > 2) {
+ LogUtils.DEBUG("First token = " + tokenizer.nextToken());
+ wrappedException = tokenizer.nextToken();
+ LogUtils.DEBUG("wrapped exception = = " + wrappedException);
+ }
+ }
+
+ ScriptExceptionRaisedException se =
+ new ScriptExceptionRaisedException(message);
+
+ se.lineNum = lineNum;
+ se.scriptName = script;
+ se.exceptionType = wrappedException;
+ se.language = "BeanShell";
+
+ LogUtils.DEBUG("UnCaught Exception error: ");
+ LogUtils.DEBUG("\tscript: " + script);
+ LogUtils.DEBUG("\tline: " + lineNum);
+ LogUtils.DEBUG("\twrapped exception: " + wrappedException);
+ LogUtils.DEBUG("\tmessage: " + message);
+
+ return se;
+
+ } else {
+ LogUtils.DEBUG("Error or ParseError Exception error: ");
+ LogUtils.DEBUG("\tscript: " + script);
+ LogUtils.DEBUG("\tline: " + lineNum);
+ LogUtils.DEBUG("\tmessage: " + message);
+
+ return new ScriptErrorRaisedException(message, null, script,
+ "BeanShell", lineNum);
+
+ }
+ }
+}
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptSourceModel.java b/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptSourceModel.java
new file mode 100644
index 000000000..165f00844
--- /dev/null
+++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptSourceModel.java
@@ -0,0 +1,125 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package com.sun.star.script.framework.provider.beanshell;
+
+import com.sun.star.script.provider.XScriptContext;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.URL;
+
+public class ScriptSourceModel {
+
+ private int currentPosition = -1;
+ private final URL file;
+ private ScriptSourceView view = null;
+
+ public ScriptSourceModel(URL file) {
+ this.file = file;
+ }
+
+ private String load() throws IOException {
+ StringBuilder buf = new StringBuilder();
+ InputStream in = file.openStream();
+
+ byte[] contents = new byte[1024];
+ int len;
+
+ while ((len = in.read(contents, 0, 1024)) != -1) {
+ buf.append(new String(contents, 0, len));
+ }
+
+ try {
+ in.close();
+ } catch (IOException ignore) {
+ }
+
+ return buf.toString();
+ }
+
+ public String getText() {
+ String result = "";
+
+ try {
+ result = load();
+ } catch (IOException ioe) {
+ // do nothing, empty string will be returned
+ }
+
+ return result;
+ }
+
+ public int getCurrentPosition() {
+ return this.currentPosition;
+ }
+
+ public void setView(ScriptSourceView view) {
+ this.view = view;
+ }
+
+ public Object execute(final XScriptContext context, ClassLoader cl)
+ throws Exception {
+ if (cl != null) {
+ // sets this threads class loader
+ // hopefully any threads spawned by this
+ // will inherit this cl
+ // this enables any class files imported
+ // from the interpreter to be loaded
+ // note: setting the classloader on the
+ // interpreter has a slightly different
+ // meaning in that the classloader for
+ // the interpreter seems only to look for
+ // source files ( bla.java ) in the classpath
+ Thread.currentThread().setContextClassLoader(cl);
+ }
+
+ bsh.Interpreter interpreter = new bsh.Interpreter();
+
+ if (cl != null) {
+ // additionally set class loader on the interpreter
+ // to allow it to load java classes defined in source
+ // files e.g. bla.java
+ interpreter.getNameSpace().clear();
+ }
+
+
+ // reset position
+ currentPosition = -1;
+ view.update();
+
+ interpreter.set("XSCRIPTCONTEXT", context);
+ interpreter.set("ARGUMENTS", new Object[0]);
+
+ Object result;
+
+ if (view.isModified()) {
+ result = interpreter.eval(view.getText());
+ } else {
+ result = interpreter.eval(getText());
+ }
+
+ return result;
+ }
+
+ public void indicateErrorLine(int lineNum) {
+ System.out.println("Beanshell indicateErrorLine " + lineNum);
+ currentPosition = lineNum - 1;
+ view.update();
+ }
+} \ No newline at end of file
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptSourceView.java b/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptSourceView.java
new file mode 100644
index 000000000..e39511c24
--- /dev/null
+++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/ScriptSourceView.java
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package com.sun.star.script.framework.provider.beanshell;
+
+public interface ScriptSourceView {
+ void clear();
+ void update();
+ boolean isModified();
+ void setModified(boolean value);
+ String getText();
+ void undo();
+ void redo();
+ void addListener(UnsavedChangesListener toAdd);
+}
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/UnsavedChangesListener.java b/scripting/java/com/sun/star/script/framework/provider/beanshell/UnsavedChangesListener.java
new file mode 100644
index 000000000..8efb2087c
--- /dev/null
+++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/UnsavedChangesListener.java
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package com.sun.star.script.framework.provider.beanshell;
+
+public interface UnsavedChangesListener {
+ void onUnsavedChanges(boolean isModified);
+}
diff --git a/scripting/java/com/sun/star/script/framework/provider/beanshell/template.bsh b/scripting/java/com/sun/star/script/framework/provider/beanshell/template.bsh
new file mode 100644
index 000000000..3bf6d22a3
--- /dev/null
+++ b/scripting/java/com/sun/star/script/framework/provider/beanshell/template.bsh
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+/*
+ Import standard OpenOffice.org API classes. For more information on
+ these classes and the OpenOffice.org API, see the OpenOffice.org
+ Developers Guide at:
+
+ https://api.libreoffice.org/
+*/
+
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.frame.XDesktop;
+import com.sun.star.frame.XModel;
+
+/*
+ Import XScriptContext class. An instance of this class is available
+ to all BeanShell scripts in the global variable "XSCRIPTCONTEXT". This
+ variable can be used to access the document for which this script
+ was invoked.
+
+ Methods available are:
+
+ XSCRIPTCONTEXT.getDocument() returns XModel
+ XSCRIPTCONTEXT.getInvocationContext() returns XScriptInvocationContext or NULL
+ XSCRIPTCONTEXT.getDesktop() returns XDesktop
+ XSCRIPTCONTEXT.getComponentContext() returns XComponentContext
+
+ For more information on using this class see the scripting
+ developer guides at:
+
+ https://api.libreoffice.org/docs/DevelopersGuide/ScriptingFramework/ScriptingFramework.xhtml
+*/
+
+// Hello World in BeanShell
+import com.sun.star.text.XTextDocument;
+import com.sun.star.text.XText;
+import com.sun.star.text.XTextRange;
+
+oDoc = UnoRuntime.queryInterface(XModel.class,XSCRIPTCONTEXT.getInvocationContext());
+if ( oDoc == null )
+ oDoc = XSCRIPTCONTEXT.getDocument();
+
+xTextDoc = (XTextDocument) UnoRuntime.queryInterface(XTextDocument.class,oDoc);
+xText = xTextDoc.getText();
+xTextRange = xText.getEnd();
+xTextRange.setString( "Hello World (in BeanShell)" );
+
+// BeanShell scripts in LibreOffice should always return 0
+return 0;