/* 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.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.text.Document; import org.mozilla.javascript.Scriptable; import com.servoy.base.util.ITagResolver; import com.servoy.j2db.ControllerUndoManager; import com.servoy.j2db.FormController; import com.servoy.j2db.IApplication; import com.servoy.j2db.component.ServoyBeanState; import com.servoy.j2db.dataui.IServoyAwareBean; import com.servoy.j2db.dataui.IServoyAwareVisibilityBean; import com.servoy.j2db.persistence.AggregateVariable; import com.servoy.j2db.persistence.Column; import com.servoy.j2db.persistence.ColumnInfo; import com.servoy.j2db.persistence.ColumnWrapper; import com.servoy.j2db.persistence.IDataProvider; import com.servoy.j2db.persistence.IDataProviderLookup; import com.servoy.j2db.persistence.IPersist; import com.servoy.j2db.persistence.LiteralDataprovider; import com.servoy.j2db.persistence.Relation; import com.servoy.j2db.persistence.RepositoryException; import com.servoy.j2db.persistence.Table; import com.servoy.j2db.query.QueryAggregate; import com.servoy.j2db.scripting.FormScope; import com.servoy.j2db.scripting.GlobalScope; import com.servoy.j2db.scripting.IExecutingEnviroment; import com.servoy.j2db.scripting.IScriptable; import com.servoy.j2db.scripting.IScriptableProvider; import com.servoy.j2db.scripting.ScopesScope; import com.servoy.j2db.scripting.SolutionScope; import com.servoy.j2db.ui.IDataRenderer; import com.servoy.j2db.ui.ISupportOnRenderCallback; import com.servoy.j2db.ui.RenderEventExecutor; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.IDestroyable; import com.servoy.j2db.util.Pair; import com.servoy.j2db.util.ScopesUtils; /** * This class encapsulates all the dataproviders for a form part (mainly the body) ,it does the creation and setup of dataAdapters<br> * An object of this class is usually hold inside a part 'Renderer'. * * @author jblok */ public class DataAdapterList implements IModificationListener, ITagResolver { private boolean visible = false; private LinkedHashMap<String, IDataAdapter> dataAdapters; private final IDataProviderLookup dataProviderLookup; private List<IServoyAwareBean> servoyAwareBeans; private List<IDisplayRelatedData> relatedDataAdapters;//for portals private List<IDisplayData> dataDisplays; private IRecordInternal currentRecord; private final IApplication application; private final ControllerUndoManager undoManager; private final FormController formController; /** * Constructor I<br> * Creates all data adapters, and dataChangeListeners between them. * * @param p the part this adapterlist is working for * @param formObjects , map with IPersist -> IDisplay * @param displays may provide map with dataProviderID -> IDataAdapter for already created adapters, or null/empty if none */ public DataAdapterList(IApplication app, IDataProviderLookup dataProviderLookup, Map<IPersist, ? extends Object> formObjects, FormController formController, LinkedHashMap<String, IDataAdapter> dataAdapters, ControllerUndoManager undoManager) throws RepositoryException { application = app; this.formController = formController; //initialize the lists relatedDataAdapters = new ArrayList<IDisplayRelatedData>(); // beans servoyAwareBeans = new ArrayList<IServoyAwareBean>(2); // data displays dataDisplays = new ArrayList<IDisplayData>(); //set available undoManager this.undoManager = undoManager; if (dataAdapters == null) { //create temp list, dataProviderID -> IDataAdapter this.dataAdapters = new LinkedHashMap<String, IDataAdapter>(); } else { this.dataAdapters = dataAdapters; } this.dataProviderLookup = dataProviderLookup; RelatedFieldHolder rfh = null; Table table = null; if (dataProviderLookup != null) { table = dataProviderLookup.getTable(); } //add for (Object obj : formObjects.values()) { if (obj instanceof IDisplayData) { IDisplayData display = (IDisplayData)obj; dataDisplays.add(display); Document doc = display.getDocument(); if (doc != null && undoManager != null) //add undoManager if needed { doc.addUndoableEditListener(undoManager); } IDataAdapter da = null; String dataProviderID = display.getDataProviderID(); if (dataProviderID != null && !ScopesUtils.isVariableScope(dataProviderID)) { int idx = dataProviderID.lastIndexOf('.'); if (idx != -1) //handle related fields { Relation[] relations = application.getFlattenedSolution().getRelationSequence(dataProviderID.substring(0, idx)); boolean ok = relations != null; if (ok) { for (Relation relation : relations) { ok &= relation.isValid(); } } if (ok) { if (rfh == null) { rfh = new RelatedFieldHolder(this); relatedDataAdapters.add(rfh); } rfh.addDisplay(display); } } } da = this.dataAdapters.get(dataProviderID); if (da == null) { da = new DisplaysAdapter(application, this, dataProviderID, display); this.dataAdapters.put(dataProviderID, da); } else if (da instanceof DisplaysAdapter) { ((DisplaysAdapter)da).addDisplay(display);//for more than one field on form showing same content... } if (display.needEditListener() && da instanceof IEditListener) { display.addEditListener((IEditListener)da); } } if (obj instanceof IDisplayRelatedData)//portals, etc { relatedDataAdapters.add((IDisplayRelatedData)obj); } if (obj instanceof IServoyAwareBean) { servoyAwareBeans.add((IServoyAwareBean)obj); } } //see if relookup listeners must be added if (table != null) { for (Column c : table.getColumns()) { ColumnInfo ci = c.getColumnInfo(); if (ci != null && ci.getAutoEnterType() == ColumnInfo.LOOKUP_VALUE_AUTO_ENTER) { String lookup = ci.getLookupValue(); if (lookup != null && lookup.length() != 0 && !ScopesUtils.isVariableScope(lookup)) { int index = lookup.lastIndexOf('.'); if (index != -1) { try { Relation[] relations = application.getFlattenedSolution().getRelationSequence(lookup.substring(0, index)); if (relations != null && relations.length > 0) { // add a RelookupAdapter for the last relation Relation relation = relations[relations.length - 1]; IDataProvider[] dps = relation.getPrimaryDataProviders(application.getFlattenedSolution()); if (dps != null) { IDataAdapter la = this.dataAdapters.get("LA:" + lookup); //$NON-NLS-1$ if (la == null) { StringBuilder prefix = new StringBuilder(); for (int r = 0; r < relations.length - 1; r++) { prefix.append(relations[r].getName()); prefix.append('.'); } Set<String> dataProviderIDs = new HashSet<String>(); for (IDataProvider dp : dps) { if (dp instanceof LiteralDataprovider) continue; String dataProviderID; if (ScopesUtils.isVariableScope(dp.getDataProviderID())) { dataProviderID = dp.getDataProviderID(); } else { dataProviderID = prefix.toString() + dp.getDataProviderID(); } dataProviderIDs.add(dataProviderID); } la = new RelookupAdapter(this, c.getDataProviderID(), lookup, dataProviderIDs); this.dataAdapters.put("LA:" + lookup, la); //$NON-NLS-1$ } else { ((RelookupAdapter)la).addDataProviderId(c.getDataProviderID()); } // Add data adapters for the other relations addDataAdaptersForRelationSequence(la, relations, relations.length - 1); } } } catch (RepositoryException e) { Debug.error(e); } } } } } } if (Debug.tracing()) { for (IDataAdapter da : this.dataAdapters.values()) { Debug.trace("-->" + da); //$NON-NLS-1$ } for (IDisplayRelatedData drd : relatedDataAdapters) { Debug.trace("R->" + drd); //$NON-NLS-1$ } } //add listener for var changes if (formController != null) // can happen for a design component { formController.getFormScope().getModificationSubject().addModificationListener(this); application.getScriptEngine().getScopesScope().getModificationSubject().addModificationListener(this); } } /** * Walk over the relation sequence to add data adapters. * * @param dataAdapter * @param relations * @param rel * @throws RepositoryException */ protected void addDataAdaptersForRelationSequence(IDataAdapter dataAdapter, Relation[] relations, int rel) throws RepositoryException { Relation relation = relations[rel]; IDataProvider[] dps = relation.getPrimaryDataProviders(application.getFlattenedSolution()); if (dps != null) { for (IDataProvider dp : dps) { if (dp instanceof LiteralDataprovider) continue; StringBuilder prefix = new StringBuilder(); for (int r = 0; r < rel; r++) { prefix.append(relations[r].getName()); prefix.append('.'); } String dataProviderID; if (ScopesUtils.isVariableScope(dp.getDataProviderID())) { dataProviderID = dp.getDataProviderID(); } else { dataProviderID = prefix.toString() + dp.getDataProviderID(); } IDataAdapter da = dataAdapters.get(dataProviderID); if (da == null) { da = new DataAdapter(dataProviderID); dataAdapters.put(dataProviderID, da); } da.addDataListener(dataAdapter); if (rel > 0) { addDataAdaptersForRelationSequence(da, relations, rel - 1); } } } } public IRecordInternal getState() { return currentRecord; } /** * Inform all dataAdapters about the new state. <br><br> * * Note on param: stopAnyEdit, the Renderer paints state 6 (record 6) after that the editor comes in and edits * that state 6, then another row must be repainted so State 7 and it calls stop editing on 6, but the editor is * still editing that state 6 * * @param state the new state (can be null to delete old state) */ public void setRecord(IRecordInternal state, boolean stopAnyEdit) { if (destroyed) { Debug.error("calling setRecord on a destroyed DataAdapterList, formcontroller: " + formController + ", currentRecord: " + currentRecord, new RuntimeException()); return; } if (undoManager != null) undoManager.setIgnoreEdits(true); if (currentRecord != null) { // NEVER enable this ,values in the state could be changed: if (currentState.equals(state)) return;//same if (!currentRecord.equals(state)) { stopUIEditing(false); } if (state != currentRecord) { currentRecord.removeModificationListener(this);//unregister } } if (state != null && state != currentRecord) { state.addModificationListener(this);//register so we are notified about javascript changes on non global vars from here } currentRecord = state; //1) handle first related data (needed for comboboxes) for (IDisplayRelatedData drd : relatedDataAdapters) { if (state != null)// && !(state instanceof PrototypeState)) { if (!(drd instanceof RelatedFieldHolder))//performance enhancement { drd.setRecord(state, stopAnyEdit); } } else { drd.setRecord(null, true);//clear } } // check if destroyed. if (dataAdapters == null) return; //2) handle all fields Iterator<IDataAdapter> it = dataAdapters.values().iterator(); while (it.hasNext()) { IDataAdapter da = it.next(); da.setRecord(state); } if (currentRecord != null && servoyAwareBeans.size() > 0) { ServoyBeanState sbr = new ServoyBeanState(state, getFormScope()); for (IServoyAwareBean da : servoyAwareBeans) { try { da.setSelectedRecord(sbr); } catch (RuntimeException e) { //never make the app break on faulty beans Debug.error(e); } } } if (undoManager != null) undoManager.setIgnoreEdits(false); } //called by relation adapter void resetRelatedFoundSet(IDisplayRelatedData drd)//called when something on the relation input(LH) changed { if (currentRecord != null) { drd.setRecord(currentRecord, true); } } /* * _____________________________________________________________ JavaScriptModificationListner */ /** * listen for global var changes via own listener and state vars(mainly columns) via state listener if via javascript any var is changed it will be noted * here,and dispatched to refresh the displays */ public void valueChanged(ModificationEvent e) { if (destroyed) { Debug.error("Destroyed DataAdapterList " + formController + " was still attached to the record: " + e.getRecord() + ", removing it if possible, currentRecord: " + currentRecord, new RuntimeException()); if (e.getRecord() != null) e.getRecord().removeModificationListener(this); else if (currentRecord != null) currentRecord.removeModificationListener(this); return; } if (formController != null && formController.isDestroyed()) { Debug.error("Destroying DataAdapterList of a destroyed " + formController, new RuntimeException()); destroy(); return; } FormScope formScope = getFormScope(); if (visible && (currentRecord != null || (formScope != null && formScope.has(e.getName(), formScope)))) { for (IDataAdapter da : dataAdapters.values()) { da.valueChanged(e); // dataAdapter should call state.getValue if name from event is same as its dataProviderID } // check if a related adapter depends on he global if (e != null && e.getName() != null) { for (IDisplayRelatedData drd : relatedDataAdapters) { boolean depends = false; String[] allRelationNames = drd.getAllRelationNames(); for (int a = 0; !depends && allRelationNames != null && a < allRelationNames.length; a++) { Relation[] relations = application.getFlattenedSolution().getRelationSequence(allRelationNames[a]); for (int r = 0; !depends && relations != null && r < relations.length; r++) { try { IDataProvider[] primaryDataProviders = relations[r].getPrimaryDataProviders(application.getFlattenedSolution()); for (int p = 0; !depends && primaryDataProviders != null && p < primaryDataProviders.length; p++) { depends = e.getName().equals(primaryDataProviders[p].getDataProviderID()); } } catch (RepositoryException ex) { Debug.log(ex); } } } if (depends) { // related adapter depends on the modified global if (drd instanceof IDisplayDependencyData) ((IDisplayDependencyData)drd).dependencyChanged(currentRecord); else drd.setRecord(currentRecord, true); } } } // inform servoy aware beans for (IServoyAwareBean drd : servoyAwareBeans) { if (drd instanceof IModificationListener) { try { ((IModificationListener)drd).valueChanged(e); } catch (RuntimeException ex) { //never make the app break on faulty beans Debug.error(ex); } } } } } //should be disabled if not showing! public void notifyVisible(boolean b, List<Runnable> invokeLaterRunnables) { visible = b; for (IDisplayRelatedData drd : relatedDataAdapters) { drd.notifyVisible(b, invokeLaterRunnables); } for (IServoyAwareBean bean : servoyAwareBeans) { if (bean instanceof IServoyAwareVisibilityBean) { ((IServoyAwareVisibilityBean)bean).notifyVisible(b); } } if (b) { //this guaranties that if related data is changes in other form bases directly on that data, the changes are visible in the relted data of this form setRecord(currentRecord, true); } } public void setFindMode(boolean b) { //IDataAdapter do cover all IDisplayData's for (IDataAdapter da : dataAdapters.values()) { da.setFindMode(b); } for (IDisplayRelatedData drd : relatedDataAdapters) { if (drd instanceof IDisplayData) { IDisplayData drdDisplay = (IDisplayData)drd; if (getFormScope() != null && drdDisplay.getDataProviderID() != null && getFormScope().get(drdDisplay.getDataProviderID()) != Scriptable.NOT_FOUND) continue; // skip for form variables } drd.setValidationEnabled(!b); } for (IServoyAwareBean drd : servoyAwareBeans) { try { drd.setValidationEnabled(!b); } catch (RuntimeException e) { //never make the app break on faulty beans Debug.error(e); } } } public FormScope getFormScope() { if (formController == null) // can happen for a design component { return null; } if (destroyed) { Debug.error("calling getFormScope on a destroyed DataAdapterList, formcontroller: " + formController + ", currentRecord: " + currentRecord, new RuntimeException()); return null; } FormScope formScope = formController.getFormScope(); if (formScope == null && formController.isDestroyed()) { Debug.error("calling getFormScope in none destroyed DataAdapterList (destroying it now) for a destroyed formcontroller: " + formController + ", currentRecord: " + currentRecord, new RuntimeException()); destroy(); return null; } return formScope; } public FormController getFormController() { return formController; } /** * when the panel gets focus the fields lose focus,the editing is presument to be stopped */ private boolean isStoppingUIEditing = false; public boolean stopUIEditing(boolean looseFocus) { // if current record is null the ui cant be in a edit mode because there is nothing to edit. if (isStoppingUIEditing || currentRecord == null) { return true; } isStoppingUIEditing = true; try { for (IDisplayRelatedData drd : relatedDataAdapters) { if (!drd.stopUIEditing(looseFocus)) return false; } for (IServoyAwareBean drd : servoyAwareBeans) { try { if (!drd.stopUIEditing(looseFocus)) return false; } catch (RuntimeException e) { //never make the app break on faulty beans Debug.error(e); } } if (currentDisplay != null) { if (!currentDisplay.stopUIEditing(looseFocus)) { return false; } if (anyInvalidDisplays(looseFocus, currentDisplay)) return false; // currentDisplay is second parameter in order not to try to stop it again if (looseFocus) { currentDisplay = null; } } return true; } finally { isStoppingUIEditing = false; } } private IDisplay currentDisplay; public void setCurrentDisplay(IDisplay cd) { if (currentDisplay != cd) { if (currentDisplay != null) { if (!currentDisplay.stopUIEditing(true)) return; if (anyInvalidDisplays(true, cd)) return; } currentDisplay = cd; } } private boolean anyInvalidDisplays(boolean looseFocus, IDisplay newCurrentDisplay) { // see if there is any invalid display - if it is, and it is not getting focus now, // try to call stopUIEditing() on it - so that it either validates or requests focus (you can end // up having multiple invalid displays - in case of check-boxes, radios, calendars and so on - and this is needed // to make sure all of them are validated) for (IDisplayData display : dataDisplays) { if ((display != newCurrentDisplay) && (!display.isValueValid()) && (!display.stopUIEditing(looseFocus))) return true; } return false; } /** * Checks whatever the dataAdapter is for count aggregate * * @param dataAdapter * @return true if dataAdapter is for count aggregate, otherwise false */ public boolean isCountOrAvgOrSumAggregateDataProvider(IDataAdapter dataAdapter) { return isCountOrAvgOrSumAggregateDataProvider(dataAdapter.getDataProviderID(), dataProviderLookup); } private static boolean isCountOrAvgOrSumAggregateDataProvider(String dataProvider, IDataProviderLookup dataProviderLookup) { try { if (dataProviderLookup == null) { return false; } IDataProvider dp = dataProviderLookup.getDataProvider(dataProvider); if (dp instanceof ColumnWrapper) { dp = ((ColumnWrapper)dp).getColumn(); } if (dp instanceof AggregateVariable) { int aggType = ((AggregateVariable)dp).getType(); return aggType == QueryAggregate.COUNT || aggType == QueryAggregate.AVG || aggType == QueryAggregate.SUM; } } catch (Exception ex) { Debug.error(ex); } return false; } public IApplication getApplication() { return application; } private boolean destroyed = false; /** * */ public void destroy() { if (currentRecord != null) { // With prototype you can still get global foundsets //setRecord(new PrototypeState(currentRecord.getParentFoundSet()), true); setRecord(null, false); } if (formController != null && !formController.isDestroyed() && formController.getFormScope() != null) { formController.getFormScope().getModificationSubject().removeModificationListener(this); } IExecutingEnviroment er = application.getScriptEngine(); if (er != null) { SolutionScope ss = er.getSolutionScope(); if (ss != null) { ScopesScope gs = ss.getScopesScope(); if (gs != null) { gs.getModificationSubject().removeModificationListener(this); } } } if (servoyAwareBeans != null) { for (IServoyAwareBean b : servoyAwareBeans) { try { if (b instanceof IDestroyable) { ((IDestroyable)b).destroy(); } } catch (RuntimeException e) { //never make the app break on faulty beans Debug.error(e); } } } servoyAwareBeans = null; if (relatedDataAdapters != null) { for (IDisplayRelatedData drd : relatedDataAdapters) { drd.destroy(); } } relatedDataAdapters = null; if (dataDisplays != null) { for (IDisplayData dd : dataDisplays) { if (dd instanceof IDestroyable) { ((IDestroyable)dd).destroy(); } } } dataDisplays = null; if (dataAdapters != null) { for (IDataAdapter da : dataAdapters.values()) { if (da instanceof IDestroyable) { ((IDestroyable)da).destroy(); } } } dataAdapters = null; currentDisplay = null; visible = false; destroyed = true; if (currentRecord != null) { Debug.error("After destroy there is still a current record in DataAdapterList of formcontroller: " + formController, new RuntimeException()); //$NON-NLS-1$ currentRecord.removeModificationListener(this); } } public String getStringValue(String name) { String stringValue = TagResolver.formatObject(getValueObject(currentRecord, name), application.getLocale(), application.getSettings()); return processValue(stringValue, name, dataProviderLookup); } public static String processValue(String stringValue, String dataProviderID, IDataProviderLookup dataProviderLookup) { if (stringValue == null) { if ("selectedIndex".equals(dataProviderID) || isCountOrAvgOrSumAggregateDataProvider(dataProviderID, dataProviderLookup)) //$NON-NLS-1$ { return "0"; //$NON-NLS-1$ } } return stringValue; } /** * Only meant to be used when a dataprovider value needs to be changed as a result of an operation other than display editing of this dataprovider. (for * example upload media would need to change "m_filename" and "m_mimetype", but media field's display adapter only handles "m"). * * @param dataProviderID * @param obj */ public void setValueObject(String dataProviderID, Object obj) { setValueObject(currentRecord, getFormScope(), dataProviderID, obj); } // helper method; not static because needs form scope public Object getValueObject(IRecord record, String dataProviderId) { return getValueObject(record, getFormScope(), dataProviderId); } public static Object getValueObject(IRecord record, FormScope fs, String dataProviderID) { if (dataProviderID == null) return null; Object value = null; Pair<String, String> scope = ScopesUtils.getVariableScope(dataProviderID); if (scope.getLeft() != null) { try { GlobalScope gs = fs.getFormController().getApplication().getScriptEngine().getScopesScope().getGlobalScope(scope.getLeft()); value = gs == null ? null : gs.get(scope.getRight()); } catch (Exception e) { Debug.error(e); } } else if (record != null) { value = record.getValue(dataProviderID); } if ((value == Scriptable.NOT_FOUND || value == null) && fs.has(dataProviderID, fs)) { value = fs.get(dataProviderID); } return value; } public static Object setValueObject(IRecord record, FormScope fs, String dataProviderID, Object obj) { if (dataProviderID == null) return null; Pair<String, String> scope = ScopesUtils.getVariableScope(dataProviderID); if (scope.getLeft() != null) { try { if (record == null) { return fs.getFormController().getApplication().getScriptEngine().getScopesScope().getGlobalScope(scope.getLeft()).put(scope.getRight(), obj); } //does an additional fire in foundset! return record.getParentFoundSet().setDataProviderValue(dataProviderID, obj); } catch (Exception e) { Debug.error(e); } } else if (fs.has(dataProviderID, fs)) { return fs.put(dataProviderID, obj); } else if (record != null) { try { return record.setValue(dataProviderID, obj); } catch (IllegalArgumentException e) { // TODO handle the validaton errors. fs.getFormController().getApplication().reportError("Validation for " + dataProviderID + " for value: " + obj + " failed.", e); } } return null; } public static void setDataRendererComponentsRenderState(IDataRenderer dataRenderer, IRecordInternal rec) { if (rec != null) { Object[] recordStatus = null; if (dataRenderer.getOnRenderComponent().getRenderEventExecutor().hasRenderCallback()) { recordStatus = getRecordIndexAndSelectStatus(rec); dataRenderer.getOnRenderComponent().getRenderEventExecutor().setRenderState(rec, ((Integer)recordStatus[0]).intValue(), ((Boolean)recordStatus[1]).booleanValue(), true); } @SuppressWarnings("rawtypes") Iterator compIte = dataRenderer.getComponentIterator(); Object comp; while (compIte.hasNext()) { comp = compIte.next(); if (comp instanceof IScriptableProvider) { IScriptable scriptable = ((IScriptableProvider)comp).getScriptObject(); if (scriptable instanceof ISupportOnRenderCallback) { RenderEventExecutor rendererEventExecutor = ((ISupportOnRenderCallback)scriptable).getRenderEventExecutor(); if (rendererEventExecutor != null && rendererEventExecutor.hasRenderCallback()) { if (recordStatus == null) recordStatus = getRecordIndexAndSelectStatus(rec); rendererEventExecutor.setRenderState(rec, ((Integer)recordStatus[0]).intValue(), ((Boolean)recordStatus[1]).booleanValue(), true); } } } } } } private static Object[] getRecordIndexAndSelectStatus(IRecordInternal rec) { int index = -1; boolean isSelected = false; IFoundSetInternal parentFoundSet = rec.getParentFoundSet(); if (parentFoundSet != null) { index = parentFoundSet.getRecordIndex(rec); if (parentFoundSet instanceof FoundSet) { int[] selectedIdxs = ((FoundSet)parentFoundSet).getSelectedIndexes(); isSelected = Arrays.binarySearch(selectedIdxs, index) >= 0; } else { isSelected = parentFoundSet.getSelectedIndex() == index; } } return new Object[] { new Integer(index), new Boolean(isSelected) }; } /** * @return */ public boolean isDestroyed() { return destroyed; } }