com.opendoorlogistics.studio.scripts.editor.ScriptEditor.java Source code

Java tutorial

Introduction

Here is the source code for com.opendoorlogistics.studio.scripts.editor.ScriptEditor.java

Source

/*******************************************************************************
 * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
 ******************************************************************************/
package com.opendoorlogistics.studio.scripts.editor;

import java.awt.BorderLayout;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.prefs.Preferences;

import javax.swing.Icon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
import javax.swing.filechooser.FileNameExtensionFilter;

import org.apache.commons.io.FilenameUtils;

import com.opendoorlogistics.api.ExecutionReport;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.ComponentConfigurationEditorAPI;
import com.opendoorlogistics.api.components.ODLComponent;
import com.opendoorlogistics.api.io.ImportFileType;
import com.opendoorlogistics.api.scripts.ScriptAdapter;
import com.opendoorlogistics.api.scripts.ScriptAdapter.ScriptAdapterType;
import com.opendoorlogistics.api.scripts.ScriptAdapterTable;
import com.opendoorlogistics.api.tables.ODLColumnType;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLListener;
import com.opendoorlogistics.api.tables.ODLTableDefinition;
import com.opendoorlogistics.core.AppConstants;
import com.opendoorlogistics.core.api.impl.scripts.ScriptOptionImpl;
import com.opendoorlogistics.core.components.ODLGlobalComponents;
import com.opendoorlogistics.core.scripts.ScriptConstants;
import com.opendoorlogistics.core.scripts.elements.AdaptedTableConfig;
import com.opendoorlogistics.core.scripts.elements.AdapterColumnConfig.SortField;
import com.opendoorlogistics.core.scripts.elements.AdapterConfig;
import com.opendoorlogistics.core.scripts.elements.InstructionConfig;
import com.opendoorlogistics.core.scripts.elements.Option;
import com.opendoorlogistics.core.scripts.elements.Script;
import com.opendoorlogistics.core.scripts.execution.ExecutionReportImpl;
import com.opendoorlogistics.core.scripts.execution.OptionsSubpath;
import com.opendoorlogistics.core.scripts.formulae.tables.EmptyTable;
import com.opendoorlogistics.core.scripts.io.ScriptIO;
import com.opendoorlogistics.core.scripts.utils.ScriptFieldsParser;
import com.opendoorlogistics.core.scripts.utils.ScriptFieldsParser.SourcedDatastore;
import com.opendoorlogistics.core.scripts.utils.ScriptFieldsParser.SourcedTable;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils.OutputWindowSyncLevel;
import com.opendoorlogistics.core.scripts.utils.TableId;
import com.opendoorlogistics.core.utils.strings.Strings;
import com.opendoorlogistics.studio.PreferencesManager;
import com.opendoorlogistics.studio.internalframes.ODLInternalFrame;
import com.opendoorlogistics.studio.scripts.editor.adapters.QueryAvailableData;
import com.opendoorlogistics.studio.scripts.execution.ScriptUIManager;
import com.opendoorlogistics.studio.utils.RunProcessWithExecReport;
import com.opendoorlogistics.studio.utils.RunProcessWithExecReport.RunMe;
import com.opendoorlogistics.utils.ui.SimpleAction;
import com.opendoorlogistics.utils.ui.SimpleActionConfig;

public abstract class ScriptEditor extends ODLInternalFrame {
    protected final Script script;
    protected final ScriptUIManager runner;
    //private final List<SimpleAction> actions = new ArrayList<>();
    protected final JPanel contentPane;
    private ScriptEditorToolbar toolBar;
    protected final ODLApi api;
    private final ODLListener dsStructureChangedListener = new ODLListener() {

        @Override
        public void tableChanged(int tableId, int firstRow, int lastRow) {
            // TODO Auto-generated method stub

        }

        @Override
        public ODLListenerType getType() {
            return ODLListenerType.DATASTORE_STRUCTURE_CHANGED;
        }

        @Override
        public void datastoreStructureChanged() {
            ScriptEditor.this.datastoreStructureChanged();
        }
    };

    protected File file;
    private String lastOutputXML;
    //   protected final JCheckBox bottomToolbarSyncCheckbox;

    protected ScriptEditorToolbar createToolbar() {
        ScriptEditorToolbar ret = new ScriptEditorToolbar(true, script.isSynchronised(), true,
                script.isLaunchMultiple()) {

            @Override
            protected void syncBoxChanged(boolean isSelected) {
                script.setSynchronised(isSelected);
            }

            @Override
            protected boolean isSyncBoxEnabled() {
                ArrayList<Option> path = new ArrayList<>();
                path.add(script);
                return ScriptUtils.getOutputWindowSyncLevel(api, path) == OutputWindowSyncLevel.MANUAL;
                //            // synchronised checkbox only enabled if one or more components can be synchronised
                //            for (InstructionConfig instruction : script.getInstructions()) {
                //               ODLComponent component = ScriptUtils.getComponent(instruction);
                //               if (component != null && (component.getFlags(api, instruction.getExecutionMode()) & ODLComponent.FLAG_OUTPUT_WINDOWS_CAN_BE_SYNCHRONISED) == ODLComponent.FLAG_OUTPUT_WINDOWS_CAN_BE_SYNCHRONISED) {
                //                  return true;
                //               }
                //            }
                //            return false;
            }

            @Override
            protected void toggleView() {
                // TODO Auto-generated method stub

            }

            @Override
            protected boolean isToggleViewEnabled() {
                return false;
            }

            @Override
            protected void launchMultipleChanged(boolean isLaunchMultiple) {
                script.setLaunchMultiple(isLaunchMultiple);
            }
        };

        ret.addAction(createSaveScriptAction());
        ret.addAction(createSaveScriptAsAction());
        ret.addAction(createCopyAction());
        ret.addAction(createTestCompileScriptAction());
        ret.addAction(createRunScriptAction());
        return ret;
    }

    /**
     * Create the frame.
     */
    public ScriptEditor(ODLApi api, Script script, File file, final ScriptUIManager runner) {
        super(file != null ? file.getAbsolutePath() : null);
        this.script = script;
        this.file = file;
        this.runner = runner;
        this.api = api;

        Icon icon = ScriptIcons.getIcon(api, script);
        if (icon != null) {
            setFrameIcon(icon);
        }

        // save the current XML of the script
        lastOutputXML = getXML();

        setSize(DisplayConstants.LEVEL1_SIZE);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(new BorderLayout());

        // create toolbar
        reinitialiseToolbar();

        //      JCheckBox syncBox = createSyncCheckbox(script);
        //      bottomToolbarSyncCheckbox = syncBox;
        //      if(bottomToolbarSyncCheckbox!=null){
        //         bottomToolbarSyncCheckbox.addActionListener(new ActionListener() {
        //            
        //            @Override
        //            public void actionPerformed(ActionEvent e) {
        //               syncCheckboxChanged();
        //            }
        //         });
        //         toolBar.add(bottomToolbarSyncCheckbox);         
        //      }
        //      toolBar.addSeparator();
        //      fillToolbar(toolBar, actions);

        // override close operation
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        addInternalFrameListener(new InternalFrameListener() {

            @Override
            public void internalFrameOpened(InternalFrameEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void internalFrameIconified(InternalFrameEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void internalFrameDeiconified(InternalFrameEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void internalFrameDeactivated(InternalFrameEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void internalFrameClosing(InternalFrameEvent e) {
                disposeWithSavePrompt();
            }

            @Override
            public void internalFrameClosed(InternalFrameEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void internalFrameActivated(InternalFrameEvent e) {
                // TODO Auto-generated method stub

            }
        });

        if (runner != null) {
            runner.registerDatastoreStructureChangedListener(dsStructureChangedListener);
        }

        toolBar.updateEnabled();
        updateTitle();
    }

    protected void reinitialiseToolbar() {
        if (toolBar != null) {
            contentPane.remove(toolBar);
        }
        toolBar = createToolbar();
        contentPane.add(toolBar, BorderLayout.SOUTH);
        toolBar.updateEnabled();
    }

    //   protected void syncCheckboxChanged(){
    //      ScriptEditor.this.script.setSynchronised(bottomToolbarSyncCheckbox.isSelected());      
    //   }

    //   protected void fillToolbar(JToolBar toolBar, List<SimpleAction> actions) {
    //      for (SimpleAction action : actions) {
    //         toolBar.add(action);
    //      }
    //   }

    //   protected void createMainActions(List<SimpleAction> actions) {
    //      actions.add(createSaveScriptAction());
    //      actions.add(createSaveScriptAsAction());
    //      actions.add(createCopyAction());
    //      actions.add(createTestCompileScriptAction());
    //      actions.add(createRunScriptAction());
    //   }

    protected SimpleAction createRunScriptAction() {
        return new SimpleAction(SimpleActionConfig.runScript) {

            @Override
            public void actionPerformed(ActionEvent e) {
                validateScriptData();
                executeScript();
            }

            @Override
            public void updateEnabledState() {
                setEnabled(isRunScriptAllowed());
            }

        };
    }

    protected boolean isRunScriptAllowed() {
        return true;
    }

    protected SimpleAction createTestCompileScriptAction() {
        return new SimpleAction(SimpleActionConfig.testCompileScript) {

            @Override
            public void actionPerformed(ActionEvent e) {
                validateScriptData();
                testCompile(runner);
            }

        };
    }

    protected SimpleAction createSaveScriptAsAction() {
        return new SimpleAction("Save as", "Save script as", "document-save-as-2-16x16.png") {

            @Override
            public void actionPerformed(ActionEvent e) {
                validateScriptData();

                JFileChooser chooser = new JFileChooser();
                chooser.setFileFilter(new FileNameExtensionFilter(
                        AppConstants.ORG_NAME + " script  (" + ScriptConstants.FILE_EXT.toLowerCase() + ")",
                        ScriptConstants.FILE_EXT));
                File file = ScriptEditor.this.file;
                if (file == null) {
                    file = createDefaultFilename();
                }

                if (file == null) {
                    file = PreferencesManager.getSingleton().getScriptsDirectory();
                }

                // ensure absolute
                if (file != null) {
                    file = file.getAbsoluteFile();
                }

                // ensure have filename
                if (file != null && file.isDirectory()) {
                    file = new File(file, createDefaultPathlessFilename());
                }

                if (file != null) {
                    chooser.setCurrentDirectory(file.getParentFile());
                    chooser.setSelectedFile(file);
                }

                if (chooser.showSaveDialog(ScriptEditor.this) == JFileChooser.APPROVE_OPTION) {
                    save(chooser.getSelectedFile());
                }
            }
        };
    }

    // ret.add(new MyAction(SimpleActionConfig.copyItem.setItemName("script"),false) {
    //
    // @Override
    // public void actionPerformed(ActionEvent e) {
    // listControl.onAction("copy");
    // }
    //
    // });

    protected SimpleAction createCopyAction() {
        return new SimpleAction("Copy", "Copy script to clipboard", "edit-copy-7.png") {

            @Override
            public void actionPerformed(ActionEvent e) {
                StringSelection stringSelection = new StringSelection(getXML());
                Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
                clpbrd.setContents(stringSelection, null);
            }
        };
    }

    protected SimpleAction createSaveScriptAction() {
        return new SimpleAction("Save", "Save script", "document-save-2-16x16.png") {

            @Override
            public void actionPerformed(ActionEvent e) {
                validateScriptData();
                save(ScriptEditor.this.file);
            }

            @Override
            public void updateEnabledState() {
                setEnabled(ScriptEditor.this.file != null && ScriptEditor.this.file.isFile());
            }
        };
    }

    @Override
    public void setTitle(String title) {
        super.setTitle(title);
    }

    protected void updateTitle() {

        String title = ""; // script.getScriptType().getDisplayName() + " -";
        String name = ScriptUtils.getDefaultScriptName(script);
        if (Strings.isEmpty(name) == false) {
            title += name + " - ";
        }

        title += "script editor - ";
        if (file != null && file.isFile()) {
            title += file.getAbsolutePath();
        } else {
            title += "untitled";
        }
        setTitle(title);

        toolBar.updateEnabled();
    }

    private void save(File saveTo) {
        try {
            // append extension if needed
            if (Strings.equalsStd(FilenameUtils.getExtension(saveTo.getAbsolutePath()),
                    ScriptConstants.FILE_EXT) == false) {
                saveTo = new File(saveTo.getAbsolutePath() + "." + ScriptConstants.FILE_EXT);
            }
            String str = getXML();
            Strings.writeToFile(str, saveTo);
            file = saveTo;
            lastOutputXML = str;

            // set the uuid based on the file
            script.setUuid(ScriptIO.getScriptUUID(file));
        } catch (Throwable e) {
            showMessage("Error saving script file. Do you have write access to the directory?");
        }

        updateTitle();
        toolBar.updateEnabled();
    }

    protected String getXML() {
        return ScriptIO.instance().toXMLString(script);
    }

    protected File createDefaultFilename() {
        // create default filename
        File ret = PreferencesManager.getSingleton().getScriptsDirectory();
        if (ret != null) {
            ret = new File(ret, createDefaultPathlessFilename());
        }
        return ret;
    }

    private String createDefaultPathlessFilename() {
        String name = ScriptUtils.getDefaultScriptName(script);
        if (Strings.isEmpty(name)) {
            name = "script";
        }
        return name.toLowerCase() + "." + ScriptConstants.FILE_EXT;
    }

    public void disposeWithSavePrompt() {
        String currentXML = getXML();
        if (currentXML.equals(lastOutputXML) == false) {
            if (JOptionPane.showConfirmDialog(ScriptEditor.this,
                    "Script has been modified but not saved, changes will be lost. Really close?",
                    "Really Closing?", JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
                ScriptEditor.this.dispose();
            }
        } else {
            ScriptEditor.this.dispose();
        }
    }

    @Override
    public void dispose() {
        super.dispose();
        if (runner != null) {
            runner.removerDatastoreStructureChangedListener(dsStructureChangedListener);
        }
    }

    protected void executeScript() {
        if (runner != null) {
            runner.executeScript(script, null, file != null ? file.getName() : null);
        }
    }

    //   protected <T> T callWithExecutionReport(Callable<T> callable){
    //      try {
    //         T result = callable.call();
    //         return result;
    //      } catch (Exception e) {
    //         // TODO: handle exception
    //      }
    //   }

    protected void executeAdapterResultViewer(final AdaptedTableConfig table, final boolean isMap,
            final AdapterConfig... includeAdapters) {
        RunMe<Void> runMe = new RunMe<Void>() {

            @Override
            public Void runMe(ExecutionReport report) {
                // get the id of the adapter containing the table
                String dsid = ScriptUtils.getAdapterId(script, table);
                if (dsid == null) {
                    throw new RuntimeException();
                }

                // Take copy of the script and set all unsynced options.
                // This will avoid problems if part of the script is set to sync incorrectly...
                Script copy = ScriptIO.instance().deepCopy(script);
                ScriptUtils.setAllUnsynced(copy);

                // Remove other instructions from the option which are unneeded (otherwise table may appear twice etc...)
                // Assume that any instructions in the option containing the target adapter table are unneeded.
                // This should cover all but some very unusual circumstances...
                String optionid = ScriptUtils.getOptionIdByAdapterId(copy, dsid);
                Option option = ScriptUtils.getOption(copy, optionid);
                option.getInstructions().clear();

                // Get adapter ids
                String[] adapterIds = new String[includeAdapters.length];
                for (int i = 0; i < adapterIds.length; i++) {
                    adapterIds[i] = includeAdapters[i].getId();
                }

                // Get the collapsed subset of the script
                TableId tableId = new TableId(dsid, table.getName());
                Script subscript = OptionsSubpath.getSubpathScript(copy, new TableId[] { tableId }, adapterIds,
                        report);
                if (report.isFailed()) {
                    return null;
                }
                if (subscript.getOptions().size() > 0) {
                    // script should now be collapsed...
                    throw new RuntimeException();
                }

                // remove all other tables in the adapter containing the target table as they are definitely not needed
                AdapterConfig adapterConfig = ScriptUtils.getAdapterById(subscript, dsid, true);
                Iterator<AdaptedTableConfig> itTable = adapterConfig.getTables().iterator();
                while (itTable.hasNext()) {
                    if (Strings.equalsStd(itTable.next().getName(), table.getName()) == false) {
                        itTable.remove();
                    }
                }

                // if we're showing a map then rename all remaining tables (could be multiple for a union) to drawables
                if (isMap) {
                    for (AdaptedTableConfig tableConfig : adapterConfig.getTables()) {
                        tableConfig.setName(api.standardComponents().map().getDrawableTableDefinition().getName());
                    }
                }

                // treat like a standard adapter, not a VLS
                adapterConfig.setAdapterType(ScriptAdapterType.NORMAL);

                //            // create a single dummy adapter which just copies the table contents, excluding any sort columns
                //            ScriptOptionImpl builder = new ScriptOptionImpl(api,null, subscript,null);
                //            ScriptAdapter adapter= builder.addDataAdapter("DummyAdapter");
                //            final String adptId =adapter.getAdapterId();
                //            ScriptAdapterTable dummyTable = adapter.addEmptyTable(table.getName());
                //            dummyTable.setSourceTable(dsid, table.getName());
                //            for(int i =0;i<table.getColumnCount(); i++){
                //               if(table.getColumn(i).getSortField() == SortField.NO){
                //                  dummyTable.addColumn(table.getColumnName(i), table.getColumnType(i), false, table.getColumnName(i));
                //               }
                //            }
                //            if(isMap){
                //               // set table to have "drawables" name
                //               dummyTable.setTableName(api.standardComponents().map().getDrawableTableDefinition().getName());
                //            }
                //            
                // add instruction to the end of the script
                ScriptOptionImpl builder = new ScriptOptionImpl(api, null, subscript, null);
                builder.addInstruction(adapterConfig.getId(), isMap ? api.standardComponents().map().getId()
                        : api.standardComponents().tableViewer().getId(), ODLComponent.MODE_DEFAULT);

                // give script a unique id
                String name = isMap ? "Map data" : "Table result";
                UUID uuid = UUID.nameUUIDFromBytes((script.getUuid().toString() + "-" + name).getBytes());
                subscript.setUuid(uuid);

                // finally run the temporary script
                runner.executeScript(subscript, null, "Result of data adapter table");
                return null;
            }
        };

        // run the runnable!
        RunProcessWithExecReport.runProcess((JFrame) SwingUtilities.getWindowAncestor(this), runMe);
    }

    //   protected void executeAdapterResultViewer(AdaptedTableConfig table, boolean isMap, AdapterConfig... includeAdapters) {
    //      // wrap the table config in its own temporary script
    //      String componentId;
    //      if (isMap) {
    //         componentId = MapViewerComponent.ID;
    //      } else {
    //         componentId = QueryReadOnlyTableComponent.ID;
    //      }
    //
    //      ODLComponent component = ODLGlobalComponents.getProvider().getComponent(componentId);
    //      Serializable componentConfig = null;
    //      if (component.getConfigClass() != null) {
    //         try {
    //            componentConfig = component.getConfigClass().newInstance();
    //         } catch (Throwable e) {
    //            throw new RuntimeException(e);
    //         }
    //      }
    //      Script tmpScript = WizardUI.createScriptFromMasterComponent(SwingUtilities.getWindowAncestor(this), component, new ODLWizardTemplateConfig(null, null, null, componentConfig), null, -1);
    //
    //      String name = isMap ? "Map data" : "Table result";
    //
    //      List<AdaptedTableConfig> tables = tmpScript.getAdapters().get(0).getTables();
    //      tables.clear();
    //      tables.add(table);
    //      UUID uuid = UUID.nameUUIDFromBytes((script.getUuid().toString() + "-" + name).getBytes());
    //      tmpScript.setUuid(uuid);
    //      tmpScript.getLastInstruction().setUuid(UUID.nameUUIDFromBytes(name.getBytes()).toString());
    //      tmpScript.setSynchronised(false);
    //      // include other adapters
    //      for (AdapterConfig adapter : includeAdapters) {
    //         tmpScript.getAdapters().add(adapter);
    //      }
    //
    //      runner.executeScript(tmpScript,null, "Result of data adapter table" );
    //
    //   }

    protected void testCompile(final ScriptUIManager runner) {
        runner.testCompileScript(script, null, file != null ? file.getName() : null);
    }

    public QueryAvailableData createAvailableOptionsQuery() {
        QueryAvailableData ret = new QueryAvailableData() {
            private String emptyTableDs = ScriptConstants.FORMULA_PREFIX + EmptyTable.KEYWORD + "(\"tablename\",1)";
            private String emptyTableName = "tablename";

            @Override
            public String[] queryAvailableFields(String datastore, String tablename) {
                ODLTableDefinition st = getTableDefinition(datastore, tablename);
                if (st == null) {
                    return new String[0];
                }
                String[] ret = new String[st.getColumnCount()];
                for (int i = 0; i < ret.length; i++) {
                    ret[i] = st.getColumnName(i);
                }

                return ret;

            }

            @Override
            public String[] queryAvailableTables(String datastore) {
                // if this is the empty table ds, add the empty table name...
                if (api.stringConventions().equalStandardised(datastore, emptyTableDs.replace(" ", ""))) {
                    return new String[] { emptyTableName };
                }

                SourcedDatastore sd = getDatastore(datastore);
                if (sd == null) {
                    return new String[0];
                }
                String[] ret = new String[sd.getDefinition().getTableCount()];
                for (int i = 0; i < ret.length; i++) {
                    ret[i] = sd.getDefinition().getTableAt(i).getName();
                }

                return ret;
            }

            @Override
            public String[] queryAvailableDatastores() {
                List<SourcedDatastore> datastores = getDatastores();
                String[] ret = new String[datastores.size()];
                ArrayList<String> arrayList = new ArrayList<>();
                for (int i = 0; i < ret.length; i++) {
                    arrayList.add(datastores.get(i).getDatastoreId());
                }

                arrayList.add(ScriptConstants.SCRIPT_EMBEDDED_TABLE_DATA_DS);
                arrayList.add(emptyTableDs);

                // add formulae..
                //   arrayList.add();
                //            arrayList.add(ScriptConstants.SHAPEFILE_DS_NAME_PREFIX + "filename.shp");
                //            arrayList.add(ImportFileType.EXCEL.name().toLowerCase() + ScriptConstants.IMPORT_LINK_POSTFIX + "filename.xls");
                //            arrayList.add(ImportFileType.EXCEL.name().toLowerCase() + ScriptConstants.IMPORT_LINK_POSTFIX + "filename.xlsx");
                //            arrayList.add(ImportFileType.CSV.name().toLowerCase() + ScriptConstants.IMPORT_LINK_POSTFIX + "filename.csv");
                //            arrayList.add(ImportFileType.TAB.name().toLowerCase() + ScriptConstants.IMPORT_LINK_POSTFIX + "filename.tab");

                return arrayList.toArray(new String[arrayList.size()]);
            }

            @Override
            public String[] queryAvailableFormula(ODLColumnType columnType) {
                return runner != null ? runner.getAvailableFieldsQuery().queryAvailableFormula(columnType)
                        : new String[0];
            }

            @Override
            public ODLTableDefinition getTableDefinition(String datastore, String tablename) {
                SourcedTable st = getTable(datastore, tablename);
                if (st != null) {
                    return st.getTableDefinition();
                }
                return null;
            }

            private SourcedDatastore getDatastore(String datastore) {
                for (SourcedDatastore sd : getDatastores()) {
                    if (Strings.equalsStd(sd.getDatastoreId(), datastore)) {
                        return sd;
                    }
                }
                return null;
            }

            private SourcedTable getTable(String datastore, String table) {
                SourcedDatastore sd = getDatastore(datastore);
                if (sd != null) {
                    for (SourcedTable st : ScriptFieldsParser.toTables(sd)) {
                        if (Strings.equalsStd(st.getTableName(), table)) {
                            return st;
                        }
                    }
                }

                return null;
            }

            @Override
            public ODLDatastore<? extends ODLTableDefinition> getDatastoreDefinition(String datastore) {
                SourcedDatastore sd = getDatastore(datastore);
                if (sd != null) {
                    return sd.getDefinition();
                }

                return null;
            }
        };
        return ret;
    }

    /**
     * Do any updating or validating of the script data needed before running
     */
    protected void validateScriptData() {

    }

    //   @Override
    //   public TableName promptUserForTable(String title) {
    //      SelectTableDialog dlg = SelectTableComboBox.createModal(api,SwingUtilities.getWindowAncestor(this), script, runner != null ? runner.getDatastoreDefinition() : null, title);
    //      dlg.setLocationRelativeTo(this);
    //      if (title != null) {
    //         dlg.setTitle(title);
    //      }
    //
    //      if (dlg.showModal() == SelectTableDialog.OK_OPTION) {
    //         return dlg.getSelectedItem();
    //      }
    //      return null;
    //   }

    protected void showMessage(String message) {
        JOptionPane.showMessageDialog(this, message);
    }

    public void updateAppearance() {
        toolBar.updateEnabled();
    }

    protected void datastoreStructureChanged() {
        updateAppearance();
    }

    protected ComponentConfigurationEditorAPI createComponentEditorAPI(final String componentId,
            final Option option, final InstructionConfig instruction) {
        return new ComponentConfigurationEditorImpl() {
            @Override
            public void onIODataChanged() {
                // repaint everything so they will check for errors, auto-corrects etc
                ScriptEditor.this.repaint();
            }

            @Override
            public ODLApi getApi() {
                return api;
            }

            @Override
            public ODLDatastore<? extends ODLTableDefinition> getAvailableInputDatastore() {
                if (instruction != null && !Strings.isEmpty(instruction.getDatastore())) {
                    if (Strings.equalsStd(ScriptConstants.EXTERNAL_DS_NAME, instruction.getDatastore())) {
                        return runner.getDatastoreDefinition();
                    }
                    AdapterConfig adapterConfig = ScriptUtils.getAdapterById(script, instruction.getDatastore(),
                            true);
                    if (adapterConfig != null) {
                        return adapterConfig.createOutputDefinition();
                    }
                }
                return null;
            }

            @Override
            public Preferences getComponentPreferences() {
                return PreferencesManager.getSingleton().node(Strings.std(componentId));
            }

            @Override
            public JFrame getAncestorFrame() {
                return (JFrame) SwingUtilities.getWindowAncestor(ScriptEditor.this);
            }

            @Override
            public boolean isInstruction() {
                return instruction != null;
            }

            @Override
            public void executeInPlace(String title, int mode) {
                ScriptEditor.this.executeInPlace(title, componentId, option, instruction, mode);
            }

        };
    }

    protected void executeInPlace(String title, final String componentId, Option option,
            InstructionConfig instruction, int mode) {
        ExecutionReportImpl report = new ExecutionReportImpl();

        // Take copy of the script and set all unsynced options.
        // This will avoid problems if part of the script is set to sync incorrectly...
        Script copy = ScriptIO.instance().deepCopy(script);
        ScriptUtils.setAllUnsynced(copy);

        // get the instruction index
        int indx = -1;
        for (int i = 0; i < option.getInstructions().size(); i++) {
            if (option.getInstructions().get(i) == instruction) {
                indx = i;
                break;
            }
        }

        // get copy of the option
        option = ScriptUtils.getOption(copy, option.getOptionId());
        if (option == null || indx == -1) {
            throw new RuntimeException("Corrupt script.");
        }

        // remove instructions on or after the target instruction
        while (indx < option.getInstructions().size()) {
            option.getInstructions().remove(option.getInstructions().size() - 1);
        }

        // add the dummy instruction
        InstructionConfig newInstruction = new InstructionConfig(instruction.getDatastore(), null, componentId,
                instruction.getComponentConfig());
        newInstruction.setExecutionMode(mode);
        option.getInstructions().add(newInstruction);

        // get a sub-path script for this option only
        Script subscript = OptionsSubpath.getSubpathScript(copy, new String[] { option.getOptionId() }, report);

        // give script a unique id
        subscript.setUuid(UUID.randomUUID());

        // finally run the temporary script
        runner.executeScript(subscript, null, title != null ? title : "");

    }

    protected List<SourcedDatastore> getDatastores() {
        return ScriptFieldsParser.getSingleLevelDatastores(api, script, script,
                runner != null ? runner.getDatastoreDefinition() : null);
    }

    //   protected List<SourcedColumn> getScriptInternalFields() {
    //      return ScriptFieldsParser.getSingleLevelColumns(api,script,script, null);
    //   }

    public File getFile() {
        return file;
    }
}