edu.ku.brc.specify.tasks.subpane.qb.QueryBldrPane.java Source code

Java tutorial

Introduction

Here is the source code for edu.ku.brc.specify.tasks.subpane.qb.QueryBldrPane.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.subpane.qb;

import static edu.ku.brc.ui.UIHelper.createButton;
import static edu.ku.brc.ui.UIHelper.createCheckBox;
import static edu.ku.brc.ui.UIHelper.createIconBtn;
import static edu.ku.brc.ui.UIHelper.createLabel;
import static edu.ku.brc.ui.UIRegistry.getResourceString;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MouseInputAdapter;

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

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import edu.ku.brc.af.core.AppContextMgr;
import edu.ku.brc.af.core.ContextMgr;
import edu.ku.brc.af.core.NavBoxLayoutManager;
import edu.ku.brc.af.core.SubPaneIFace;
import edu.ku.brc.af.core.SubPaneMgr;
import edu.ku.brc.af.core.Taskable;
import edu.ku.brc.af.core.UsageTracker;
import edu.ku.brc.af.core.db.DBFieldInfo;
import edu.ku.brc.af.core.db.DBRelationshipInfo;
import edu.ku.brc.af.core.db.DBRelationshipInfo.RelationshipType;
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.tasks.subpane.BaseSubPane;
import edu.ku.brc.af.ui.db.ERTICaptionInfo;
import edu.ku.brc.af.ui.db.ERTICaptionInfo.ColInfo;
import edu.ku.brc.af.ui.forms.formatters.DataObjDataField;
import edu.ku.brc.af.ui.forms.formatters.DataObjDataFieldFormatIFace;
import edu.ku.brc.af.ui.forms.formatters.UIFieldFormatterIFace;
import edu.ku.brc.dbsupport.DataProviderFactory;
import edu.ku.brc.dbsupport.DataProviderSessionIFace;
import edu.ku.brc.dbsupport.DataProviderSessionIFace.QueryIFace;
import edu.ku.brc.dbsupport.RecordSetIFace;
import edu.ku.brc.dbsupport.RecordSetItemIFace;
import edu.ku.brc.helpers.SwingWorker;
import edu.ku.brc.specify.config.SpecifyAppContextMgr;
import edu.ku.brc.specify.conversion.BasicSQLUtils;
import edu.ku.brc.specify.datamodel.Agent;
import edu.ku.brc.specify.datamodel.Attachment;
import edu.ku.brc.specify.datamodel.CollectionObject;
import edu.ku.brc.specify.datamodel.CollectionRelationship;
import edu.ku.brc.specify.datamodel.Container;
import edu.ku.brc.specify.datamodel.DataModelObjBase;
import edu.ku.brc.specify.datamodel.Discipline;
import edu.ku.brc.specify.datamodel.Division;
import edu.ku.brc.specify.datamodel.Institution;
import edu.ku.brc.specify.datamodel.SpExportSchema;
import edu.ku.brc.specify.datamodel.SpExportSchemaItem;
import edu.ku.brc.specify.datamodel.SpExportSchemaItemMapping;
import edu.ku.brc.specify.datamodel.SpExportSchemaMapping;
import edu.ku.brc.specify.datamodel.SpQuery;
import edu.ku.brc.specify.datamodel.SpQueryField;
import edu.ku.brc.specify.datamodel.SpReport;
import edu.ku.brc.specify.datamodel.SpecifyUser;
import edu.ku.brc.specify.datamodel.TreeDefIface;
import edu.ku.brc.specify.datamodel.TreeDefItemIface;
import edu.ku.brc.specify.datamodel.Treeable;
import edu.ku.brc.specify.datamodel.Workbench;
import edu.ku.brc.specify.dbsupport.RecordTypeCodeBuilder;
import edu.ku.brc.specify.tasks.ExportMappingTask;
import edu.ku.brc.specify.tasks.ExpressSearchTask;
import edu.ku.brc.specify.tasks.QueryTask;
import edu.ku.brc.specify.tasks.ReportsBaseTask;
import edu.ku.brc.specify.tasks.subpane.ExpressSearchResultsPaneIFace;
import edu.ku.brc.specify.tasks.subpane.JasperCompilerRunnable;
import edu.ku.brc.specify.tasks.subpane.wb.WorkbenchJRDataSource;
import edu.ku.brc.specify.tools.export.ConceptMapUtils;
import edu.ku.brc.specify.tools.export.MappedFieldInfo;
import edu.ku.brc.ui.CommandAction;
import edu.ku.brc.ui.CommandDispatcher;
import edu.ku.brc.ui.CommandListener;
import edu.ku.brc.ui.CustomDialog;
import edu.ku.brc.ui.DropDownButton;
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.util.Pair;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.util.JRLoader;

/**
 * @author rod
 * 
 * @code_status Alpha
 * 
 * Feb 23, 2007
 * 
 */
@SuppressWarnings("serial")
public class QueryBldrPane extends BaseSubPane implements QueryFieldPanelContainerIFace, CommandListener {
    protected static final Logger log = Logger.getLogger(QueryBldrPane.class);
    protected static final Color TITLEBAR_COLOR = new Color(82, 160, 52);
    protected static final int ExportSchemaPreviewSize = 120;
    //the maximum number of times the Parent relationship can be opened for recursive relationships.
    //This is currently only used for Containers (parent relationship has been unavailable for some time for Treeable tables).
    //It was originally necessary as a workaround to prevent a memory leak when the parent relationship was recursively opened, 
    //but now is used just to limit the recursion to a sane depth
    protected static final int maxParentChainLen = 7;

    protected JList tableList;
    protected Vector<QueryFieldPanel> queryFieldItems = new Vector<QueryFieldPanel>();
    protected QueryFieldPanel selectedQFP = null;
    protected int currentInx = -1;
    protected JPanel queryFieldsPanel;
    protected JScrollPane queryFieldsScroll;

    protected SpQuery query = null;

    protected JButton addBtn;

    protected ImageIcon blankIcon = IconManager.getIcon("BlankIcon", IconManager.IconSize.Std24);

    protected String columnDefStr = null;

    protected JPanel listBoxPanel;
    protected Vector<JList> listBoxList = new Vector<JList>();
    protected JScrollPane scrollPane;
    protected Vector<JScrollPane> spList = new Vector<JScrollPane>();
    protected Vector<TableTree> tableTreeList = new Vector<TableTree>();
    protected JPanel contextPanel;
    protected DropDownButton saveBtn;
    protected JButton searchBtn;
    protected JCheckBox distinctChk;
    protected JCheckBox countOnlyChk;
    protected JCheckBox searchSynonymyChk;
    protected boolean searchSynonymy = false;
    protected JCheckBox smushedChk;
    protected boolean smushed = false;

    /**
     * When countOnly is true, count of matching records is displayed, but the records are not displayed.
     */
    protected boolean countOnly = false;

    protected Hashtable<String, Boolean> fieldsToSkipHash = new Hashtable<String, Boolean>();
    protected QryListRenderer qryRenderer = new QryListRenderer(IconManager.STD_ICON_SIZE);

    protected int listCellHeight;

    protected TableTree tableTree;
    protected Hashtable<String, TableTree> tableTreeHash;
    protected boolean processingLists = false;

    protected RolloverCommand queryNavBtn = null;

    // Reordering
    protected JButton orderUpBtn = null;
    protected JButton orderDwnBtn = null;

    protected ExpressSearchResultsPaneIFace esrp = null;
    protected boolean isHeadless = false;

    protected SpExportSchema exportSchema;
    protected SpExportSchemaMapping schemaMapping;
    protected boolean isExportMapping = false;
    protected AtomicBoolean isUpdatingAvailableConcepts = new AtomicBoolean(false);

    /**
     * True if warning to reload after schema/treeDef changes has been shown.
     */
    protected boolean reloadMsgShown = false;

    protected final AtomicReference<QBQueryForIdResultsHQL> runningResults = new AtomicReference<QBQueryForIdResultsHQL>();
    protected final AtomicReference<QBQueryForIdResultsHQL> completedResults = new AtomicReference<QBQueryForIdResultsHQL>();
    protected final AtomicLong doneTime = new AtomicLong(-1);
    protected final AtomicLong startTime = new AtomicLong(-1);

    /**
     * Constructor.
     * 
     * @param name name of subpanel
     * @param task the owning task
     */
    public QueryBldrPane(final String name, final Taskable task, final SpQuery query)
            throws QueryTask.QueryBuilderContextException {
        this(name, task, query, false);
    }

    /**
     * Constructor.
     * 
     * @param name name of subpanel
     * @param task the owning task
     */
    public QueryBldrPane(final String name, final Taskable task, final SpQuery query, final boolean isHeadless)
            throws QueryTask.QueryBuilderContextException {
        this(name, task, query, isHeadless, null, null);
    }

    /**
     * Constructor.
     * 
     * @param name name of subpanel
     * @param task the owning task
     */
    public QueryBldrPane(final String name, final Taskable task, final SpQuery query, final boolean isHeadless,
            final SpExportSchema exportSchema, final SpExportSchemaMapping schemaMapping)
            throws QueryTask.QueryBuilderContextException {
        super(name, task);

        this.query = query;
        this.isHeadless = isHeadless;
        this.isExportMapping = exportSchema != null || schemaMapping != null;
        this.exportSchema = exportSchema != null ? exportSchema
                : schemaMapping != null ? schemaMapping.getSpExportSchema() : null;
        if (schemaMapping == null && isExportMapping) {
            this.schemaMapping = new SpExportSchemaMapping();
            this.schemaMapping.initialize();
            this.schemaMapping.setSpExportSchema(exportSchema);
        } else {
            this.schemaMapping = schemaMapping;
        }
        String[] skipItems = { "TimestampCreated", "LastEditedBy", "TimestampModified" };
        for (String nameStr : skipItems) {
            fieldsToSkipHash.put(nameStr, true);
        }

        //loadAutoMaps();
        //writeAutoMapsToXml();

        QueryTask qt = (QueryTask) task;
        Pair<TableTree, Hashtable<String, TableTree>> trees = qt.getTableTrees();
        tableTree = trees.getFirst();
        tableTreeHash = trees.getSecond();

        createUI();

        setupUI(false);

        CommandDispatcher.register(ReportsBaseTask.REPORTS, this);
    }

    //    protected void loadAutoMaps() {

    //        for (Map.Entry<String, AutoMap> am : autoMaps.entrySet())
    //        {
    //           System.out.println(am.getKey() + ": " + am.getValue());
    //        }

    //from older darwin cores 
    //       autoMaps.put("scientificnameauthor", new AutoMap(
    //            "1,9-determinations,4-preferredTaxon.taxon.author", "author",
    //            "1,9-determinations,4-preferredTaxon", false));
    //      autoMaps.put("collector", new AutoMap(
    //            "1,10,30-collectors.collector.collectors", "collectors",
    //            "1,10,30-collectors", true));
    //      autoMaps.put("globaluniqueidentifier", new AutoMap(
    //            "1.collectionobject.guid", "guid", "1", false));
    //      autoMaps.put("daycollected", new AutoMap(
    //            "1,10.collectingevent.startDate", "startDate", "1,10", false));
    //      
    //      //from darwin core used by ipt
    //      autoMaps.put("catalognumber", new AutoMap("1.collectionobject.catalogNumber", "catalogNumber", "1", false));
    //      autoMaps.put("class", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Class", "Class", "1,9-determinations,4-preferredTaxon", false));
    //      autoMaps.put("collectioncode", new AutoMap("1,23.collection.code", "code", "1,23", false));
    //      autoMaps.put("continent", new AutoMap("1,10,2,3.geography.Continent", "Continent", "1,10,2,3", false));
    //      
    //      autoMaps.put("coordinateuncertaintyinmeters", new AutoMap("1,10,2.locality.latLongAccuracy", "latLongAccuracy", "1,10,2", false, false));
    //      
    //      autoMaps.put("country", new AutoMap("1,10,2,3.geography.Country", "Country", "1,10,2,3", false));
    //      autoMaps.put("county", new AutoMap("1,10,2,3.geography.County", "County", "1,10,2,3", false));
    //      
    //      autoMaps.put("dateidentified", new AutoMap("1,9-determinations.determination.determinedDate", "determinedDate", "1,9-determinations", false, false));
    //      
    //      autoMaps.put("decimallatitude", new AutoMap("1,10,2.locality.latitude1", "latitude1", "1,10,2", false));
    //      autoMaps.put("decimallongitude", new AutoMap("1,10,2.locality.longitude1", "longitude1", "1,10,2", false));
    //      autoMaps.put("eventdate", new AutoMap("1,10.collectingevent.startDate", "startDate", "1,10", false));
    //      
    //      autoMaps.put("eventremarks", new AutoMap("1,10.collectingevent.remarks", "remarks", "1,10", false, false));
    //      autoMaps.put("eventtime", new AutoMap("1,10.collectingevent.startTime", "startTime", "1,10", false, false));
    //      
    //      autoMaps.put("family", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Family", "Family", "1,9-determinations,4-preferredTaxon", false));
    //      autoMaps.put("fieldnumber", new AutoMap("1.collectionobject.fieldNumber", "fieldNumber", "1", false));
    //      autoMaps.put("genus", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Genus", "Genus", "1,9-determinations,4-preferredTaxon", false));
    //      
    //      autoMaps.put("geodeticdatum", new AutoMap("1,10,2.locality.datum", "datum", "1,10,2", false, false));
    //      autoMaps.put("georeferencedby", new AutoMap("1,10,2,123-geoCoordDetails,5-geoRefDetBy.agent.geoRefDetBy", "geoRefDetBy", "1,10,2,123-geoCoordDetails,5-geoRefDetBy", false, false));
    //      autoMaps.put("georeferenceprotocol", new AutoMap("1,10,2,123-geoCoordDetails.geocoorddetail.protocol", "protocol", "1,10,2,123-geoCoordDetails", false, false));
    //      autoMaps.put("georeferenceremarks", new AutoMap("1,10,2,123-geoCoordDetails.geocoorddetail.geoRefRemarks", "geoRefRemarks", "1,10,2,123-geoCoordDetails", false, false));
    //      autoMaps.put("georeferencesources", new AutoMap("1,10,2,123-geoCoordDetails.geocoorddetail.source", "source", "1,10,2,123-geoCoordDetails", false, false));
    //      autoMaps.put("georeferenceverificationstatus", new AutoMap("1,10,2,123-geoCoordDetails.geocoorddetail.geoRefVerificationStatus", "geoRefVerificationStatus", "1,10,2,123-geoCoordDetails", false, false));
    //      autoMaps.put("habitat", new AutoMap("1,10,92.collectingeventattribute.text9", "text9", "1,10,92", false, false));
    //      autoMaps.put("identificationqualifier", new AutoMap("1,9-determinations.determination.qualifier", "qualifier", "1,9-determinations", false, false));
    //      autoMaps.put("identificationreferences", new AutoMap("1,9-determinations,38-determinationCitations.determinationcitation.determinationCitations", "determinationCitations", "1,9-determinations,38-determinationCitations", false, false));
    //      autoMaps.put("identificationremarks", new AutoMap("1,9-determinations.determination.remarks", "remarks", "1,9-determinations", false, false));
    //      
    //      autoMaps.put("identifiedby", new AutoMap("1,9-determinations,5-determiner.agent.determiner", "determiner", "1,9-determinations,5-determiner", false));
    //      autoMaps.put("individualcount", new AutoMap("1.collectionobject.countAmt", "countAmt", "1", false));
    //      autoMaps.put("infraspecificepithet", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Subspecies", "Subspecies", "1,9-determinations,4-preferredTaxon", false));
    //      autoMaps.put("institutioncode", new AutoMap("1,23,26,96,94.institution.code", "code", "1,23,26,96,94", false));
    //      
    //      autoMaps.put("island", new AutoMap("1,10,2,124-localityDetails.localitydetail.island", "island", "1,10,2,124-localityDetails", false, false));
    //      autoMaps.put("islandgroup", new AutoMap("1,10,2,124-localityDetails.localitydetail.islandGroup", "islandGroup", "1,10,2,124-localityDetails", false, false));
    //      
    //      autoMaps.put("kingdom", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Kingdom", "Kingdom", "1,9-determinations,4-preferredTaxon", false));
    //      
    //      autoMaps.put("lifestage", new AutoMap("1,93.collectionobjectattribute.text4", "text4", "1,93", false, false));
    //      
    //      autoMaps.put("locality", new AutoMap("1,10,2.locality.localityName", "localityName", "1,10,2", false));
    //      
    //      autoMaps.put("locationremarks", new AutoMap("1,10,2.locality.remarks", "remarks", "1,10,2", false, false));
    //      autoMaps.put("maximumdepthinmeters", new AutoMap("1,10,92.collectingeventattribute.text2", "text2", "1,10,92", false, false));
    //      
    //      autoMaps.put("maximumelevationinmeters", new AutoMap("1,10,2.locality.maxElevation", "maxElevation", "1,10,2", false));
    //      
    //      autoMaps.put("minimumdepthinmeters", new AutoMap("1,10,92.collectingeventattribute.text1", "text1", "1,10,92", false, false));
    //      
    //      autoMaps.put("minimumelevationinmeters", new AutoMap("1,10,2.locality.minElevation", "minElevation", "1,10,2", false));
    //      
    //      autoMaps.put("occurrenceremarks", new AutoMap("1.collectionobject.remarks", "remarks", "1", false, false));
    //      
    //      autoMaps.put("order", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Order", "Order", "1,9-determinations,4-preferredTaxon", false));
    //      
    //      autoMaps.put("othercatalognumbers", new AutoMap("1.collectionobject.altCatalogNumber", "altCatalogNumber", "1", false, false));
    //      
    //      autoMaps.put("phylum", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Phylum", "Phylum", "1,9-determinations,4-preferredTaxon", false));
    //      autoMaps.put("preparations", new AutoMap("1,63-preparations.preparation.preparations", "preparations", "1,63-preparations", false));
    //      
    //      autoMaps.put("previousidentifications", new AutoMap("1,9-determinations.determination.determinations", "determinations", "1,9-determinations", false, false));
    //      
    //      autoMaps.put("recordedby", new AutoMap(
    //            "1,10,30-collectors.collector.collectors", "collectors",
    //            "1,10,30-collectors", true));
    //      
    //      autoMaps.put("reproductivecondition", new AutoMap("1,93.collectionobjectattribute.text3", "text3", "1,93", false, false));
    //      
    //      autoMaps.put("scientificname", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.fullName", "fullName", "1,9-determinations,4-preferredTaxon", false));
    //      
    //      autoMaps.put("scientificnameauthorship", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.author", "author", "1,9-determinations,4-preferredTaxon", false, false));
    //      autoMaps.put("sex", new AutoMap("1,93.collectionobjectattribute.text1", "text1", "1,93", false, false));
    //      
    //      autoMaps.put("specificepithet", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Species", "Species", "1,9-determinations,4-preferredTaxon", false));
    //      autoMaps.put("stateprovince", new AutoMap("1,10,2,3.geography.State", "State", "1,10,2,3", false));
    //      
    //      autoMaps.put("subgenus", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.Subgenus", "Subgenus", "1,9-determinations,4-preferredTaxon", false, false));
    //      autoMaps.put("taxonremarks", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.remarks", "remarks", "1,9-determinations,4-preferredTaxon", false, false));
    //      
    //      autoMaps.put("typestatus", new AutoMap("1,9-determinations.determination.typeStatusName", "typeStatusName", "1,9-determinations", false));
    //      
    //      autoMaps.put("verbatimelevation", new AutoMap("1,10,2.locality.verbatimElevation", "verbatimElevation", "1,10,2", false, false));
    //      autoMaps.put("verbatimeventdate", new AutoMap("1,10.collectingevent.verbatimDate", "verbatimDate", "1,10", false, false));
    //      autoMaps.put("verbatimlocality", new AutoMap("1,10.collectingevent.verbatimLocality", "verbatimLocality", "1,10", false, false));
    //      autoMaps.put("vernacularname", new AutoMap("1,9-determinations,4-preferredTaxon.taxon.commonName", "commonName", "1,9-determinations,4-preferredTaxon", false, false));
    //      autoMaps.put("waterbody", new AutoMap("1,10,2,124-localityDetails.localitydetail.waterBody", "waterBody", "1,10,2,124-localityDetails", false, false));   
    //   }

    //    protected void writeAutoMapsToXml()
    //    {
    //       File out = new File("/home/timo/automap.xml");
    //       Vector<String> lines = new Vector<String>();
    //       for (Map.Entry<String, AutoMap> me : autoMaps.entrySet())
    //       {
    //          String line = "<default_mapping name=\"" + me.getKey() + "\" " + me.getValue().toXML() + "/>" ;
    //          lines.add(line);
    //       }
    //       try
    //       {
    //          FileUtils.writeLines(out, lines);
    //       } catch(Exception ex)
    //       {
    //          ex.printStackTrace();
    //       }
    //    }

    /**
     * @return
     */
    protected DBFieldInfo getCatalogNumberField() {
        return DBTableIdMgr.getInstance().getInfoById(CollectionObject.getClassTableId())
                .getFieldByName("catalogNumber");
    }

    /**
     * @return
     */
    protected String getCatalogNumberTitle() {
        return getCatalogNumberField().getTitle();
    }

    /**
     * @return
     */
    protected boolean isSmushableContext() {
        boolean result = false;
        if (query != null && !isForSchemaExport() && Integer.valueOf(CollectionObject.getClassTableId())
                .equals(Integer.valueOf(query.getContextTableId()))) {
            DBFieldInfo cat = getCatalogNumberField();
            result = cat != null && !cat.isHidden() && cat.getFormatter() != null && cat.getFormatter().isNumeric();
        }
        return result;
    }

    /**
     * @return
     */
    protected boolean isSmushable() {
        return isSmushableContext() && getSmushedCol() != -1;
    }

    /**
     * Probably should be called on swing thread.
     */
    protected void updateSmushBtn() {
        boolean wasSelected = smushedChk.isSelected();
        boolean wasEnabled = smushedChk.isEnabled();
        smushedChk.setEnabled(isSmushable());
        if (wasSelected && wasEnabled && !isSmushable()) {
            smushedChk.setSelected(false);
        }
    }

    /**
     * @param enabled
     */
    protected void setSaveBtnEnabled(boolean enabled) {
        if (!enabled) {
            saveBtn.setEnabled(false);
        } else {
            saveBtn.setEnabled(enabled);
            /* Since leaving 'Save As' enabled all the time doesn't hurt anything, 
             * don't worry about its enablement
                 
            //Disable 'Save As' if nothing has been saved.
            List<JComponent> items = saveBtn.getMenus();
            if (items.size() == 2) {
               items.get(1).setEnabled(query != null && query.getId() != null);
            }
            */
        }
    }

    /**
     * create the query builder UI.
     */
    protected void createUI() {
        removeAll();

        JMenuItem saveItem = new JMenuItem(UIRegistry.getResourceString("QB_SAVE"));
        Action saveActionListener = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                if (saveQuery(false)) {
                    try {
                        String selId = null;
                        if (selectedQFP != null && selectedQFP.getQueryField() != null) {
                            selId = selectedQFP.getQueryField().getStringId();
                        }
                        final String selectedFldId = selId;
                        setupUI(true);
                        SwingUtilities.invokeLater(new Runnable() {

                            /* (non-Javadoc)
                             * @see java.lang.Runnable#run()
                             */
                            @Override
                            public void run() {
                                if (selectedFldId != null) {
                                    for (QueryFieldPanel qfp : queryFieldItems) {
                                        if (qfp.getQueryField() != null
                                                && selectedFldId.equals(qfp.getQueryField().getStringId())) {
                                            selectQFP(qfp);
                                            return;
                                        }
                                    }
                                    selectQFP(queryFieldItems.get(0));
                                }
                            }

                        });
                    } catch (Exception ex) {

                    }
                    setSaveBtnEnabled(false);
                }
            }
        };
        saveItem.addActionListener(saveActionListener);

        JMenuItem saveAsItem = new JMenuItem(UIRegistry.getResourceString("QB_SAVE_AS"));
        Action saveAsActionListener = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                if (saveQuery(true)) {
                    setSaveBtnEnabled(false);
                }
            }
        };
        saveAsItem.addActionListener(saveAsActionListener);
        JComponent[] itemSample = { saveItem, saveAsItem };
        saveBtn = new DropDownButton(UIRegistry.getResourceString("QB_SAVE"), null, 1,
                java.util.Arrays.asList(itemSample));
        saveBtn.addActionListener(saveActionListener);
        String ACTION_KEY = "SAVE";
        KeyStroke ctrlS = KeyStroke.getKeyStroke(KeyEvent.VK_S,
                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
        InputMap inputMap = saveBtn.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        inputMap.put(ctrlS, ACTION_KEY);
        ActionMap actionMap = saveBtn.getActionMap();
        actionMap.put(ACTION_KEY, saveActionListener);
        ACTION_KEY = "SAVE_AS";
        KeyStroke ctrlA = KeyStroke.getKeyStroke(KeyEvent.VK_A,
                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
        inputMap.put(ctrlA, ACTION_KEY);
        actionMap.put(ACTION_KEY, saveAsActionListener);
        saveBtn.setActionMap(actionMap);

        UIHelper.setControlSize(saveBtn);
        //saveBtn.setOverrideBorder(true, BasicBorders.getButtonBorder());

        listBoxPanel = new JPanel(new HorzLayoutManager(2, 2));

        Vector<TableQRI> list = new Vector<TableQRI>();
        for (int k = 0; k < tableTree.getKids(); k++) {
            list.add(tableTree.getKid(k).getTableQRI());
        }

        Collections.sort(list);
        DefaultListModel model = new DefaultListModel();
        for (TableQRI qri : list) {
            model.addElement(qri);
        }

        tableList = new JList(model);
        QryListRenderer qr = new QryListRenderer(IconManager.IconSize.Std16);
        qr.setDisplayKidIndicator(false);
        tableList.setCellRenderer(qr);

        JScrollPane spt = new JScrollPane(tableList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        Dimension pSize = spt.getPreferredSize();
        pSize.height = 200;
        spt.setPreferredSize(pSize);

        JPanel topPanel = new JPanel(new BorderLayout());

        scrollPane = new JScrollPane(listBoxPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);

        tableList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                if (!e.getValueIsAdjusting()) {
                    int inx = tableList.getSelectedIndex();
                    if (inx > -1) {
                        fillNextList(tableList);
                    } else {
                        listBoxPanel.removeAll();
                    }
                }
            }
        });

        addBtn = new JButton(IconManager.getImage("PlusSign", IconManager.IconSize.Std16));
        addBtn.setEnabled(false);
        addBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                BaseQRI qri = (BaseQRI) listBoxList.get(currentInx).getSelectedValue();
                if (qri.isInUse) {
                    return;
                }

                try {
                    FieldQRI fieldQRI = buildFieldQRI(qri);
                    if (fieldQRI == null) {
                        throw new Exception("null FieldQRI");
                    }
                    SpQueryField qf = new SpQueryField();
                    qf.initialize();
                    qf.setFieldName(fieldQRI.getFieldName());
                    qf.setStringId(fieldQRI.getStringId());
                    query.addReference(qf, "fields");

                    if (!isExportMapping) {
                        addQueryFieldItem(fieldQRI, qf, false);
                    } else {
                        addNewMapping(fieldQRI, qf, null, false);
                    }
                } catch (Exception ex) {
                    log.error(ex);
                    UsageTracker.incrHandledUsageCount();
                    edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, ex);
                    return;
                }
            }
        });

        contextPanel = new JPanel(new BorderLayout());
        contextPanel.add(createLabel("Search Context", SwingConstants.CENTER), BorderLayout.NORTH); // I18N
        contextPanel.add(spt, BorderLayout.CENTER);
        contextPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));

        JPanel schemaPanel = new JPanel(new BorderLayout());
        schemaPanel.add(scrollPane, BorderLayout.CENTER);

        topPanel.add(contextPanel, BorderLayout.WEST);
        topPanel.add(schemaPanel, BorderLayout.CENTER);
        add(topPanel, BorderLayout.NORTH);

        queryFieldsPanel = new JPanel();
        queryFieldsPanel.setLayout(new NavBoxLayoutManager(0, 2));
        queryFieldsScroll = new JScrollPane(queryFieldsPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        queryFieldsScroll.setBorder(null);
        add(queryFieldsScroll);

        //if (!isExportMapping)
        //{
        final JPanel mover = buildMoverPanel(false);
        add(mover, BorderLayout.EAST);
        // }

        String searchLbl = schemaMapping == null ? getResourceString("QB_SEARCH")
                : getResourceString("QB_EXPORT_PREVIEW");
        searchBtn = createButton(searchLbl);
        searchBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                //               int m = ae.getModifiers();
                //               boolean ors = (m & ActionEvent.ALT_MASK) > 0 && (m & ActionEvent.CTRL_MASK) > 0 && (m & ActionEvent.SHIFT_MASK) > 0;
                //               if (ors)
                //               {
                //                  System.out.println("Disjunctional conjoinment desire gesture detected");
                //               }
                //               doSearch(ors);
                doSearch(false);
            }
        });
        distinctChk = createCheckBox(UIRegistry.getResourceString("QB_DISTINCT"));
        distinctChk.setVisible(schemaMapping == null);
        if (schemaMapping == null) {
            distinctChk.setSelected(false);
            distinctChk.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    new SwingWorker() {

                        /* (non-Javadoc)
                         * @see edu.ku.brc.helpers.SwingWorker#construct()
                         */
                        @Override
                        public Object construct() {
                            if (distinctChk.isSelected()) {
                                UsageTracker.incrUsageCount("QB.DistinctOn");
                            } else {
                                UsageTracker.incrUsageCount("QB.DistinctOff");
                            }
                            if ((isTreeLevelSelected() || isAggFieldSelected()) && countOnly
                                    && distinctChk.isSelected()) {
                                countOnlyChk.setSelected(false);
                                countOnly = false;
                            }
                            query.setCountOnly(countOnly);
                            query.setSelectDistinct(distinctChk.isSelected());
                            setSaveBtnEnabled(thereAreItems());
                            return null;
                        }
                    }.start();
                }
            });
        }
        countOnlyChk = createCheckBox(UIRegistry.getResourceString("QB_COUNT_ONLY"));
        countOnlyChk.setSelected(false);
        countOnlyChk.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                new SwingWorker() {

                    /* (non-Javadoc)
                     * @see edu.ku.brc.helpers.SwingWorker#construct()
                     */
                    @Override
                    public Object construct() {
                        //Don't allow change while query is running.
                        if (runningResults.get() == null) {
                            countOnly = !countOnly;
                            if (countOnly) {
                                UsageTracker.incrUsageCount("QB.CountOnlyOn");
                            } else {
                                UsageTracker.incrUsageCount("QB.CountOnlyOff");
                            }
                            if ((isTreeLevelSelected() || isAggFieldSelected()) && countOnly
                                    && (distinctChk.isSelected() || searchSynonymyChk.isSelected())) {
                                distinctChk.setSelected(false);
                                searchSynonymyChk.setSelected(false);
                            }
                        } else {
                            //This might be awkward and/or klunky...
                            countOnlyChk.setSelected(countOnly);
                        }
                        query.setCountOnly(countOnly);
                        query.setSelectDistinct(distinctChk.isSelected());
                        setSaveBtnEnabled(thereAreItems());
                        return null;
                    }
                }.start();
            }
        });

        searchSynonymyChk = createCheckBox(UIRegistry.getResourceString("QB_SRCH_SYNONYMS"));
        searchSynonymyChk.setSelected(searchSynonymy);
        searchSynonymyChk.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                new SwingWorker() {

                    /* (non-Javadoc)
                     * @see edu.ku.brc.helpers.SwingWorker#construct()
                     */
                    @Override
                    public Object construct() {
                        searchSynonymy = !searchSynonymy;
                        if (!searchSynonymy) {
                            UsageTracker.incrUsageCount("QB.SearchSynonymyOff");
                        } else {
                            UsageTracker.incrUsageCount("QB.SearchSynonymyOn");
                        }
                        if (isTreeLevelSelected() && countOnly && searchSynonymyChk.isSelected()) {
                            countOnlyChk.setSelected(false);
                            countOnly = false;
                        }
                        query.setSearchSynonymy(searchSynonymy);
                        setSaveBtnEnabled(thereAreItems());
                        return null;
                    }
                }.start();
            }
        });

        smushedChk = createCheckBox(UIRegistry.getResourceString("QB_SMUSH_RESULTS"));
        smushedChk.setVisible(isSmushableContext());
        if (isSmushableContext()) {
            smushedChk.setSelected(smushed);
            smushedChk.setToolTipText(
                    String.format(UIRegistry.getResourceString("QB_SMUSH_RESULTS_HINT"), getCatalogNumberTitle()));
            smushedChk.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    new SwingWorker() {

                        /*
                         * (non-Javadoc)
                         * 
                         * @see edu.ku.brc.helpers.SwingWorker#construct()
                         */
                        @Override
                        public Object construct() {
                            smushed = !smushed;
                            if (!smushed) {
                                UsageTracker.incrUsageCount("QB.SmushedOff");
                            } else {
                                UsageTracker.incrUsageCount("QB.SmushedOn");
                            }
                            query.setSmushed(smushed);
                            setSaveBtnEnabled(thereAreItems());
                            return null;
                        }
                    }.start();
                }
            });
        }

        PanelBuilder outer = new PanelBuilder(
                new FormLayout("p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p, 6dlu, p", "p"));

        CellConstraints cc = new CellConstraints();
        outer.add(smushedChk, cc.xy(1, 1));
        outer.add(searchSynonymyChk, cc.xy(3, 1));
        outer.add(distinctChk, cc.xy(5, 1));
        outer.add(countOnlyChk, cc.xy(7, 1));
        outer.add(searchBtn, cc.xy(9, 1));
        outer.add(saveBtn, cc.xy(11, 1));

        JPanel bottom = new JPanel(new BorderLayout());
        bottom.add(outer.getPanel(), BorderLayout.EAST);

        JButton helpBtn = UIHelper.createHelpIconButton(getHelpBtnContext());
        bottom.add(helpBtn, BorderLayout.WEST);
        add(bottom, BorderLayout.SOUTH);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    }

    /**
     * @return
     */
    protected String getHelpBtnContext() {
        if (getTask() instanceof ExportMappingTask) {
            return "schema_mapping";
        } else {
            return "QB";
        }
    }

    /**
     * @param fieldQRI
     * @param qf
     * 
     * Adds a new mapping or condition for schema export.
     */
    protected void addNewMapping(FieldQRI fieldQRI, SpQueryField qf, QueryFieldPanel aQfp, boolean loading) {
        QueryFieldPanel qfp = aQfp == null ? addQueryFieldItem(fieldQRI, qf, false) : aQfp;
        if (qfp != null) {
            SpExportSchemaItemMapping newMapping = new SpExportSchemaItemMapping();
            newMapping.initialize();
            newMapping.setExportSchemaItem(null);
            newMapping.setExportSchemaMapping(schemaMapping);
            newMapping.setQueryField(qf);
            schemaMapping.getMappings().add(newMapping);
            qf.setMapping(newMapping);
            fieldQRI.setIsInUse(true);
            updateUIAfterAddOrMap(fieldQRI, qfp, loading, aQfp == null, true);
            updateAvailableConcepts();
        } else {
            //wtf??
        }
    }

    /**
     * @param schemaItem
     * @return true if schemaItem was removed
     */
    protected boolean removeSchemaItemMapping(SpExportSchemaItemMapping itemMapping) {
        int size = schemaMapping.getMappings().size();
        //XXX Most probably not necessary to worry multiple about copies of mapping items.
        SpExportSchemaItemMapping theOne = null;
        for (SpExportSchemaItemMapping esim : schemaMapping.getMappings()) {
            if (esim.getQueryField().getStringId().equals(itemMapping.getQueryField().getStringId())) {
                theOne = esim;
                break;
            }
        }
        if (theOne != null) {
            schemaMapping.getMappings().remove(theOne);
            theOne.setExportSchemaItem(null);
            theOne.setExportSchemaMapping(null);
            theOne.getQueryField().setMapping(null);
            if (theOne != itemMapping) {
                itemMapping.setExportSchemaMapping(null);
                itemMapping.getQueryField().setMapping(null);
                itemMapping.setQueryField(null);
            }
        }
        return schemaMapping.getMappings().size() < size;
    }

    /**
     * @param toRemove
     * @return true if the field was actually removed
     * 
     */
    protected boolean removeFieldFromQuery(SpQueryField toRemove) {
        int size = query.getFields().size();
        //XXX probably don't need to worry about multiple copies of QueryFields and Mappings anymore???
        SpQueryField theFieldObjectInTheFieldsSetToRemove = null;
        for (SpQueryField fld : query.getFields()) {
            if (fld == toRemove) {
                theFieldObjectInTheFieldsSetToRemove = fld;
                break;
            } else if (fld.getId() != null && toRemove.getId() != null && fld.getId().equals(toRemove.getId())) {
                theFieldObjectInTheFieldsSetToRemove = fld;
                break;
            } else if (fld.getStringId().equals(toRemove.getStringId())) {
                theFieldObjectInTheFieldsSetToRemove = fld;
                break;
            }
        }
        if (theFieldObjectInTheFieldsSetToRemove != null) {
            query.getFields().remove(theFieldObjectInTheFieldsSetToRemove);
            theFieldObjectInTheFieldsSetToRemove.setQuery(null);
        }

        //XXX probably not necessary to check for this anymore ???
        if (toRemove != theFieldObjectInTheFieldsSetToRemove) {
            toRemove.getQuery().getFields().remove(toRemove);
            toRemove.setQuery(null);
        }
        return query.getFields().size() < size;
    }

    /**
     * @param esrp the esrp to set
     */
    public void setEsrp(ExpressSearchResultsPaneIFace esrp) {
        this.esrp = esrp;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#doSearch()
     */
    public void doSearch() {
        doSearch(false);
    }

    public void doSearch(boolean doOr) {
        if (canSearch()) {
            if (distinctChk.isSelected()) {
                UsageTracker.incrUsageCount("QB.DoSearchDistinct." + query.getContextName());
            } else {
                UsageTracker.incrUsageCount("QB.DoSearch." + query.getContextName());
            }
            doSearch((TableQRI) tableList.getSelectedValue(), distinctChk.isSelected(), doOr);
        } else {
            cancelSearch();
        }
    }

    /**
     * cancel the currently exeecuting search.
     */
    protected void cancelSearch() {
        if (runningResults.get() != null) {
            log.debug("cancelling search");
            UsageTracker.incrUsageCount("QB.CancelSearch." + query.getContextName());
            runningResults.get().cancel();
        }
    }

    /**
     * @param fieldName
     * @return fieldName with lower-cased first character.
     */
    public static String fixFieldName(final String fieldName) {
        return fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
    }

    /**
     * @param q
     * @param container
     * @param tblTree
     * @param ttHash
     * @return a Vector of QueryFieldPanel objects for the supplied fields parameter.
     */
    protected static Vector<QueryFieldPanel> getQueryFieldPanels(final SpQuery q,
            final QueryFieldPanelContainerIFace container, final TableTree tblTree,
            final Hashtable<String, TableTree> ttHash) {
        return getQueryFieldPanels(container, q.getFields(), tblTree, ttHash, null, null);
    }

    /**
     * 
     */
    protected void setupUI(final boolean isPostSave) throws QueryTask.QueryBuilderContextException {
        if (!isHeadless && !SwingUtilities.isEventDispatchThread()) {
            throw new RuntimeException("Method called from invalid thread.");
        }

        queryFieldsPanel.removeAll();
        queryFieldItems.clear();
        queryFieldsPanel.validate();
        columnDefStr = null;
        tableList.clearSelection();
        contextPanel.setVisible(query == null);
        tableList.setSelectedIndex(-1);
        if (query != null) {
            //query.forceLoad(true); 
            Short tblId = query.getContextTableId();
            if (tblId != null) {
                for (int i = 0; i < tableList.getModel().getSize(); i++) {
                    TableQRI qri = (TableQRI) tableList.getModel().getElementAt(i);
                    if (qri.getTableInfo().getTableId() == tblId.intValue()) {
                        tableList.setSelectedIndex(i);
                        break;
                    }
                }
            }
        }

        List<QueryFieldPanel> qfps = null;
        boolean dirty = false;
        List<String> missingFlds = new LinkedList<String>();
        boolean doAutoMap = query == null || query.getId() == null;
        if (query != null) {
            TableQRI qri = (TableQRI) tableList.getSelectedValue();
            if (qri == null) {
                //throw new RuntimeException("Invalid context for query."); 
                throw ((QueryTask) task).new QueryBuilderContextException();
            }
            //query.forceLoad(true);                   
            qfps = !isExportMapping
                    ? getQueryFieldPanels(this, query.getFields(), tableTree, tableTreeHash, saveBtn, missingFlds)
                    : getQueryFieldPanelsForMapping(this, query.getFields(), tableTree, tableTreeHash, saveBtn,
                            schemaMapping, missingFlds,
                            (doAutoMap ? ConceptMapUtils.getDefaultDarwinCoreMappings() : null));
            if (missingFlds.size() > 0) {
                JList list = new JList(new Vector<String>(missingFlds));
                CellConstraints cc = new CellConstraints();
                PanelBuilder pb = new PanelBuilder(new FormLayout("f:p:g", "p:g,2px,f:p:g"));
                pb.add(UIHelper.createI18NLabel("QB_FIELDS_NOT_ADDED"), cc.xy(1, 1));
                pb.add(UIHelper.createScrollPane(list), cc.xy(1, 3));
                pb.setDefaultDialogBorder();

                dirty = qfps.size() > 0;
                CustomDialog dlg = new CustomDialog((Frame) UIRegistry.getTopWindow(),
                        UIRegistry.getResourceString("QB_FIELD_MISSING_TITLE"), true, CustomDialog.OK_BTN,
                        pb.getPanel());
                dlg.setOkLabel(UIRegistry.getResourceString("CLOSE"));
                dlg.setVisible(true);
            }
        }

        boolean header = true;
        for (final QueryFieldPanel qfp : qfps) {
            if (header) {
                header = false;
                this.queryFieldsScroll.setColumnHeaderView(qfp);
            } else {
                queryFieldItems.add(qfp);
                qfp.addMouseListener(new MouseInputAdapter() {
                    @Override
                    public void mousePressed(MouseEvent e) {
                        selectQFP(qfp);
                    }
                });
                qfp.resetValidator();
                queryFieldsPanel.add(qfp);
                //doAutoMap &= qfp.getFieldQRI() == null;
            }
        }
        qualifyFieldLabels();

        for (QueryFieldPanel qfp : queryFieldItems) {
            if (qfp.isAutoMapped()) {
                SpQueryField qf = new SpQueryField();
                qf.initialize();
                qf.setFieldName(qfp.getFieldQRI().getFieldName());
                qf.setStringId(qfp.getFieldQRI().getStringId());
                qfp.setQueryFieldForAutomapping(qf);
                query.addReference(qf, "fields");
                addNewMapping(qfp.getFieldQRI(), qf, qfp, false);
                dirty = true;
                qfp.setAutoMapped(false);
            }
        }

        /*if (doAutoMap)
        {
           for (QueryFieldPanel qfp : queryFieldItems)
           {
          if (!qfp.isConditionForSchema())
        {
           Vector<MappedFieldInfo> mappedTos = ConceptMapUtils.getDefaultDarwinCoreMappings().get(qfp.getSchemaItem().getFieldName().toLowerCase());
               
           if (mappedTos != null)
           {
              for (MappedFieldInfo mappedTo : mappedTos)
              {
                 FieldQRI fqri = getFieldQRI(tableTree, mappedTo
                    .getFieldName(), mappedTo.isRel(), mappedTo
                    .getStringId(), getTableIds(mappedTo
                    .getTableIds()), 0, tableTreeHash);
                 if (fqri != null)
                 {
                    SpQueryField qf = new SpQueryField();
                    qf.initialize();
                    qf.setFieldName(fqri.getFieldName());
                    qf.setStringId(fqri.getStringId());
                    query.addReference(qf, "fields");
                    addNewMapping(fqri, qf, qfp);
                    dirty = true;
                    break;
                 }
              }
           }
        }
           }
        }*/

        fillNextList(tableList);

        updateAvailableConcepts();

        SwingUtilities.invokeLater(new Runnable() {

            /* (non-Javadoc)
             * @see java.lang.Runnable#run()
             */
            @Override
            public void run() {
                distinctChk.setSelected(query.isSelectDistinct());
                countOnlyChk.setSelected(query.getCountOnly() == null ? false : query.getCountOnly());
                countOnly = countOnlyChk.isSelected();
                searchSynonymyChk.setSelected(
                        query.getSearchSynonymy() == null ? searchSynonymy : query.getSearchSynonymy());
                searchSynonymy = searchSynonymyChk.isSelected();
                smushedChk.setSelected(query.getSmushed() == null ? smushed : query.getSmushed());
                smushed = smushedChk.isSelected();
            }

        });
        final boolean saveBtnEnabled = dirty;
        if (!isHeadless) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    adjustPanelUI(saveBtnEnabled, isPostSave);
                }
            });
        } else {
            adjustPanelUI(saveBtnEnabled, isPostSave);
        }
    }

    /**
     * @return true if query is savable. 
     */
    protected boolean canSave() {
        return canSave(false);
    }

    /**
     * @return true if query is savable and the current user has permission to save.
     */
    protected boolean canSave(boolean isSchemaMapping) {
        boolean result = true;
        //if the query builder is enabled for a user then the user can save queries.
        //        if (AppContextMgr.isSecurityOn() && (!task.getPermissions().canAdd() || !task.getPermissions().canModify()))
        //        {
        //            if (!task.getPermissions().canAdd() && !task.getPermissions().canModify())
        //            {
        //                result = false;
        //            }
        //            else
        //            {
        //                boolean newQ = query == null || query.getId() == null;
        //                //if canAdd but !canModify then some strange behavior may result
        //                result = newQ ? task.getPermissions().canAdd() : task.getPermissions().canModify();
        //            }
        //        }

        if (isExportMapping) {
            //check that 'concept' names are unique
            Set<String> concepts = new TreeSet<String>();
            for (QueryFieldPanel qp : queryFieldItems) {
                if (qp.isForDisplay()) {
                    final String concept = qp.getSchemaItemCBX().getSelectedItem().toString();
                    if (concepts.contains(concept.toLowerCase())) {
                        if (isSchemaMapping) {
                            SwingUtilities.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    // TODO Auto-generated method stub
                                    UIRegistry.displayErrorDlgLocalized("QueryBldrPane.ConceptDuplicated", concept);
                                }
                            });
                        }
                        result = false;
                        break;
                    } else {
                        concepts.add(concept.toLowerCase());
                    }
                }
            }
        }

        return result;
    }

    /**
     * 
     */
    /**
     * @param saveBtnEnabled
     * @param selected
     */
    protected void adjustPanelUI(boolean saveBtnEnabled, boolean isPostSave) {
        //Sorry, but a new context can't be selected if any fields are selected from the current context.
        tableList.setEnabled(queryFieldItems.size() == 0);
        if (queryFieldItems.size() > 0 && !isPostSave) {
            selectQFP(queryFieldItems.get(0));
        }
        for (JList list : listBoxList) {
            list.repaint();
        }
        setSaveBtnEnabled(saveBtnEnabled);
        saveBtn.setVisible(canSave());
        updateSearchBtn();
        QueryBldrPane.this.validate();
    }

    public static HQLSpecs buildHQL(final TableQRI rootTable, final boolean distinct,
            final Vector<QueryFieldPanel> qfps, final TableTree tblTree, final RecordSetIFace keysToRetrieve,
            final boolean searchSynonymy, final boolean isSchemaExport, final Timestamp lastExportTime)
            throws ParseException {
        return buildHQL(rootTable, distinct, qfps, tblTree, keysToRetrieve, searchSynonymy, isSchemaExport,
                lastExportTime, false);
    }

    /**
     * @param rootTable
     * @param distinct
     * @param qfps
     * @param tblTree
     * @param keysToRetrieve
     * @return HQLSpecs for the current fields and settings.
     */
    public static HQLSpecs buildHQL(final TableQRI rootTable, final boolean distinct,
            final Vector<QueryFieldPanel> qfps, final TableTree tblTree, final RecordSetIFace keysToRetrieve,
            final boolean searchSynonymy, final boolean isSchemaExport, final Timestamp lastExportTime,
            final boolean disjunct) throws ParseException {
        if (qfps.size() == 0)
            return null;

        if (keysToRetrieve != null && keysToRetrieve.getNumItems() == 0)
            return null;

        StringBuilder fieldsStr = new StringBuilder();
        Vector<BaseQRI> list = new Vector<BaseQRI>();
        StringBuilder criteriaStr = new StringBuilder();
        StringBuilder orderStr = new StringBuilder();
        LinkedList<SortElement> sortElements = new LinkedList<SortElement>();
        boolean postSortPresent = false;
        boolean debug = false;
        ProcessNode root = new ProcessNode();
        int fldPosition = distinct ? 0 : 1;

        for (QueryFieldPanel qfi : qfps) {
            if (qfi.getFieldQRI() == null) {
                continue;
            }

            qfi.updateQueryField();

            if (qfi.isForDisplay()) {
                fldPosition++;
            }

            if (debug) {
                log.debug("\nNode: " + qfi.getFieldName());
            }

            SortElement orderSpec = qfi.getOrderSpec(distinct ? fldPosition - 1 : fldPosition - 2);
            if (orderSpec != null) {
                boolean isPostSortSpec = qfi.getFieldQRI() instanceof TreeLevelQRI
                        || qfi.getFieldQRI() instanceof RelQRI;
                //dis regard post sorts that may have been saved before
                //fix for bug #9407
                if (!isSchemaExport) {
                    postSortPresent |= isPostSortSpec;
                }
                if (!isPostSortSpec || !isSchemaExport) {
                    sortElements.add(orderSpec);
                }
            }

            // Create a Stack (list) of parent from
            // the current node up to the top
            // basically we are creating a path of nodes
            // to determine if we need to create a new node in the tree
            list.clear();
            FieldQRI pqri = qfi.getFieldQRI();
            TableTree parent = pqri.getTableTree();
            if (qfi.isForDisplay() || qfi.hasCriteria() || orderSpec != null || pqri instanceof RelQRI) {
                boolean addToList = true;
                if (pqri instanceof RelQRI) {
                    RelQRI relQRI = (RelQRI) pqri;
                    RelationshipType relType = relQRI.getRelationshipInfo().getType();

                    // XXX Formatter.getSingleField() checks for ZeroOrOne and
                    // OneToOne rels.

                    if (!relType.equals(RelationshipType.ManyToOne)
                            && !relType.equals(RelationshipType.ManyToMany)/*
                                                                           * treat
                                                                           * manytomany
                                                                           * as
                                                                           * onetomany
                                                                           */) // Maybe
                                                                                                                                // need
                                                                                                                                // to
                                                                                                                                // consider
                                                                                                                                // some
                                                                                                                                // types
                                                                                                                                // of
                                                                                                                                // OneToOne
                                                                                                                                // also?????????
                    {
                        parent = parent.getParent();
                        if (isSchemaExport && lastExportTime != null) {
                            addToList = true;
                        } else {
                            // parent will initially point to the related table
                            // and don't need to add related table unless it has
                            // children displayed/queried,
                            addToList = false;
                        }
                    } else {
                        DataObjDataFieldFormatIFace formatter = relQRI.getDataObjFormatter(qfi.getFormatName());
                        if (formatter != null) {
                            boolean isSingleSimpleFormat = formatter.getSingleField() != null
                                    && formatter.getFields()[0].getSep() == null;
                            addToList = isSingleSimpleFormat || (isSchemaExport && lastExportTime != null);
                        } else {
                            addToList = false;
                        }
                    }
                }
                if (addToList) {
                    list.insertElementAt(pqri, 0);
                }
                while (parent != tblTree) {
                    list.insertElementAt(parent.getTableQRI(), 0);
                    parent = parent.getParent();
                }

                if (debug) {
                    log.debug("Path From Top Down:");
                    for (BaseQRI qri : list) {
                        log.debug("  " + qri.getTitle());
                    }
                }

                // Now walk the stack top (the top most parent)
                // down and if the path form the top down doesn't
                // exist then add a new node
                ProcessNode parentNode = root;
                int q = 0;
                for (BaseQRI qri : list) {
                    if (debug) {
                        log.debug("ProcessNode[" + qri.getTitle() + "]");
                    }
                    q++;
                    if (!parentNode.contains(qri) && (qri instanceof TableQRI || q == list.size())) {
                        ProcessNode newNode = new ProcessNode(qri);
                        parentNode.getKids().add(newNode);
                        if (debug) {
                            log.debug("Adding new node[" + newNode.getQri().getTitle() + "] to Node["
                                    + (parentNode.getQri() == null ? "root" : parentNode.getQri().getTitle())
                                    + "]");
                        }
                        parentNode = newNode;
                    } else {
                        for (ProcessNode kidNode : parentNode.getKids()) {
                            if (kidNode.getQri().equals(qri)) {
                                parentNode = kidNode;
                                break;
                            }
                        }
                    }
                }

                if (debug) {
                    log.debug("Current Tree:");
                    printTree(root, 0);
                }
            }
        }

        if (debug) {
            printTree(root, 0);
        }

        StringBuilder fromStr = new StringBuilder();
        TableAbbreviator tableAbbreviator = new TableAbbreviator();
        List<Pair<DBTableInfo, String>> fromTbls = new LinkedList<Pair<DBTableInfo, String>>();
        boolean hqlHasSynJoins = processTree(root, fromStr, fromTbls, 0, tableAbbreviator, tblTree, qfps,
                searchSynonymy, isSchemaExport, lastExportTime);

        StringBuilder sqlStr = new StringBuilder();
        sqlStr.append("select ");
        //if (distinct /*|| hqlHasSynJoins*/)
        {
            sqlStr.append("distinct ");
        }
        if (!distinct) {
            fieldsStr.append(tableAbbreviator.getAbbreviation(rootTable.getTableTree()));
            fieldsStr.append(".");
            fieldsStr.append(rootTable.getTableInfo().getIdFieldName());
        }

        List<Pair<String, Object>> paramsToSet = new LinkedList<Pair<String, Object>>();
        boolean visibleFldExists = false;
        for (QueryFieldPanel qfi : qfps) {
            if (qfi.getFieldQRI() == null) {
                continue;
            }

            if (qfi.isForDisplay()) {
                visibleFldExists = true;
                String fldSpec = qfi.getFieldQRI().getSQLFldSpec(tableAbbreviator, false, isSchemaExport,
                        qfi.getFormatName());
                if (StringUtils.isNotEmpty(fldSpec)) {
                    if (fieldsStr.length() > 0) {
                        fieldsStr.append(", ");
                    }
                    fieldsStr.append(fldSpec);
                }
            }
            if (keysToRetrieve == null || qfi.isEnforced()) {
                String criteria = qfi.getCriteriaFormula(tableAbbreviator, paramsToSet);
                boolean isDisplayOnly = StringUtils.isEmpty(criteria);
                if (!isDisplayOnly) {
                    if (criteria.equals("2+2=2") && qfi.isNegated()) {
                        criteria = "";
                    }
                    if (criteria.length() > 0 && hqlHasSynJoins && isSynSearchable(qfi.getFieldQRI())
                            && !qfi.isEmptyCriterion()) {
                        criteria = adjustForSynSearch(
                                tableAbbreviator.getAbbreviation(qfi.getFieldQRI().getTable().getTableTree()),
                                criteria, qfi.isNegated());
                    }
                    if (!isDisplayOnly && criteriaStr.length() > 0 && criteria.length() > 0) {
                        criteriaStr.append(disjunct ? " OR " : " AND ");
                    }
                    criteriaStr.append(criteria);
                }
            }
        }
        if (!visibleFldExists) {
            throw new ParseException(getResourceString("QueryBldrPane.NoVisibleColumns"), -1);
        }

        sqlStr.append(fieldsStr);

        sqlStr.append(" from ");
        sqlStr.append(fromStr);

        if (keysToRetrieve != null) {
            if (!StringUtils.isEmpty(criteriaStr.toString())) {
                criteriaStr.append(" and ");
            }
            criteriaStr.append("(");
            criteriaStr.append(tableAbbreviator.getAbbreviation(rootTable.getTableTree()) + "."
                    + rootTable.getTableInfo().getIdFieldName() + " in(");
            boolean comma = false;
            int maxInClauseLen = 2500;
            int inClauseLen = 0;
            for (RecordSetItemIFace item : keysToRetrieve.getOrderedItems()) {
                if (inClauseLen == maxInClauseLen) {
                    criteriaStr.append(") or ");
                    criteriaStr.append(tableAbbreviator.getAbbreviation(rootTable.getTableTree()) + "."
                            + rootTable.getTableInfo().getIdFieldName() + " in(");
                    inClauseLen = 0;
                } else if (comma) {
                    criteriaStr.append(",");
                } else {
                    comma = true;
                }
                criteriaStr.append(item.getRecordId());
                inClauseLen++;
            }
            criteriaStr.append("))");
        } else {
            //Assuming that this not necessary when keysToRetrieve is non-null because
            //the keys will already been filtered properly. (???)

            // Add extra where's for system fields for root table only, see notes below at end of for block
            boolean isRootTbl = true;
            for (Pair<DBTableInfo, String> fromTbl : fromTbls) {
                String specialColumnWhere = null;
                if (fromTbl.getFirst().getTableId() == Attachment.getClassTableId()) {
                    String prefix = fromTbl.getSecond() + ".";
                    specialColumnWhere = "((" + prefix + "scopeType = 0 and " + prefix + "scopeID = "
                            + AppContextMgr.getInstance()
                                    .getClassObject(edu.ku.brc.specify.datamodel.Collection.class).getCollectionId()
                            + ") or" + "(" + prefix + "scopeType = 1 and " + prefix + "scopeID = "
                            + AppContextMgr.getInstance().getClassObject(Discipline.class).getDisciplineId()
                            + ") or" + "(" + prefix + "scopeType = 2 and " + prefix + "scopeID = "
                            + AppContextMgr.getInstance().getClassObject(Division.class).getDivisionId() + ") or"
                            + "(" + prefix + "scopeType = 3 and " + prefix + "scopeID = "
                            + AppContextMgr.getInstance().getClassObject(Institution.class).getInstitutionId()
                            + "))";
                } else {
                    specialColumnWhere = QueryAdjusterForDomain.getInstance().getSpecialColumns(fromTbl.getFirst(),
                            true, !isRootTbl && true/* XXX should only use left join when necessary */,
                            fromTbl.getSecond());
                }
                isRootTbl = false;
                if (StringUtils.isNotEmpty(specialColumnWhere)) {
                    if (criteriaStr.length() > 0) {
                        criteriaStr.append(" AND ");
                    }
                    criteriaStr.append(specialColumnWhere);
                }
                //Actually, assuming data is valid, it should only be necessary to add the Adjustments for the root table?
                //XXX if this works, fix this loop. Also, join parameter code in getSpecialColumns will probably be irrelevant.
                break;
            }
            //...done adding system whereses

            //get only records modified/added since last export of the schema...
            if (isSchemaExport && lastExportTime != null) {
                if (criteriaStr.length() > 0) {
                    criteriaStr.append(" AND (");
                }
                String timestampParam = "spparam" + paramsToSet.size();
                paramsToSet.add(new Pair<String, Object>(timestampParam, lastExportTime));
                criteriaStr.append(getTimestampWhere(fromTbls, timestampParam, lastExportTime));
                criteriaStr.append(") ");
            }
        }

        if (criteriaStr.length() > 0) {
            sqlStr.append(" where ");
            sqlStr.append(criteriaStr);
        }

        if (sortElements.size() > 0 && !postSortPresent) {
            for (SortElement se : sortElements) {
                if (!StringUtils.isEmpty(orderStr.toString())) {
                    orderStr.append(", ");
                }
                orderStr.append(distinct ? se.getColumn() + 1 : se.getColumn() + 2);
                if (se.getDirection() == SortElement.DESCENDING) {
                    orderStr.append(" DESC");
                }
            }
            sortElements.clear();
        }

        if (orderStr.length() > 0) {
            sqlStr.append(" order by ");
            sqlStr.append(orderStr);
        }

        if (debug) {
            log.debug(sqlStr.toString());
            log.debug("sort:");
            for (SortElement s : sortElements) {
                log.debug("  " + s.getColumn() + " - " + s.getDirection());
            }
        }

        String result = sqlStr.toString();
        if (!checkHQL(result))
            return null;

        log.info(result);
        return new HQLSpecs(result, paramsToSet, sortElements, hqlHasSynJoins);
    }

    /**
     * @param hql
     * @return
     */
    protected static boolean checkHQL(String hql) {
        DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
        try {
            try {
                session.createQuery(hql, false);
                return true;
            } catch (Exception ex) {
                log.error(ex);
                UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, ex);
                return false;
            }
        } finally {
            session.close();
        }
    }

    /**
     * @param hql
     * @return hql transformed into hql that just counts the matching records.
     */
    public static String getCountHql(final String hql) {
        //Assumes the ID field is selected by hql -
        //Assumes that  'select' is lower case.

        int fromStart = hql.toLowerCase().indexOf(" from ");
        int orderStart = hql.toLowerCase().indexOf(" order by ");

        return "select count(*) "
                + (orderStart > fromStart ? hql.substring(fromStart, orderStart) : hql.substring(fromStart));

        //       int idEnd = hql.indexOf(',', 0); 
        //        String fldPart = hql.substring(0, idEnd);
        //        if (fldPart.indexOf("distinct") != -1)
        //        {
        //           fldPart = fldPart.replaceFirst("select distinct ", "select ");
        //        }
        //        String countHql = fldPart + " " + hql.substring(fromStart);
        //       return countHql;

    }

    /**
     * @param hql
     * @return
     */
    public static String getCountDistinctHql(final String hql) {
        //Assumes the ID field is selected by hql -
        //Assumes that  'select', is lower case.
        int fromStart = hql.toLowerCase().indexOf(" from ");
        int orderStart = hql.toLowerCase().indexOf(" order by ");
        int idEnd = hql.indexOf(',', 0);
        String fldPart = hql.substring(0, idEnd);
        if (fldPart.indexOf("distinct") != -1) {
            fldPart = fldPart.replaceFirst("select distinct ", "select ");
        }
        String distinctFldPart = fldPart.replaceFirst("select ", "select count(distinct ") + ") ";
        String distinctHql = distinctFldPart + " "
                + (orderStart > fromStart ? hql.substring(fromStart, orderStart) : hql.substring(fromStart));
        return distinctHql;
    }

    /**
     * @param hql
     * @return true if each record in the query defined by hql has
     * a unique key.
     * 
     * NOTE: for large databases this method might take a long time.
     * It probably should be called from a SwingWorker. 
     */
    public static Pair<Boolean, Long> checkUniqueRecIds(final String hql, final List<Pair<String, Object>> params) {
        String countHql = getCountHql(hql);
        String distinctHql = getCountDistinctHql(hql);

        DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
        try {
            try {
                QueryIFace q1 = session.createQuery(countHql, false);
                QueryIFace q2 = session.createQuery(distinctHql, false);
                for (Pair<String, Object> param : params) {
                    q1.setParameter(param.getFirst(), param.getSecond());
                    q2.setParameter(param.getFirst(), param.getSecond());
                }
                Long q1Size = Long.valueOf(q1.list().get(0).toString());
                Long q2Size = Long.valueOf(q2.list().get(0).toString());
                return new Pair<Boolean, Long>(q1Size.equals(q2Size), q1Size);
            } catch (Exception ex) {
                UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, ex);
            }
        } finally {
            session.close();
        }
        return new Pair<Boolean, Long>(false, 0L);
    }

    /**
     * @param rootAlias
     * @param node
     * @return reasonably likely to be unique alias for table represented by node.
     */
    protected static String getNextAlias(String rootAlias, ProcessNode node) {
        return rootAlias + "_"
                + DBTableIdMgr.getInstance().getByClassName(node.getRel().getClassName()).getAbbrev();
    }

    /**
     * @param rootAlias
     * @param node
     * @param fromTbls
     * @return a from clause for use in adding timestamp conditions for the tables related by the tree rooted by node.
     * The ProcessNode tree is expected to have been created by processFormatter().
     */
    protected static String getTimestampFrom(String rootAlias, ProcessNode node,
            List<Pair<DBTableInfo, String>> fromTbls) {
        String nextAlias = getNextAlias(rootAlias, node);
        String result = " left join " + rootAlias + "." + node.getRel().getName() + " " + nextAlias;
        fromTbls.add(new Pair<DBTableInfo, String>(null, nextAlias));
        for (ProcessNode kid : node.getKids()) {
            result += getTimestampFrom(nextAlias, kid, fromTbls);
        }
        return result;
    }

    /**
     * @param fromTbls
     * @param timestampParam
     * @param lastExportTime
     * @return a set of conditions to get records modified or created after lastExportTime for each table in fromTbls. 
     */
    protected static String getTimestampWhere(List<Pair<DBTableInfo, String>> fromTbls, String timestampParam,
            Timestamp lastExportTime) {
        String result = "";
        int f = 0;
        for (Pair<DBTableInfo, String> fromTbl : fromTbls) {
            if (f > 0) {
                result += " or ";
            }
            f++;

            result += fromTbl.getSecond() + ".timestampModified > :" + timestampParam;
            result += " or ";
            result += fromTbl.getSecond() + ".timestampCreated > :" + timestampParam;
            if (fromTbl.getFirst() != null && fromTbl.getFirst().getClassObj() != null
                    && Treeable.class.isAssignableFrom(fromTbl.getFirst().getClassObj())) {
                //String keyFld = fromTbl.getFirst().getIdFieldName();

                String tbl = fromTbl.getFirst().getShortClassName();
                String alias = fromTbl.getFirst().getAbbrev();
                String joinAlias = fromTbl.getSecond();
                /*Original Fix for #10212...
                result += " or exists(select id from " + tbl + " " + alias + " where " + fromTbl.getSecond() + ".nodeNumber between " + alias + ".nodeNumber and " + alias + ".highestChildNodeNumber and " 
                 + "(" + alias + ".timestampModified > :" + timestampParam + " or " + alias + ".timestampCreated > :" + timestampParam + "))";
                ... end original fix for #10212 */

                Discipline d = AppContextMgr.getInstance().getClassObject(Discipline.class);
                TreeDefIface<?, ?, ?> td = null;
                if ("taxon".equalsIgnoreCase(tbl)) {
                    td = d.getTaxonTreeDef();
                } else if ("geography".equalsIgnoreCase(tbl)) {
                    td = d.getGeographyTreeDef();
                } else if ("lithostrat".equalsIgnoreCase(tbl)) {
                    td = d.getLithoStratTreeDef();
                } else if ("geologictimeperiod".equalsIgnoreCase(tbl)) {
                    td = d.getGeologicTimePeriodTreeDef();
                }
                if (td == null && "storage".equalsIgnoreCase(tbl)) {
                    td = ((Institution) AppContextMgr.getInstance().getClassObject(Institution.class))
                            .getStorageTreeDef();
                }
                if (td != null) {
                    List<Object> ranks = BasicSQLUtils
                            .querySingleCol("select rankid from " + tbl.toLowerCase() + "treedefitem where "
                                    + tbl.toLowerCase() + "treedefid = " + td.getTreeDefId() + " order by 1 desc");
                    ranks.remove(0);
                    ranks.remove(ranks.size() - 1);
                    String joins = "";
                    String whenWheres = "";
                    String recWheres = "";
                    String prevRank = "";
                    for (Object rank : ranks) {
                        String prevAlias = "".equals(prevRank) ? alias : alias + "rnk" + prevRank;
                        String newAlias = alias + "rnk" + rank;
                        joins += " left join " + prevAlias + ".parent " + newAlias;
                        if (!"".equals(whenWheres))
                            whenWheres += " or ";
                        if (!"".equals(recWheres))
                            recWheres += " or ";
                        whenWheres += newAlias + ".timestampModified > :" + timestampParam + " or " + newAlias
                                + ".timestampCreated > :" + timestampParam;
                        recWheres += joinAlias + ".id=" + newAlias + ".id";
                        prevRank = rank.toString();
                    }
                    //String subsql = " or exists (select " + alias + ".id from " + tbl + " " + alias + joins + " where (" + whenWheres + ") and (" + recWheres + "))";
                    String subsql = " or exists (select " + alias + ".id from " + tbl + " " + alias + joins
                            + " where (" + whenWheres + ") and (" + joinAlias + ".id=" + alias + ".id" + "))";
                    result += subsql;
                }

            }
            //            result += " or ";
            //            result += fromTbl.getSecond() + ".timestampModified is null";
            //            result += " or ";
            //            result += fromTbl.getSecond() + ".timestampCreated is null";
        }
        return result;
    }

    /**
     * @param formatter
     * 
     * Eventually this will add conditions to check for changed data when exporting a schema.
     */
    protected static void processFormatter(DataObjDataFieldFormatIFace formatter, ProcessNode node) {
        for (DataObjDataField fld : formatter.getFields()) {
            if (fld.getObjFormatter() != null) {
                ProcessNode subNode = null;
                for (DataObjDataFieldFormatIFace subformatter : fld.getObjFormatter().getFormatters()) {
                    if (subNode == null) {
                        subNode = new ProcessNode(fld.getRelInfo());
                        node.getKids().add(subNode);
                    }
                    processFormatter(subformatter, subNode);
                }
            }
        }
    }

    /**
     * @param tblAlias
     * @param criteria
     * @return supplied criteria parameter with adjustments to enable synonymy searching.
     */
    protected static String adjustForSynSearch(final String tblAlias, final String criteria,
            final boolean isNegated) {
        String result = "(" + criteria;

        String chunk = criteria.replace(tblAlias + ".", getAcceptedChildrenAlias(tblAlias) + ".");
        if (isNegated) {
            chunk = "(" + getAcceptedChildrenAlias(tblAlias) + ".nodeNumber is null or " + chunk.substring(1);
            result += " AND " + chunk;
        } else {
            result += " OR " + chunk;
        }
        chunk = criteria.replace(tblAlias + ".", getAcceptedParentAlias(tblAlias) + ".");
        if (isNegated) {
            chunk = "(" + getAcceptedParentAlias(tblAlias) + ".nodeNumber is null or " + chunk.substring(1);
            result += " AND " + chunk;
        } else {
            result += " OR " + chunk;
        }
        chunk = criteria.replace(tblAlias + ".", getAcceptedParentChildrenAlias(tblAlias) + ".");
        if (isNegated) {
            chunk = "(" + getAcceptedParentChildrenAlias(tblAlias) + ".nodeNumber is null or " + chunk.substring(1);
            result += " AND " + chunk + ") ";
        } else {
            result += " OR " + chunk + ") ";
        }

        return result;
    }

    /**
     * Performs the Search by building the HQL String.
     */
    protected void doSearch(final TableQRI rootTable, boolean distinct, boolean disjunct) {
        try {
            //XXX need to determine exportQuery params (probably)
            HQLSpecs hql = buildHQL(rootTable, distinct, queryFieldItems, tableTree, null, searchSynonymy, false,
                    null, disjunct);
            processSQL(queryFieldItems, hql, rootTable.getTableInfo(), distinct);
        } catch (Exception ex) {
            String msg = StringUtils.isBlank(ex.getLocalizedMessage()) ? getResourceString("QB_RUN_ERROR")
                    : ex.getLocalizedMessage();
            //ex.printStackTrace();
            UIRegistry.getStatusBar().setErrorMessage(msg, ex);
            UIRegistry.writeTimedSimpleGlassPaneMsg(msg, Color.RED);
            runningResults.set(null);
            completedResults.set(null);
        }
    }

    /**
     * @return Panel with up and down arrows for moving fields up and down in queryFieldsPanel.
     */
    protected JPanel buildMoverPanel(final boolean horizontal) {
        orderUpBtn = createIconBtn("ReorderUp", "QB_FLD_MOVE_UP", new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                orderUp();
            }
        });
        orderDwnBtn = createIconBtn("ReorderDown", "QB_FLD_MOVE_DOWN", new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                orderDown();
            }
        });

        PanelBuilder upDownPanel;
        if (horizontal) {
            upDownPanel = new PanelBuilder(new FormLayout("f:p:g, p, 2px, p, f:p:g", "p"));
            CellConstraints cc = new CellConstraints();
            upDownPanel.add(orderUpBtn, cc.xy(2, 1));
            upDownPanel.add(orderDwnBtn, cc.xy(4, 1));
        } else {
            upDownPanel = new PanelBuilder(new FormLayout("p", "f:p:g, p, 2px, p, f:p:g"));
            CellConstraints cc = new CellConstraints();
            upDownPanel.add(orderUpBtn, cc.xy(1, 2));
            upDownPanel.add(orderDwnBtn, cc.xy(1, 4));
        }
        return upDownPanel.getPanel();
    }

    /**
     * Moves selected QFP up in queryFieldsPanel
     */
    protected void orderUp() {
        moveField(selectedQFP, queryFieldItems.get(queryFieldItems.indexOf(selectedQFP) - 1));
    }

    /**
     * Moves selected QFP down in queryFieldsPanel
     */
    protected void orderDown() {
        moveField(selectedQFP, queryFieldItems.get(queryFieldItems.indexOf(selectedQFP) + 1));
    }

    /**
     * @param fld
     * @return true if criteria have been entered for fld.
     */
    protected static boolean fieldHasCriteria(final FieldQRI fld, List<QueryFieldPanel> fieldPanels) {
        for (QueryFieldPanel fldPanel : fieldPanels) {
            if (fldPanel.getFieldQRI() != null) {
                if (fldPanel.getFieldQRI() == fld
                        || fldPanel.getFieldQRI().getStringId().equals(fld.getStringId())) {
                    return fldPanel.hasCriteria();
                }
            }
        }
        return false;
    }

    /**
     * @param node
     * @return " left join " unless it can be determined that data returned on other side of the join
     * cannot be null in which case " inner join " is returned.
     */
    protected static String getJoin(final ProcessNode node) {
        //XXX really should only use left join when necessary.
        //XXX if this is ever modified to use inner join when conditions exists in the related table
        //the 'allowNulls' setting must be checked and left join used when it is true.
        return " left join ";
    }

    /**
     * @param taxAlias
     * @return an alias for the acceptedParent joined table for table with alias taxAlias.
    */
    protected static String getAcceptedParentAlias(final String taxAlias) {
        return taxAlias + "accpar";
    }

    /**
     * @param taxAlias
     * @return an alias for the acceptedParentChildren joined table for table with alias taxAlias.
    */
    protected static String getAcceptedParentChildrenAlias(final String taxAlias) {
        return taxAlias + "accparchi";
    }

    /**
     * @param taxAlias
     * @return an alias for the acceptedChildren joined table for table with alias taxAlias.
    */
    protected static String getAcceptedChildrenAlias(final String taxAlias) {
        return taxAlias + "accchi";
    }

    /**
     * @param fld
     * @return true if the the field is a name field for a treeable table.
    */
    protected static boolean isSynSearchable(final FieldQRI fld) {
        //XXX It would be good to have a way of knowing if synonymy is actually supported for a tree or treeable class.   
        //System.out.println("isSynSearchble " + fld.getTitle());
        if (!Treeable.class.isAssignableFrom(fld.getTableInfo().getClassObj())) {
            return false;
        }

        SpecifyAppContextMgr spMgr = (SpecifyAppContextMgr) AppContextMgr.getInstance();

        @SuppressWarnings("unchecked")
        TreeDefIface<?, ?, ?> treeDef = spMgr
                .getTreeDefForClass((Class<? extends Treeable<?, ?, ?>>) fld.getTableInfo().getClassObj());

        if (treeDef.isSynonymySupported()) {
            //System.out.println(fld.getFieldName() + "  --  " + fld.getClass().getSimpleName());
            return fld.getFieldName().equalsIgnoreCase("name") || fld.getFieldName().equalsIgnoreCase("fullname")
                    || fld instanceof TreeLevelQRI;
        }
        return false;
    }

    /**
     * @param parent
     * @param sqlStr
     * @param level
     * @param searchSynonymy
     * 
     * returns true if Joins on synonymizable tree tables are present.
     */
    protected static boolean processTree(final ProcessNode parent, final StringBuilder sqlStr,
            final List<Pair<DBTableInfo, String>> fromTbls, final int level,
            final TableAbbreviator tableAbbreviator, final TableTree tblTree, List<QueryFieldPanel> fieldPanels,
            final boolean searchSynonymy, final boolean isSchemaExport, final Timestamp exportTimestamp) {
        BaseQRI qri = parent.getQri();
        boolean hqlHasSynJoins = false;
        if (qri != null && qri.getTableTree() != tblTree) {
            if (qri instanceof TableQRI) {
                TableTree tt = qri.getTableTree();
                String alias = tableAbbreviator.getAbbreviation(tt);
                fromTbls.add(new Pair<DBTableInfo, String>(tt.getTableInfo(), alias));
                if (level == 1) {
                    sqlStr.append(tt.getName());
                    sqlStr.append(' ');
                    sqlStr.append(alias);
                    sqlStr.append(' ');

                } else {
                    sqlStr.append(getJoin(parent));

                    sqlStr.append(tableAbbreviator.getAbbreviation(tt.getParent()));
                    sqlStr.append('.');
                    sqlStr.append(tt.getField());
                    sqlStr.append(' ');
                    sqlStr.append(alias);
                    sqlStr.append(' ');
                }
                if (searchSynonymy
                        && Treeable.class.isAssignableFrom(((TableQRI) qri).getTableInfo().getClassObj())) {
                    //check to see if Name is inUse and if so, add joins for accepted taxa
                    TableQRI tqri = (TableQRI) qri;
                    boolean addSynJoin = false;
                    for (QueryFieldPanel qfp : fieldPanels) {
                        if (qfp.getFieldQRI() != null) {
                            if (isSynSearchable(qfp.getFieldQRI()) && qfp.hasCriteria()) {
                                addSynJoin = true;
                                break;
                            }
                        }
                    }
                    if (addSynJoin) {
                        hqlHasSynJoins = true;
                        sqlStr.append("left join ");
                        sqlStr.append(alias + ".acceptedChildren " + getAcceptedChildrenAlias(alias) + " ");
                        sqlStr.append("left join ");
                        sqlStr.append(alias + ".accepted" + tqri.getTableInfo().getShortClassName() + " "
                                + getAcceptedParentAlias(alias) + " left join " + getAcceptedParentAlias(alias)
                                + ".acceptedChildren " + getAcceptedParentChildrenAlias(alias) + " ");
                    }
                }
                //XXX - should only use left joins when necessary (see 4th param below)
                //XXX - actually since the domain adjuster adds joins to system tables that should always 
                //contain related records, AND because now joins are only made when domain criteria are specified,
                //It is ok to use inner join.
                boolean skipExtraJoin = level > 1;// && tt.getTableInfo().getTableId() == Agent.getClassTableId();
                if (!skipExtraJoin) {
                    String extraJoin = QueryAdjusterForDomain.getInstance().getJoinClause(tt.getTableInfo(), true,
                            alias, false);

                    if (StringUtils.isNotEmpty(extraJoin)) {
                        sqlStr.append(extraJoin + " ");
                    }
                }
            } else if (qri instanceof RelQRI && isSchemaExport && exportTimestamp != null) {
                RelQRI relQRI = (RelQRI) qri;
                RelationshipType relType = relQRI.getRelationshipInfo().getType();
                String formatName = null;
                for (QueryFieldPanel qfp : fieldPanels) {
                    if (qfp.getFieldQRI() == qri) {
                        formatName = qfp.getFormatName();
                        break;
                    }
                }
                DataObjDataFieldFormatIFace formatter = relQRI.getDataObjFormatter(formatName);
                if ((!relType.equals(RelationshipType.ManyToOne) && !relType.equals(RelationshipType.ManyToMany))
                        || formatter.getSingleField() == null
                        || (formatter.getSingleField() != null && formatter.getFields()[0].getSep() != null)) {
                    ProcessNode newNode = new ProcessNode(relQRI);
                    if (formatter != null) {
                        processFormatter(formatter, newNode);
                    }
                    String rootAlias = tableAbbreviator.getAbbreviation(relQRI.getTableTree().getParent());
                    String formFrom = getTimestampFrom(rootAlias, newNode, fromTbls);
                    sqlStr.append(" ");
                    sqlStr.append(formFrom);
                    sqlStr.append(" ");
                }
            }
        }
        for (ProcessNode kid : parent.getKids()) {
            hqlHasSynJoins |= processTree(kid, sqlStr, fromTbls, level + 1, tableAbbreviator, tblTree, fieldPanels,
                    searchSynonymy, isSchemaExport, exportTimestamp);
        }
        return hqlHasSynJoins;
    }

    /**
     * @param fldName
     * @return fldName formatted for use as a field in a JasperReports data source or
     *  JasperReports data connection.
     */
    public static String fixFldNameForJR(final String fldName) {
        //not totally sure this is necessary.
        //other transformations may eventually be necessary.
        return fldName.trim().replaceAll(" ", "_");
    }

    /**
     * @param fqri
     * @param forSchemaExport
     * @return the formatter for the column displaying fqri's data.
     * Generally the default or user-defined formatter is used, except in special cases for
     * Schema mapping queries.
     */
    protected static UIFieldFormatterIFace getColumnFormatter(final QueryFieldPanel qfp,
            final boolean forSchemaExport) {
        FieldQRI fqri = qfp.getFieldQRI();
        if (fqri instanceof RelQRI) {
            DataObjDataFieldFormatIFace formatter = ((RelQRI) fqri).getDataObjFormatter(qfp.getFormatName());
            if (formatter != null && formatter.getSingleField() != null
                    && formatter.getFields()[0].getSep() == null) {
                return fqri.getTableInfo().getFieldByName(formatter.getSingleField()).getFormatter();
            }
            return null;
        }
        if (forSchemaExport && (fqri.getDataClass().equals(Calendar.class)
                || java.util.Date.class.isAssignableFrom(fqri.getDataClass()))) {
            return new DateExportFormatter();
        }
        return fqri.getFormatter();
    }

    /**
     * @param queryFieldItemsArg
     * @param fixLabels
     * @return ERTICaptionInfo for the visible columns returned by a query.
     */
    public static List<ERTICaptionInfoQB> getColumnInfo(final Vector<QueryFieldPanel> queryFieldItemsArg,
            final boolean fixLabels, final DBTableInfo rootTbl, boolean forSchemaExport) {
        List<ERTICaptionInfoQB> result = new Vector<ERTICaptionInfoQB>();
        Vector<ERTICaptionInfoTreeLevelGrp> treeGrps = new Vector<ERTICaptionInfoTreeLevelGrp>(5);
        for (QueryFieldPanel qfp : queryFieldItemsArg) {
            if (qfp.getFieldQRI() == null) {
                continue;
            }

            //System.out.println(qfp.getFieldQRI().getFieldName());

            DBFieldInfo fi = qfp.getFieldInfo();
            DBTableInfo ti = null;
            if (fi != null) {
                ti = fi.getTableInfo();
            }
            String colName = qfp.getFieldName();
            if (ti != null && fi != null) {
                colName = ti.getAbbrev() + '.' + fi.getColumn();
            }
            if (qfp.isForDisplay()) {
                String lbl = qfp.getSchemaItem() == null
                        ? (qfp.getItemMapping() == null ? qfp.getLabel() : qfp.getExportedFieldName())
                        : qfp.getSchemaItem().getFieldName();
                if (fixLabels) {
                    lbl = fixFldNameForJR(lbl);
                }
                ERTICaptionInfoQB erti = null;

                //Test to see if it is actually necessary to use a ERTICaptionInfoRel for the field.
                boolean buildRelERTI = false;
                if (qfp.getFieldQRI() instanceof RelQRI) {
                    //Test to see if it is actually necessary to use a ERTICaptionInfoRel for the field.
                    RelationshipType relType = ((RelQRI) qfp.getFieldQRI()).getRelationshipInfo().getType();
                    //XXX Formatter.getSingleField() checks for ZeroOrOne and OneToOne rels.
                    if (relType != RelationshipType.ManyToOne /*&& relType != RelationshipType.ZeroOrOne && relType != RelationshipType.OneToOne*/) {
                        buildRelERTI = true;
                    } else {
                        DataObjDataFieldFormatIFace formatter = ((RelQRI) qfp.getFieldQRI())
                                .getDataObjFormatter(qfp.getFormatName());
                        if (formatter != null) {
                            buildRelERTI = formatter.getSingleField() == null || (formatter.getSingleField() != null
                                    && formatter.getFields()[0].getSep() != null);
                        } else {
                            buildRelERTI = true;
                        }
                    }
                }

                if (buildRelERTI) {
                    RelQRI rqri = (RelQRI) qfp.getFieldQRI();
                    RelationshipType relType = rqri.getRelationshipInfo().getType();
                    boolean useCache;
                    if (relType == RelationshipType.ManyToOne || relType == RelationshipType.ManyToMany) {
                        useCache = true;
                    } else {
                        //XXX actually need to be sure that this rel's table has a many-to-one relationship (direct or indirect) to the root.
                        useCache = rootTbl != null && rootTbl.getTableId() != rqri.getTableInfo().getTableId();
                    }
                    erti = new ERTICaptionInfoRel(colName, lbl, true, qfp.getFieldQRI().getFormatter(), 0,
                            qfp.getStringId(), ((RelQRI) qfp.getFieldQRI()).getRelationshipInfo(), useCache, null,
                            qfp.getFormatName());
                } else if (qfp.getFieldQRI() instanceof TreeLevelQRI) {
                    TreeLevelQRI tqri = (TreeLevelQRI) qfp.getFieldQRI();
                    for (ERTICaptionInfoTreeLevelGrp tg : treeGrps) {
                        erti = tg.addRank((TreeLevelQRI) qfp.getFieldQRI(), colName, lbl, qfp.getStringId(),
                                tqri.getRealFieldName());
                        if (erti != null) {
                            break;
                        }
                    }
                    if (erti == null) {
                        ERTICaptionInfoTreeLevelGrp newTg = new ERTICaptionInfoTreeLevelGrp(tqri.getTreeDataClass(),
                                tqri.getTreeDefId(), tqri.getTableAlias(), true, null);
                        erti = newTg.addRank(tqri, colName, lbl, qfp.getStringId(), tqri.getRealFieldName());
                        treeGrps.add(newTg);
                    }
                } else {
                    erti = new ERTICaptionInfoQB(colName, lbl, true, getColumnFormatter(qfp, forSchemaExport), 0,
                            qfp.getStringId(), qfp.getPickList(), fi);
                }
                erti.setColClass(qfp.getFieldQRI().getDataClass());
                if (qfp.getFieldInfo() != null && !(qfp.getFieldQRI() instanceof DateAccessorQRI)
                        && qfp.getFieldQRI().getFieldInfo().isPartialDate()) {
                    String precName = qfp.getFieldQRI().getFieldInfo().getDatePrecisionName();

                    Vector<ColInfo> colInfoList = new Vector<ColInfo>();
                    ColInfo columnInfo = erti.new ColInfo(StringUtils.capitalize(precName), precName);
                    columnInfo.setPosition(0);
                    colInfoList.add(columnInfo);

                    columnInfo = erti.new ColInfo(qfp.getFieldQRI().getFieldInfo().getColumn(),
                            qfp.getFieldQRI().getFieldInfo().getName());
                    columnInfo.setPosition(1);
                    colInfoList.add(columnInfo);
                    erti.setColInfoList(colInfoList);
                    erti.setColName(null);
                    // XXX We need to get this from the SchemaConfig
                    //erti.setUiFieldFormatter(UIFieldFormatterMgr.getInstance().getFormatter("PartialDate"));
                }
                if (forSchemaExport) {
                    erti.setVisible(qfp.getQueryField().getIsDisplay());
                }
                result.add(erti);
            }
        }
        for (ERTICaptionInfoTreeLevelGrp tg : treeGrps) {
            try {
                tg.setUp();
            } catch (SQLException ex) {
                UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, ex);
                ex.printStackTrace();
                throw new RuntimeException(ex);
            }
        }
        return result;
    }

    /**
     * @param queryName
     * @param fixLabels
     * @return ERTICaptionInfo for the visible columns returned by query queryName.
     */
    public static List<ERTICaptionInfo> getColumnInfo(final String queryName, final boolean fixLabels) {
        DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
        try {
            SpQuery fndQuery = session.getData(SpQuery.class, "name", queryName,
                    DataProviderSessionIFace.CompareType.Equals);
            if (fndQuery == null) {
                throw new Exception("Unable to load query " + queryName);
            }
            return getColumnInfoSp(fndQuery.getFields(), fixLabels);
        } catch (Exception e) {
            UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, e);
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            session.close();
        }
    }

    /**
     * @param qf
     * @param fi
     * 
     * @return the data class for the result column defined by qf and fi.
     */
    protected static Class<?> getDataClass(final SpQueryField qf, final DBFieldInfo fi) {
        if (Calendar.class.isAssignableFrom(fi.getDataClass())) {
            //sleazy way to see if qf is a DatePartAccessor
            String idString = qf.getStringId();
            if (idString.endsWith(fi.getName() + DateAccessorQRI.DATEPART.NumericDay.toString())
                    || idString.endsWith(fi.getName() + DateAccessorQRI.DATEPART.NumericMonth.toString())
                    || idString.endsWith(fi.getName() + DateAccessorQRI.DATEPART.NumericYear.toString())) {
                return Integer.class;
            }
        }
        return fi.getDataClass();
    }

    /**
     * @param queryFields
     * @param fixLabels
     * @return ERTICaptionInfo for the visible columns represented in an SpQuery's queryFields.
     * 
     * This method is used by QBDataSourceConnection object which current do not actually need to
     * retrieve any data, so ERTICaptionInfoQB objects are used for all columns.
     */
    public static List<ERTICaptionInfo> getColumnInfoSp(final Set<SpQueryField> queryFields,
            final boolean fixLabels) {
        List<ERTICaptionInfo> result = new Vector<ERTICaptionInfo>();
        for (SpQueryField qf : queryFields) {
            if (qf.getContextTableIdent() != null) {
                DBTableInfo ti = DBTableIdMgr.getInstance().getInfoById(qf.getContextTableIdent());
                if (!AppContextMgr.isSecurityOn() || ti.getPermissions().canView()) {
                    DBFieldInfo fi = ti.getFieldByColumnName(qf.getFieldName());
                    String colName = ti.getAbbrev() + '.' + qf.getFieldName();
                    if (qf.getIsDisplay()) {
                        String lbl = qf.getColumnAliasTitle();
                        if (fixLabels) {
                            lbl = lbl.replaceAll(" ", "_");
                            lbl = lbl.replaceAll("/", "_");
                            lbl = lbl.replaceAll("#", "_");
                        }
                        ERTICaptionInfo erti;
                        if (fi != null) {
                            erti = new ERTICaptionInfoQB(colName, lbl, true, fi.getFormatter(), 0, qf.getStringId(),
                                    RecordTypeCodeBuilder.getTypeCode(fi), fi);
                            erti.setColClass(getDataClass(qf, fi));
                        } else {
                            erti = new ERTICaptionInfoQB(colName, lbl, true, null, 0, qf.getStringId(), null, fi);
                            erti.setColClass(String.class);
                        }
                        result.add(erti);
                    }
                }
            } else
                log.error("null contextTableIdent for " + qf.getFieldName());
        }
        return result;
    }

    /**
     * @return list reports that use this.query as a data source.
     */
    protected List<SpReport> getReports() {
        DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
        try {
            //currently experiencing hibernate weirdness with loading of query's reports set so...
            //Object id = query.getId();
            return session.getDataList(SpReport.class, "query", query);
        } finally {
            session.close();
        }
    }

    /**
     * @param report
     * 
     * Loads and runs the query that acts as data source for report. Then runs report.
     */
    public static void runReport(final SpReport report, final String title, final RecordSetIFace rs) {
        //XXX This is now also used to run Workbench reports. Really should extract the general stuff out
        //to a higher level...
        boolean isQueryBuilderRep = report.getReportObject() instanceof SpQuery;
        if (isQueryBuilderRep) {
            UsageTracker.incrUsageCount("QB.RunReport." + report.getQuery().getContextName());
        } else {
            UsageTracker.incrUsageCount("WB.RunReport");
        }
        TableTree tblTree = null;
        Hashtable<String, TableTree> ttHash = null;
        QueryParameterPanel qpp = null;
        if (isQueryBuilderRep) {
            UsageTracker.incrUsageCount("QB.RunReport." + report.getQuery().getContextName());
            QueryTask qt = (QueryTask) ContextMgr.getTaskByClass(QueryTask.class);
            if (qt != null) {
                Pair<TableTree, Hashtable<String, TableTree>> trees = qt.getTableTrees();
                tblTree = trees.getFirst();
                ttHash = trees.getSecond();
            } else {
                log.error("Could not find the Query task when running report " + report.getName());
                //blow up
                throw new RuntimeException("Could not find the Query task when running report " + report.getName());
            }
            qpp = new QueryParameterPanel();
            qpp.setQuery(report.getQuery(), tblTree, ttHash);
        }
        boolean go = true;
        try {
            JasperCompilerRunnable jcr = new JasperCompilerRunnable(null, report.getName(), null);
            jcr.findFiles();
            if (jcr.isCompileRequired()) {
                jcr.get();
            }
            //if isCompileRequired() is still true, then an error probably occurred compiling the report.
            JasperReport jr = !jcr.isCompileRequired() ? (JasperReport) JRLoader.loadObject(jcr.getCompiledFile())
                    : null;
            ReportParametersPanel rpp = jr != null ? new ReportParametersPanel(jr, true) : null;
            JRDataSource src = null;
            if (rs == null && ((qpp != null && qpp.getHasPrompts()) || (rpp != null && rpp.getParamCount() > 0))) {
                Component pane = null;
                if (qpp != null && qpp.getHasPrompts() && rpp != null && rpp.getParamCount() > 0) {
                    pane = new JTabbedPane();
                    ((JTabbedPane) pane).addTab(UIRegistry.getResourceString("QB_REP_RUN_CRITERIA_TAB_TITLE"),
                            new JScrollPane(qpp, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                                    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER));

                    ((JTabbedPane) pane).addTab(UIRegistry.getResourceString("QB_REP_RUN_PARAM_TAB_TITLE"),
                            new JScrollPane(rpp, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                                    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER));
                } else if (qpp != null && qpp.getHasPrompts()) {
                    pane = new JScrollPane(qpp, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
                } else {
                    pane = new JScrollPane(rpp, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
                }
                CustomDialog cd = new CustomDialog((Frame) UIRegistry.getTopWindow(),
                        UIRegistry.getResourceString("QB_GET_REPORT_CONTENTS_TITLE"), true,
                        CustomDialog.OKCANCELHELP, pane);
                cd.setHelpContext("RepRunSettings");
                cd.createUI();
                Dimension ps = cd.getPreferredSize();
                ps.setSize(ps.getWidth() * 1.3, ps.getHeight());
                cd.setSize(ps);
                UIHelper.centerAndShow(cd);
                go = !cd.isCancelled();
                cd.dispose();
            }
            if (go) {
                if (isQueryBuilderRep) {
                    TableQRI rootQRI = null;
                    int cId = report.getQuery().getContextTableId();
                    for (TableTree tt : ttHash.values()) {
                        if (cId == tt.getTableInfo().getTableId()) {
                            rootQRI = tt.getTableQRI();
                            break;
                        }
                    }
                    Vector<QueryFieldPanel> qfps = new Vector<QueryFieldPanel>(qpp.getFields());
                    for (int f = 0; f < qpp.getFields(); f++) {
                        qfps.add(qpp.getField(f));
                    }

                    HQLSpecs sql = null;

                    // XXX need to allow modification of SelectDistinct(etc) ???
                    //boolean includeRecordIds = true;
                    boolean includeRecordIds = !report.getQuery().isSelectDistinct();

                    try {
                        //XXX Is it safe to assume that query is not an export query? 
                        sql = QueryBldrPane.buildHQL(rootQRI, !includeRecordIds, qfps, tblTree, rs,
                                report.getQuery().getSearchSynonymy() == null ? false
                                        : report.getQuery().getSearchSynonymy(),
                                false, null);
                    } catch (Exception ex) {
                        String msg = StringUtils.isBlank(ex.getLocalizedMessage())
                                ? getResourceString("QB_RUN_ERROR")
                                : ex.getLocalizedMessage();
                        UIRegistry.getStatusBar().setErrorMessage(msg, ex);
                        UIRegistry.writeTimedSimpleGlassPaneMsg(msg, Color.RED);
                        return;
                    }
                    int smushedCol = (report.getQuery().getSmushed() != null && report.getQuery().getSmushed())
                            ? getSmushedCol(qfps) + 1
                            : -1;
                    src = new QBDataSource(sql.getHql(), sql.getArgs(), sql.getSortElements(),
                            getColumnInfo(qfps, true, rootQRI.getTableInfo(), false), includeRecordIds,
                            report.getRepeats(), smushedCol, /*getRecordIdCol(qfps)*/0);
                    ((QBDataSource) src).startDataAcquisition();
                } else {
                    DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
                    try {
                        boolean loadedWB = false;
                        if (rs != null && rs.getOnlyItem() != null) {
                            Workbench wb = session.get(Workbench.class, rs.getOnlyItem().getRecordId());
                            if (wb != null) {
                                wb.forceLoad();
                                src = new WorkbenchJRDataSource(wb, true, report.getRepeats());
                                loadedWB = true;
                            }
                        }
                        if (!loadedWB) {
                            UIRegistry.displayErrorDlgLocalized("QueryBldrPane.WB_LOAD_ERROR_FOR_REPORT",
                                    rs != null ? rs.getName() : "[" + UIRegistry.getResourceString("NONE") + "]");
                            return;
                        }
                    } finally {
                        session.close();
                    }
                }

                final CommandAction cmd = new CommandAction(ReportsBaseTask.REPORTS, ReportsBaseTask.PRINT_REPORT,
                        src);
                cmd.setProperty("title", title);
                cmd.setProperty("file", report.getName());
                if (rs == null) {
                    cmd.setProperty("skip-parameter-prompt", "true");
                }
                //if isCompileRequired is true then an error probably occurred while compiling,
                //and, if so, it will be caught again and reported in the report results pane.
                if (!jcr.isCompileRequired()) {
                    cmd.setProperty("compiled-file", jcr.getCompiledFile());
                }
                if (rpp != null && rpp.getParamCount() > 0) {
                    StringBuilder params = new StringBuilder();
                    for (int p = 0; p < rpp.getParamCount(); p++) {
                        Pair<String, String> param = rpp.getParam(p);
                        if (StringUtils.isNotBlank(param.getSecond())) {
                            params.append(param.getFirst());
                            params.append("=");
                            params.append(param.getSecond());
                            params.append(";");
                        }
                        cmd.setProperty("params", params.toString());
                    }
                }
                CommandDispatcher.dispatch(cmd);
            }
        } catch (JRException ex) {
            UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, ex);
            log.error(ex);
            ex.printStackTrace();
        }
    }

    /**
     * called when retrieved results have been displayed.
     */
    public void resultsComplete() {
        //debug
        //System.out.println((System.nanoTime() - startTime.get()) / 1000000000L);

        completedResults.set(runningResults.get());
        runningResults.set(null);
        if (completedResults.get() != null && !completedResults.get().getCancelled()) {
            int results = completedResults.get().getQuery().getDataObjects().size();
            if (results > completedResults.get().getMaxTableRows() && !countOnly
                    && !completedResults.get().getQuery().isCancelled()) {
                if (schemaMapping == null) {
                    UIRegistry.displayInfoMsgDlgLocalized("QB_PARTIAL_RESULTS_DISPLAY",
                            completedResults.get().getMaxTableRows(), results);
                } else {
                    UIRegistry.displayInfoMsgDlgLocalized("QB_PREVIEW_DISPLAY",
                            completedResults.get().getMaxTableRows(), results);
                }
            } else {
                if (schemaMapping != null) {
                    UIRegistry.displayInfoMsgDlgLocalized("QB_PREVIEW_DISPLAY_TINY");
                }
            }
        }
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                String searchLbl = schemaMapping == null ? getResourceString("QB_SEARCH")
                        : getResourceString("QB_EXPORT_PREVIEW");
                QueryBldrPane.this.searchBtn.setText(searchLbl);
                if (query != null) {
                    UIRegistry.getStatusBar().setProgressDone(query.getName());
                }
                UIRegistry.displayStatusBarText("");
            }
        });
    }

    /**
     * @return countOnly
     */
    public boolean isCountOnly() {
        return countOnly;
    }

    protected void launchPartialResultDisplayMsg(final String hql) {
        new SwingWorker() {
            protected Integer count = null;

            /* (non-Javadoc)
             * @see edu.ku.brc.helpers.SwingWorker#construct()
             */
            @Override
            public Object construct() {
                String countHql = QueryBldrPane.getCountHql(hql);
                DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
                try {
                    QueryIFace q = session.createQuery(countHql, false);
                    count = (Integer) q.list().get(0);
                } finally {
                    session.close();
                }
                return null;
            }

            /* (non-Javadoc)
             * @see edu.ku.brc.helpers.SwingWorker#finished()
             */
            @Override
            public void finished() {
                if (count != null && count > ExpressSearchTask.RESULTS_THRESHOLD) {
                    String msg = String.format(
                            UIRegistry.getResourceString("QB_DISPLAYING_RETRIEVED_RESULTS_PARTIAL"),
                            String.valueOf(count),
                            String.format("%04.2f", (doneTime.get() - startTime.get()) / 1000000000D),
                            String.valueOf(ExpressSearchTask.RESULTS_THRESHOLD));
                    //System.out.println(msg);
                    UIRegistry.displayStatusBarText(msg);
                }
            }

        }.start();
    }

    /**
     * Called when the db record retrieval task has completed.
     */
    public boolean queryTaskDone() {
        boolean result = true;
        doneTime.set(System.nanoTime());
        if (runningResults.get() != null && runningResults.get().getQuery() != null) {
            if (!countOnly && !runningResults.get().getQuery().isCancelled()
                    && !runningResults.get().getQuery().isInError()) {
                final int results = runningResults.get().getQuery().getDataObjects().size();

                if (results == ExpressSearchTask.RESULTS_THRESHOLD) {
                    launchPartialResultDisplayMsg(runningResults.get().getHQL());
                }

                String msg = "";
                if (results <= runningResults.get().getMaxTableRows()) {
                    msg = String.format(UIRegistry.getResourceString("QB_DISPLAYING_RETRIEVED_RESULTS"),
                            String.valueOf(results),
                            String.format("%04.2f", (doneTime.get() - startTime.get()) / 1000000000D));
                } else if (!runningResults.get().isPostSorted()) {
                    msg = String.format(UIRegistry.getResourceString("QB_DISPLAYING_RETRIEVED_RESULTS_PARTIAL"),
                            String.valueOf(results),
                            String.format("%04.2f", (doneTime.get() - startTime.get()) / 1000000000D),
                            String.valueOf(runningResults.get().getMaxTableRows()));
                } else {
                    result = false;
                }
                if (result) {
                    UIRegistry.displayStatusBarText(msg);
                    if (query != null && runningResults != null && runningResults.get() != null) {
                        final String qName = query.getName();
                        final int qResults = results;
                        final int qMaxRows = runningResults.get().getMaxTableRows();
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                //UIRegistry.getStatusBar().setIndeterminate(query.getName(), true);
                                UIRegistry.getStatusBar().setProgressRange(qName, 0, Math.min(qResults, qMaxRows));
                            }
                        });
                    }
                }
            }

            boolean isExtraOK = false;
            if (runningResults != null && runningResults.get() != null && runningResults.get().getQuery() != null) {
                isExtraOK = runningResults.get().getQuery().isCancelled()
                        || runningResults.get().getQuery().isInError();
            }

            if (!result || countOnly || isExtraOK) {
                UIRegistry.displayStatusBarText("");
                final String qName = query.getName();
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        String searchLbl = schemaMapping == null ? getResourceString("QB_SEARCH")
                                : getResourceString("QB_EXPORT_PREVIEW");
                        QueryBldrPane.this.searchBtn.setText(searchLbl);
                        UIRegistry.getStatusBar().setProgressDone(qName);

                    }
                });
                result = false;
            }
            if (!result || (countOnly && !runningResults.get().getQuery().isCancelled()
                    && !runningResults.get().getQuery().isInError())) {
                if (countOnly) {
                    Object resultObj = runningResults.get().getQuery().getDataObjects().get(0);
                    final int count = (Integer) resultObj;

                    UIRegistry.showLocalizedMsg("QB_COUNT_TITLE", "QB_COUNT_MSG", count);

                    //UIRegistry.showLocalizedMsg("QB_COUNT_TITLE", "QB_COUNT_MSG", runningResults.get().getQuery().getDataObjects().size());
                } else if (runningResults.get().isPostSorted()) {
                    //this should be the case where more records were returned than can be post-sorted...

                    PanelBuilder pb = new PanelBuilder(
                            new FormLayout("5dlu, f:p:g, 5dlu", "5dlu, f:p:g, 2dlu, f:p:g, 2dlu, f:p:g, 5dlu"));
                    pb.add(new JLabel(String.format(UIRegistry.getResourceString("QB_CANT_DISPLAY_MSG1"),
                            runningResults.get().getQuery().getDataObjects().size())),
                            new CellConstraints().xy(2, 2));
                    pb.add(new JLabel(String.format(UIRegistry.getResourceString("QB_CANT_DISPLAY_MSG2"),
                            runningResults.get().getMaxTableRows())), new CellConstraints().xy(2, 4));
                    pb.add(new JLabel(UIRegistry.getResourceString("QB_CANT_DISPLAY_MSG3")),
                            new CellConstraints().xy(2, 6));

                    CustomDialog dlg = new CustomDialog((Frame) UIRegistry.getTopWindow(),
                            UIRegistry.getResourceString("QB_CANT_DISPLAY_TITLE"), true, CustomDialog.OKHELP,
                            pb.getPanel());
                    dlg.setHelpContext("QBTooManyRecordsForSort");
                    UIHelper.centerAndShow(dlg);
                    dlg.dispose();
                }
            }
        }
        return result;
    }

    /**
     * @param queryFieldItemsArg
     * @param sql
     * @param params
     * @param rootTable
     * @param distinct
     */
    @SuppressWarnings("unchecked")
    protected void processSQL(final Vector<QueryFieldPanel> queryFieldItemsArg, final HQLSpecs hqlSpecs,
            final DBTableInfo rootTable, final boolean distinct) {
        List<? extends ERTICaptionInfo> captions = getColumnInfo(queryFieldItemsArg, false, rootTable, false);

        String iconName = distinct ? "BlankIcon" : rootTable.getClassObj().getSimpleName();
        int tblId = distinct ? -1 : rootTable.getTableId();
        final QBQueryForIdResultsHQL qri = new QBQueryForIdResultsHQL(TITLEBAR_COLOR,
                getResourceString("QB_SEARCH_RESULTS"), iconName, tblId, this);

        String hql = hqlSpecs.getHql();
        qri.setCount(countOnly);
        qri.setSQL(hql);
        qri.setParams(hqlSpecs.getArgs());
        qri.setSort(hqlSpecs.getSortElements());
        // XXX check generics reference book. (unchecked conversion here)
        qri.setCaptions((List<ERTICaptionInfo>) captions);
        qri.setExpanded(true);
        qri.setHasIds(!distinct);
        boolean filterDups = hqlSpecs.isHasSynJoins();
        if (!filterDups && distinct) {
            for (ERTICaptionInfo caption : captions) {
                if (caption instanceof ERTICaptionInfoTreeLevel) {
                    filterDups = true;
                    break;
                } else if (caption instanceof ERTICaptionInfoRel) {
                    RelationshipType relType = ((ERTICaptionInfoRel) caption).getRelationship().getType();
                    if (relType.equals(RelationshipType.OneToMany) || relType.equals(RelationshipType.ManyToMany)) {
                        filterDups = true;
                        break;
                    }
                }
            }
        }
        qri.setFilterDups(filterDups);
        if (schemaMapping != null) {
            qri.setMaxTableRows(ExportSchemaPreviewSize);
        } else {
            qri.setMaxTableRows(ExpressSearchTask.RESULTS_THRESHOLD);
        }
        runningResults.set(qri);
        doneTime.set(-1);

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                if (runningResults.get() != null && !runningResults.get().getCancelled()) {
                    UIRegistry.getStatusBar().setText(UIRegistry.getResourceString("QB_SEARCHING"));
                    searchBtn.setText(UIRegistry.getResourceString("QB_CANCEL"));
                    UIRegistry.getStatusBar().setIndeterminate(query.getName(), true);
                }
                //else the query got cancelled or crashed before this thread was executed
            }
        });

        new SwingWorker() {
            @Override
            public Object construct() {
                if (schemaMapping != null
                        && !countOnly /*this means the duplicate msg won't appear when counts are done */) {
                    if (!checkUniqueRecIds(hqlSpecs.getHql(), hqlSpecs.getArgs()).getFirst()) {
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                UIRegistry.displayErrorDlg(
                                        UIRegistry.getResourceString("ExportPanel.DUPLICATE_KEYS_EXPORT"));
                            }
                        });
                        runningResults.set(null);
                        resultsComplete();
                        return null;
                    }
                }
                if (esrp == null) {
                    CommandAction cmdAction = new CommandAction("Express_Search", "HQL", qri);
                    cmdAction.setProperty("reuse_panel", true);
                    CommandDispatcher.dispatch(cmdAction);
                } else {
                    esrp.addSearchResults(qri);
                }
                return null;
            }
        }.start();
        startTime.set(System.nanoTime());
    }

    /**
     * @param pn
     * @param lvl
     */
    protected static void printTree(ProcessNode pn, int lvl) {
        for (int i = 0; i < lvl; i++) {
            log.debug(" ");
            System.out.print(" ");
        }
        log.debug(pn);
        System.out.println(pn);
        for (ProcessNode kid : pn.getKids()) {
            printTree(kid, lvl + 1);
        }
    }

    /**
     * @return a clone of the current query object.
     */
    protected SpQuery cloneTheQuery() {
        SpQuery result = new SpQuery();
        result.initialize();
        result.setSpecifyUser(AppContextMgr.getInstance().getClassObject(SpecifyUser.class));
        result.setName(query.getName());
        result.setContextTableId(query.getContextTableId());
        result.setContextName(query.getContextName());
        //result.setCreatedByAgent(query.getCreatedByAgent());
        for (QueryFieldPanel qfp : queryFieldItems) {
            SpQueryField qf = qfp.getQueryField();

            if (qf != null) {
                SpQueryField newQf = new SpQueryField();
                newQf.initialize();
                newQf.setFieldName(qf.getFieldName());
                newQf.setPosition(qf.getPosition());
                qfp.justSetTheQueryField(newQf);
                qfp.updateQueryField(newQf);
                result.addReference(newQf, "fields");
            }
        }
        return result;
    }

    /**
     * @param clonedQuery
     * @return
     * 
     */
    protected SpExportSchemaMapping cloneTheSchemaMapping(SpQuery clonedQuery) {
        SpExportSchemaMapping result = new SpExportSchemaMapping();
        result.initialize();
        result.setMappingName(schemaMapping.getMappingName());
        result.setDescription(schemaMapping.getDescription());
        result.setSpExportSchema(schemaMapping.getSpExportSchema());
        for (SpExportSchemaItemMapping item : schemaMapping.getMappings()) {
            SpExportSchemaItemMapping newItem = new SpExportSchemaItemMapping();
            newItem.initialize();
            newItem.setExportSchemaItem(item.getExportSchemaItem());
            SpQueryField qf = item.getQueryField();
            for (SpQueryField newQf : clonedQuery.getFields()) {
                if (qf.getStringId().equalsIgnoreCase(newQf.getStringId())) {
                    newItem.setQueryField(newQf);
                    newQf.setMapping(newItem);
                    break;
                }
            }
            newItem.setExportSchemaMapping(result);
            result.getMappings().add(newItem);

        }
        return result;
    }

    /**
     * @return an un-used name based on the schema name and version.
     * 
     * 
     * NOTE: sets query.name AND schemaMapping.mappingName
     */
    protected boolean getExportMappingQueryName() {
        //not worrying about multi-user issues
        String baseName = exportSchema.getSchemaName() + exportSchema.getSchemaVersion();
        String result = baseName;
        int cnt = BasicSQLUtils.getCount("select count(*) from spquery where name = '" + result + "'");
        int suffix = 2;
        while (cnt > 0) {
            result = baseName + "_" + suffix;
            cnt = BasicSQLUtils.getCount("select count(*) from spquery where name = '" + result + "'");
            suffix++;
        }
        query.setName(result);
        schemaMapping.setMappingName(result);
        return true;
    }

    /**
     * @param saveAs
     * @return
     */
    protected boolean saveQuery(final boolean saveAs) {
        boolean result = false;
        if (!canSave(true)) {
            setSaveBtnEnabled(false);
            return false;
        }

        //if (!isExportMapping)
        //{
        if (!query.isNamed() || saveAs) {
            if (!getQueryNameFromUser(saveAs)) {
                return false;
            }
        }
        //} else
        //{
        //   if (!query.isNamed() || saveAs)
        //   {
        //      if (!getExportMappingQueryName())
        //      {
        //         return false;
        //      }
        //   }      
        //}

        UsageTracker.incrUsageCount("QB.SaveQuery." + query.getContextName());

        //This is necessary to indicate that a query has been changed when only field deletes have occurred.
        //If the query's timestampModified is not modified the schema export tool doesn't know the 
        //export schema needs to be rebuilt.
        if (!saveAs && query.getId() != null) {
            int origCount = BasicSQLUtils
                    .getCountAsInt("select count(*) from spqueryfield where spqueryid=" + query.getId());
            if (origCount > query.getFields().size()) {
                query.setTimestampModified(new Timestamp(System.currentTimeMillis()));
            }
        }

        TableQRI tableQRI = (TableQRI) tableList.getSelectedValue();
        if (tableQRI != null) {
            short position = 0;

            Set<Integer> queryFldsWithoutPanels = new HashSet<Integer>();
            for (SpQueryField qf : query.getFields()) {
                //System.out.println(qf.getFieldName());
                queryFldsWithoutPanels.add(qf.getId());
            }

            for (QueryFieldPanel qfp : queryFieldItems) {
                if (qfp.getQueryField() != null) {
                    SpQueryField qf = qfp.getQueryField();
                    queryFldsWithoutPanels.remove(qf.getId());
                    qf.setPosition(position);
                    qfp.updateQueryField();

                    position++;
                }
            }

            if (!checkCriteriaLengths(query)) {
                return false;
            }

            //Remove query fields for which panels could be created in order to prevent
            //repeat of missing fld message in getQueryFieldPanels() whenever this query is loaded.
            for (Integer qfId : queryFldsWithoutPanels) {
                //this is real lame but should hardly ever need to be executed
                for (SpQueryField qf : query.getFields()) {
                    if (qfId != null && qf != null && qf.getId() != null && qfId.equals(qf.getId())) {
                        query.getFields().remove(qf);
                        break;
                    }
                }
            }

            if (query.getSpQueryId() == null || saveAs) {
                if (query.getSpQueryId() != null && saveAs) {
                    query = cloneTheQuery();
                    if (schemaMapping != null) {
                        schemaMapping = cloneTheSchemaMapping(query);
                    }
                    queryNavBtn.setEnabled(true);
                }

                queryNavBtn = ((QueryTask) task).saveNewQuery(query, schemaMapping, false); // false tells it to
                // disable the
                // navbtn

                query.setNamed(true); //XXX this isn't getting persisted!!!!!!!!!
                //                if (query.getSpQueryId() != null && saveAs)
                //                {
                //                    try 
                //                    {
                //                       this.setupUI();
                //                    } catch (QueryTask.QueryBuilderContextException e) {
                //                       //It can't happen here. 
                //                    }
                //                }   

                SubPaneMgr.getInstance().renamePane(this, query.getName());

                return true;
            }

            if (schemaMapping != null) {
                //result =  DataModelObjBase.saveWithError(true, query, schemaMapping);
                result = DataModelObjBase.saveWithError(true, schemaMapping, query);
            } else {
                result = DataModelObjBase.saveWithError(true, query);
            }
            if (result) {
                DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
                try {
                    query = session.get(SpQuery.class, query.getId());
                    query.forceLoad(true);
                    schemaMapping = query.getMapping();
                    if (schemaMapping != null) {
                        schemaMapping.forceLoad();
                    }
                } finally {
                    session.close();
                }
            }
            return result;
        }
        //else
        {
            log.error("No Context selected!");
            return false;
        }
    }

    /**
     * @param query
     * @return true if query can be saved
     * 
     * checks that criteria lengths can fit in to the db fields that store them.
     * if un-saveable values exists, user is allowed to cancel save or
     * save with the un-saveable values discarded.
     */
    protected boolean checkCriteriaLengths(final SpQuery query) {
        boolean result = true;
        Set<SpQueryField> flds = query.getFields();

        DBTableInfo tblInfo = DBTableIdMgr.getInstance().getInfoByTableName("spqueryfield");
        int maxStartLen = tblInfo.getFieldByColumnName("StartValue").getLength();
        int maxEndLen = tblInfo.getFieldByColumnName("EndValue").getLength();
        Vector<String> badFields = new Vector<String>();
        if (flds != null) {
            for (SpQueryField fld : flds) {
                Integer startLen = fld.getStartValue() == null ? 0 : fld.getStartValue().length();
                Integer endLen = fld.getEndValue() == null ? 0 : fld.getEndValue().length();
                if (startLen > maxStartLen || endLen > maxEndLen) {
                    badFields.add(fld.getColumnAliasTitle());
                }
            }
            if (badFields.size() > 0) {
                String fldList = "";
                for (int f = 0; f < 5 && f < badFields.size(); f++) {
                    if (f > 0)
                        fldList += ", ";
                    fldList += badFields.get(f);
                }
                if (badFields.size() > 5)
                    fldList += ", ... ";
                result = UIRegistry
                        .displayConfirm(UIRegistry.getResourceString("QueryTask.CRITERIA_TOO_LONG_TITLE"),
                                String.format(UIRegistry.getResourceString("QueryTask.CRITERIA_TOO_LONG_MSG"),
                                        fldList),
                                UIRegistry.getResourceString("Ok"), UIRegistry.getResourceString("Cancel"),
                                JOptionPane.WARNING_MESSAGE);
                if (result) {
                    for (SpQueryField fld : flds) {
                        Integer startLen = fld.getStartValue() == null ? 0 : fld.getStartValue().length();
                        Integer endLen = fld.getEndValue() == null ? 0 : fld.getEndValue().length();
                        if (startLen > maxStartLen) {
                            fld.setStartValue("");
                        }
                        if (endLen > maxEndLen) {
                            fld.setEndValue("");
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     * @return
     */
    protected String getNameSavePrompt() {
        return this.task instanceof ExportMappingTask ? UIRegistry.getResourceString("MappingNamePrompt")
                : UIRegistry.getResourceString("QB_Q_NAME_PROMPT");
    }

    /**
     * @return
     */
    protected String getSaveDlgTitle(boolean saveAs) {
        String key = this.task instanceof ExportMappingTask ? saveAs ? "SaveMappingAsTitle" : "SaveMappingTitle"
                : saveAs ? "QB_SAVE_Q_AS_TITLE" : "QB_SAVE_Q_TITLE";
        return UIRegistry.getResourceString(key);
    }

    /**
     * @return true if a valid query name was obtained from user
     */
    protected boolean getQueryNameFromUser(final boolean saveAs) {
        DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
        try {
            String newQueryName = query.getName();
            String oldQueryName = query.getName();
            SpQuery fndQuery = null;
            boolean good = false;
            do {
                //if (QueryTask.askUserForInfo("Query", getResourceString("QB_DATASET_INFO"), query))
                //Using above method causes Hibernate 'Dirty Collection' issues for save as.
                //Currently the only info involved is the name so using a simple dialog box is good enuf.
                JTextField nameText = new JTextField(newQueryName);
                JLabel nameLbl = UIHelper.createLabel(getNameSavePrompt());
                PanelBuilder pane = new PanelBuilder(
                        new FormLayout("4dlu, p, 2dlu, fill:p:grow, 4dlu", "5dlu, p, 5dlu"));
                CellConstraints cc = new CellConstraints();
                pane.add(nameLbl, cc.xy(2, 2));
                pane.add(nameText, cc.xy(4, 2));
                CustomDialog cd = new CustomDialog((Frame) UIRegistry.getTopWindow(), getSaveDlgTitle(saveAs), true,
                        CustomDialog.OKCANCELHELP, pane.getPanel());
                cd.setHelpContext("QBSave");
                UIHelper.centerAndShow(cd);
                if (!cd.isCancelled()) {
                    //newQueryName = query.getName();
                    newQueryName = nameText.getText();
                    if (StringUtils.isNotEmpty(newQueryName) && newQueryName.length() > 64) {
                        UIRegistry.getStatusBar().setErrorMessage(getResourceString("QB_NAME_TOO_LONG"));
                        UIRegistry.displayErrorDlg(getResourceString("QB_NAME_TOO_LONG"));
                    } else if (StringUtils.isEmpty(newQueryName)) {
                        UIRegistry.getStatusBar().setErrorMessage(getResourceString("QB_ENTER_A_NAME"));
                        UIRegistry.displayErrorDlg(getResourceString("QB_ENTER_A_NAME"));
                    } else if (!UIHelper.isValidNameForDB(newQueryName)) {
                        UIRegistry.displayErrorDlg(getResourceString("INVALID_CHARS_NAME"));
                        UIRegistry.displayLocalizedStatusBarError("INVALID_CHARS_NAME");
                        Toolkit.getDefaultToolkit().beep();
                    } else {
                        fndQuery = session.getData(SpQuery.class, "name", newQueryName,
                                DataProviderSessionIFace.CompareType.Equals);
                        if (fndQuery != null) {
                            UIRegistry.getStatusBar().setErrorMessage(
                                    String.format(getResourceString("QB_QUERY_EXISTS"), newQueryName));
                            UIRegistry.displayErrorDlg(
                                    String.format(getResourceString("QB_QUERY_EXISTS"), newQueryName));
                        } else {
                            good = true;
                            query.setName(newQueryName);
                        }
                    }
                } else {
                    query.setName(oldQueryName);
                    return false;
                }
            } while (!good);
        } catch (Exception ex) {
            UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, ex);
            log.error(ex);

        } finally {
            session.close();
        }
        UIRegistry.getStatusBar().setText("");
        if (isExportMapping) {
            schemaMapping.setMappingName(query.getName());
        }
        return true;
    }

    /**
     * @param parentTT
     * @param nameArg
     * @return
     */
    protected static TableTree findTableTree(final TableTree parentTT, final String nameArg) {
        for (int k = 0; k < parentTT.getKids(); k++) {
            TableTree tt = parentTT.getKid(k);
            if (tt.getName().equals(nameArg)) {
                return tt;
            }
        }
        return null;
    }

    /**
     * @param parentQRI
     * @param tableTree
     * @param model
     */
    protected void createNewList(final TableQRI tblQRI, final DefaultListModel model) {

        model.clear();
        if (tblQRI != null) {
            Vector<BaseQRI> sortList = new Vector<BaseQRI>();

            for (int f = 0; f < tblQRI.getFields(); f++) {
                if (!tblQRI.getField(f).isFieldHidden()) {
                    sortList.add(tblQRI.getField(f));
                }
            }
            for (int k = 0; k < tblQRI.getTableTree().getKids(); k++) {
                boolean addIt;
                TableTree kidK = tblQRI.getTableTree().getKid(k);
                if (kidK.isAlias()) {
                    //                   if (!fixAliases(kidK, tableTreeHash))
                    //                    {
                    //                        addIt = false;
                    //                    }
                    //                    else
                    //                    {
                    //                        addIt = tblIsDisplayable(kidK, tableTreeHash.get(kidK.getName())
                    //                                .getTableInfo());
                    //                    }
                    addIt = tblIsDisplayable(kidK, tableTreeHash.get(kidK.getName()).getTableInfo())
                            && fixAliases(kidK, tableTreeHash);
                } else {
                    addIt = !kidK.getTableInfo().isHidden() && tblIsDisplayable(kidK, kidK.getTableInfo());
                }
                if (addIt) {
                    if (kidK.getTableQRI().getRelationship() == null
                            || !kidK.getTableQRI().getRelationship().isHidden()) {
                        sortList.add(tblQRI.getTableTree().getKid(k).getTableQRI());
                    }
                }
            }

            Collections.sort(sortList);
            checkFldUsage(tblQRI.getTableTree(), sortList);
            for (QryListRendererIFace qri : sortList) {
                model.addElement(qri);
            }
        }
    }

    /**
     * @param tblTree
     * @param flds
     * 
     * Checks and updates isInUse status for fields in a newly created list of search fields.
     */
    protected void checkFldUsage(final TableTree tblTree, final Vector<BaseQRI> flds) {
        String treeStr = tblTree.getPathFromRootAsString() + tblTree.getField();

        for (QueryFieldPanel qfp : this.queryFieldItems) {
            FieldQRI qri = qfp.getFieldQRI();
            if (qri instanceof RelQRI) {
                //if (qri.getTable().getTableTree().getParent() == tblTree)
                TableTree qriTT = qri.getTable().getTableTree().getParent();
                String qriTTStr = qriTT.getPathFromRootAsString() + qriTT.getField();
                if (qriTTStr.equals(treeStr)) {
                    for (BaseQRI fld : flds) {
                        if (fld instanceof TableQRI) {
                            if (((TableQRI) fld).getRelationship() == ((RelQRI) qri).getRelationshipInfo()) {
                                fld.setIsInUse(true);
                            }
                        }
                    }
                }
            } else if (qri != null
                    && ((qri.getTableTree().getPathFromRootAsString() + qri.getTableTree().getField())
                            .equals(treeStr))) {
                for (BaseQRI fld : flds) {
                    if (fld instanceof FieldQRI) {
                        FieldQRI qri2 = (FieldQRI) fld;
                        if (qri2.getStringId().equals(qri.getStringId())) {
                            qri2.setIsInUse(true);
                        }
                    }
                }
            }
        }
    }

    /**
     * @param aliasTbl
     * @param tblInfo
     * @return true if aliasTbl should be displayed in the fields list for the current context.
     */
    protected static boolean tblIsDisplayable(final TableTree aliasTbl, final DBTableInfo tblInfo) {
        /*
        if (aliasTbl.isAlias())
        {
        return !isCyclic(aliasTbl, tblInfo.getTableId()) || isCyclicable(aliasTbl, tblInfo);
        }
        //else
        return true;
        */

        return !isCyclic(aliasTbl, tblInfo.getTableId()) || isCyclicable(aliasTbl, tblInfo);

    }

    /**
     * @param alias
     * @param tblId
     * @return true if the specified alias represents a table that is already
     * present in the alias' tabletree.
     */
    protected static boolean isCyclic(final TableTree alias, final int tblId) {
        TableTree parent = alias.getParent();
        while (parent != null) {
            if (parent.getTableInfo() != null && parent.getTableInfo().getTableId() == tblId) {
                return true;
            }
            parent = parent.getParent();
        }
        return false;
    }

    /**
     * @param alias
     * @param tblInfo
     * @return true if it is OK for the specified alias to create a cycle.
     */
    protected static boolean isCyclicable(final TableTree alias, final DBTableInfo tblInfo) {
        if (Treeable.class.isAssignableFrom(tblInfo.getClassObj())) {
            if (alias.getField() != null
                    && (alias.getField().startsWith("accepted") || alias.getField().startsWith("hybrid"))) {
                TableTree parent = alias.getParent();
                int loop = 0;
                while (parent != null) {
                    if (parent.getTableInfo() != null
                            && parent.getTableInfo().getTableId() == tblInfo.getTableId()) {
                        if (parent.getField() != null && (alias.getField().startsWith("accepted")
                                || alias.getField().startsWith("hybrid"))) {
                            if (++loop > 1) {
                                return false;
                            }
                        }
                    } else {
                        break;
                    }
                    parent = parent.getParent();
                }
            }
            return true;
        } else if (Container.class.isAssignableFrom(tblInfo.getClassObj())) {
            TableTree parent = alias.getParent();
            if (alias.getField().equals("parent")) {
                if (parent != null) {
                    //prevent loop back to parent container from expansion of Container.children
                    if (parent.getTableInfo() != null
                            && parent.getTableInfo().getTableId() == tblInfo.getTableId()) {
                        if (parent.getField() != null && parent.getField().equals("children")) {
                            return false;
                        }
                    }
                }
                int parentCount = 0;
                while (parent != null && parentCount < maxParentChainLen) {
                    parentCount++;
                    TableTree grandParent = null;
                    if (parent.getTableInfo() != null
                            && parent.getTableInfo().getTableId() == tblInfo.getTableId()) {
                        if (parent.getField() != null && parent.getField().equals("parent")) {
                            grandParent = parent.getParent();
                        }
                    }
                    parent = grandParent;
                }
                if (parentCount == maxParentChainLen) {
                    return false;
                }

            } else if (alias.getField().equals("children")) {
                if (parent != null) {
                    //prevent loop back to children container from expansion of Container.parent
                    if (parent.getTableInfo() != null
                            && parent.getTableInfo().getTableId() == tblInfo.getTableId()) {
                        if (parent.getField() != null && parent.getField().equals("parent")) {
                            return false;
                        }
                    }
                }

            } else if (alias.getField().equals("container")) {
                if (parent != null) {
                    //prevent loop back to container from expansion of Container.collectionObjects relationship
                    if (parent.getTableInfo() != null
                            && parent.getTableInfo().getTableId() == CollectionObject.getClassTableId()) {
                        if (parent.getField() != null && parent.getField().equals("collectionObjects")) {
                            return false;
                        }
                    }

                    //prevent loop back to continer from expansion of Container.collectionObjectKids relationship
                    //Assuming that a container's collectionobject can't be contained in another container. 
                    if (parent.getTableInfo() != null
                            && parent.getTableInfo().getTableId() == CollectionObject.getClassTableId()) {
                        if (parent.getField() != null && parent.getField().equals("collectionObjectKids")) {
                            return false;
                        }
                    }
                }

            } else if (alias.getField().equals("containerOwner")) {
                // prevent loop back to container from expansion of Container.collectionObjects relationship
                // Assuming that collectionobjects linked by this relationship won't be containers.
                if (parent.getTableInfo() != null
                        && parent.getTableInfo().getTableId() == CollectionObject.getClassTableId()) {
                    if (parent.getField() != null && parent.getField().equals("collectionObjects")) {
                        return false;
                    }
                }
                if (parent != null) {
                    //prevent loop back to continer owner from expansion of Container.collectionObjectKids relationship
                    if (parent.getTableInfo() != null
                            && parent.getTableInfo().getTableId() == CollectionObject.getClassTableId()) {
                        if (parent.getField() != null && parent.getField().equals("collectionObjectKids")) {
                            return false;
                        }
                    }

                }
            }
            return true;
        } else if (CollectionObject.class.isAssignableFrom(tblInfo.getClassObj())) {
            TableTree parent = alias.getParent();
            //          if (parent != null && parent.getTableInfo().getTableId() == Container.getClassTableId())
            //          {
            //             return true;
            //          }
            if (parent != null && parent.getTableInfo().getTableId() == CollectionRelationship.getClassTableId()) {
                //prevent looping back to left side when leftSideRels has been opened from parent
                if (alias.getField().equals("leftSide") && parent.getField() != null
                        && parent.getField().equals("leftSideRels")) {
                    return false;
                }
                //prevent looping back to right side when rightSideRels has been opened from parent
                if (alias.getField().equals("rightSide") && parent.getField() != null
                        && parent.getField().equals("rightSideRels")) {
                    return false;
                }
            }

            return true;
        }

        else if (CollectionRelationship.class.isAssignableFrom(tblInfo.getClassObj())) {
            return true;
        }

        else if (Agent.class.isAssignableFrom(tblInfo.getClassObj())) {
            if (alias.getParent() != null && ("members".equals(alias.getParent().getField())
                    || "groups".equals(alias.getParent().getField()))) {
                return true;
                //This allows CreatedByAgent and ModifiedByAgent to be expanded. But there is another check somewhere that prevents "loop backs":
                //If you expand CreatedByAgent, in the resulting fields list CreatedByAgent is not expandable.
            } else if (/*alias.getParent() != null && alias.getParent().getTableInfo().getTableId() != Agent.getClassTableId() 
                       && */("modifiedByAgent".equals(alias.getField())
                    || "createdByAgent".equals(alias.getField()))) {
                return true;
            } else {
                return false;
            }
        }

        return false;
        //special conditions... (may be needed. For example for Determination and Taxon, but on the other hand
        //Determination <-> Taxon behavior seems ok for now.

        ////assuming isCyclic
        //&& !Taxon.class.isAssignableFrom(tblInfo.getClassObj()) || !isAncestorClass(alias, Determination.class);
    }

    //    protected boolean isAncestorClass(final TableTree tbl, final Class<?> cls)
    //    {
    //        TableTree parent = tbl.getParent();
    //        while (parent != null)
    //        {
    //            if (parent.getTableInfo() != null && parent.getTableInfo().getClassObj().equals(cls))
    //            {
    //                return true;
    //            }
    //            parent = parent.getParent();
    //        }
    //        return false;
    //    }

    /**
     * @param parentList
     */
    protected void fillNextList(final JList parentList) {
        if (processingLists) {
            return;
        }

        processingLists = true;

        final int curInx = listBoxList.indexOf(parentList);
        if (curInx > -1) {
            int startSize = listBoxPanel.getComponentCount();
            for (int i = curInx + 1; i < listBoxList.size(); i++) {
                listBoxPanel.remove(spList.get(i));
            }
            int removed = startSize - listBoxPanel.getComponentCount();
            for (int i = 0; i < removed; i++) {
                tableTreeList.remove(tableTreeList.size() - 1);
            }

        } else {
            listBoxPanel.removeAll();
            tableTreeList.clear();
        }

        QryListRendererIFace item = (QryListRendererIFace) parentList.getSelectedValue();
        if (item instanceof ExpandableQRI) {
            JList newList;
            DefaultListModel model;
            JScrollPane sp;

            if (curInx == listBoxList.size() - 1) {
                newList = new JList(model = new DefaultListModel());
                newList.addMouseListener(new MouseAdapter() {

                    /* (non-Javadoc)
                     * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
                     */
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        if (e.getClickCount() == 2) {
                            if (currentInx != -1) {
                                JList list = (JList) e.getSource();
                                QryListRendererIFace qriFace = (QryListRendererIFace) list.getSelectedValue();
                                if (BaseQRI.class.isAssignableFrom(qriFace.getClass())) {
                                    BaseQRI qri = (BaseQRI) qriFace;
                                    if (qri.isInUse()) {
                                        //remove the field
                                        for (QueryFieldPanel qfp : QueryBldrPane.this.queryFieldItems) {
                                            FieldQRI fqri = qfp.getFieldQRI();
                                            if (fqri == qri || (fqri instanceof RelQRI && fqri.getTable() == qri)) {
                                                boolean clearIt = qfp.getSchemaItem() != null;
                                                QueryBldrPane.this.removeQueryFieldItem(qfp);
                                                if (clearIt) {
                                                    qfp.setField(null, null);
                                                }
                                                break;
                                            }
                                        }
                                    } else {
                                        // add the field
                                        try {
                                            FieldQRI fieldQRI = buildFieldQRI(qri);
                                            if (fieldQRI == null) {
                                                throw new Exception("null FieldQRI");
                                            }
                                            SpQueryField qf = new SpQueryField();
                                            qf.initialize();
                                            qf.setFieldName(fieldQRI.getFieldName());
                                            qf.setStringId(fieldQRI.getStringId());
                                            query.addReference(qf, "fields");
                                            if (!isExportMapping) {
                                                addQueryFieldItem(fieldQRI, qf, false);
                                            } else {
                                                addNewMapping(fieldQRI, qf, null, false);
                                            }
                                        } catch (Exception ex) {
                                            log.error(ex);
                                            UsageTracker.incrHandledUsageCount();
                                            edu.ku.brc.exceptions.ExceptionTracker.getInstance()
                                                    .capture(QueryBldrPane.class, ex);
                                            return;
                                        }
                                    }
                                }
                            }
                        }
                    }
                });
                newList.setCellRenderer(qryRenderer);
                listBoxList.add(newList);
                sp = new JScrollPane(newList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                        ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
                JLabel colHeader = UIHelper.createLabel(item.getTitle());
                colHeader.setHorizontalAlignment(SwingConstants.CENTER);
                colHeader.setBackground(listBoxPanel.getBackground());
                colHeader.setOpaque(true);

                sp.setColumnHeaderView(colHeader);

                spList.add(sp);

                newList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent e) {
                        if (!e.getValueIsAdjusting()) {
                            fillNextList(listBoxList.get(curInx + 1));
                        }
                    }
                });

            } else {
                newList = listBoxList.get(curInx + 1);
                model = (DefaultListModel) newList.getModel();
                sp = spList.get(curInx + 1);
                JLabel colHeaderLbl = (JLabel) sp.getColumnHeader().getComponent(0);
                if (item instanceof TableQRI) {
                    colHeaderLbl.setText(((TableQRI) item).getTitle());
                } else {
                    colHeaderLbl.setText(getResourceString("QueryBldrPane.QueryFields"));
                }
            }

            createNewList((TableQRI) item, model);

            listBoxPanel.remove(addBtn);
            listBoxPanel.add(sp);
            tableTreeList.add(((ExpandableQRI) item).getTableTree());
            listBoxPanel.add(addBtn);
            currentInx = -1;

        } else {
            listBoxPanel.add(addBtn);
        }

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                updateAddBtnState();

                // Is all this really necessary
                listBoxPanel.validate();
                listBoxPanel.repaint();
                scrollPane.validate();
                scrollPane.invalidate();
                scrollPane.doLayout();
                scrollPane.repaint();
                validate();
                invalidate();
                doLayout();
                repaint();
                UIRegistry.forceTopFrameRepaint();
            }
        });

        processingLists = false;
        currentInx = curInx;

    }

    //    /**
    //     * @param rel
    //     * @param classObj
    //     * @return false if rel represents a 'system' relationship.
    //     */
    //    protected static boolean isRelevantRel(final DBRelationshipInfo rel, final Class<?> classObj)
    //    {
    //        if (classObj.equals(edu.ku.brc.specify.datamodel.Agent.class))
    //        {
    //            return rel.getColName() == null ||
    //                (!rel.getColName().equalsIgnoreCase("modifiedbyagentid") &&
    //                 !rel.getColName().equalsIgnoreCase("createdbyagentid"));
    //        }
    //        return true;
    //    }

    /**
     * @param qri
     * @return
     */
    protected static boolean isTablePickList(final TableQRI qri) {
        //PickListDBAdapterIFace pl = PickListDBAdapterFactory.getInstance().create(qri.getTableInfo().getName(), false);
        //return (pl instanceof PickListTableAdapter);

        return false;
        //return qri.getTableInfo().getName().equals("preptype");
    }

    /**
     * @param qri
     * @return
     */
    protected static FieldQRI buildFieldQRIForTablePickList(final TableQRI qri) {
        return null;
    }

    /**
     * @param qri
     * @return qri if it is already a FieldQRI, else constructs a RelQRI and returns it.
     */
    protected static FieldQRI buildFieldQRI(final BaseQRI qri) {
        if (qri instanceof FieldQRI) {
            return (FieldQRI) qri;
        }
        if (qri instanceof TableQRI) {
            if (isTablePickList((TableQRI) qri)) {
                //System.out.println(((TableQRI )qri).getTableInfo().getName() + " is a table picklist.");
                return buildFieldQRIForTablePickList((TableQRI) qri);

            } else {
                DBRelationshipInfo relInfo = ((TableQRI) qri).getRelationship();
                if (relInfo != null) {
                    return new RelQRI((TableQRI) qri, relInfo);
                }
                throw new RuntimeException(QueryBldrPane.class.getName() + ": unable to determine relationship."
                        + ((TableQRI) qri).getTableTree().getField() + " <-> "
                        + ((TableQRI) qri).getTableTree().getParent().getField());
            }
        }
        return null;
    }

    /**
     * Enables or disables the button used to add fields to the query, depending on the
     * current situation.
     */
    protected void updateAddBtnState() {
        if (currentInx != -1) {
            BaseQRI qri = (BaseQRI) listBoxList.get(currentInx).getSelectedValue();
            addBtn.setEnabled(qri != null && !qri.isInUse());
        }
    }

    /**
     * Removes it from the List.
     * 
     * @param qfp QueryFieldPanel to be removed
     */
    public void removeQueryFieldItem(final QueryFieldPanel qfp) {
        //refreshQuery();
        if (query.getReports().size() > 0) {
            CustomDialog cd = new CustomDialog((Frame) UIRegistry.getTopWindow(),
                    UIRegistry.getResourceString("REP_CONFIRM_DELETE_TITLE"), true, CustomDialog.OKCANCELHELP,
                    new QBReportInfoPanel(query,
                            UIRegistry.getResourceString("QB_USED_BY_REP_FLD_DELETE_CONFIRM")));
            cd.setHelpContext("QBFieldRemovedAndReports");
            UIHelper.centerAndShow(cd);
            cd.dispose();
            if (cd.isCancelled()) {
                return;
            }
        }
        if (qfp.getFieldQRI() != null) {
            qfp.getFieldQRI().setIsInUse(false);
        }
        if (qfp.getQueryField() != null) {
            //query.removeReference(qfp.getQueryField(), "fields");
            removeFieldFromQuery(qfp.getQueryField());
            if (qfp.getItemMapping() != null) {
                removeSchemaItemMapping(qfp.getItemMapping());
            }
        }
        final FieldQRI qfpqri = qfp.getFieldQRI();
        queryFieldItems.remove(qfp);
        //XXX field label qualification issues for schema maps??
        qualifyFieldLabels();

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                if (selectedQFP == qfp) {
                    selectQFP(null);
                }
                queryFieldsPanel.getLayout().removeLayoutComponent(qfp);
                queryFieldsPanel.remove(qfp);
                queryFieldsPanel.validate();
                updateAddBtnState();

                // Sorry, but a new context can't be selected if any fields
                // are selected from the current context.
                tableList.setEnabled(queryFieldItems.size() == 0);

                try {
                    BaseQRI qri = qfpqri instanceof RelQRI ? qfpqri.getTable() : qfpqri;
                    //BaseQRI qri = qfp.getFieldQRI(); 
                    boolean done = false;
                    for (JList lb : listBoxList) {
                        if (lb.isVisible()) {
                            for (int i = 0; i < ((DefaultListModel) lb.getModel()).getSize(); i++) {
                                BaseQRI qriI = (BaseQRI) ((DefaultListModel) lb.getModel()).getElementAt(i);
                                if (qriI != null) {
                                    boolean match = qriI == qri;
                                    if (!match) {
                                        match = buildFieldQRI(qri).getStringId()
                                                .equals(buildFieldQRI(qriI).getStringId());
                                    }
                                    if (match) {
                                        qriI.setIsInUse(false);
                                        lb.repaint();
                                        done = true;
                                        break;
                                    }
                                }
                            }
                        }
                        if (done) {
                            break;
                        }
                    }
                } catch (Exception ex) {
                    UsageTracker.incrHandledUsageCount();
                    edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, ex);
                    log.error(ex);
                }
                queryFieldsPanel.repaint();
                setSaveBtnEnabled(thereAreItems() && canSave());
                updateSearchBtn();
                updateSmushBtn();
                UIRegistry.displayStatusBarText(null);
            }
        });
    }

    /**
     * @return true if query or schemamapping contains fields.
     * 
     */
    protected boolean thereAreItems() {
        if (!isExportMapping) {
            return queryFieldItems.size() > 0;
        }

        for (QueryFieldPanel qfp : queryFieldItems) {
            if (qfp.getFieldQRI() != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return path from root treeTable to treeTable for rightmost displayed list.
     */
    protected List<TableTreePathPoint> getCurrentDisplayPath() {
        if (tableTreeList.size() > 0) {
            return tableTreeList.get(tableTreeList.size() - 1).getPathFromRoot();
        }
        return new Vector<TableTreePathPoint>();
    }

    /**
     * Updates lists in list panel to display field as the current selection.
     * 
     * @param field
     */
    protected void displayField(final FieldQRI field) {
        List<TableTreePathPoint> displayedPath = getCurrentDisplayPath();
        List<TableTreePathPoint> fieldPath = field.getTableTree().getPathFromRoot();

        if (tableTreeList.size() != listBoxPanel.getComponentCount() - 1) {
            log.error("tableTreeList and listBoxPanel are out of sync");
        }

        int p = 0;
        while (p < displayedPath.size() && p < fieldPath.size()) {
            if (!displayedPath.get(p).equals(fieldPath.get(p))) {
                break;
            }
            p++;
        }
        if (!(p == fieldPath.size() && fieldPath.size() == displayedPath.size())) {
            if (p == fieldPath.size()) {
                if (p == 1) {
                    fillNextList(tableList);
                } else {
                    fillNextList(listBoxList.get(p - 2));
                }
            } else {
                p--;
                while (p < fieldPath.size() - 1) {
                    JList currList = p < 0 ? tableList : listBoxList.get(p);
                    ListModel model = currList.getModel();
                    // find and select item in path
                    int i = 0;
                    boolean foundPathItem = false;
                    while (i < model.getSize() && !foundPathItem) {
                        QryListRendererIFace item = (BaseQRI) model.getElementAt(i);
                        if (item.hasChildren()) {
                            TableTree tt = ((BaseQRI) item).getTableTree();
                            if (fieldPath.get(p + 1).equals(new TableTreePathPoint(tt))) {
                                currList.setSelectedIndex(i);
                                foundPathItem = true;
                            }
                        }
                        i++;
                    }
                    if (foundPathItem) {
                        //fillNextList(currList);
                        p++;
                    } else {
                        log.error("unable to locate field: " + field.getFieldName());
                        return;
                    }
                }
            }
        }
        ListModel model = listBoxList.get(fieldPath.size() - 1).getModel();
        for (int f = 0; f < model.getSize(); f++) {
            BaseQRI item = (BaseQRI) model.getElementAt(f);
            if (item.getTitle().equals(field.getTitle())) {
                processingLists = true;
                listBoxList.get(fieldPath.size() - 1).setSelectedIndex(f);
                listBoxList.get(fieldPath.size() - 1).ensureIndexIsVisible(f);
                processingLists = false;
                break;
            }
        }

    }

    public FieldQRI getFieldQRI(final SpQueryField field) {
        //       return getFieldQRI(tableTree, field, getTableIds(field.getTableList()),
        //             0, tableTreeHash);
        return getFieldQRI(tableTree, field.getFieldName(), field.getIsRelFld() != null && field.getIsRelFld(),
                field.getStringId(), getTableIds(field.getTableList()), 0, tableTreeHash);
    }

    /**
     * @param kids
     * @param field
     * @param tableIds
     * @param level
     * @return
     */
    protected static FieldQRI getFieldQRI(final TableTree tbl,
            //final SpQueryField field,
            final String fieldName, final boolean isRelFld, final String fldStringId,
            final Vector<TableTreePathPoint> tableIds, final int level, final Hashtable<String, TableTree> ttHash) {
        TableTreePathPoint id = tableIds.get(level);
        for (int k = 0; k < tbl.getKids(); k++) {
            TableTree kid = tbl.getKid(k);
            boolean checkKid = kid.isAlias() ? fixAliases(kid, ttHash) : true;
            if (checkKid && (kid.getTableQRI().getRelationship() == null
                    || !kid.getTableQRI().getRelationship().isHidden())) {
                if (id.equals(new TableTreePathPoint(kid))) {
                    if (level == (tableIds.size() - 1)) {
                        //if (field.getIsRelFld() == null || !field.getIsRelFld())
                        if (!isRelFld) {
                            for (int f = 0; f < kid.getTableQRI().getFields(); f++) {
                                //if (kid.getTableQRI().getField(f).getStringId().equals(field.getStringId())) 
                                if (kid.getTableQRI().getField(f).getStringId().equalsIgnoreCase(fldStringId)) {
                                    return kid.getTableQRI().getField(f);
                                }
                            }
                        }
                        //else if (kid.field.equalsIgnoreCase(field.getFieldName()))
                        else if (kid.field.equalsIgnoreCase(fieldName)) {
                            return buildFieldQRI(kid.getTableQRI());
                        }
                    } else {
                        //FieldQRI fi = getFieldQRI(kid, field, tableIds, level + 1, ttHash);
                        FieldQRI fi = getFieldQRI(kid, fieldName, isRelFld, fldStringId, tableIds, level + 1,
                                ttHash);
                        if (fi != null) {
                            return fi;
                        }
                    }
                }
            }
        }
        return null;
    }

    protected static Vector<TableTreePathPoint> getTableIds(final String tableIdsList) {
        String[] points = StringUtils.split(tableIdsList, ",");
        Vector<TableTreePathPoint> result = new Vector<TableTreePathPoint>();
        for (String point : points) {
            result.add(new TableTreePathPoint(point));
        }
        return result;
    }

    /**
     * @param container
     * @param fields
     * @param tblTree
     * @param ttHash
     * @param saveBtn
     * @param missingFlds
     * 
     * @return a Vector of QueryFieldPanel objects for the supplied fields parameter.
     */
    protected static Vector<QueryFieldPanel> getQueryFieldPanels(final QueryFieldPanelContainerIFace container,
            final Set<SpQueryField> fields, final TableTree tblTree, final Hashtable<String, TableTree> ttHash,
            final Component saveBtn, final List<String> missingFlds) {
        Vector<QueryFieldPanel> result = new Vector<QueryFieldPanel>();
        List<SpQueryField> orderedFlds = new ArrayList<SpQueryField>(fields);
        Collections.sort(orderedFlds);
        result.add(bldQueryFieldPanel(container, null, null, container.getColumnDefStr(), saveBtn));
        for (SpQueryField fld : orderedFlds) {
            //System.out.println(fld.getFieldName()+" - "+fld.getStringId());
            FieldQRI fieldQRI = getFieldQRI(tblTree, fld.getFieldName(),
                    fld.getIsRelFld() != null && fld.getIsRelFld(), fld.getStringId(),
                    getTableIds(fld.getTableList()), 0, ttHash);
            if (fieldQRI != null) {
                result.add(bldQueryFieldPanel(container, fieldQRI, fld, container.getColumnDefStr(), saveBtn));

                fieldQRI.setIsInUse(true);
                if (fieldQRI.isFieldHidden() && !container.isPromptMode() && !container.isForSchemaExport()) {
                    UIRegistry.showLocalizedMsg("QB_FIELD_HIDDEN_TITLE", "QB_FIELD_HIDDEN_SHOULD_REMOVE",
                            fieldQRI.getTitle());
                }
            } else {
                String tableName = null;
                if (tblTree.getTableInfo() == null && fld.getContextTableIdent() != null) {
                    tableName = DBTableIdMgr.getInstance().getTitleForId(fld.getContextTableIdent());
                } else {
                    tableName = tblTree.getTableInfo() == null ? fld.getQuery().getContextName()
                            : tblTree.getTableInfo().getTitle();
                }
                log.error("Couldn't find [" + fld.getFieldName() + "] [" + fld.getTableList() + "]");
                fields.remove(fld);
                fld.setQuery(null);
                if (missingFlds != null) {
                    String fldText = fld.getColumnAlias() != null ? fld.getColumnAlias() : fld.getFieldName();
                    missingFlds.add(String.format("%s -> %s", tableName, fldText));
                }
            }
        }
        return result;
    }

    protected static SpQueryField getQueryFieldMapping(final SpExportSchemaMapping schemaMapping,
            final SpExportSchemaItem schemaItem) {
        if (schemaMapping != null) {
            for (SpExportSchemaItemMapping mapping : schemaMapping.getMappings()) {
                if (mapping.getExportSchemaItem() != null
                        && mapping.getExportSchemaItem().getId().equals(schemaItem.getId())) {
                    return mapping.getQueryField();
                }
            }
        }
        return null;
    }

    /**
     * @return
     */
    public static Vector<QueryFieldPanel> getQueryFieldPanelsForMapping(
            final QueryFieldPanelContainerIFace container, Set<SpQueryField> fields, final TableTree tblTree,
            final Hashtable<String, TableTree> ttHash, final Component saveBtn, SpExportSchemaMapping schemaMapping,
            List<String> missingFlds, Map<String, Vector<MappedFieldInfo>> autoMaps) {
        Vector<QueryFieldPanel> result = new Vector<QueryFieldPanel>();
        //Need to change columnDefStr if mapMode...
        //result.add(bldQueryFieldPanel(this, null, null, getColumnDefStr(), saveBtn));
        result.add(new QueryFieldPanel(container, null, container.getColumnDefStr(), saveBtn, null, schemaMapping,
                null));

        Vector<SpExportSchemaItem> sis = new Vector<SpExportSchemaItem>();
        if (schemaMapping.getSpExportSchema() != null) {
            sis.addAll(schemaMapping.getSpExportSchema().getSpExportSchemaItems());
        }
        Collections.sort(sis, new Comparator<SpExportSchemaItem>() {

            /* (non-Javadoc)
             * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
             */
            @Override
            public int compare(SpExportSchemaItem o1, SpExportSchemaItem o2) {
                return o1.getFieldName().compareTo(o2.getFieldName());
            }

        });
        for (SpExportSchemaItem schemaItem : sis) {
            //System.out.println("SchemaItem: " + schemaItem.getFieldName());
            boolean autoMapped = false;
            SpQueryField fld = getQueryFieldMapping(schemaMapping, schemaItem);
            FieldQRI fieldQRI = null;
            if (fld == null && autoMaps != null) {
                Vector<MappedFieldInfo> mappedTos = autoMaps.get(schemaItem.getFieldName().toLowerCase());

                if (mappedTos != null) {
                    for (MappedFieldInfo mappedTo : mappedTos) {
                        fieldQRI = getFieldQRI(tblTree, mappedTo.getFieldName(), mappedTo.isRel(),
                                mappedTo.getStringId(), getTableIds(mappedTo.getTableIds()), 0, ttHash);
                        if (fieldQRI != null) {
                            if (!fieldQRI.isFieldHidden()) {
                                autoMapped = true;
                            } else {
                                fieldQRI = null;
                            }
                            break;
                        }
                    }
                }
                //result.add(new QueryFieldPanel(container, null, 
                //   container.getColumnDefStr(), saveBtn, fld, schemaMapping, schemaItem));
            } else if (fld != null) {
                fieldQRI = getFieldQRI(tblTree, fld.getFieldName(), fld.getIsRelFld() != null && fld.getIsRelFld(),
                        fld.getStringId(), getTableIds(fld.getTableList()), 0, ttHash);
            }
            if (fieldQRI != null) {
                QueryFieldPanel newPanel = new QueryFieldPanel(container, fieldQRI, container.getColumnDefStr(),
                        saveBtn, fld, schemaMapping, schemaItem);
                newPanel.setAutoMapped(autoMapped);
                result.add(newPanel);
                fieldQRI.setIsInUse(true);
                if (fieldQRI.isFieldHidden() && !container.isPromptMode() && !container.isForSchemaExport()) {
                    UIRegistry.showLocalizedMsg("QB_FIELD_HIDDEN_TITLE", "QB_FIELD_HIDDEN_SHOULD_REMOVE",
                            fieldQRI.getTitle());
                }
            } else if (fld != null) {
                log.error("Couldn't find [" + fld.getFieldName() + "] [" + fld.getTableList() + "]");
                if (!container.isForSchemaExport() && !container.isPromptMode()) {
                    for (SpQueryField field : fields) {
                        //ain't superstitious but checking ids in case 
                        //fld and field are different java objects
                        if (field.getId().equals(fld.getId())) {
                            SpExportSchemaItemMapping mappingForField = null;
                            for (SpExportSchemaItemMapping m : schemaMapping.getMappings()) {
                                if (m.getQueryField() != null && field.getId().equals(m.getQueryField().getId())) {
                                    mappingForField = m;
                                    break;
                                }
                            }
                            if (mappingForField != null) {
                                schemaMapping.getMappings().remove(mappingForField);
                                mappingForField.setExportSchemaItem(null);
                                mappingForField.setExportSchemaMapping(null);
                                mappingForField.setQueryField(null);
                            }
                            fields.remove(field);
                            field.setQuery(null);
                            fld.setQuery(null);
                            break;
                        }
                    }
                }
                if (missingFlds != null) {
                    String fldText = fld.getColumnAlias() != null ? fld.getColumnAlias() : fld.getFieldName();
                    missingFlds.add(fldText);
                }
            }
        }

        List<SpQueryField> toRemove = new ArrayList<SpQueryField>();

        //add 'auto-mapped' fields not mapped to a concept
        if (autoMaps != null && fields.size() == 0 /* a new mapping */) {
            int cnt = 0;
            for (Map.Entry<String, Vector<MappedFieldInfo>> me : autoMaps.entrySet()) {
                if (me.getKey().startsWith("Unmapped:")) {
                    MappedFieldInfo fi = me.getValue().get(0);
                    SpQueryField fld = new SpQueryField();
                    fld.initialize();

                    fld.setIsNot(false);
                    fld.setAlwaysFilter(false);
                    fld.setIsPrompt(true);
                    fld.setIsRelFld(false);
                    fld.setSortType(Byte.valueOf("0"));
                    fld.setPosition(Short.valueOf(String.valueOf(result.size() - 1 + cnt++)));

                    fld.setSpQueryFieldId(-1);
                    fld.setIsDisplay(false);
                    fld.setOperStart(fi.getOperator());
                    fld.setFieldName(fi.getFieldName());
                    fld.setStringId(fi.getStringId());
                    fld.setTableList(fi.getTableIds());
                    fld.setContextTableIdent(fi.getContextTableId());

                    fields.add(fld);
                    toRemove.add(fld);
                }
            }

        }

        //now add un-mapped fields
        for (SpQueryField fld : fields) {
            //int insertAt = 0;
            if (fld.getMapping() == null || fld.getMapping().getExportSchemaItem() == null) {
                FieldQRI fieldQRI = getFieldQRI(tblTree, fld.getFieldName(),
                        fld.getIsRelFld() != null && fld.getIsRelFld(), fld.getStringId(),
                        getTableIds(fld.getTableList()), 0, ttHash);
                if (fieldQRI != null) {
                    //                 result.insertElementAt(new QueryFieldPanel(container, fieldQRI, 
                    //                        container.getColumnDefStr(), saveBtn, fld, null, true), insertAt++);
                    QueryFieldPanel newQfp = new QueryFieldPanel(container, fieldQRI, container.getColumnDefStr(),
                            saveBtn, fld, schemaMapping, null);
                    result.add(newQfp);
                    fieldQRI.setIsInUse(true);
                    if (fld.getSpQueryFieldId() == -1) {
                        newQfp.setAutoMapped(true);
                        newQfp.setQueryFieldForAutomapping(null);
                    }
                    if (fieldQRI.isFieldHidden() && !container.isPromptMode() && !container.isForSchemaExport()) {
                        UIRegistry.showLocalizedMsg("QB_FIELD_HIDDEN_TITLE", "QB_FIELD_HIDDEN_SHOULD_REMOVE",
                                fieldQRI.getTitle());
                    }
                } else {
                    log.error("Couldn't find [" + fld.getFieldName() + "] [" + fld.getTableList() + "]");
                    if (!container.isForSchemaExport() && !container.isPromptMode()) {
                        for (SpQueryField field : fields) {
                            //ain't superstitious but checking ids in case 
                            //fld and field are different java objects
                            if (field.getId().equals(fld.getId())) {
                                SpExportSchemaItemMapping mappingForField = null;
                                for (SpExportSchemaItemMapping m : schemaMapping.getMappings()) {
                                    if (m.getQueryField() != null
                                            && field.getId().equals(m.getQueryField().getId())) {
                                        mappingForField = m;
                                        break;
                                    }
                                }
                                if (mappingForField != null) {
                                    schemaMapping.getMappings().remove(mappingForField);
                                    mappingForField.setExportSchemaItem(null);
                                    mappingForField.setExportSchemaMapping(null);
                                    mappingForField.setQueryField(null);
                                }
                                toRemove.add(field);
                                field.setQuery(null);
                                fld.setQuery(null);
                                break;
                            }
                        }
                    }
                    if (missingFlds != null) {
                        missingFlds.add(fld.getColumnAlias());
                    }
                }
            }
        }
        for (SpQueryField f : toRemove) {
            fields.remove(f);
        }

        // now add placeHolder panel for adding new condition
        //result.add(new QueryFieldPanel(container, null, 
        //      container.getColumnDefStr(), saveBtn, null, null, null, true));

        //now sort on queryfield position
        Collections.sort(result, new Comparator<QueryFieldPanel>() {

            /* (non-Javadoc)
             * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
             */
            @Override
            public int compare(QueryFieldPanel o1, QueryFieldPanel o2) {
                SpQueryField f1 = o1.getQueryField();
                SpQueryField f2 = o2.getQueryField();
                if (f1 != null && f2 != null) {
                    return f1.getPosition().compareTo(f2.getPosition());
                } else if (f1 != null) {
                    return 1;
                } else if (f2 != null) {
                    return -1;
                } else
                    return 0;
            }

        });

        return result;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#getColumnDefStr()
     */
    //@Override
    public String getColumnDefStr() {
        return columnDefStr;
    }

    /**
     * @param container
     * @param fieldQRI
     * @param fld
     * @param colDefStr
     * @param saveBtn
     * @return a QueryFieldPanel for the field represented by the fieldQRI object.
     */
    protected static QueryFieldPanel bldQueryFieldPanel(final QueryFieldPanelContainerIFace container,
            final FieldQRI fieldQRI, final SpQueryField fld, String colDefStr, final Component saveBtn) {
        if (colDefStr == null) {
            return new QueryFieldPanel(container, fieldQRI, colDefStr, saveBtn, null);
        }
        return new QueryFieldPanel(container, fieldQRI, colDefStr, saveBtn, fld);
    }

    protected void updateUIAfterAddOrMap(final FieldQRI fieldQRI, final QueryFieldPanel qfp, final boolean loading,
            final boolean isAdd, final boolean isSchemaMapping) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                if (currentInx > -1) {
                    if (isAdd) {
                        queryFieldsPanel.add(qfp);
                        queryFieldsPanel.validate();
                    }
                    if (fieldQRI instanceof RelQRI) {
                        BaseQRI qri = fieldQRI.getTable();
                        for (JList lb : listBoxList) {
                            if (lb.isVisible()) {
                                if (((DefaultListModel) lb.getModel()).contains(qri)) {
                                    lb.repaint();
                                }
                            }
                        }
                    } else {
                        listBoxList.get(currentInx).repaint();
                    }

                    updateAddBtnState();
                    selectQFP(qfp);
                    updateSmushBtn();
                    queryFieldsPanel.repaint();
                    if (!loading) {
                        setSaveBtnEnabled(canSave(isSchemaMapping));
                        updateSearchBtn();
                    }
                    //Sorry, but a new context can't be selected if any fields are selected from the current context.
                    tableList.setEnabled(queryFieldItems.size() == 0);
                    if (fieldQRI instanceof TreeLevelQRI && distinctChk.isSelected() && countOnly) {
                        countOnly = false;
                        countOnlyChk.setSelected(false);
                        UIRegistry.displayLocalizedStatusBarText("QB_NO_COUNT_WITH_DISTINCT_WITH_TREELEVEL");
                    } else {
                        UIRegistry.displayStatusBarText(null);
                    }
                }
            }
        });
    }

    /**
     * Add QueryFieldItem to the list created with a TableFieldPair.
     * 
     * @param fieldItem the TableFieldPair to be in the list
     */
    protected QueryFieldPanel addQueryFieldItem(final FieldQRI fieldQRI, final SpQueryField queryField,
            final boolean loading) {
        QueryFieldPanel result = null;
        if (fieldQRI != null) {
            final QueryFieldPanel qfp = new QueryFieldPanel(this, fieldQRI, columnDefStr, saveBtn, queryField,
                    this.schemaMapping);
            result = qfp;
            qfp.addMouseListener(new MouseInputAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    selectQFP(qfp);
                }
            });
            queryFieldItems.add(qfp);
            qualifyFieldLabels();
            fieldQRI.setIsInUse(true);
            updateUIAfterAddOrMap(fieldQRI, qfp, loading, true, false);
        }
        return result;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#selectQFP(edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanel)
     * 
     * Use runSelectQFP if not calling from Swing thread.
     */
    public void selectQFP(final QueryFieldPanel qfp) {
        if (!SwingUtilities.isEventDispatchThread()) {
            //apparently this never happens, but...
            runSelectQFP(qfp);
        } else {
            if (selectedQFP != null) {
                selectedQFP.setSelected(false);
                selectedQFP.repaint();
            }
            selectedQFP = qfp;
            if (selectedQFP != null) {
                selectedQFP.setSelected(true);
                selectedQFP.repaint();
                //scrollQueryFieldsToRect(selectedQFP.getBounds());
            }
            updateMoverBtns();
            if (qfp != null) {
                FieldQRI fqri = qfp.getFieldQRI();
                if (fqri != null) {
                    displayField(fqri);
                }
            }
        }
    }

    /**
     * @param qfp
     * 
     * runs selectQFP() in Swing thread.
     */
    private void runSelectQFP(final QueryFieldPanel qfp) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                selectQFP(qfp);
            }
        });
    }

    /**
     * Enables field mover buttons as appropriate for position of currently select QueryFieldPanel.
     */
    protected void updateMoverBtns() {
        //if (!isExportMapping)
        //{
        int idx = queryFieldItems.indexOf(selectedQFP);
        orderUpBtn.setEnabled(idx > 0);
        orderDwnBtn.setEnabled(idx > -1 && idx < queryFieldItems.size() - 1);
        //}
    }

    /**
     * Adds qualifiers (TableOrRelationship/Field Title) to query fields where necessary.
     * 
     */
    protected void qualifyFieldLabels() {
        List<String> labels = new ArrayList<String>(queryFieldItems.size());
        Map<String, List<QueryFieldPanel>> map = new HashMap<String, List<QueryFieldPanel>>();
        for (QueryFieldPanel qfp : queryFieldItems) {
            if (qfp.getFieldQRI() != null && qfp.getFieldTitle() != null) //this means tree levels won't get qualified.
            {
                if (!map.containsKey(qfp.getFieldTitle())) {
                    map.put(qfp.getFieldTitle(), new LinkedList<QueryFieldPanel>());
                }
                map.get(qfp.getFieldTitle()).add(qfp);
                labels.add(qfp.getFieldTitle());
            }
        }

        for (Map.Entry<String, List<QueryFieldPanel>> entry : map.entrySet()) {
            if (entry.getValue().size() > 1 || entry.getValue().get(0).isLabelQualified()) {
                for (QueryFieldPanel q : entry.getValue()) {
                    labels.remove(entry.getKey());
                    labels.add(q.qualifyLabel(labels, entry.getValue().size() == 1));
                }
            }
        }
    }

    /**
     * @param tbl
     * @param hash
     */
    protected static boolean fixAliases(final TableTree tbl, final Hashtable<String, TableTree> hash) {
        if (tbl.isAlias()) {
            TableTree tt = hash.get(tbl.getName());
            if (tt != null) {
                if (!tt.getTableInfo().isHidden() && tblIsDisplayable(tbl, tt.getTableInfo())) {
                    tbl.clearKids();
                    try {
                        for (int k = 0; k < tt.getKids(); k++) {
                            //                            if (tblIsDisplayable(tt.getKid(k), tableTreeHash.get(tt.getKid(k).getName()).getTableInfo());
                            //                           
                            //                           if (tt.getKid(k).getTableInfo() == null)
                            //                            {
                            //                               System.out.println("TableInfo is null for " + tt.getKid(k).getName() + " - " + tt.getKid(k).getField());
                            //                            }
                            //                            else if (tt.getKid(k).getTableInfo() != null && tblIsDisplayable(tt.getKid(k), tt.getKid(k).getTableInfo()))
                            //                            {
                            tbl.addKid((TableTree) tt.getKid(k).clone());
                            //                            } else 
                            //                            {
                            //                               System.out.println("Skipping " +  tt.getKid(k).getName() + " - " + tt.getKid(k).getField());
                            //                            }
                        }
                        tbl.setTableInfo(tt.getTableInfo());
                        tbl.setTableQRIClone(tt.getTableQRI());
                        return true;
                    } catch (CloneNotSupportedException ex) {
                        UsageTracker.incrHandledUsageCount();
                        edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(QueryBldrPane.class, ex);
                        throw new RuntimeException(ex);
                    }
                }
                return false;
            }
            log.error("Couldn't find [" + tbl.getName() + "] in the hash.");
            return false;
        }
        return true;
    }

    /**
     * @param columnDefStr the columnDefStr to set
     */
    public void setColumnDefStr(String columnDefStr) {
        this.columnDefStr = columnDefStr;
    }

    /**
     * @return the btn that launched the editor
     */
    public RolloverCommand getQueryNavBtn() {
        return queryNavBtn;
    }

    /**
     * @param queryNavBtn
     */
    public void setQueryNavBtn(RolloverCommand queryNavBtn) {
        this.queryNavBtn = queryNavBtn;
    }

    /*
     * (non-Javadoc)
     * 
     * @see edu.ku.brc.af.tasks.subpane.BaseSubPane#shutdown()
     */
    @Override
    public void shutdown() {
        super.shutdown();

        CommandDispatcher.unregister(ReportsBaseTask.REPORTS, this);

        if (saveBtn != null && saveBtn.isEnabled()) {
            setSaveBtnEnabled(false);
        }

        if (runningResults.get() != null) {
            runningResults.get().cancel();
        }

        if (completedResults.get() != null) {
            completedResults.get().cancel();
        }

        //This is safe as long as we continue to allow only 1 qb result.
        //and the qbresult pane always returns true for aboutToShutdown()
        QBResultsSubPane qbResultPane = null;
        for (SubPaneIFace subPane : SubPaneMgr.getInstance().getSubPanes()) {
            if (subPane instanceof QBResultsSubPane) {
                qbResultPane = (QBResultsSubPane) subPane;
                break;
            }
        }
        if (qbResultPane != null) {
            QBResultsTablePanel tblPane = qbResultPane.getResultsTable();
            if (tblPane != null) {
                QBResultSetTableModel tblModel = tblPane.getTableModel();
                if (tblModel != null) {
                    tblModel.cancelBackgroundLoads();
                }
            }
            SubPaneMgr.getInstance().removePane(qbResultPane);
        }

        query = null;
        if (queryNavBtn != null) {
            queryNavBtn.setEnabled(true);
        }

        /*NOTE: runningResults or completedResults may still be pointing to this so
        garbage collection will be hampered, but since only one QueryResult is allowed
        at any time it should not be a problem. (???)
        */
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.af.tasks.subpane.BaseSubPane#aboutToShutdown()
     */
    @Override
    public boolean aboutToShutdown() {
        boolean result = true;
        unlock();
        if (isChanged()) {
            String msg = String.format(getResourceString("SaveChanges"), getTitle());
            JFrame topFrame = (JFrame) UIRegistry.getTopWindow();

            int rv = JOptionPane.showConfirmDialog(topFrame, msg, getResourceString("SaveChangesTitle"),
                    JOptionPane.YES_NO_CANCEL_OPTION);
            if (rv == JOptionPane.YES_OPTION) {
                saveQuery(false);
            } else if (rv == JOptionPane.CANCEL_OPTION || rv == JOptionPane.CLOSED_OPTION) {
                return false;
            } else if (rv == JOptionPane.NO_OPTION) {
                // nothing
            }
        }
        return result;
    }

    /**
     * Frees mapping lock if necessary
     */
    protected void unlock() {
        if (query != null && query.getMapping() != null) {
            ExportMappingTask.unlockMapping(query.getMapping());
        }
    }

    /**
     * @return true if there are unsaved changes to the query.
     */
    protected boolean isChanged() {
        return saveBtn.isEnabled(); //el cheapo
    }

    /**
     * @param toMove
     * @param moveTo
     * 
     * Moves toMove to moveTo's position and shifts other panels to fill toMove's former position.
     */
    protected void moveField(final QueryFieldPanel toMove, final QueryFieldPanel moveTo) {
        int fromIdx = queryFieldItems.indexOf(toMove);
        int toIdx = queryFieldItems.indexOf(moveTo);
        if (fromIdx == toIdx) {
            return;
        }

        queryFieldItems.remove(fromIdx);
        queryFieldItems.insertElementAt(toMove, toIdx);

        ((NavBoxLayoutManager) queryFieldsPanel.getLayout()).moveLayoutComponent(toMove, moveTo);

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                queryFieldsPanel.doLayout();
                queryFieldsPanel.validate();
                //scrollQueryFieldsToRect(toMove.getBounds());
                queryFieldsPanel.repaint();
                updateMoverBtns();
                setSaveBtnEnabled(canSave());
            }
        });
    }

    /**
     * @param rect - the rectangle to make visible.
     * 
     * Wrapper for JViewport.scrollReectToVisible() with a work around for a java bug.
     */
    protected void scrollQueryFieldsToRect(final Rectangle rect) {
        //scrollRectToVisible doesn't work when newBounds is above the viewport.
        //This is a java bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6333318
        //        if (rect.y < queryFieldsScroll.getViewport().getViewPosition().y)
        //        {
        //            queryFieldsScroll.getViewport().setViewPosition(new Point(rect.x,rect.y));
        //        }

        queryFieldsScroll.getViewport().scrollRectToVisible(rect);

        //scrollRectToVisible doesn't work when newBounds is above the viewport.
        //This is a java bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6333318

        if (rect.y < queryFieldsScroll.getViewport().getViewPosition().y) {
            queryFieldsScroll.getViewport().setViewPosition(new Point(rect.x, rect.y));
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#getFields()
     */
    public int getFields() {
        return queryFieldItems.size();
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#getField(int)
     */
    public QueryFieldPanel getField(int index) {
        return queryFieldItems.get(index);
    }

    /**
     * Disables search button when there is nothing to search for.
     */
    protected void updateSearchBtn() {
        searchBtn.setEnabled(thereAreItems());
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#isPromptMode()
     */
    //@Override
    public boolean isPromptMode() {
        return false;
    }

    /**
     * @return true if it is possible for the QueryBuilder to execute a search.
     */
    protected boolean canSearch() {
        if (runningResults.get() == null) {
            return true;
        }
        //        if (runningResults.get().getQueryTask() == null)
        //        {
        //            //something has gone wrong?
        //            runningResults.set(null);
        //            return true;
        //        }
        return false;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.CommandListener#doCommand(edu.ku.brc.ui.CommandAction)
     */
    //@Override
    public void doCommand(CommandAction cmdAction) {
        if (cmdAction.isType(ReportsBaseTask.REPORTS)) {
            refreshQuery(); //currently done for all commands
            if (cmdAction.isAction(ReportsBaseTask.REFRESH)) {
                //nothing else to do
            }
            //            else if (cmdAction.isAction(ReportsBaseTask.REPORT_DELETED))
            //            {
            //                if (runningResults.get() != null)
            //                {
            //                    
            //                   runningResults.get().reportDeleted((Integer)cmdAction.getData());
            //                }
            //                if (completedResults.get() != null)
            //                {
            //                    completedResults.get().reportDeleted((Integer)cmdAction.getData());
            //                }
            //            }
        }
    }

    /**
     * Get latest persisted version of this query.
     */
    protected void refreshQuery() {
        if (query != null && query.getId() != null && !this.isChanged()) {
            DataProviderSessionIFace session = DataProviderFactory.getInstance().createSession();
            try {
                session.refresh(query);
                query.forceLoad(true);
            } finally {
                session.close();
            }
        }
    }

    /**
     * @return Set of reports that are based on this query.
     */
    public Set<SpReport> getReportsForQuery() {
        //assuming query.forceLoad() has been called
        return query.getReports();
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.af.tasks.subpane.BaseSubPane#showingPane(boolean)
     */
    @Override
    public void showingPane(boolean show) {
        if (show && ((QueryTask) task).needToRebuildTableTree() && !reloadMsgShown) {
            //It seems that no serious problems will occur so for now just show a message:
            UIRegistry.showLocalizedMsg("QB_TREEDEF_LOCALIZ_CHANGES_TITLE", "QB_TREEDEF_LOCALIZ_CHANGES_WARN");
            reloadMsgShown = true;
        }
    }

    /**
     * @return the query
     */
    public SpQuery getQuery() {
        return query;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#getAddBtn()
     */
    //@Override
    public JButton getAddBtn() {
        return addBtn;
    }

    protected boolean isQRIClassSelected(Class<?> qriClass) {
        for (QueryFieldPanel qfp : this.queryFieldItems) {
            if (qfp.getFieldQRI() != null && qriClass.isAssignableFrom(qfp.getFieldQRI().getClass())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return true if the query's fields list contains a TreeLevel field. 
     */
    protected boolean isTreeLevelSelected() {
        return isQRIClassSelected(TreeLevelQRI.class);
    }

    /**
     * @return true if the query's fields list contains an aggregated ( field. 
     */
    protected boolean isAggFieldSelected() {
        for (QueryFieldPanel qfp : this.queryFieldItems) {
            if (qfp.getFieldQRI() instanceof RelQRI) {
                DBRelationshipInfo info = ((RelQRI) qfp.getFieldQRI()).getRelationshipInfo();

                if (info != null && (info.getType().equals(RelationshipType.ManyToMany)
                        || info.getType().equals(RelationshipType.OneToMany))) {
                    return true;
                }
            }
        }
        return false;
    }

    protected List<SpExportSchemaItem> getAvailableConcepts() {
        List<SpExportSchemaItem> result = null;
        if (this.isExportMapping) {
            result = new ArrayList<SpExportSchemaItem>();
            //XXX Don't forget that technically, theoretically, eventually schemaMappings can have more than one associated
            //exportSchema...
            if (exportSchema != null) {
                result.addAll(exportSchema.getSpExportSchemaItems());
                for (QueryFieldPanel qfp : queryFieldItems) {
                    SpExportSchemaItem qi = qfp.getSchemaItem();
                    if (qi != null) {
                        result.remove(qi);
                    }
                }
            }
            Collections.sort(result);
        }
        return result;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#updateAvailableConcepts()
     */
    public void updateAvailableConcepts() {
        if (!isUpdatingAvailableConcepts.get() && this.isExportMapping) {
            isUpdatingAvailableConcepts.set(true);
            try {
                List<SpExportSchemaItem> available = this.getAvailableConcepts();
                for (QueryFieldPanel qfp : queryFieldItems) {
                    JComboBox bx = qfp.getSchemaItemCBX();
                    if (bx != null) {
                        DefaultComboBoxModel bxm = (DefaultComboBoxModel) bx.getModel();
                        bxm.removeAllElements();
                        for (SpExportSchemaItem i : available) {
                            bxm.addElement(i);
                        }
                        SpExportSchemaItem qi = qfp.getSchemaItem();
                        if (qi != null && qi.getSpExportSchemaItemId() != null) {
                            SpExportSchemaItem unMappedItem = new SpExportSchemaItem();
                            unMappedItem.setFieldName(getResourceString("QueryBldrPane.UnmappedSchemaItemName"));
                            bxm.insertElementAt(unMappedItem, 0);
                            bxm.insertElementAt(qi, 1);
                            bx.setSelectedIndex(1);
                        } else {
                            //System.out.println("Setting unmapped concept field name to " + qfp.getExportedFieldName());
                            SpExportSchemaItem unMappedItem = new SpExportSchemaItem();
                            String expFldName = qfp.getExportedFieldName();
                            if (StringUtils.isBlank(expFldName)) {
                                if (available.size() > 0) {
                                    unMappedItem.setFieldName(
                                            getResourceString("QueryBldrPane.UnmappedSchemaItemName"));
                                } else {
                                    unMappedItem.setFieldName(qfp.getFieldTitle());
                                }
                            } else {
                                unMappedItem.setFieldName(expFldName);
                            }
                            bxm.insertElementAt(unMappedItem, 0);
                            bx.setSelectedIndex(0);
                        }
                        bx.setEditable(qi == null || qi.getSpExportSchemaItemId() == null);
                    }
                }
            } finally {
                isUpdatingAvailableConcepts.set(false);
            }
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#isUpdatingAvailableConcepts()
     */
    @Override
    public boolean isUpdatingAvailableConcepts() {
        return isUpdatingAvailableConcepts.get();
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#isAvailableExportFieldName(edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanel, java.lang.String)
     */
    @Override
    public boolean isAvailableExportFieldName(QueryFieldPanel aQfp, String name) {
        //System.out.println("isAvailableExportFieldName???");
        for (QueryFieldPanel qfp : queryFieldItems) {
            if (qfp != aQfp) {
                String n = qfp.getSchemaItem() != null ? qfp.getSchemaItem().getFieldName()
                        : qfp.getExportedFieldName();
                if (name.equalsIgnoreCase(n)) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public boolean isForSchemaExport() {
        return false;
    }

    /**
     * @return
     */
    public boolean isSmushed() {
        return smushed && isSmushable(queryFieldItems);
    }

    /**
     * @return
     */
    public static boolean isSmushable(List<QueryFieldPanel> qfps) {
        return getSmushedCol(qfps) != -1;
    }

    /**
     * @return
     */
    public int getSmushedCol() {
        return getSmushedCol(queryFieldItems);
    }

    /**
     * @return
     */
    public static int getSmushedCol(List<QueryFieldPanel> qfps) {
        int result = -1;
        int col = 0;
        for (QueryFieldPanel qfp : qfps) {
            DBFieldInfo fi = qfp.getFieldInfo();
            if (fi != null && fi.getTableInfo().getTableId() == CollectionObject.getClassTableId()
                    && "catalogNumber".equalsIgnoreCase(fi.getColumn())) {
                UIFieldFormatterIFace formatter = fi.getFormatter();
                if (formatter != null && formatter.isNumeric() && qfp.isForDisplay()) {
                    result = col;
                }
            } else if (qfp.getFieldQRI() instanceof RelQRI) {
                RelQRI qri = (RelQRI) qfp.getFieldQRI();
                if (qri.getRelationshipInfo().getType().equals(DBRelationshipInfo.RelationshipType.OneToMany)) {
                    result = -1;
                    break;
                }
            }
            if (qfp.isForDisplay()) {
                col++;
            }
        }
        return result;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.qb.QueryFieldPanelContainerIFace#changeNotification(java.lang.Object)
     */
    @Override
    public void changeNotification(Object changed) {
        if (QueryFieldPanel.class.isAssignableFrom(changed.getClass()) && isSmushableContext()) {
            QueryFieldPanel qfp = (QueryFieldPanel) changed;
            DBFieldInfo fi = qfp.getFieldInfo();
            if (fi != null && fi.getTableInfo().getTableId() == CollectionObject.getClassTableId()
                    && "catalogNumber".equalsIgnoreCase(fi.getColumn())) {
                updateSmushBtn();
            }
        }
    }

    /**
     * @return
     */
    public int getRecordIdCol() {
        return getRecordIdCol(queryFieldItems);
    }

    /**
     * @return
     */
    public static int getRecordIdCol(List<QueryFieldPanel> qfps) {
        int col = 0;
        for (QueryFieldPanel qfp : qfps) {
            if (qfp.isForDisplay()) {
                col++;
            }
        }
        return col;
    }
}