edu.ku.brc.specify.tasks.RecordSetTask.java Source code

Java tutorial

Introduction

Here is the source code for edu.ku.brc.specify.tasks.RecordSetTask.java

Source

/* Copyright (C) 2015, University of Kansas Center for Research
 * 
 * Specify Software Project, specify@ku.edu, Biodiversity Institute,
 * 1345 Jayhawk Boulevard, Lawrence, Kansas, 66045, USA
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package edu.ku.brc.specify.tasks;

import static edu.ku.brc.ui.UIRegistry.getResourceString;
import static org.apache.commons.lang.StringUtils.isNotEmpty;

import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import edu.ku.brc.af.core.AppContextMgr;
import edu.ku.brc.af.core.ContextMgr;
import edu.ku.brc.af.core.NavBox;
import edu.ku.brc.af.core.NavBoxButton;
import edu.ku.brc.af.core.NavBoxIFace;
import edu.ku.brc.af.core.NavBoxItemIFace;
import edu.ku.brc.af.core.NavBoxMgr;
import edu.ku.brc.af.core.ServiceInfo;
import edu.ku.brc.af.core.SubPaneIFace;
import edu.ku.brc.af.core.TaskMgr;
import edu.ku.brc.af.core.UsageTracker;
import edu.ku.brc.af.core.db.DBFieldInfo;
import edu.ku.brc.af.core.db.DBTableIdMgr;
import edu.ku.brc.af.core.db.DBTableInfo;
import edu.ku.brc.af.core.expresssearch.QueryAdjusterForDomain;
import edu.ku.brc.af.prefs.AppPreferences;
import edu.ku.brc.af.prefs.PreferencesDlg;
import edu.ku.brc.af.tasks.subpane.SimpleDescPane;
import edu.ku.brc.af.ui.forms.FormDataObjIFace;
import edu.ku.brc.af.ui.forms.FormHelper;
import edu.ku.brc.dbsupport.DBConnection;
import edu.ku.brc.dbsupport.DataProviderFactory;
import edu.ku.brc.dbsupport.DataProviderSessionIFace;
import edu.ku.brc.dbsupport.RecordSetIFace;
import edu.ku.brc.dbsupport.RecordSetItemIFace;
import edu.ku.brc.specify.datamodel.Agent;
import edu.ku.brc.specify.datamodel.Collection;
import edu.ku.brc.specify.datamodel.CollectionObject;
import edu.ku.brc.specify.datamodel.RecordSet;
import edu.ku.brc.specify.datamodel.RecordSetItem;
import edu.ku.brc.specify.datamodel.SpecifyUser;
import edu.ku.brc.specify.prefs.FormattingPrefsPanel;
import edu.ku.brc.specify.ui.ChooseRecordSetDlg;
import edu.ku.brc.ui.CommandAction;
import edu.ku.brc.ui.CommandDispatcher;
import edu.ku.brc.ui.CustomDialog;
import edu.ku.brc.ui.DataFlavorTableExt;
import edu.ku.brc.ui.IconManager;
import edu.ku.brc.ui.RolloverCommand;
import edu.ku.brc.ui.UIHelper;
import edu.ku.brc.ui.UIRegistry;
import edu.ku.brc.ui.dnd.DataActionEvent;
import edu.ku.brc.ui.dnd.Trash;

/**
 * Takes care of offering up record sets, updating, deleting and creating them.
 *
 * @code_status Alpha
 *
 * @author rods
 *
 */
public class RecordSetTask extends BaseTask implements PropertyChangeListener {
    private static final Logger log = Logger.getLogger(RecordSetTask.class);

    // Static Data Members
    public static final String RECORD_SET = "Record_Set";
    public static final String SAVE_RECORDSET = "Save";
    public static final String ADD_TO_NAV_BOX = "AddToNavBox";

    public static final DataFlavor RECORDSET_FLAVOR = new DataFlavor(RecordSetTask.class, RECORD_SET);

    protected Vector<DataFlavor> draggableFlavors = new Vector<DataFlavor>();
    protected Vector<DataFlavor> droppableFlavors = new Vector<DataFlavor>();

    // Data Members
    protected NavBox navBox = null;

    /**
     * Default Constructor
     *
     */
    public RecordSetTask() {
        super(RECORD_SET, getResourceString(RECORD_SET));

        draggableFlavors.add(Trash.TRASH_FLAVOR);
        //draggableFlavors.add(RecordSetTask.RECORDSET_FLAVOR);

        CommandDispatcher.register(RECORD_SET, this);
        CommandDispatcher.register(PreferencesDlg.PREFERENCES, this);
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.core.Taskable#initialize()
     */
    public void initialize() {
        if (!isInitialized) {
            super.initialize(); // sets isInitialized to false

            // Register all Tables as being able to be saved in a RecordSet
            // Although some system tables we may not want, they won't be searchable anyway.
            for (DBTableInfo ti : DBTableIdMgr.getInstance().getTables()) {
                ContextMgr.registerService(1, ti.getTitle(), ti.getTableId(),
                        new CommandAction(RECORD_SET, SAVE_RECORDSET), this, RECORD_SET,
                        getResourceString("CreateRecordSetTT"));
            }

            //navBox = new DroppableNavBox(title, RECORDSET_FLAVOR, RECORD_SET, SAVE_RECORDSET);
            navBox = new NavBox(title);

            // TODO RELEASE Search for the the users or group's RecordSets!
            //List<?> recordSets = session.getDataList("FROM RecordSet where type = 0");
            SpecifyUser spUser = AppContextMgr.getInstance().getClassObject(SpecifyUser.class);

            //SQLExecutionProcessor sqlProc = new SQ
            Collection collection = AppContextMgr.getInstance().getClassObject(Collection.class);

            String sqlStr = "SELECT rs.RecordSetID, Type, rs.Name, TableID, rs.Remarks FROM recordset as rs INNER JOIN specifyuser ON rs.SpecifyUserID = specifyuser.SpecifyUserID"
                    + " WHERE type = 0 AND specifyuser.specifyUserID = " + spUser.getId()
                    + " AND CollectionMemberID = " + collection.getId();

            Connection connection = null;
            Statement stmt = null;
            ResultSet rs = null;
            try {
                connection = DBConnection.getInstance().createConnection();
                stmt = connection.createStatement();
                rs = stmt.executeQuery(sqlStr);

                while (rs.next()) {
                    int dbTableId = rs.getInt(4);
                    DBTableInfo tableInfo = DBTableIdMgr.getInstance().getInfoById(dbTableId);
                    RecordSetProxy rsProxy = new RecordSetProxy(rs.getInt(1), rs.getByte(2), rs.getString(3),
                            dbTableId, rs.getString(5), tableInfo.getClassObj());
                    addToNavBox(rsProxy);
                }

            } catch (Exception ex) {
                ex.printStackTrace();
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);

            } finally {
                try {
                    if (rs != null) {
                        rs.close();
                    }
                    if (stmt != null) {
                        stmt.close();
                    }
                    if (connection != null) {
                        connection.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                    edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, e);
                }
            }

            /*for (Object row : rows)
            {
            Object[] data = (Object[])row;
                
            Integer     dbTableId = (Integer)data[1];
            DBTableInfo tableInfo = DBTableIdMgr.getInstance().getInfoById(dbTableId);
            RecordSetProxy rsProxy = new RecordSetProxy((Integer)data[0],
                                                        (Byte)data[1],
                                                        (String)data[1],
                                                        dbTableId,
                                                        tableInfo.getClassObj(),
                                                        null);
            addToNavBox(rsProxy);*/

            /*RecordSetIFace recordSet = (RecordSetIFace)iter.next();
                
            recordSet.getItems(); // loads all lazy object 
                                  // TODO Probably don't want to do this defer it to later when they are used.
            session.evict(recordSet);
                
            addToNavBox(recordSet);
            (*/
            //}
            navBoxes.add(navBox);
            //session.close();

        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.af.tasks.BaseTask#getNavBoxes()
     */
    @Override
    public List<NavBoxIFace> getNavBoxes() {
        if (AppContextMgr.isSecurityOn() && permissions != null && !permissions.canView()) {
            return null;
        }
        return super.getNavBoxes();
    }

    /**
     * Adds a RecordSet to the Left Pane NavBox
     * @param recordSet the recordset to be added
     * @return the nav box
     */
    protected NavBoxItemIFace addToNavBox(final RecordSetIFace recordSet) {
        //NavBoxItemIFace nbi = addNavBoxItem(navBox, recordSet.getName(), name, new CommandAction(RECORD_SET, DELETE_CMD_ACT, recordSet), recordSet);

        boolean delOK = permissions == null || permissions.canDelete();
        boolean modOK = permissions == null || permissions.canModify();
        CommandAction delCmdAction = delOK ? new CommandAction(RECORD_SET, DELETE_CMD_ACT, recordSet) : null;

        final RolloverCommand roc = (RolloverCommand) makeDnDNavBtn(navBox, recordSet.getName(), RECORD_SET, null,
                delCmdAction, true, true);// true means make it draggable
        roc.setData(recordSet);
        addPopMenu(roc, delOK, modOK);

        NavBoxItemIFace nbi = (NavBoxItemIFace) roc;

        DBTableInfo tblInfo = DBTableIdMgr.getInstance().getInfoById(recordSet.getDbTableId());
        if (tblInfo != null) {
            ImageIcon rsIcon = tblInfo.getIcon(IconManager.STD_ICON_SIZE);
            if (rsIcon != null) {
                nbi.setIcon(rsIcon);
            }
        }

        roc.addDragDataFlavor(new DataFlavorTableExt(RecordSetTask.class, RECORD_SET, recordSet.getDbTableId()));
        roc.addDropDataFlavor(new DataFlavorTableExt(RecordSetTask.class, RECORD_SET, recordSet.getDbTableId()));

        addDraggableDataFlavors(nbi);
        addDroppableDataFlavors(nbi);

        return nbi;
    }

    /**
     * Adds the Context PopupMenu for the RecordSet.
     * @param roc the RolloverCommand btn to add the pop to
     */
    public void addPopMenu(final RolloverCommand roc, final boolean isOKDelete, final boolean isOKModify) {
        if (roc.getLabelText() != null) {
            final JPopupMenu popupMenu = new JPopupMenu();

            if (isOKModify) {
                JMenuItem renameMenuItem = new JMenuItem(UIRegistry.getResourceString("Rename"));
                renameMenuItem.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent actionEvent) {
                        roc.startEditting(RecordSetTask.this);
                    }
                });
                popupMenu.add(renameMenuItem);
            }

            if (isOKDelete) {
                JMenuItem delMenuItem = new JMenuItem(UIRegistry.getResourceString("Delete"));
                delMenuItem.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent actionEvent) {
                        CommandDispatcher.dispatch(new CommandAction(RECORD_SET, DELETE_CMD_ACT, roc));
                    }
                });
                popupMenu.add(delMenuItem);
            }

            JMenuItem viewMenuItem = new JMenuItem(UIRegistry.getResourceString("View"));
            viewMenuItem.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent actionEvent) {
                    CommandAction cmdAction = new CommandAction("Express_Search", "ViewRecordSet", roc);
                    cmdAction.setProperty("canModify", isOKDelete);
                    CommandDispatcher.dispatch(cmdAction);
                }
            });
            popupMenu.add(viewMenuItem);

            MouseListener mouseListener = new MouseAdapter() {
                private boolean showIfPopupTrigger(MouseEvent mouseEvent) {
                    if (roc.isEnabled() && mouseEvent.isPopupTrigger() && popupMenu.getComponentCount() > 0) {
                        popupMenu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
                        return true;
                    }
                    return false;
                }

                @Override
                public void mousePressed(MouseEvent mouseEvent) {
                    if (roc.isEnabled()) {
                        showIfPopupTrigger(mouseEvent);
                    }
                }

                @Override
                public void mouseReleased(MouseEvent mouseEvent) {
                    if (roc.isEnabled()) {
                        showIfPopupTrigger(mouseEvent);
                    }
                }
            };
            roc.addMouseListener(mouseListener);
        }
    }

    /**
     * Adds the appropriate flavors to make it draggable
     * @param nbi the item to be made draggable
     */
    protected void addDraggableDataFlavors(final NavBoxItemIFace nbi) {
        NavBoxButton roc = (NavBoxButton) nbi;
        for (DataFlavor df : draggableFlavors) {
            roc.addDragDataFlavor(df);
        }

        roc.addActionListener(new RecordSetSelectedAction((NavBoxButton) nbi, (RecordSetIFace) roc.getData()));
    }

    /**
     * Adds the appropriate flavors to make it draggable
     * @param nbi the item to be made draggable
     */
    protected void addDroppableDataFlavors(final NavBoxItemIFace nbi) {
        NavBoxButton roc = (NavBoxButton) nbi;

        for (DataFlavor df : droppableFlavors) {
            roc.addDropDataFlavor(df);
        }
    }

    /**
     * Save a record set.
     * @param recordSet the rs to be saved
     */
    public void saveNewRecordSet(final RecordSet recordSet) {
        boolean isOKToAdd = true;
        /*
         * This Don't work for some reason, and it should
        if (AppContextMgr.isSecurityOn())
        {
        isOKToAdd = SecurityMgr.getInstance().checkPermission("Task.Record_Set", BasicSpPermission.add);
        }
         */

        if (isOKToAdd) {
            UsageTracker.incrUsageCount("RS.SAVENEW");

            recordSet.setTimestampCreated(new Timestamp(System.currentTimeMillis()));
            recordSet.setOwner(AppContextMgr.getInstance().getClassObject(SpecifyUser.class));

            if (persistRecordSet(recordSet)) {
                addRecordSetToNavBox(recordSet);
            }
        }
    }

    /**
     * Adds recordSet to the Recordset navbox list. And notifies the Labels task.
     * 
     * @param recordSet
     */
    public void addRecordSetToNavBox(final RecordSet recordSet) {
        RecordSetProxy rsProxy = new RecordSetProxy(recordSet.getId(), recordSet.getType(), recordSet.getName(),
                recordSet.getDbTableId(), recordSet.getRemarks(), recordSet.getDataClass());

        NavBoxItemIFace nbi = addToNavBox(rsProxy);

        NavBoxMgr.getInstance().addBox(navBox);

        // XXX this is pathetic and needs to be generisized
        navBox.invalidate();
        navBox.setSize(navBox.getPreferredSize());
        navBox.doLayout();
        navBox.repaint();
        NavBoxMgr.getInstance().invalidate();
        NavBoxMgr.getInstance().doLayout();
        NavBoxMgr.getInstance().repaint();
        UIRegistry.forceTopFrameRepaint();

        CommandDispatcher.dispatch(new CommandAction("Labels", "NewRecordSet", nbi));

    }

    /**
     * Delete a record set
     * @param recordSet the recordSet to be deleted
     */
    protected void deleteRecordSet(final RecordSetIFace recordSet) {
        UsageTracker.incrUsageCount("RS.DEL");
        // Deleting this manually because the RecordSet may not be loaded (with Hibernate)
        // and the items are loaded EAGER, and there is not reason to take all the time (and memory)
        // to load them all just to delete them.
        // So doing this manually with JDBC is the faster way.
        Connection connection = null;
        Statement updateStatement = null;
        try {
            connection = DBConnection.getInstance().createConnection();
            String deleteRS = "DELETE FROM recordset WHERE RecordSetID = " + recordSet.getRecordSetId();
            String deleteRSI = "DELETE FROM recordsetitem WHERE RecordSetID = " + recordSet.getRecordSetId();
            updateStatement = connection.createStatement();
            updateStatement.executeUpdate(deleteRSI);
            updateStatement.executeUpdate(deleteRS);
            updateStatement.clearBatch();
            updateStatement.close();

        } catch (Exception ex) {
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);
            ex.printStackTrace();

        } finally {
            try {
                if (updateStatement != null) {
                    updateStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, e);
                e.printStackTrace();
            }
        }
    }

    /**
     * Delete a record set
     * @param rsController the recordSet to be deleted
     */
    public static void deleteItems(final Integer rsId, final List<Integer> rsiIds, final Integer rsiId) {
        // Deleting this manually because the RecordSet may not be loaded (with Hibernate)
        // and the items are loaded EAGER, and there is not reason to take all the time (and memory)
        // to load them all just to delete them.
        // So doing this manually with JDBC is the faster way.
        Connection connection = null;
        Statement updateStatement = null;
        try {
            connection = DBConnection.getInstance().createConnection();
            StringBuilder sb = new StringBuilder("DELETE FROM recordsetitem WHERE RecordSetID = ");
            sb.append(rsId);
            sb.append(" AND RecordID ");
            if (rsiIds != null) {
                sb.append("in (");
                for (int i = 0; i < rsiIds.size(); i++) {
                    if (i > 0)
                        sb.append(',');
                    sb.append(rsiIds.get(i));
                }
                sb.append(")");
            } else {
                sb.append("= " + rsiId);
            }
            updateStatement = connection.createStatement();
            log.debug(sb.toString());
            int numItems = updateStatement.executeUpdate(sb.toString());
            log.debug(numItems + "  " + sb.toString());
            updateStatement.clearBatch();
            updateStatement.close();

        } catch (Exception ex) {
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);
            ex.printStackTrace();

        } finally {
            try {
                if (updateStatement != null) {
                    updateStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, e);
                e.printStackTrace();
            }
        }
    }

    @SuppressWarnings("unchecked")
    protected void processDeleteRSItems(final CommandAction cmd) {
        Object data = cmd.getData();
        if (data instanceof Object[]) {
            RecordSetIFace rs = (RecordSetIFace) ((Object[]) data)[0];
            List<Integer> ids = (List<Integer>) ((Object[]) data)[1];

            if (rs instanceof RecordSetProxy) {
                RecordSetProxy recordSet = (RecordSetProxy) rs;

                deleteItems(recordSet.getRecordSetId(), ids, null);

            } else if (rs instanceof RecordSet) {
                RecordSet recordSet = (RecordSet) rs;
                if (recordSet.getRecordSetId() == null) {
                    for (RecordSetItem rsi : new Vector<RecordSetItem>(recordSet.getRecordSetItems())) {
                        recordSet.getRecordSetItems().remove(rsi);
                    }

                } else {
                    // TODO Add StaleObject Code from FormView
                    DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
                    try {
                        FormHelper.updateLastEdittedInfo(recordSet);

                        session.beginTransaction();

                        Vector<RecordSetItemIFace> items = new Vector<RecordSetItemIFace>(
                                recordSet.getOrderedItems());
                        for (Integer id : ids) {
                            for (RecordSetItemIFace rsi : items) {
                                if (rsi.getRecordId().intValue() == id.intValue()) {
                                    //System.out.println(recordSet.getItems().contains(rsi));
                                    recordSet.removeItem(rsi);
                                    session.delete(rsi);
                                    //System.out.println(recordSet.getItems().contains(rsi));
                                }
                            }
                        }

                        items.clear();
                        session.saveOrUpdate(recordSet);
                        session.commit();
                        session.flush();

                    } catch (Exception ex) {
                        edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                        edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);
                        ex.printStackTrace();
                        //log.error(ex);

                    } finally {
                        session.close();
                    }

                }
            }
        }
    }

    /**
     * Delete the RecordSet from the UI, which really means remove the NavBoxItemIFace.
     * This method first checks to see if the boxItem is not null and uses that, i
     * f it is null then it looks the box up by name ans used that
     * @param boxItem the box item to be deleted
     * @param recordSet the record set that is "owned" by some UI object that needs to be deleted (used for secodary lookup
     */
    protected void deleteRecordSetFromUI(final NavBoxItemIFace boxItem, final RecordSetIFace recordSet) {
        deleteDnDBtn(navBox, boxItem != null ? boxItem : getBoxByTitle(navBox, recordSet.getName()));
    }

    /**
     * Returns a list of RecordSets for a given Table Id (never returns null)
     * @param tableId the matching tableId or -1 if all the RecordSets should be returned
     * @return a list of RecordSets (never returns null)
     */
    public List<RecordSetIFace> getRecordSets(final int tableId) {
        List<RecordSetIFace> list = new ArrayList<RecordSetIFace>();
        for (NavBoxItemIFace nbi : navBox.getItems()) {
            RecordSetIFace rs = (RecordSetIFace) nbi.getData();
            if (tableId == -1 || tableId == rs.getDbTableId()) {
                list.add(rs);
            }
        }
        return list;
    }

    /**
     * Returns all the recordsets (never returns null)
     * @return all the recordsets (never returns null)
     */
    public List<RecordSetIFace> getRecordSets() {
        return getRecordSets(-1);
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.core.BaseTask#getStarterPane()
     */
    public SubPaneIFace getStarterPane() {
        return starterPane = new SimpleDescPane(name, this, getResourceString("RecordSetTask.DROP_BUNDLE_HERE"));
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.plugins.Taskable#getTaskClass()
     */
    public Class<? extends BaseTask> getTaskClass() {
        return this.getClass();
    }

    /**
     * Checks to see if the recordset AND the NavBtn can be renamed. The scope is the 'user' space.
     * @param roc the rollover navbtn
     * @param rs the recordset
     * @param oldName the old name
     * @param newName the new name
     */
    protected void renameRecordSet(final RolloverCommand roc, final RecordSetIFace rs, final String oldName,
            final String newName) {
        String sqlStr = "select count(rs.name) From RecordSet as rs Inner Join rs.specifyUser as user where rs.name = '"
                + newName + "' AND user.specifyUserId = "
                + AppContextMgr.getInstance().getClassObject(SpecifyUser.class).getSpecifyUserId();

        DataProviderSessionIFace session = null;
        int count = -1;
        try {
            session = DataProviderFactory.getInstance().createSession();
            Object result = session.getData(sqlStr);
            count = result != null ? (Integer) result : 0;

        } catch (Exception ex) {
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);

        } finally {
            if (session != null) {
                session.close();
            }
        }

        if (count == 0) {
            RecordSet recordSet = null;
            if (rs instanceof RecordSetProxy) {
                recordSet = ((RecordSetProxy) rs).getRecordSet();
                ((RecordSetProxy) rs).setName(roc.getLabelText());

            } else if (rs instanceof RecordSet) {
                recordSet = (RecordSet) rs;
            }

            recordSet.setName(roc.getLabelText());
            persistRecordSet(recordSet);

        } else {
            String msg = String.format(UIRegistry.getResourceString("RecordSetTask.RENAMING_ERROR"), newName);
            UIRegistry.getStatusBar().setErrorMessage(msg);
            rs.setName(oldName);
            roc.setLabelText(oldName);
            roc.repaint();
        }
    }

    /**
     * Merge two RecordSets removes duplicates and saves the destination RecordSet to the database
     * @param srcObj the source data (better be a RecordSet)
     * @param dstObj the destination data (better be a RecordSet)
     */
    protected void createRecordSet(final RecordSet recordSet) {
        if (recordSet != null) {
            UsageTracker.incrUsageCount("RS.ASKRS");
            ChooseRecordSetDlg dlg = new ChooseRecordSetDlg(recordSet.getDbTableId(), true);
            dlg.setVisible(true); // modal (waits for answer here)
            if (!dlg.isCancelled()) {
                if (dlg.getBtnPressed() == CustomDialog.OK_BTN) {
                    mergeRecordSets(recordSet, dlg.getSelectedRecordSet());

                } else {
                    String rsName = getUniqueRecordSetName("");
                    if (isNotEmpty(rsName)) {
                        recordSet.setName(rsName);
                        saveNewRecordSet(recordSet);
                    }
                }
            }
        } else {
            UIRegistry.showError("The Record Set was null and shouldn't have been!");
        }
    }

    /**
     * Merge two RecordSets removes duplicates and saves the destination RecordSet to the database
     * @param srcObj the source data (better be a RecordSet)
     * @param dstObj the destination data (better be a RecordSet)
     */
    protected void mergeRecordSets(final Object srcObj, final Object dstObj) {
        UsageTracker.incrUsageCount("RS.MERGE");
        String MERGE_ERR = "RecordSetTask.MERGE_ERROR";

        if (srcObj != dstObj && srcObj != null && dstObj != null && srcObj instanceof RecordSetIFace
                && dstObj instanceof RecordSetIFace) {
            RecordSetIFace srcRecordSet = srcObj instanceof RecordSetProxy
                    ? ((RecordSetProxy) srcObj).getRecordSet()
                    : (RecordSetIFace) srcObj;
            RecordSetIFace dstRecordSet = dstObj instanceof RecordSetProxy
                    ? ((RecordSetProxy) dstObj).getRecordSet()
                    : (RecordSetIFace) dstObj;

            if (srcRecordSet != null && dstRecordSet != null) {
                // It' just easier to build this up front
                DBTableInfo srcTI = DBTableIdMgr.getInstance().getInfoById(srcRecordSet.getDbTableId());
                DBTableInfo dstTI = DBTableIdMgr.getInstance().getInfoById(dstRecordSet.getDbTableId());
                String mergeErrStr = String.format(getResourceString(MERGE_ERR),
                        new Object[] { srcTI.getShortClassName(), dstTI.getShortClassName() });

                if (srcRecordSet.getDbTableId().equals(dstRecordSet.getDbTableId())) {
                    List<Integer> dstIdList = RecordSet.getIdList(dstRecordSet.getRecordSetId(), null);
                    List<Integer> srcIdList;
                    if (srcRecordSet.getRecordSetId() == null) {
                        srcIdList = new Vector<Integer>();
                        for (RecordSetItemIFace rsi : srcRecordSet.getOrderedItems()) {
                            srcIdList.add(rsi.getRecordId());
                        }
                    } else {
                        srcIdList = RecordSet.getIdList(srcRecordSet.getRecordSetId(), null);
                    }

                    boolean debug = true;
                    if (debug) {
                        log.debug("Source:");
                        for (Integer id : srcIdList) {
                            log.debug(" " + id);
                        }
                        log.debug("Dest:");
                        for (Integer id : dstIdList) {
                            log.debug(" " + id);
                        }
                        log.debug("");
                    }

                    Hashtable<Integer, Boolean> dupHash = new Hashtable<Integer, Boolean>();
                    for (Integer id : dstIdList) {
                        dupHash.put(id, true);
                    }

                    List<Integer> newIdsList = new ArrayList<Integer>(srcIdList.size() + dstIdList.size());
                    for (Integer id : srcIdList) {
                        if (dupHash.get(id) == null) {
                            //dstRecordSet.addItem(id); // for saving with Hibernate
                            newIdsList.add(id);
                        }
                    }

                    if (debug) {
                        log.debug("");
                        log.debug("New Dest:");
                        for (RecordSetItemIFace rsi : dstRecordSet.getItems()) {
                            log.debug(" " + rsi.getRecordId());
                        }
                        log.debug("");
                    }

                    if (newIdsList.size() > 0) {
                        //long start = System.currentTimeMillis();
                        boolean success = false;

                        if (true) // Use SQL to save the merge RecordSets
                        {
                            boolean doRollback = false;
                            Connection connection = DBConnection.getInstance().getConnection();
                            Statement stmt = null;
                            try {
                                connection.setAutoCommit(false);
                                stmt = connection.createStatement();
                                for (int i = 0; i < newIdsList.size(); i++) {
                                    stmt.executeUpdate(
                                            "INSERT INTO recordsetitem (RecordSetID, RecordID, OrderNumber) VALUES ("
                                                    + dstRecordSet.getRecordSetId() + "," + newIdsList.get(i) + ","
                                                    + i + ")");
                                }
                                connection.commit();
                                success = true;

                            } catch (SQLException ex) {
                                edu.ku.brc.af.core.UsageTracker.incrSQLUsageCount();
                                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class,
                                        ex);
                                ex.printStackTrace();
                                doRollback = true;

                            } finally {
                                try {
                                    if (doRollback) {
                                        connection.rollback();
                                    }
                                    if (stmt != null) {
                                        stmt.close();
                                    }

                                    connection.setAutoCommit(true);

                                } catch (SQLException ex2) {
                                    edu.ku.brc.af.core.UsageTracker.incrSQLUsageCount();
                                    edu.ku.brc.exceptions.ExceptionTracker.getInstance()
                                            .capture(RecordSetTask.class, ex2);
                                    ex2.printStackTrace();
                                }
                            }
                            //} else
                            //{
                            //    success = persistRecordSet(dstRecordSet);
                        }
                        //System.err.println("Time: "+(System.currentTimeMillis() - start));

                        if (success) {
                            String msg = String.format(getResourceString("RecordSetTask.MERGE_SUCCESS"),
                                    new Object[] { srcTI.getShortClassName(), dstTI.getShortClassName() });
                            UIRegistry.displayStatusBarText(msg);
                        } else {
                            JOptionPane.showMessageDialog(UIRegistry.getTopWindow(), mergeErrStr,
                                    getResourceString("Error"), JOptionPane.ERROR_MESSAGE);
                        }
                    }
                } else {
                    JOptionPane.showMessageDialog(UIRegistry.getTopWindow(), mergeErrStr,
                            getResourceString("Error"), JOptionPane.ERROR_MESSAGE);
                }
            } else {
                log.error("The src or the dst RecordSet were null src[" + srcRecordSet + "]  dst[" + dstRecordSet
                        + "]");
            }
        }
    }

    //-------------------------------------------------------
    // static methods for loading and saving a RecordSet
    //-------------------------------------------------------

    public static RecordSet copyRecordSet(final RecordSetIFace recordSetArg) {
        RecordSetIFace recordSet = recordSetArg instanceof RecordSetProxy
                ? ((RecordSetProxy) recordSetArg).getRecordSet()
                : recordSetArg;
        if (recordSet instanceof RecordSet) {
            try {
                RecordSet rs = (RecordSet) ((RecordSet) recordSet).clone(); // shallow clone

                for (RecordSetItemIFace rsi : recordSet.getOrderedItems()) {
                    rs.addItem(rsi.getRecordId());
                }
                rs.setType(RecordSet.HIDDEN);
                return rs;

            } catch (CloneNotSupportedException ex) {
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);
                throw new RuntimeException(ex);
            }
        }
        throw new RuntimeException(
                "copyRecordSet doesn't support class of type [" + recordSet.getClass().getSimpleName() + "]");
    }

    /**
     * Loads (or makes sure a RecordSet if loaded. This is a necessary because the RS in question
     * maybe a RecordSetProxy and this returns the actual RecordSet from the database.
     * @param recordSet the RecordSetIFace which could be proxy.
     * @return the actual RecordSet from the database.
     */
    public static RecordSetIFace loadRecordSet(final RecordSetIFace recordSet) {
        if (recordSet.getRecordSetId() == null) {
            throw new RuntimeException("Try to load a RecordSet that has a null id");
        }

        if (recordSet instanceof RecordSetProxy) {
            return ((RecordSetProxy) recordSet).getRecordSet();
        }

        DataProviderSessionIFace session = null;
        try {
            session = DataProviderFactory.getInstance().createSession();
            RecordSet rs = session.get(RecordSet.class, recordSet.getRecordSetId());
            return rs;

        } catch (Exception ex) {
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);
            ex.printStackTrace();
            log.error(ex);

        } finally {
            if (session != null) {
                session.close();
            }
        }
        return null;
    }

    /**
     * Save the RecordSet to the Database.
     * @param recordSet the RecordSetIFace
     */
    public static boolean persistRecordSet(final RecordSetIFace recordSet) {
        if (recordSet instanceof RecordSet) {
            // TODO Add StaleObject Code from FormView
            DataProviderSessionIFace session = null;
            try {
                session = DataProviderFactory.getInstance().createSession();

                Object mergedRS = recordSet.getRecordSetId() != null ? session.merge(recordSet) : recordSet;

                session.beginTransaction();
                FormHelper.updateLastEdittedInfo(mergedRS);
                session.saveOrUpdate(mergedRS);
                session.commit();
                session.flush();

                return true;

            } catch (Exception ex) {
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);
                ex.printStackTrace();
                log.error(ex);

            } finally {
                if (session != null) {
                    session.close();
                }
            }
            return false;
        }
        throw new RuntimeException(
                "Trying to save object of class[" + recordSet.getClass() + "] and it must be RecordSet.");
    }

    /**
     * @param intialName
     * @return
     */
    public String getUniqueRecordSetName(final String intialName) {
        String rsName = null;
        DBFieldInfo fi = DBTableIdMgr.getInstance().getInfoById(RecordSet.getClassTableId())
                .getFieldByColumnName("Name");
        int maxLen = fi == null ? 64 : fi.getLength();
        do {
            rsName = JOptionPane.showInputDialog(UIRegistry.get(UIRegistry.FRAME),
                    getResourceString("RecordSetTask.ASKFORNAME") + ":", intialName);
            if (rsName == null) {
                UIRegistry.displayStatusBarText("");
                return null;
            }

            if (!UIHelper.isValidNameForDB(rsName)) {
                UIRegistry.displayLocalizedStatusBarError("INVALID_CHARS_NAME");
                UIRegistry.displayErrorDlgLocalized("INVALID_CHARS_NAME");
                Toolkit.getDefaultToolkit().beep();
                continue;
            }

            if (rsName.length() > maxLen) {
                UIRegistry.displayStatusBarErrMsg(
                        String.format(UIRegistry.getResourceString("RecordSetTask.RS_NAME_TOO_LONG"), maxLen));
                UIRegistry.displayErrorDlg(
                        String.format(UIRegistry.getResourceString("RecordSetTask.RS_NAME_TOO_LONG"), maxLen));
                Toolkit.getDefaultToolkit().beep();
                continue;
            }

            rsName = UIHelper.escapeName(rsName);

            rsName = StringUtils.replace(rsName, "\"", "`");

            DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
            try {
                String sql = String.format(
                        "SELECT count(*) FROM RecordSet rs INNER JOIN rs.specifyUser spu WHERE rs.name = '%s' AND rs.collectionMemberId = COLLID AND spu.specifyUserId = SPECIFYUSERID",
                        rsName);
                sql = QueryAdjusterForDomain.getInstance().adjustSQL(sql);
                Object obj = session.getData(sql);
                if (obj instanceof Integer) {
                    if (((Integer) obj) == 0) {
                        UIRegistry.displayStatusBarText("");
                        return rsName;
                    }

                } else {
                    throw new RuntimeException("Return value should have been an Integer!");
                }
                UIRegistry.displayErrorDlg(String.format(getResourceString("RecordSetTask.RS_NAME_DUP"), rsName));
                UIRegistry.getStatusBar()
                        .setErrorMessage(String.format(getResourceString("RecordSetTask.RS_NAME_DUP"), rsName));
                Toolkit.getDefaultToolkit().beep();

            } catch (Exception ex) {
                ex.printStackTrace();
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(RecordSetTask.class, ex);
                //log.error(ex);

            } finally {
                session.close();
            }
        } while (rsName != null);

        return null;
    }

    //-------------------------------------------------------
    // PropertyChangeListener Interface
    //-------------------------------------------------------

    /* (non-Javadoc)
     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
     */
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getSource() instanceof RolloverCommand) {
            RolloverCommand roc = (RolloverCommand) evt.getSource();
            renameRecordSet(roc, (RecordSetIFace) roc.getData(), (String) evt.getOldValue(),
                    (String) evt.getNewValue());
        }
    }

    //-------------------------------------------------------
    // CommandListener Interface
    //-------------------------------------------------------

    /**
     * Processes all Commands of type RECORD_SET.
     * @param cmdAction the command to be processed
     */
    protected void processRecordSetCommands(final CommandAction cmdAction) {
        if (cmdAction.isAction(SAVE_RECORDSET)) {
            Object data = cmdAction.getData();
            if (data instanceof RecordSetIFace) {
                // If there is only one item in the RecordSet then the User will most likely want it named the same
                // as the "identity" of the data object. So this goes and gets the Identity name and
                // pre-sets the name in the dialog.
                String intialName = "";
                RecordSetIFace recordSet = (RecordSetIFace) cmdAction.getData();
                if (recordSet.getNumItems() == 1) {
                    RecordSetItemIFace item = recordSet.getOrderedItems().iterator().next();
                    DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
                    String sqlStr = DBTableIdMgr.getInstance().getQueryForTable(recordSet.getDbTableId(),
                            item.getRecordId());
                    if (StringUtils.isNotEmpty(sqlStr)) {
                        Object dataObj = session.getData(sqlStr);
                        if (dataObj != null) {
                            intialName = ((FormDataObjIFace) dataObj).getIdentityTitle();
                        }
                    }
                    session.close();
                }
                String rsName = getUniqueRecordSetName(intialName);
                if (isNotEmpty(rsName)) {
                    RecordSet rs = (RecordSet) data;
                    rs.setName(rsName);
                    rs.setModifiedByAgent(Agent.getUserAgent());
                    saveNewRecordSet(rs);
                }
            }
        } else if (cmdAction.isAction(DELETE_CMD_ACT)) {
            RecordSetIFace recordSet = null;
            if (cmdAction.getData() instanceof RecordSetIFace) {
                recordSet = (RecordSetIFace) cmdAction.getData();

            } else if (cmdAction.getData() instanceof RolloverCommand) {
                RolloverCommand roc = (RolloverCommand) cmdAction.getData();
                if (roc.getData() instanceof RecordSetIFace) {
                    recordSet = (RecordSetIFace) roc.getData();
                }
            }

            if (recordSet != null) {
                int option = JOptionPane.showOptionDialog(UIRegistry.getMostRecentWindow(),
                        String.format(UIRegistry.getResourceString("RecordSetTask.CONFIRM_DELETE"),
                                recordSet.getName()),
                        UIRegistry.getResourceString("RecordSetTask.CONFIRM_DELETE_TITLE"),
                        JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, JOptionPane.NO_OPTION); // I18N

                if (option == JOptionPane.YES_OPTION) {
                    deleteRecordSet(recordSet);
                    deleteRecordSetFromUI(null, recordSet);
                }
            }

        } else if (cmdAction.isAction("Dropped")) {
            mergeRecordSets(cmdAction.getSrcObj(), cmdAction.getDstObj());

        } else if (cmdAction.isAction("AskForNewRS")) {
            createRecordSet((RecordSet) cmdAction.getSrcObj());

        } else if (cmdAction.isAction("DELETEITEMS")) {
            processDeleteRSItems(cmdAction);
        } else if (cmdAction.isAction(ADD_TO_NAV_BOX)) {
            addToNavBox((RecordSet) cmdAction.getData());
        }

    }

    /* (non-Javadoc)
     * @see edu.ku.brc.af.tasks.BaseTask#doProcessAppCommands(edu.ku.brc.ui.CommandAction)
     */
    @Override
    protected void doProcessAppCommands(CommandAction cmdAction) {
        super.doProcessAppCommands(cmdAction);

        if (cmdAction.isAction(APP_RESTART_ACT)) {
            for (DBTableInfo ti : DBTableIdMgr.getInstance().getTables()) {
                String srvName = ServiceInfo.getHashKey(ti.getTitle(), this, ti.getTableId());
                ContextMgr.unregisterService(srvName); // Must passed in the hashed Name
            }

            isInitialized = false;
            this.initialize();
        }
    }

    /**
     * 
     */
    protected void prefsChanged(final AppPreferences appPrefs) {
        if (appPrefs == AppPreferences.getRemote()) {
            String iconNameStr = appPrefs.get(FormattingPrefsPanel.getDisciplineImageName(), "CollectionObject");
            ImageIcon iconImage = IconManager.getIcon(iconNameStr, IconManager.STD_ICON_SIZE);
            if (iconImage != null) {
                for (NavBoxIFace nb : navBoxes) {
                    for (NavBoxItemIFace nbi : nb.getItems()) {
                        Object data = nbi.getData();
                        if (data instanceof RecordSetIFace) {
                            RecordSetIFace rsi = (RecordSetIFace) data;
                            if (rsi.getDbTableId() == CollectionObject.getClassTableId()) {
                                nbi.setIcon(iconImage);
                            }
                        }
                    }
                }
            }
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.ui.CommandListener#doCommand(edu.ku.brc.specify.ui.CommandAction)
     */
    @Override
    public void doCommand(CommandAction cmdAction) {
        super.doCommand(cmdAction);

        if (cmdAction.isType(RECORD_SET)) {
            processRecordSetCommands(cmdAction);

        } else if (cmdAction.isType(PreferencesDlg.PREFERENCES)) {
            prefsChanged((AppPreferences) cmdAction.getData());
        }
    }

    //--------------------------------------------------------------
    // Inner Classes
    //--------------------------------------------------------------

    /**
    *
    * @author rods
    *
    */
    class RecordSetSelectedAction implements ActionListener {
        private NavBoxButton ro;
        private RecordSetIFace rs;

        public RecordSetSelectedAction(final NavBoxButton ro, final RecordSetIFace rs) {
            this.ro = ro;
            this.rs = rs;
        }

        public void actionPerformed(ActionEvent e) {
            Object src = e.getSource();

            //log.debug(src.hashCode()+"  "+ro.hashCode());

            if (e instanceof DataActionEvent) {
                DataActionEvent dataActionEv = (DataActionEvent) e;
                if (dataActionEv.getSourceObj() != null) {
                    Object data = dataActionEv.getSourceObj().getData();
                    if (data instanceof CommandAction) {
                        CommandAction cmdAction = (CommandAction) data;
                        cmdAction.setData(rs);
                        CommandDispatcher.dispatch(cmdAction);
                    } else {
                        log.debug(data);
                    }

                }
                CommandDispatcher.dispatch(new CommandAction(RECORD_SET, src == ro ? "Clicked" : "Dropped",
                        dataActionEv.getData(), rs, null));
            } else {
                throw new RuntimeException("How did we get here?");
            }
        }
    }

    /**
     * Adds a DataFlavor to the list of Draggable DataFlavors that each RecordSet supports.
     * @param df the new DataFlavor
     */
    public static void addDraggableDataFlavor(final DataFlavor df) {
        RecordSetTask rst = (RecordSetTask) TaskMgr.getTask(RecordSetTask.RECORD_SET);
        if (df != null && rst != null) {
            rst.draggableFlavors.add(df);
        }
    }

    /**
     * Adds a DataFlavor to the list of Droppable DataFlavors that each RecordSet supports.
     * @param df the new DataFlavor
     */
    public static void addDroppableDataFlavor(final DataFlavor df) {
        RecordSetTask rst = (RecordSetTask) TaskMgr.getTask(RecordSetTask.RECORD_SET);
        if (df != null && rst != null) {
            rst.droppableFlavors.add(df);
        }
    }

    //------------------------------------------------------------------------
    //-- Static Helper Methods
    //------------------------------------------------------------------------

    /**
     * Displays UI that asks the user to select a predefined label.
     * @param tableId the table id
     * @return returns the selected RecordSet or null
     */
    public static RecordSetIFace askForRecordSet(final int tableId) {
        return askForRecordSet(tableId, null);
    }

    /**
     * Displays UI that asks the user to select a predefined label.
     * @param tableId the table id
     * @param additionalRS additional RecordSets to be added to the list
     * @return returns the selected RecordSet or null
     */
    public static RecordSetIFace askForRecordSet(final int tableId, final Vector<RecordSetIFace> additionalRS) {
        Vector<Integer> id = new Vector<Integer>(1);
        id.add(tableId);
        return askForRecordSet(id, additionalRS, false);
    }

    /**
     * @param tableIds
     * @param additionalRS
     * @param msgIfNoRecordsets
     * @return
     */
    public static RecordSetIFace askForRecordSet(final Vector<Integer> tableIds,
            final Vector<RecordSetIFace> additionalRS, final boolean msgIfNoRecordsets) {
        UsageTracker.incrUsageCount("RS.ASKRS");
        ChooseRecordSetDlg dlg = new ChooseRecordSetDlg(tableIds);
        if (additionalRS != null && additionalRS.size() > 0) {
            dlg.addAdditionalObjectsAsRecordSets(additionalRS);
        }

        if (dlg.hasRecordSets()) {
            // Commenting out because user want a chance to say 
            // yes or cancel even when there is one item
            /*if (dlg.getRecordSets().size() == 1)
            {
            return dlg.getRecordSets().get(0);
            }*/
            // else
            UIHelper.centerAndShow(dlg); // modal (waits for answer here)
            return dlg.isCancelled() ? null : dlg.getSelectedRecordSet();
        }

        // else
        if (msgIfNoRecordsets) {
            UIRegistry.displayLocalizedStatusBarText("RecordSetTask.NoRecordsets");
        }
        return null;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.af.tasks.BaseTask#getPermsArray()
     */
    @Override
    protected boolean[][] getPermsArray() {
        return new boolean[][] { { true, true, true, true }, { true, true, true, true }, { true, true, true, true },
                { true, true, true, true } };
    }

}