/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.dataprocessing; import java.io.Serializable; import java.lang.reflect.Method; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableModel; import org.mozilla.javascript.BaseFunction; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.MemberBox; import org.mozilla.javascript.NativeJavaArray; import org.mozilla.javascript.NativeJavaMethod; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.Wrapper; import org.mozilla.javascript.annotations.JSFunction; import com.servoy.base.scripting.api.IJSDataSet; import com.servoy.j2db.IServiceProvider; import com.servoy.j2db.J2DBGlobals; import com.servoy.j2db.documentation.ServoyDocumented; import com.servoy.j2db.persistence.Column; import com.servoy.j2db.persistence.IColumn; import com.servoy.j2db.persistence.ITable; import com.servoy.j2db.persistence.Table; import com.servoy.j2db.scripting.IExecutingEnviroment; import com.servoy.j2db.scripting.annotations.AnnotationManagerReflection; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.HtmlUtils; import com.servoy.j2db.util.IDelegate; import com.servoy.j2db.util.Pair; import com.servoy.j2db.util.ServoyException; import com.servoy.j2db.util.Utils; /** * Scriptable dataset wrapper * @author jblok */ @ServoyDocumented(category = ServoyDocumented.RUNTIME, scriptingName = "JSDataSet") public class JSDataSet implements Wrapper, IDelegate<IDataSet>, Scriptable, Serializable, IJSDataSet { private static final long serialVersionUID = 1L; private static Map<String, NativeJavaMethod> jsFunctions = new HashMap<String, NativeJavaMethod>(); static { try { Method[] methods = JSDataSet.class.getMethods(); for (Method m : methods) { String name = null; if (m.getName().startsWith("js_")) //$NON-NLS-1$ { name = m.getName().substring(3); } else if (AnnotationManagerReflection.getInstance().isAnnotationPresent(m, JSDataSet.class, JSFunction.class)) { name = m.getName(); } if (name != null) { NativeJavaMethod nativeJavaMethod = jsFunctions.get(name); if (nativeJavaMethod == null) { nativeJavaMethod = new NativeJavaMethod(m, name); } else { nativeJavaMethod = new NativeJavaMethod(Utils.arrayAdd(nativeJavaMethod.getMethods(), new MemberBox(m), true), name); } jsFunctions.put(name, nativeJavaMethod); } } } catch (Exception e) { Debug.error(e); } } private static JSDataSet prototype = new JSDataSet(); private IDataSetWithIndex set; private ServoyException exception; private final IServiceProvider application; public JSDataSet() //only for use JS engine { this.application = null; this.set = new DataSetWithIndex(new BufferedDataSet()); } public JSDataSet(IServiceProvider application) { this(application, new DataSetWithIndex(new BufferedDataSet())); } public JSDataSet(IServiceProvider application, int rows, String[] cols) { this.application = application; if (rows >= 0 && cols.length >= 0) { List<Object[]> emptyRows = new ArrayList<Object[]>(rows); for (int i = 0; i < rows; i++) { emptyRows.add(new Object[cols.length]); } this.set = new DataSetWithIndex(new BufferedDataSet(cols, emptyRows)); } if (application != null) { setParentScope(application.getScriptEngine().getSolutionScope()); } } public JSDataSet(IDataSet set) { this(null, set); } public JSDataSet(IServiceProvider application, IDataSet set) { this.application = application; if (set instanceof IDataSetWithIndex) { this.set = (IDataSetWithIndex)set; } else { this.set = new DataSetWithIndex(set); } if (application != null) { setParentScope(application.getScriptEngine().getSolutionScope()); } else if (J2DBGlobals.getServiceProvider() != null && J2DBGlobals.getServiceProvider().getScriptEngine() != null) { setParentScope(J2DBGlobals.getServiceProvider().getScriptEngine().getSolutionScope()); } } public JSDataSet(ServoyException e) { application = null; set = null; exception = e; } public void setPrototype(Scriptable prototype) { } public Scriptable getPrototype() { if (prototype != this) { return prototype; } return null; } /** * Get the number of rows in the dataset. * * @sample * //assuming the variable dataset contains a dataset * var totalRows = dataset.getMaxRowIndex(); * * @return int number of rows. */ public int js_getMaxRowIndex() { if (set != null) { return set.getRowCount(); } return 0; } /** * Get the number of columns in the dataset. * * @sample * //assuming the variable dataset contains a dataset * for (var i = 1; i <= dataset.getMaxColumnIndex(); i++) * { * colArray[i-1] = dataset.getColumnName(i) * //have to subtract 1, because an array is zero based and a dataset is 1 based. * } * * @return int number of columns. */ public int js_getMaxColumnIndex() { if (set != null) { return set.getColumnCount(); } return 0; } /** * Get or set the record index of the dataset. * * @sample * //assuming the variable dataset contains a dataset * //to set the rowIndex: * dataset.rowIndex = 1 //sets the rowIndex to the first row (dataset is 1-based) * //to retrieve the rowIndex of the currently selected row * var currRow = dataset.rowIndex */ public int js_getRowIndex() { if (set != null) { return set.getRowIndex(); } return -1; } public void js_setRowIndex(int r) { if (set != null) { if (r > 0 && r <= set.getRowCount()) { set.setRowIndex(r); } } } /** * Remove a row from the dataset. * * @sample * //assuming the variable dataset contains a dataset * dataset.removeRow(1); //removes the first row * dataset.removeRow(-1); //removes all rows * * @param row row index to remove, -1 for all rows */ public void js_removeRow(int row) { if (set != null) { if (row == -1 || (row > 0 && row <= set.getRowCount())) { set.removeRow(row == -1 ? -1 : (row - 1)); if (row == -1) { htmlAttributes = null; } else { correctAttributeIndex(true, false, row - 1); } if (tableModelWrapper != null) tableModelWrapper.fireTableStructureChanged(); } } } /** * Add a row to the dataset. The row will be added as the last row. * * @sample * //assuming the variable dataset contains a dataset * dataset.addRow(new Array(1,2,3,4,5,6,7,7)); //adds a row with 8 columns * dataset.addRow(2, new Array(1,2,3,4,5,6,7,7)); //adds a row with 8 columns at row 2 * * @param array row data */ public void js_addRow(Object[] array) { js_addRow(js_getMaxRowIndex() + 1, array); } /** * Add a row to the dataset. * * @sample * //assuming the variable dataset contains a dataset * dataset.addRow(new Array(1,2,3,4,5,6,7,7)); //adds a row with 8 columns * dataset.addRow(2, new Array(1,2,3,4,5,6,7,7)); //adds a row with 8 columns at row 2 * * @param index index to add row (1-based) * @param array row data */ public void js_addRow(int index, Object[] array) { Object[] row = array; if (set != null && row != null) { if (row.length < set.getColumnCount()) { Object[] tmp = new Object[set.getColumnCount()]; System.arraycopy(row, 0, tmp, 0, row.length); row = tmp; } set.addRow(index - 1, row); correctAttributeIndex(true, true, index - 1); if (tableModelWrapper != null) tableModelWrapper.fireTableStructureChanged(); } } /** * @clonedesc js_addColumn(String) * @sampleas js_addColumn(String) * * @param name column name. * @param index column index number between 1 and getMaxColumnIndex(). * * @return true if succeeded, else false. */ public boolean js_addColumn(String name, Number index) { return js_addColumn(name, index, Integer.valueOf(0)); } /** * adds a column with the specified name to the dataset. * * @sample * //assuming the variable dataset contains a dataset * var success = dataset.addColumn('columnName',1); * * @param name column name. * * @return true if succeeded, else false. */ public boolean js_addColumn(String name) { return js_addColumn(name, null, Integer.valueOf(0)); } /** * @clonedesc js_addColumn(String) * @sampleas js_addColumn(String) * * @param name column name. * * @param index column index number between 1 and getMaxColumnIndex(). * * @param type the type of column, see JSColumn constants. * * @return true if succeeded, else false. */ public boolean js_addColumn(String name, Number index, Number type) { if (set == null) return false; int _type = Utils.getAsInteger(type); // use Integer in stead of int to allow old vararg calls addColumn(name,null, type) int columnIndex = (index == null ? 0 : index.intValue()) - 1; boolean result = set.addColumn(columnIndex, name, _type); if (result) { makeColumnMap(); correctAttributeIndex(false, true, columnIndex); } return result; } /** * Remove a column by index from the dataset. * * @sample * //assuming the variable dataset contains a dataset * var success = dataset.removeColumn(1); // removes first column * * @param index index of column to remove (1-based) * * @return true if succeeded, else false. */ public boolean js_removeColumn(int index) { boolean result = set.removeColumn(index - 1);//all Javascript calls are 1 based if (result) { makeColumnMap(); correctAttributeIndex(false, false, index - 1); } return result; } private void correctAttributeIndex(final boolean row_col, boolean add_del, int index) { if (htmlAttributes != null) { int plus_minus = (add_del ? +1 : -1); Map<Pair<Integer, Integer>, Map<String, String>> newhtmlAttributes = new HashMap<Pair<Integer, Integer>, Map<String, String>>(); //replace the row ref List<Pair<Integer, Integer>> keys = new ArrayList<Pair<Integer, Integer>>(htmlAttributes.keySet()); Collections.sort(keys, new Comparator<Pair<Integer, Integer>>() { public int compare(Pair<Integer, Integer> o1, Pair<Integer, Integer> o2) { return (row_col ? o1.getLeft() - o2.getLeft() : o1.getRight() - o2.getRight()); } }); Iterator<Pair<Integer, Integer>> it = keys.iterator(); while (it.hasNext()) { Pair<Integer, Integer> pair = it.next(); Map<String, String> value = htmlAttributes.get(pair); if (row_col) { //row if (pair.getLeft().intValue() > index) pair.setLeft(Integer.valueOf(pair.getLeft().intValue() + plus_minus)); } else { //col if (pair.getRight().intValue() > index) pair.setRight(Integer.valueOf(pair.getRight().intValue() + plus_minus)); } newhtmlAttributes.put(pair, value);//rehash } htmlAttributes = newhtmlAttributes; } } /** * Get a column name based on index. * * @sample * //assuming the variable dataset contains a dataset * var firstColumName = dataset.getColumnName(1) //retrieves the first columnname into the variable firstColumName * //using a loop you can get all columnames in an array: * var query = 'select * from customers'; * var dataset = databaseManager.getDataSetByQuery(databaseManager.getDataSourceServerName(controller.getDataSource()), query, null, 100); * var colArray = new Array() * for (var i = 1; i <= dataset.getMaxColumnIndex(); i++) * { * colArray[i-1] = dataset.getColumnName(i) * //note the -1, because an array is zero based and dataset is 1 based. * } * * @param index index of column (1-based). * * @return String column name. */ public String js_getColumnName(int index) { if (set != null) { String[] columnNames = set.getColumnNames(); if (columnNames != null && index > 0 && index <= columnNames.length) { return columnNames[index - 1];//all Javascript calls are 1 based } } return null; } /** * Get the column names of a dataset. * * @sample * var query = 'select * from customers'; * var dataset = databaseManager.getDataSetByQuery(databaseManager.getDataSourceServerName(controller.getDataSource()), query, null, 100); * var columnNames = dataset.getColumnNames(); * * @return String[] column names */ public String[] js_getColumnNames() { if (set != null) { String[] columnNames = set.getColumnNames(); return columnNames; } return null; } /** * Set a column name based on index. * * @sample * var query = 'select customerid, customername from customers'; * var dataset = databaseManager.getDataSetByQuery(databaseManager.getDataSourceServerName(controller.getDataSource()), query, null, -1); * dataset.setColumnName(2, 'name_of_customer') // change the column name for second column. * * @param index index of column (1-based). * @param columnName new column name. */ public void js_setColumnName(int index, String columnName) { if (set != null) { String[] columnNames = set.getColumnNames(); if (columnNames != null && index > 0 && index <= columnNames.length) { set.setColumnName(index - 1, columnName); // all Javascript calls are 1 based } } } /** * Get a column type based on index. * * @sample * //assuming the variable dataset contains a dataset * var firstColumnType = dataset.getColumnType(1) //retrieves the first column's type into the variable firstColumnType * if (firstColumnType == JSColumn.NUMBER) { } * * @param index index of column (1-based). * * @return Number the column type (JSColumn constant) * @since 6.1.4 */ public Number js_getColumnType(int index) { if (set != null) { int[] types = set.getColumnTypes(); if (types != null && index > 0 && index <= types.length) { return Integer.valueOf(Column.mapToDefaultType(types[index - 1]));//all Javascript calls are 1 based } } return null; } /** * Get the column data of a dataset as an Array. * * @sample * //assuming the variable dataset contains a dataset * var dataArray = dataset.getColumnAsArray(1); //puts the contents from the first column of the dataset into an array * //once you have it as an array you can loop through it or feed it to a custom valuelist for example * * @param index index of column (1-based). * * @return Object array of data. */ public Object[] js_getColumnAsArray(int index) { if (set != null) { if (index > 0 && index <= set.getColumnCount()) { Object[] array = new Object[set.getRowCount()]; for (int j = 0; j < set.getRowCount(); j++) { array[j] = set.getRow(j)[index - 1];//all Javascript calls are 1 based } return array; } } return null; } /** * Get the row data of a dataset as an Array. * * @sample * //assuming the variable dataset contains a dataset * var dataArray = dataset.getRowAsArray(1); //puts the contents from the first row of the dataset into an array * //once you have it as an array you can loop through it * * @param index index of row (1-based). * * @return Object array of data. */ public Object[] js_getRowAsArray(int index) { if (set != null) { if (index > 0 && index <= set.getRowCount()) { return set.getRow(index - 1); } } return null; } private AbstractTableModel tableModelWrapper; /** * Returns the dataset as a Swing tablemodel. * * @deprecated As of release 5.0, replaced by {@link #createDataSource(String, Object)} * * @sample * //assuming the variable dataset contains a dataset * var model = dataset.getAsTableModel() //gets a Java/Swing tablemodel to be used in beans * elements.inmemDatagridBean.setModel(model) * * @return TableModel * */ @Deprecated public TableModel js_getAsTableModel() { if (tableModelWrapper == null) { tableModelWrapper = new DataModel(); } return tableModelWrapper; } /** * Create a datasource from the data set with specified name and using specified types. * * A temporary datasource cannot be removed because once created there may always be forms or relations that refer to it. * When the client exits, all datasources used by that client are removed automatically. * * Most resources used by the datasource can be released by deleting all records: * dataset.removeRow(-1) or databaseManager.getFoundSet(datasource).deleteAllRecords() * * A datasource can be reused if the data has the same signature (column names and types). * A new createDataSource() call will clear the datasource contents from a previous call and insert the current data. * * @sample * ds.addColumn('my_id'); // note: use regular javascript identifiers so they can be used in scripting * ds.addColumn('my_label'); * var uri = ds.createDataSource('mydata', [JSColumn.INTEGER, JSColumn.TEXT]); * var jsform = solutionModel.newForm(fname, uri, null, true, 300, 300); * * var query = 'select customerid, address, city, country from customers'; * var ds2 = databaseManager.getDataSetByQuery('example_data', query, null, 999); * var uri2 = ds2.createDataSource('mydata2'); // types are inferred from query result * * @param name datasource name * * @param types array of types as defined in JSColumn * * @return String uri reference to the created datasource. */ public String js_createDataSource(String name, Object types) throws ServoyException { return js_createDataSource(name, types, null); } /** * Create a datasource from the data set with specified name and using specified types. * * A temporary datasource cannot be removed because once created there may always be forms or relations that refer to it. * When the client exits, all datasources used by that client are removed automatically. * * Most resources used by the datasource can be released by deleting all records: * dataset.removeRow(-1) or databaseManager.getFoundSet(datasource).deleteAllRecords() * * @sample * ds.addColumn('my_id'); // note: use regular javascript identifiers so they can be used in scripting * ds.addColumn('my_label'); * var uri = ds.createDataSource('mydata', [JSColumn.INTEGER, JSColumn.TEXT], ['my_id']); * var jsform = solutionModel.newForm(fname, uri, null, true, 300, 300); * * var query = 'select customerid, address, city, country from customers'; * var ds2 = databaseManager.getDataSetByQuery('example_data', query, null, 999); * var uri2 = ds2.createDataSource('mydata2', null, ['customerid']); // types are inferred from query result, use customerid as pk * * @param name datasource name * * @param types array of types as defined in JSColumn, when null types are inferred from the query result * * @param pkNames array of pk names, when null a hidden pk-column will be added * * @return String uri reference to the created datasource. */ public String js_createDataSource(String name, Object types, String[] pkNames) throws ServoyException { if (set == null) return null; if (set instanceof FoundsetDataSet) { // already created a datasource for this data set. return ((FoundsetDataSet)set).getDataSource(); } if (types instanceof Wrapper) { types = ((Wrapper)types).unwrap(); } int[] intTypes = null; if (types instanceof Object[]) { intTypes = new int[((Object[])types).length]; for (int i = 0; i < ((Object[])types).length; i++) { intTypes[i] = Utils.getAsInteger(((Object[])types)[i]); } } String dataSource = application.getFoundSetManager().createDataSourceFromDataSet(name, set, intTypes /* inferred from dataset when null */, pkNames); if (dataSource != null) { // create a new foundSet for the temp table IFoundSetInternal foundSet = application.getFoundSetManager().getSharedFoundSet(dataSource); foundSet.loadAllRecords(); // wrap the new foundSet to redirect all IDataSet methods to the foundSet set = new FoundsetDataSet(foundSet, dataSource, pkNames); } return dataSource; } /** * Create a datasource from the data set with specified name and using specified types. * The types are inferred from the data if possible. * * A temporary datasource cannot be removed because once created there may always be forms or relations that refer to it. * When the client exits, all datasources used by that client are removed automatically. * * Most resources used by the datasource can be released by deleting all records: * dataset.removeRow(-1) or databaseManager.getFoundSet(datasource).deleteAllRecords() * * @sample * ds.addColumn('my_id'); // note: use regular javascript identifiers so they can be used in scripting * ds.addColumn('my_label'); * var uri = ds.createDataSource('mydata', [JSColumn.INTEGER, JSColumn.TEXT]); * var jsform = solutionModel.newForm(fname, uri, null, true, 300, 300); * * var query = 'select customerid, address, city, country from customers'; * var ds2 = databaseManager.getDataSetByQuery('example_data', query, null, 999); * var uri2 = ds2.createDataSource('mydata2'); // types are inferred from query result * * @param name datasource name * * @return String uri reference to the created datasource. */ public String js_createDataSource(String name) throws ServoyException { return js_createDataSource(name, null); } /** * Get the dataset as an html table, do not escape values or spaces, no multi_line_markup, do not add indentation, add column names. * * @sampleas js_getAsHTML(Boolean, Boolean, Boolean, Boolean, Boolean) * * @return String html. */ public String js_getAsHTML() { return js_getAsHTML(Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, Boolean.TRUE); } /** * Get the dataset as an html table, do not escape spaces, no multi_line_markup, do not add indentation, add column names. * * @sampleas js_getAsHTML(Boolean, Boolean, Boolean, Boolean, Boolean) * * @param escape_values if true, replaces illegal HTML characters with corresponding valid escape sequences. * * @return String html. */ public String js_getAsHTML(Boolean escape_values) { return js_getAsHTML(escape_values, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE, Boolean.TRUE); } /** * Get the dataset as an html table, no multi_line_markup, do not add indentation, add column names. * * @sampleas js_getAsHTML(Boolean, Boolean, Boolean, Boolean, Boolean) * * @param escape_values if true, replaces illegal HTML characters with corresponding valid escape sequences. * * @param escape_spaces if true, replaces text spaces with non-breaking space tags ( ) and tabs by four non-breaking space tags. * * @return String html. */ public String js_getAsHTML(Boolean escape_values, Boolean escape_spaces) { return js_getAsHTML(escape_values, escape_spaces, Boolean.FALSE, Boolean.FALSE, Boolean.TRUE); } /** * Get the dataset as an html table, do not add indentation, add column names. * * @sampleas js_getAsHTML(Boolean, Boolean, Boolean, Boolean, Boolean) * * @param escape_values if true, replaces illegal HTML characters with corresponding valid escape sequences. * * @param escape_spaces if true, replaces text spaces with non-breaking space tags ( ) and tabs by four non-breaking space tags. * * @param multi_line_markup if true, multiLineMarkup will enforce new lines that are in the text; single new lines will be replaced by <br>, multiple new lines will be replaced by <p> * * @return String html. */ public String js_getAsHTML(Boolean escape_values, Boolean escape_spaces, Boolean multi_line_markup) { return js_getAsHTML(escape_values, escape_spaces, multi_line_markup, Boolean.FALSE, Boolean.TRUE); } /** * Get the dataset as an html table, add column names. * * @sampleas js_getAsHTML(Boolean, Boolean, Boolean, Boolean, Boolean) * * @param escape_values if true, replaces illegal HTML characters with corresponding valid escape sequences. * * @param escape_spaces if true, replaces text spaces with non-breaking space tags ( ) and tabs by four non-breaking space tags. * * @param multi_line_markup if true, multiLineMarkup will enforce new lines that are in the text; single new lines will be replaced by <br>, multiple new lines will be replaced by <p> * * @param pretty_indent if true, adds indentation for more readable HTML code. * * @return String html. */ public String js_getAsHTML(Boolean escape_values, Boolean escape_spaces, Boolean multi_line_markup, Boolean pretty_indent) { return js_getAsHTML(escape_values, escape_spaces, multi_line_markup, pretty_indent, Boolean.TRUE); } /** * Get the dataset as an html table. * * @sample * //gets a dataset based on a query * //useful to limit the number of rows * var maxReturnedRows = 10; * var query = 'select c1,c2,c3 from test_table where start_date = ?'; * * //to access data by name, do not use '.' or special characters in names or aliases * var args = new Array(); * args[0] = order_date //or new Date(); * var dataset = databaseManager.getDataSetByQuery(databaseManager.getDataSourceServerName(controller.getDataSource()),query,args,maxReturnedRows); * * // gets a dataset with escape values; escape spaces (lines will not wrap); no multi-line markup; with pretty indentation; shows column names * var htmlTable = dataset.getAsHTML(true, true, false, true, true); * * //assigns the dataset to a field and sets the display type to HTML_AREA * //assuming the html_field is a global text variable * scopes.globals.html_field = '<html>'+dataset.getAsHTML()+'</html>'; * * //Note: To display an HTML_AREA field as an HTML page, add HTML tags at the beginning '<html>' and at the end '</html>'. * * @param escape_values if true, replaces illegal HTML characters with corresponding valid escape sequences. * * @param escape_spaces if true, replaces text spaces with non-breaking space tags ( ) and tabs by four non-breaking space tags. * * @param multi_line_markup if true, multiLineMarkup will enforce new lines that are in the text; single new lines will be replaced by <br>, multiple new lines will be replaced by <p> * * @param pretty_indent if true, adds indentation for more readable HTML code. * * @param add_column_names if false, column headers will not be added to the table. * * @return String html. */ public String js_getAsHTML(Boolean escape_values, Boolean escape_spaces, Boolean multi_line_markup, Boolean pretty_indent, Boolean add_column_names) { boolean _escape_values = Utils.getAsBoolean(escape_values); boolean _escape_spaces = Utils.getAsBoolean(escape_spaces); boolean _multi_line_markup = Utils.getAsBoolean(multi_line_markup); boolean _pretty_indent = Utils.getAsBoolean(pretty_indent); boolean _add_column_names = (add_column_names == null ? true : add_column_names.booleanValue()); StringBuilder out = new StringBuilder(); if (set != null) { if (htmlAttributes == null) { //-2=apply to container, -1=apply to all, x=apply to specific addHTMLProperty(-2, -2, "BORDER", "1"); //$NON-NLS-1$ //$NON-NLS-2$ addHTMLProperty(-2, -2, "CELLPADDING", "1"); //$NON-NLS-1$ //$NON-NLS-2$ addHTMLProperty(-2, -2, "CELLSPACING", "0"); //$NON-NLS-1$ //$NON-NLS-2$ addHTMLProperty(-1, -1, "class", "text"); //$NON-NLS-1$ //$NON-NLS-2$ } int numberOfColumns = set.getColumnCount(); out.append("<TABLE "); //$NON-NLS-1$ out.append(getHTMLProperties(-2, -2)); out.append('>'); String[] columnNames = set.getColumnNames(); if (columnNames != null && _add_column_names) { if (_pretty_indent) out.append("\n\t"); //$NON-NLS-1$ out.append("<TR"); //$NON-NLS-1$ if (!Utils.stringIsEmpty(getHTMLProperties(-1, -1))) { out.append(' ');//any row out.append(getHTMLProperties(-1, -1));//any row } out.append('>'); for (int x = 0; x < numberOfColumns; x++) { if (_pretty_indent) out.append("\n\t\t"); //$NON-NLS-1$ out.append("<TD"); //$NON-NLS-1$ if (!Utils.stringIsEmpty(getHTMLProperties(-1, x))) { out.append(' '); out.append(getHTMLProperties(-1, x));//specific column } out.append('>'); out.append("<B>"); //$NON-NLS-1$ if (_escape_values) { String val = HtmlUtils.escapeMarkup(columnNames[x], _escape_spaces).toString(); if (_multi_line_markup) { out.append(Utils.toMultilineMarkup(val).toString()); } else { out.append(val); } } else { out.append(columnNames[x]); } out.append("</B></TD>"); //$NON-NLS-1$ } if (_pretty_indent) out.append("\n\t"); //$NON-NLS-1$ out.append("</TR>"); //$NON-NLS-1$ } for (int j = 0; j < set.getRowCount(); j++) { Object[] row = set.getRow(j); if (_pretty_indent) out.append("\n\t"); //$NON-NLS-1$ out.append("<TR"); //$NON-NLS-1$ if (!Utils.stringIsEmpty(getHTMLProperties(-1, -1)) || !Utils.stringIsEmpty(getHTMLProperties(j, -1))) { out.append(' '); } out.append(getHTMLProperties(-1, -1));//any row out.append(getHTMLProperties(j, -1));//specific row out.append('>'); for (int x = 0; x < numberOfColumns; x++) { if (_pretty_indent) out.append("\n\t\t"); //$NON-NLS-1$ out.append("<TD"); //$NON-NLS-1$ if (!Utils.stringIsEmpty(getHTMLProperties(-1, x)) || !Utils.stringIsEmpty(getHTMLProperties(j, x))) { out.append(' '); } out.append(getHTMLProperties(-1, x));//specific column out.append(getHTMLProperties(j, x));//specific cell out.append('>'); if (_escape_values) { String val = HtmlUtils.escapeMarkup("" + row[x], _escape_spaces).toString(); //$NON-NLS-1$ if (_multi_line_markup) { out.append(Utils.toMultilineMarkup(val).toString()); } else { out.append(val); } } else { out.append(row[x]); } out.append("</TD>"); //$NON-NLS-1$ } if (_pretty_indent) out.append("\n\t"); //$NON-NLS-1$ out.append("</TR>"); //$NON-NLS-1$ } if (_pretty_indent) out.append('\n'); out.append("</TABLE>"); //$NON-NLS-1$ } return out.toString(); } /** * Get the dataset as formatted text. * * @sample * //assuming the variable dataset contains a dataset * //you can create csv or tab delimited results * var csv = dataset.getAsText(',','\n','"',true) * var tab = dataset.getAsText('\t','\n','"',true) * * @param column_separator any specified column separator; examples: tab '\t'; comma ','; semicolon ';'; space ' ' . * * @param row_separator the specified row separator; examples: new line '\n'. * * @param value_delimiter the specified value delimiter; null means empty string; example: double quote '"'. * * @param add_column_names boolean if true column names will be added as a first row. * * @return String formatted text. */ public String js_getAsText(String column_separator, String row_separator, String value_delimiter, boolean add_column_names) { if (column_separator == null || row_separator == null) return null; if (value_delimiter == null) value_delimiter = ""; //$NON-NLS-1$ StringBuilder out = new StringBuilder(); if (set != null) { int numberOfColumns = set.getColumnCount(); String[] columnNames = set.getColumnNames(); if (add_column_names && columnNames != null) { for (int x = 0; x < numberOfColumns; x++) { out.append(value_delimiter); out.append(Utils.stringReplace(columnNames[x], value_delimiter, value_delimiter + value_delimiter)); out.append(value_delimiter); if (x < numberOfColumns - 1) out.append(column_separator); } out.append(row_separator); } for (int j = 0; j < set.getRowCount(); j++) { for (int x = 0; x < numberOfColumns; x++) { out.append(value_delimiter); Object val = set.getRow(j)[x]; out.append(Utils.stringReplace((val != null ? val.toString() : ""), value_delimiter, value_delimiter + value_delimiter)); //$NON-NLS-1$ out.append(value_delimiter); if (x < numberOfColumns - 1) out.append(column_separator); } out.append(row_separator); } } return out.toString(); } /** * returns the contents of the database error message if an error occurred * * @deprecated As of release 5.0, replaced by {@link #getException()} */ @Deprecated public String js_getExceptionMsg() { return (exception == null ? null : exception.getMessage()); } /** * Get the database exception if an error occurred. * * @sample * //assuming the variable dataset contains a dataset * var dbException = dataset.getException(); * * @return ServoyException exception or null when not available. */ public ServoyException js_getException() { return exception; } /** * Get the value specified by row and column position from the dataset. * * @sample * //assuming the variable dataset contains a dataset * var dataAtRow2Col1 = dataset.getValue(2, 1); * * @param row row number, 1-based * * @param col column number, 1-based * * @return Object value */ public Object js_getValue(int row, int col) { return getValue(row - 1, col - 1); } public Object getValue(int r, int c) { if (set != null) { if (r >= 0 && r < set.getRowCount()) { Object[] array = set.getRow(r); if (c >= 0 && c < array.length) { return array[c]; } } } return null; } private Map<Pair<Integer, Integer>, Map<String, String>> htmlAttributes = null; /** * Add an HTML property to an HTML tag produced in getAsHTML(). * * For row and col parameters use: * 1 = applies to the container * 0 = applies to all * >0 = applies to specific cell * * @sample * * //adds a container property (to TABLE tag) * dataset.addHTMLProperty(-1,-1,'cellspacing','3'); * dataset.addHTMLProperty(-1,-1,'style','border-collapse:collapse;'); //to have a single line border * * //adds a row property to all rows (to TR tag) * dataset.addHTMLProperty(0,0,'class','text'); * * //adds a row property to second row (to TR tag) * dataset.addHTMLProperty(2,0,'class','text'); * * //adds a column property to all 3rd columns (to TD tag) * dataset.addHTMLProperty(0,3,'class','redcolumn') ; * * //adds a specific cell property (to TD tag) * dataset.addHTMLProperty(2,4,'color','blue'); * * scopes.globals.html_field = '<html>'+dataset.getAsHTML()+'</html>'; * * @param row row number * * @param col column number * * @param name String property name * * @param value String property value */ public void js_addHTMLProperty(int row, int col, String name, String value)//one based { addHTMLProperty(row - 1, col - 1, name, value); } public void addHTMLProperty(int rowIdent, int c, String pname, String pvalue) { if (htmlAttributes == null) htmlAttributes = new HashMap<Pair<Integer, Integer>, Map<String, String>>(); // if (rowIdent instanceof Integer) // { // int r = Utils.getAsInteger(rowIdent); // if (r >= 0 && r < set.getRowCount()) // { // rowIdent = set.getRow(r); // } // } Pair<Integer, Integer> key = new Pair<Integer, Integer>(Integer.valueOf(rowIdent), Integer.valueOf(c)); Map<String, String> value = htmlAttributes.get(key); if (value == null) { value = new LinkedHashMap<String, String>(); htmlAttributes.put(key, value); } value.put(pname, pvalue); } private String getHTMLProperties(int rowIdent, int c) { StringBuilder sb = new StringBuilder(); Pair<Integer, Integer> key = new Pair<Integer, Integer>(Integer.valueOf(rowIdent), Integer.valueOf(c)); Map<String, String> value = htmlAttributes.get(key); if (value != null) { Iterator<Map.Entry<String, String>> it = value.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = it.next(); sb.append(entry.getKey()); Object val = entry.getValue(); if (val != null) { sb.append("=\""); //$NON-NLS-1$ sb.append(val); sb.append('"'); } if (it.hasNext()) { sb.append(' '); } } } return sb.toString(); } /** * Set the value specified by row and column position from the dataset. * Use row = -1, to set columnnames. * * @sample * //assuming the variable dataset contains a dataset * dataset.setValue(2, 1,'data'); * * @param row row number, 1-based * * @param col column number, 1-based * * @param obj the value to be stored at the given row and column. */ public void js_setValue(int row, int col, Object obj)//one based { setValue(row - 1, col - 1, obj); if (tableModelWrapper != null) tableModelWrapper.fireTableDataChanged(); } public void setValue(int r, int c, Object val) { Object obj = val; if (set != null) { if (obj instanceof Wrapper) { obj = ((Wrapper)obj).unwrap(); } if (r < 0) { if (obj == null) obj = ""; //$NON-NLS-1$ String[] columnNames = set.getColumnNames(); if (columnNames != null) { if (c >= 0 && c < columnNames.length) { columnNames[c] = obj.toString(); } columnameMap = null;//clear to be rebuild } } if (r >= 0 && r < set.getRowCount()) { Object[] array = set.getRow(r); if (c >= 0 && c < array.length) { array[c] = obj; } set.setRow(r, array); } } } /** * Return true if there is more data in the resultset then specified by maxReturnedRows at query time. * * @sample * var ds = databaseManager.getDataSetByQuery('example_data', 'select order_id from orders', null, 10000) * if (ds.hadMoreData()) * { * // handle large result * } * @return boolean more data available */ public boolean js_hadMoreData() { return set != null && set.hadMoreRows(); } /** * Sort the dataset on the given column (1-based) in ascending or descending. * * @sample * // sort using column number * //assuming the variable dataset contains a dataset * dataset.sort(1, false) * * @param col column number, 1-based * * @param sort_direction boolean ascending (true) or descending (false) */ public void js_sort(Number col, Boolean sort_direction) { int _col = Utils.getAsInteger(col); boolean _sort_direction = Utils.getAsBoolean(sort_direction); if (set != null && _col > 0 && _col <= set.getColumnCount()) { set.sort(_col - 1, _sort_direction); } } /** * Sort the dataset using the function as comparator. * The comparator function is called to compare two rows, that are passed as arguments, and * it will return -1/0/1 if the first row is less/equal/greater then the second row. * * NOTE: starting with 7.2 release, when called on datasource(foundset) dataset, this function doesn't save the data anymore * * @sample * //sort using comparator * dataset.sort(mySortFunction); * * function mySortFunction(r1, r2) * { * var o = 0; * if(r1[0] < r2[0]) * { * o = -1; * } * else if(r1[0] > r2[0]) * { * o = 1; * } * return o; * } * * @param comparator comparator function */ public void js_sort(final Function comparator) { if (set != null && comparator != null) { final IExecutingEnviroment scriptEngine = application.getScriptEngine(); final Scriptable rowComparatorScope = comparator.getParentScope(); set.sort(new Comparator<Object[]>() { public int compare(Object[] o1, Object[] o2) { try { Object[] param1 = o1; Object[] param2 = o2; if (set instanceof FoundsetDataSet) // o1 and o2 are pks, get the raw data to pass to rowComparator { IFoundSetInternal foundset = ((FoundsetDataSet)set).getFoundSet(); if (foundset instanceof FoundSet) { param1 = ((FoundSet)foundset).getRecord(o1).getRawData().getRawColumnData(); param2 = ((FoundSet)foundset).getRecord(o2).getRawData().getRawColumnData(); if (((FoundsetDataSet)set).pkNames == null) { // hide servoy internal pk column when pknames is null Object[] res = new Object[param1.length - 1]; System.arraycopy(param1, 1, res, 0, res.length); param1 = res; res = new Object[param2.length - 1]; System.arraycopy(param2, 1, res, 0, res.length); param2 = res; } } } Object compareResult = scriptEngine.executeFunction(comparator, rowComparatorScope, rowComparatorScope, new Object[] { param1, param2 }, false, true); return Utils.getAsInteger(compareResult, true); } catch (Exception ex) { Debug.error(ex); } return 0; } }); } } private Map<String, Integer> columnameMap; private Scriptable getNamedColumns(int index) { String[] colNamesSorted = getColumnNamesSorted(); Object[] row = set.getRow(index - 1); Context cx = Context.enter(); try { Scriptable ar = ScriptRuntime.newArrayLiteral(new Object[0], null, cx, this); for (int i = 0; i < row.length; i++) { String cname = colNamesSorted[i]; ar.put("(" + (i + 1) + ") " + cname, ar, row[i]); //$NON-NLS-1$ //$NON-NLS-2$ } return ar; } finally { Context.exit(); } } public Object get(String name, Scriptable start) { Object mobj = jsFunctions.get(name); if (mobj != null) { ScriptRuntime.setFunctionProtoAndParent((BaseFunction)mobj, start); return mobj; } if (set != null) { if ("rowIndex".equals(name)) //$NON-NLS-1$ { return Integer.valueOf(js_getRowIndex()); } if (columnameMap == null) { makeColumnMap(); } Integer iindex = columnameMap.get(name); if (iindex != null) { if (set == null || set.getRowIndex() <= 0 || set.getRowIndex() > set.getRowCount()) { return null; } Object[] array = set.getRow(set.getRowIndex() - 1); int index = iindex.intValue(); if (index > 0 && index <= array.length) { Context cx = Context.enter(); try { return array[index - 1] != null ? cx.getWrapFactory().wrap(cx, this, array[index - 1], array[index - 1].getClass()) : null; } finally { Context.exit(); } } } } if (name.startsWith("row_") && set != null) //$NON-NLS-1$ { int index; try { index = Integer.parseInt(name.substring(4)); } catch (NumberFormatException e) { return Scriptable.NOT_FOUND; } return getNamedColumns(index); } if (name.endsWith("rowColumns")) //$NON-NLS-1$ { return NativeJavaArray.wrap(this, getColumnNamesSorted()); } if (name.endsWith("rowValue")) //$NON-NLS-1$ { int selectedIndex = js_getRowIndex(); if (selectedIndex != -1) { return getNamedColumns(selectedIndex); } } return Scriptable.NOT_FOUND; } public Object get(int index, Scriptable start) { if (set != null) { if (set.getRowIndex() > 0 && set.getRowIndex() <= set.getRowCount()) { Object[] array = set.getRow(set.getRowIndex() - 1); if (index > 0 && index <= array.length) { return array[index - 1]; } } else { Object[] array = set.getRow(index); if (array != null) { Context cx = Context.enter(); try { Scriptable arrayScriptable = (Scriptable)cx.getWrapFactory().wrap(cx, start, array, Object[].class); String[] colNamesSorted = getColumnNamesSorted(); for (int i = 0; i < colNamesSorted.length; i++) { String name = colNamesSorted[i]; Object object = array[i]; arrayScriptable.put(name, arrayScriptable, object != null ? cx.getWrapFactory().wrap(cx, start, object, object.getClass()) : null); } return arrayScriptable; } finally { Context.exit(); } } } } return null; } private void makeColumnMap() { if (columnameMap == null) columnameMap = new HashMap<String, Integer>(); else columnameMap.clear(); if (set != null) { String[] columnNames = set.getColumnNames(); if (columnNames != null) { for (int x = 0; x < columnNames.length; x++) { String name = columnNames[x]; if (name == null) { name = "c" + (x + 1); //$NON-NLS-1$ columnNames[x] = name; } else { name = name.toLowerCase(); } columnameMap.put(name, Integer.valueOf(x + 1)); } } } else { columnameMap.put("error", Integer.valueOf(1)); //$NON-NLS-1$ } } public boolean has(String name, Scriptable start) { if ("rowIndex".equals(name)) return true; //$NON-NLS-1$ if (columnameMap == null) makeColumnMap(); return columnameMap.get(name) != null; } public void put(String name, Scriptable start, java.lang.Object value) { if (jsFunctions.containsKey(name)) return;//dont allow to set if (value instanceof Wrapper) { value = ((Wrapper)value).unwrap(); } if ("rowIndex".equals(name)) //$NON-NLS-1$ { js_setRowIndex(Utils.getAsInteger(value)); } else { if (set != null) { if (columnameMap == null) { makeColumnMap(); } Integer iindex = columnameMap.get(name); if (iindex != null) { int index = iindex.intValue(); if (set != null && set.getRowIndex() > 0 && set.getRowIndex() <= set.getRowCount()) { Object[] array = set.getRow(set.getRowIndex() - 1); if (index > 0 && index <= array.length) { array[index - 1] = value; set.setRow(set.getRowIndex() - 1, array); return; } } } } } } public String getClassName() { return "JSDataSet"; //$NON-NLS-1$ } public Object[] getAllIds() { return getIds(); } public Object[] getIds() { List<String> al = new ArrayList<String>(); al.add("rowIndex"); //$NON-NLS-1$ al.add("rowColumns"); //$NON-NLS-1$ if (js_getRowIndex() != -1) { al.add("rowValue"); //$NON-NLS-1$ } if (set != null) { int maxRows = set.getRowCount() > 1000 ? 999 : set.getRowCount(); String format = maxRows < 10 ? "0" : maxRows < 100 ? "00" : "000"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ DecimalFormat df = new DecimalFormat(format); for (int i = 0; i < maxRows; i++) { al.add("row_" + df.format(i + 1)); //$NON-NLS-1$ } } al.addAll(jsFunctions.keySet()); return al.toArray(); } public Object unwrap() { if (set != null) { return set; } if (exception != null) { return exception.toString(); } return "<no result>"; //$NON-NLS-1$ } @Override public String toString() { if (exception != null) { return "JSDataSet:exception:" + exception; //$NON-NLS-1$ } return "JSDataSet:size:" + (set != null ? set.getRowCount() : 0) + ",selectedRow:" + (set != null ? set.getRowIndex() : -1); //$NON-NLS-1$ //$NON-NLS-2$ } public IDataSet getDataSet() { return set; } public IDataSet getDelegate() { return set; } private String[] getColumnNamesSorted() { if (columnameMap == null) makeColumnMap(); String[] colNamesSorted = new String[columnameMap.size()]; for (Entry<String, Integer> column : columnameMap.entrySet()) { colNamesSorted[column.getValue().intValue() - 1] = column.getKey(); } return colNamesSorted; } private class DataModel extends AbstractTableModel { @Override public Class<Object> getColumnClass(int columnIndex) { return Object.class; } public int getColumnCount() { return js_getMaxColumnIndex(); } @Override public String getColumnName(int columnIndex) { String cname = js_getColumnName(columnIndex + 1); if (cname == null) return ""; //$NON-NLS-1$ return cname; } public int getRowCount() { return js_getMaxRowIndex(); } public Object getValueAt(int rowIdx, int columnIndex) { return getValue(rowIdx, columnIndex); } @Override public boolean isCellEditable(int rowIdx, int columnIndex) { return true; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { setValue(rowIndex, columnIndex, aValue); } } /** * Wrapper class for a foundset to implement IDataSet methods. * * @author rgansevles * */ static class FoundsetDataSet implements IDataSetWithIndex, IFoundSetEventListener { private final IFoundSetInternal foundSet; private final String dataSource; private Integer rowCount = null; private final String[] pkNames; public FoundsetDataSet(IFoundSetInternal foundSet, String dataSource, String[] pkNames) { this.foundSet = foundSet; this.dataSource = dataSource; this.pkNames = pkNames; // note that this will also creates a reference from the foundSet to this FoundsetDataSet, when GC'ed both go at the same time foundSet.addFoundSetEventListener(this); } public IFoundSetInternal getFoundSet() { return foundSet; } @Override public FoundsetDataSet clone() { return new FoundsetDataSet(foundSet, dataSource, pkNames); } public String getDataSource() { return dataSource; } //////// IFoundSetEventListener methods /////////// public void foundSetChanged(FoundSetEvent e) { if (e.getType() == FoundSetEvent.CONTENTS_CHANGED) { // flush cached row count rowCount = null; } } //////// IDataSet methods /////////// public boolean addColumn(int columnIndex, String columnName, int columnType) { throw new UnsupportedOperationException("addColumn after createDataSource is not supported on data set"); //$NON-NLS-1$ } public void setColumnName(int columnIndex, String columnName) { throw new UnsupportedOperationException("setColumnName after createDataSource is not supported on data set"); //$NON-NLS-1$ } public void addRow(Object[] array) { addRow(-1, array); } public void addRow(int index, Object[] array) { int newIndex; try { newIndex = foundSet.newRecord(Integer.MAX_VALUE, true); // add at the end of the list } catch (ServoyException e) { Debug.log(e); return; } if (index != -1 && index != newIndex) { Debug.log("Warning: addRow index paramer after createDataSource call is ignored on data set"); //$NON-NLS-1$ } setRow(newIndex, array); } public void clearHadMoreRows() { } public int getColumnCount() { int offset = pkNames == null ? 1 : 0; return foundSet.getTable().getColumnNames().length - offset; // hide servoy internal pk column when pknames is null } public String[] getColumnNames() { String[] columnNames = foundSet.getTable().getDataProviderIDs(); if (pkNames != null) { return columnNames; } // hide servoy internal pk column when pknames is null String[] res = new String[columnNames.length - 1]; System.arraycopy(columnNames, 1, res, 0, res.length); return res; } public int[] getColumnTypes() { ITable table = foundSet.getTable(); String[] columnNames = table.getColumnNames(); int offset = pkNames == null ? 1 : 0; // hide servoy internal pk column when pknames is null int[] res = new int[columnNames.length - offset]; for (int i = offset; i < columnNames.length; i++) { res[i - offset] = table.getColumnType(columnNames[i]); } return res; } public Object[] getRow(int row) { IRecordInternal record = foundSet.getRecord(row); if (record == null) { return null; } String[] columnNames = foundSet.getTable().getDataProviderIDs(); int offset = pkNames == null ? 1 : 0; // hide servoy internal pk column when pknames is null Object[] res = new Object[columnNames.length - offset]; for (int i = offset; i < columnNames.length; i++) { res[i - offset] = record.getValue(columnNames[i]); // get value via record so that conversion is done } return res; } public int getRowCount() { if (rowCount == null) { // possibly expensive, so cache the value rowCount = Integer.valueOf(foundSet.getFoundSetManager().getFoundSetCount(foundSet)); } return rowCount.intValue(); } public boolean hadMoreRows() { return false; } public boolean removeColumn(int columnIndex) { throw new UnsupportedOperationException("removeColumn after createDataSource is not supported on data set"); //$NON-NLS-1$ } public void removeRow(int index) { try { if (index == -1) { foundSet.deleteAllRecords(); } else { foundSet.deleteRecord(index); } } catch (ServoyException e) { Debug.log(e); } } public void setRow(int index, Object[] array) { IRecordInternal record = foundSet.getRecord(index); if (record.startEditing()) { String[] columnNames = foundSet.getTable().getDataProviderIDs(); int offset = pkNames == null ? 1 : 0; // hide servoy internal pk column when pknames is null for (int i = 0; i < array.length && (i + offset) < columnNames.length; i++) { record.setValue(columnNames[i + offset], array[i]); } } } public void sort(int column, boolean ascending) { int offset = pkNames == null ? 1 : 0; // hide servoy internal pk column when pknames is null IColumn c = (IColumn)((Table)foundSet.getTable()).getColumns().toArray()[column + offset]; SortColumn sortColumn = new SortColumn(c); sortColumn.setSortOrder(ascending ? SortColumn.ASCENDING : SortColumn.DESCENDING); List<SortColumn> sortColumns = new ArrayList<SortColumn>(1); sortColumns.add(sortColumn); try { foundSet.sort(sortColumns, false); } catch (ServoyException e) { Debug.log(e); } } public void sort(Comparator<Object[]> rowComparator) { foundSet.sort(rowComparator); } @Override public int getRowIndex() { return foundSet.getSelectedIndex() + 1; } @Override public void setRowIndex(int rowIndex) { if (rowIndex >= 1) { foundSet.setSelectedIndex(rowIndex - 1); } } } public boolean hasInstance(Scriptable instance) { return false; } public void put(int index, Scriptable start, Object value) { // ignore } public void delete(String name) { // ignore } public void delete(int index) { // ignore } public boolean has(int index, Scriptable start) { return false; } private Scriptable parentScope; public Scriptable getParentScope() { if (parentScope == null && application != null && application.getScriptEngine() != null) { return application.getScriptEngine().getSolutionScope(); } return parentScope; } public void setParentScope(Scriptable parent) { parentScope = parent; } public Object getDefaultValue(Class< ? > hint) { return toString(); } }