edu.ku.brc.af.ui.forms.formatters.DataObjFieldFormatMgr.java Source code

Java tutorial

Introduction

Here is the source code for edu.ku.brc.af.ui.forms.formatters.DataObjFieldFormatMgr.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.af.ui.forms.formatters;

import static edu.ku.brc.helpers.XMLHelper.getAttr;

import java.io.File;
import java.math.BigDecimal;
import java.security.AccessController;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dom4j.Element;

import edu.ku.brc.af.auth.PermissionSettings;
import edu.ku.brc.af.core.AppContextMgr;
import edu.ku.brc.af.core.db.DBFieldInfo;
import edu.ku.brc.af.core.db.DBRelationshipInfo;
import edu.ku.brc.af.core.db.DBTableIdMgr;
import edu.ku.brc.af.core.db.DBTableInfo;
import edu.ku.brc.af.ui.forms.DataObjectGettable;
import edu.ku.brc.af.ui.forms.DataObjectGettableFactory;
import edu.ku.brc.af.ui.forms.FormDataObjIFace;
import edu.ku.brc.af.ui.forms.FormHelper;
import edu.ku.brc.dbsupport.DataProviderFactory;
import edu.ku.brc.dbsupport.DataProviderSessionIFace;
import edu.ku.brc.helpers.XMLHelper;
import edu.ku.brc.ui.UIHelper;

/**
 * This class manages all the Data Object Formatters. A DataObjectFormatter is used to create a string representation 
 * of a data object. Much of the time this is a single field, but sometimes it is a concatenation of several fields.
 * 
 * Although this class behaves as a singleton, other free instances of this Mgr can be created to simulate caches of formatters.
 * These are used to isolate the changes made to field and data object formatters and aggregators on the respective dialogs
 * to the objects held by the respective managers. An object of this class holds clones of the formatters that are affected
 * by the changes made on the dialogs. Those changes are only committed if the object holder (the objects that called the
 * dialogs) choose to do so (e.g. when the user clicks SAVE on the Localizer Tool frame).
 *   
 * @author rods, ricardo
 *
 * @code_status Complete
 *
 * Created Date: Jan 17, 2007
 *
 */
public class DataObjFieldFormatMgr {
    public static final String factoryName = "edu.ku.brc.ui.forms.formatters.DataObjFieldFormatMgr";

    protected static final Logger log = Logger.getLogger(DataObjFieldFormatMgr.class);

    protected static AtomicReference<DataObjFieldFormatMgr> instance = new AtomicReference<DataObjFieldFormatMgr>(
            null);

    protected static boolean doingLocal = false;

    protected boolean domFound = false;

    protected Hashtable<String, DataObjSwitchFormatter> formatHash = new Hashtable<String, DataObjSwitchFormatter>();
    protected Hashtable<Class<?>, DataObjSwitchFormatter> formatClassHash = new Hashtable<Class<?>, DataObjSwitchFormatter>();
    protected Hashtable<String, DataObjAggregator> aggHash = new Hashtable<String, DataObjAggregator>();
    protected Hashtable<Class<?>, DataObjAggregator> aggClassHash = new Hashtable<Class<?>, DataObjAggregator>();

    protected Hashtable<String, Class<?>> typeHash = new Hashtable<String, Class<?>>();
    protected Hashtable<Class<?>, String> typeHashRevMap = new Hashtable<Class<?>, String>(); // reverse mapping

    protected String localFileName = null;
    protected boolean hasChanged = false;
    protected AppContextMgr appContextMgr = null;

    /**
     * Protected Constructor
     */
    protected DataObjFieldFormatMgr() {
        init();
        load();
    }

    /**
     * Constructor
     */
    public DataObjFieldFormatMgr(final AppContextMgr appContextMgr) {
        this.appContextMgr = appContextMgr;
        init();
        load();
    }

    /**
     * Resets the Mgr so it gets reloaded.
     */
    public void reset() {
        if (instance.get() != null) {
            instance.get().save();
        }
        instance.set(null);
    }

    /**
     * 
     */
    private void init() {
        localFileName = "backstop" + File.separator + "dataobj_formatters.xml";

        Object[] initTypeData = { "string", String.class, "int", Integer.class, "long", Long.class, "float",
                Float.class, "double", Double.class, "boolean", Boolean.class, "bigdecimal", BigDecimal.class,
                "short", Short.class, "byte", Byte.class };
        for (int i = 0; i < initTypeData.length; i++) {
            typeHash.put((String) initTypeData[i], (Class<?>) initTypeData[i + 1]);
            typeHashRevMap.put((Class<?>) initTypeData[i + 1], (String) initTypeData[i]);
            i++;
        }
    }

    /**
     * @return the contextMgr
     */
    public AppContextMgr getAppContextMgr() {
        if (appContextMgr == null) {
            appContextMgr = AppContextMgr.getInstance();
        }
        return appContextMgr;
    }

    /**
     * @param contextMgr the contextMgr to set
     */
    public void setAppContextMgr(AppContextMgr appContextMgr) {
        this.appContextMgr = appContextMgr;
    }

    /**
     * @param cls the class
     * @return the string for the java type class
     */
    public String getStrForType(final Class<?> cls) {
        return typeHashRevMap.get(cls);
    }

    /**
     * Copy constructor
     * @param source Source to copy from, usually the static instance
     */
    @SuppressWarnings("unchecked")
    public DataObjFieldFormatMgr(final DataObjFieldFormatMgr source) {
        formatHash = (Hashtable<String, DataObjSwitchFormatter>) source.getFormatHash().clone();
        formatClassHash = (Hashtable<Class<?>, DataObjSwitchFormatter>) source.getFormatClassHash().clone();
        aggHash = (Hashtable<String, DataObjAggregator>) source.getAggHash().clone();
        aggClassHash = (Hashtable<Class<?>, DataObjAggregator>) source.getAggClassHash().clone();
    }

    /**
     * Transfer state of free manager instance to static instance
     */
    public void copyFrom(final DataObjFieldFormatMgr source) {
        this.hasChanged = source.hasChanged;
        setFormatHash(source.getFormatHash());
        setFormatClassHash(source.getFormatClassHash());
        setAggHash(source.getAggHash());
        setAggClassHash(source.getAggClassHash());
    }

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

    /**
     * @return the localFileName
     */
    public String getLocalFileName() {
        return localFileName;
    }

    /**
     * @param doLocal the doLocal to set
     */
    public static void setDoingLocal(boolean doLocal) {
        DataObjFieldFormatMgr.doingLocal = doLocal;
    }

    /**
     * @return the DOM to process
     */
    protected Element getDOM() throws Exception {
        return XMLHelper.readDOMFromConfigDir("backstop/dataobj_formatters.xml"); //$NON-NLS-1$
    }

    /**
     * Resets the cache.
     */
    protected void resetInternal() {
        formatHash.clear();
        formatClassHash.clear();
        aggHash.clear();
        aggClassHash.clear();
    }

    /**
     * Loads formats from config file
     *
     */
    public void load() {
        resetInternal();

        try {
            Element root = getDOM();

            if (root != null) {
                domFound = true;
                List<?> formatters = root.selectNodes("/formatters/format");
                for (Object formatObj : formatters) {
                    Element formatElement = (Element) formatObj;

                    String name = formatElement.attributeValue("name");
                    String title = formatElement.attributeValue("title");
                    String className = formatElement.attributeValue("class");
                    String format = formatElement.attributeValue("format");
                    boolean isDefault = XMLHelper.getAttr(formatElement, "default", false);

                    if (StringUtils.isEmpty(title)) {
                        title = name;
                    }

                    Class<?> dataClass = null;
                    if (StringUtils.isNotEmpty(className)) {
                        try {
                            dataClass = Class.forName(className);
                        } catch (Exception ex) {
                            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                            edu.ku.brc.exceptions.ExceptionTracker.getInstance()
                                    .capture(DataObjFieldFormatMgr.class, ex);
                            log.error("Couldn't load class [" + className + "]");
                        }
                    } else {
                        log.error("Class name [" + className + "] is empty and can't be. Skipping.");
                        continue;
                    }

                    Element switchElement = (Element) formatElement.selectObject("switch");
                    if (switchElement != null) {
                        boolean isSingle = getAttr(switchElement, "single", false);
                        String switchField = getAttr(switchElement, "field", null);

                        DataObjSwitchFormatter switchFormatter = new DataObjSwitchFormatter(name, title, isSingle,
                                isDefault, dataClass, switchField);

                        if (formatHash.get(name) == null) {
                            formatHash.put(name, switchFormatter);

                        } else {
                            throw new RuntimeException("Duplicate formatter name[" + name + "]");
                        }

                        DataObjSwitchFormatter sf = formatClassHash.get(dataClass);
                        if (sf == null || isDefault) {
                            if (isDefault) {
                                DataObjSwitchFormatter curDO = formatClassHash.get(dataClass);
                                if (curDO != null && curDO.isDefault()) {
                                    throw new RuntimeException(
                                            "There are two default DataObjectFormatters current [" + curDO.getName()
                                                    + "] adding[" + sf.getName() + "] for class "
                                                    + dataClass.getSimpleName());
                                }
                            }
                            formatClassHash.put(dataClass, switchFormatter);
                        }

                        Element external = (Element) switchElement.selectSingleNode("external");
                        if (external != null) {
                            String externalClassName = getAttr(external, "class", (String) null);
                            if (StringUtils.isNotEmpty(externalClassName)) {
                                Properties props = new Properties();

                                List<?> paramElements = external.selectNodes("param");
                                for (Object param : paramElements) {
                                    String nameStr = getAttr((Element) param, "name", null);
                                    String val = ((Element) param).getTextTrim();
                                    if (StringUtils.isNotEmpty(nameStr) && StringUtils.isNotEmpty(val)) {
                                        props.put(nameStr, val);
                                    }
                                }
                                try {
                                    DataObjDataFieldFormatIFace fmt = Class.forName(externalClassName)
                                            .asSubclass(DataObjDataFieldFormatIFace.class).newInstance();
                                    fmt.init(name, props);
                                    switchFormatter.add(fmt);

                                } catch (Exception ex) {
                                    edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                                    edu.ku.brc.exceptions.ExceptionTracker.getInstance()
                                            .capture(DataObjFieldFormatMgr.class, ex);
                                    log.error(ex);
                                    ex.printStackTrace();
                                }
                            } else {
                                throw new RuntimeException(
                                        "The 'class' attribute cannot be empty for an external formatter! [" + name
                                                + "]");
                            }
                        } else {
                            DBTableInfo tableInfo = switchFormatter.getTableInfo();

                            List<?> fieldsElements = switchElement.selectNodes("fields");
                            for (Object fieldsObj : fieldsElements) {
                                Element fieldsElement = (Element) fieldsObj;
                                String valueStr = getAttr(fieldsElement, "value", null);

                                List<?> fldList = fieldsElement.selectNodes("field");
                                DataObjDataField[] fields = new DataObjDataField[fldList.size()];
                                int inx = 0;
                                for (Object fldObj : fldList) {
                                    Element fieldElement = (Element) fldObj;
                                    String fieldName = fieldElement.getTextTrim();
                                    String dataTypeStr = getAttr(fieldElement, "type", null);
                                    String formatStr = getAttr(fieldElement, "format", null);
                                    String sepStr = getAttr(fieldElement, "sep", null);
                                    String formatterName = getAttr(fieldElement, "formatter", null);
                                    String uifieldformatter = getAttr(fieldElement, "uifieldformatter", null);

                                    DBFieldInfo fieldInfo = tableInfo.getFieldByName(fieldName);
                                    DBRelationshipInfo relInfo = fieldInfo == null
                                            ? tableInfo.getRelationshipByName(fieldName)
                                            : null;

                                    Class<?> classObj;
                                    if (dataTypeStr == null) {
                                        if (fieldInfo != null) {
                                            classObj = fieldInfo.getDataClass();
                                        } else {
                                            classObj = String.class;
                                        }
                                    } else {
                                        classObj = typeHash.get(dataTypeStr);
                                    }

                                    if (classObj == null) {
                                        log.error("Couldn't map standard type[" + dataTypeStr + "]");
                                    }
                                    fields[inx] = new DataObjDataField(fieldName, classObj, formatStr, sepStr,
                                            formatterName, uifieldformatter);
                                    fields[inx].setDbInfo(tableInfo, fieldInfo, relInfo, true);

                                    inx++;
                                }
                                switchFormatter.add(new DataObjDataFieldFormat(name, dataClass, isDefault, format,
                                        valueStr, fields));
                            }
                        }
                    } else {
                        log.error("No switch element! [" + name + "]"); // not needed once we start using a DTD/Schema
                    }
                }

                for (Object aggObj : root.selectNodes("/formatters/aggregators/aggregator")) {
                    Element aggElement = (Element) aggObj;

                    String name = aggElement.attributeValue("name");
                    String title = aggElement.attributeValue("title");
                    String separator = aggElement.attributeValue("separator");
                    String countStr = aggElement.attributeValue("count");
                    String ending = aggElement.attributeValue("ending");
                    String format = aggElement.attributeValue("format");
                    String ordFldName = XMLHelper.getAttr(aggElement, "orderfieldname", null);
                    boolean isDefault = XMLHelper.getAttr(aggElement, "default", true);
                    String dataClassName = XMLHelper.getAttr(aggElement, "class", null);

                    Integer count = StringUtils.isNotEmpty(countStr) && StringUtils.isNumeric(countStr)
                            ? Integer.parseInt(countStr)
                            : null;

                    Class<?> dataClass = null;
                    if (StringUtils.isNotEmpty(dataClassName)) {
                        try {
                            dataClass = Class.forName(dataClassName);
                        } catch (Exception ex) {
                            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                            edu.ku.brc.exceptions.ExceptionTracker.getInstance()
                                    .capture(DataObjFieldFormatMgr.class, ex);
                            log.error("Couldn't load class [" + dataClassName + "]");
                        }
                    } else {
                        log.error("Class name [" + dataClassName + "] is empty and can't be. Skipping.");
                        continue;
                    }

                    // TODO check for duplicates!
                    aggHash.put(name, new DataObjAggregator(name, title, dataClass, isDefault, separator, count,
                            ending, format, ordFldName));
                }

            } else {
                log.debug("Couldn't get resource [DataObjFormatters]");
            }

            // This needs to be refactored so we don't have to do this here
            // I think it is because 'load' is being called from the constructor.
            if (instance.get() == null) {
                instance.set(this);
            }
            // now that all formats have been loaded, set table/field/formatter info\
            // must be executed after the instance is set
            for (DataObjSwitchFormatter format : instance.get().formatHash.values()) {
                format.setTableAndFieldInfo();
            }

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

    /**
     * Copies the internal data structures from the source to this object. But only if they have changed.
     * @param source the source of the changes
     */
    public void applyChanges(final DataObjFieldFormatMgr source) {
        if (source.hasChanged) {
            copyFrom(source);
            save();

        } else {
            log.debug("Not saved = No Changes");
        }
    }

    /**
     * Saves formatters.
     */
    public void save() {
        DataObjFieldFormatMgr localInstance = DataObjFieldFormatMgr.getInstance();

        // can only save the static instance
        //if (localInstance.getLocalFileName() == null)
        //{
        //    return;
        //}

        StringBuilder sb = new StringBuilder(1024);

        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");

        // data obj formatters
        sb.append("<formatters>\n");

        Vector<DataObjSwitchFormatter> formatVector = new Vector<DataObjSwitchFormatter>(
                localInstance.getFormatHash().values());
        Collections.sort(formatVector, new Comparator<DataObjSwitchFormatter>() {
            public int compare(DataObjSwitchFormatter o1, DataObjSwitchFormatter o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

        for (DataObjSwitchFormatter format : formatVector) {
            format.toXML(sb);
        }

        // aggregators
        sb.append("  <aggregators>\n");

        Vector<DataObjAggregator> aggVector = new Vector<DataObjAggregator>(localInstance.getAggHash().values());
        Collections.sort(aggVector, new Comparator<DataObjAggregator>() {
            public int compare(DataObjAggregator o1, DataObjAggregator o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        for (DataObjAggregator agg : aggVector) {
            agg.toXML(sb);
        }

        sb.append("  </aggregators>\n");
        sb.append("\n\n</formatters>\n");

        log.debug(sb.toString());

        saveXML(sb.toString());
    }

    /**
     * Persists the XML.
     * @param xml the xml string.
     */
    protected void saveXML(final String xml) {
        // save resource back to database
        if (doingLocal) {
            File outFile = XMLHelper.getConfigDir(instance.get().getLocalFileName());
            try {
                FileUtils.writeStringToFile(outFile, xml);

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

    /**
     * Adds a new formatter
     */
    public void addFormatter(DataObjSwitchFormatter formatter) {
        formatHash.put(formatter.getName(), formatter);
        formatClassHash.put(formatter.getDataClass(), formatter);
        hasChanged = true;
    }

    /**
     * Deletes a formatter from the 
     */
    public void removeFormatter(DataObjSwitchFormatter formatter) {
        formatHash.remove(formatter.getName());
        formatClassHash.remove(formatter.getName());
        hasChanged = true;
    }

    /**
     * Returns a data formatter.
     * @param formatName the name
     * @return the formatter
     */
    public DataObjSwitchFormatter getFormatter(final String formatName) {
        return formatHash.get(formatName);
    }

    /**
     * Returns all the formatters as a Collection
     * @return all the formatters
     */
    public Collection<DataObjSwitchFormatter> getFormatters() {
        return formatHash.values();
    }

    /**
     * Format a data object using a named formatter.
     * @param dataObj the data object for which fields will be formatted for it
     * @param formatName the name of the formatter to use
     * @return the string result of the format
     */
    private DataObjDataFieldFormatIFace getDataFormatter(final Object dataObj, final String formatName) {
        DataObjSwitchFormatter switcherFormatter = formatHash.get(formatName);
        if (switcherFormatter != null) {
            return switcherFormatter.getDataFormatter(dataObj);

        }
        return null;
    }

    /**
     * Format a data object using a named formatter.
     * @param formatName the name of the formatter to use
     * @return the string result of the format
     */
    public DataObjSwitchFormatter getDataFormatter(final String formatName) {
        return formatHash.get(formatName);
    }

    /**
     * Returns a list of formatters that match the class, the default (if there is one) is at the beginning of the list.
     * @param clazz the class of the data that the formatter is used for.
     * @return return a list of formatters that match the class
     */
    public List<DataObjSwitchFormatter> getFormatterList(final Class<?> clazz) {
        Vector<DataObjSwitchFormatter> list = new Vector<DataObjSwitchFormatter>();
        DataObjSwitchFormatter defFormatter = null;

        for (Enumeration<DataObjSwitchFormatter> e = formatHash.elements(); e.hasMoreElements();) {
            DataObjSwitchFormatter f = e.nextElement();
            if (clazz == f.getDataClass()) {
                if (f.isDefault() && defFormatter == null) {
                    defFormatter = f;
                } else {
                    list.add(f);
                }
            }
        }
        if (defFormatter != null) {
            list.insertElementAt(defFormatter, 0);
        }
        return list;
    }

    /**
     * Tries to get the value and if it gets a lazy exception .
     * @param fieldNames
     * @param dataObj
     * @param getter
     * @return
     */
    private Object[] getFieldValues(final String[] fieldNames, final Object dataObj,
            final DataObjectGettable getter) {
        Object[] values = null;
        try {
            values = UIHelper.getFieldValues(fieldNames, dataObj, getter);

        } catch (org.hibernate.LazyInitializationException hbex) // XXX this Exception should be made generic
        {
            DataProviderSessionIFace session = null;
            try {
                session = DataProviderFactory.getInstance().createSession();
                session.attach(dataObj);

                values = UIHelper.getFieldValues(fieldNames, dataObj, getter);

            } catch (Exception ex) {

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

    /**
     * Format a data object using a named formatter
     * @param dataObj the data object for which fields will be formatted for it
     * @return the string result of the format
     */
    protected String formatInternal(final DataObjDataFieldFormatIFace format, final Object dataObj) {
        String restricted = FormHelper.checkForRestrictedValue(dataObj);
        if (restricted != null) {
            return restricted;
        }

        if (format != null) {
            if (format.isDirectFormatter()) {
                return format.format(dataObj);
            }

            DataObjectGettable getter = DataObjectGettableFactory.get(format.getDataClass().getName(),
                    FormHelper.DATA_OBJ_GETTER);

            if (getter != null) {
                StringBuilder strBuf = new StringBuilder(128);
                for (DataObjDataField field : format.getFields()) {
                    Class<?> fieldClass = field.getType();

                    Object[] values = getFieldValues(new String[] { field.getName() }, dataObj, getter);
                    Object value = values != null ? values[0] : null;

                    // NOTE: if the field was a Date or Calendar object it has already been reformatted to a String
                    // so we change the fieldClass to string so everything works out.
                    if (fieldClass == Date.class || fieldClass == Calendar.class) {
                        fieldClass = String.class;
                    }

                    if (value != null) {
                        if (AppContextMgr.isSecurityOn() && value instanceof FormDataObjIFace) {
                            DBTableInfo tblInfo = DBTableIdMgr.getInstance()
                                    .getByShortClassName(value.getClass().getSimpleName());
                            if (tblInfo != null) {
                                PermissionSettings perm = tblInfo.getPermissions();
                                if (perm != null) {
                                    if (!perm.canView()) {
                                        return "";
                                    }
                                }
                            }
                        }

                        if (field.getDataObjFormatterName() != null) {
                            String fmtStr = formatInternal(getDataFormatter(value, field.getDataObjFormatterName()),
                                    value);
                            if (fmtStr != null) {
                                strBuf.append(fmtStr);
                            }

                        } else if (field.getUiFieldFormatterName() != null) {
                            UIFieldFormatterIFace fmt = UIFieldFormatterMgr.getInstance()
                                    .getFormatter(field.getUiFieldFormatterName());
                            DBTableInfo tblInfo = DBTableIdMgr.getInstance()
                                    .getByShortClassName(dataObj.getClass().getSimpleName());
                            if (tblInfo != null) {
                                DBFieldInfo fi = tblInfo.getFieldByName(field.getName());
                                if (fi != null && fi.getFormatter() != null) {
                                    fmt = fi.getFormatter();
                                }
                            }

                            if (fmt != null) {
                                strBuf.append(fmt.formatToUI(value));
                            } else {
                                strBuf.append(value);
                            }

                        } else if (value.getClass() == fieldClass) {
                            // When format is null then it is a string
                            if (fieldClass == String.class && (field.getFormat() == null || format.equals("%s"))) {
                                if (field.getSep() != null) {
                                    strBuf.append(field.getSep());
                                }
                                strBuf.append(value.toString());
                            } else {
                                String sep = field.getSep();
                                if (sep != null) {
                                    strBuf.append(sep);
                                }
                                //log.debug("["+value+"]["+format+"]");
                                if (field.getFormat() != null) {
                                    Object[] arg = { value };
                                    Formatter formatter = new Formatter();
                                    formatter.format(field.getFormat(), arg);
                                    strBuf.append(formatter.toString());

                                } else {
                                    strBuf.append(value.toString());
                                }
                            }
                        } else {
                            log.error("Mismatch of types data retrieved as class["
                                    + (value != null ? value.getClass().getSimpleName() : "N/A")
                                    + "] and the format requires ["
                                    + (field != null ? (fieldClass != null ? fieldClass.getSimpleName() : "N/A 2")
                                            : "N/A")
                                    + "]");
                        }
                    }
                }
                return strBuf.toString();
            }
        }
        return "";
    }

    /**
     * Format a data object using a named formatter
     * @param dataObj the data object for which fields will be formatted for it
     * @return the string result of the format
     */
    protected String formatInternal(final DataObjDataFieldFormat format, final Object[] dataObjs) {
        if (format != null) {
            DataObjectGettable getter = DataObjectGettableFactory.get(format.getDataClass().getName(),
                    FormHelper.DATA_OBJ_GETTER);
            if (getter != null) {
                StringBuilder strBuf = new StringBuilder(128);

                if (dataObjs.length == format.getFields().length) {
                    int inx = 0;
                    for (DataObjDataField field : format.getFields()) {
                        Object value = dataObjs[inx++];
                        if (value != null) {
                            if (field.getDataObjFormatterName() != null) {
                                String fmtStr = formatInternal(
                                        getDataFormatter(value, field.getDataObjFormatterName()), value);
                                if (fmtStr != null) {
                                    strBuf.append(fmtStr);
                                }

                            } else if (field.getUiFieldFormatterName() != null) {
                                UIFieldFormatterIFace fmt = UIFieldFormatterMgr.getInstance()
                                        .getFormatter(field.getUiFieldFormatterName());
                                if (fmt != null) {
                                    strBuf.append(fmt.formatToUI(value));
                                } else {
                                    strBuf.append(value);
                                }

                            } else if (value.getClass() == field.getType()) {
                                // When format is null then it is a string
                                if (field.getType() == String.class
                                        && (field.getFormat() == null || format.equals("%s"))) {
                                    if (field.getSep() != null) {
                                        strBuf.append(field.getSep());
                                    }
                                    strBuf.append(value.toString());
                                } else {
                                    Object[] arg = { value };
                                    Formatter formatter = new Formatter();
                                    formatter.format(format.getFormat(), arg);
                                    strBuf.append(formatter.toString());
                                }
                            } else {
                                log.error("Mismatch of types data retrieved as class["
                                        + value.getClass().getSimpleName() + "] and the format requires ["
                                        + field.getType().getSimpleName() + "]");
                            }
                        }
                    }
                } else {
                    log.error("Data Array sent to formatter is not the same length [" + dataObjs.length
                            + "] as the formatter [" + format.getFields().length + "]");
                }
                return strBuf.toString();
            }
        }
        return "";
    }

    /**
     * Aggregates all the items in a Collection into a string given a formatter 
     * @param items the collection of items
     * @param aggName the name of the aggregator to use
     * @return a string representing a collection of all the objects 
     */
    @SuppressWarnings("unchecked")
    protected String aggregateInternal(final Collection<?> items, final DataObjAggregator agg) {
        if (agg != null) {
            StringBuilder aggStr = new StringBuilder(128);

            Collection<?> itemsAsCol = items;
            //            if (StringUtils.isNotBlank(agg.getOrderFieldName()))
            //            {
            //               try
            //               {
            //                  String orderFld = agg.getOrderFieldName();
            //                  String methodName = "get" + orderFld.substring(0,1).toUpperCase() + orderFld.substring(1);
            //                  final Method orderFldGetter = agg.getDataClass().getMethod(methodName, (Class<?>[])null);
            //                  if (Comparable.class.isAssignableFrom(orderFldGetter.getReturnType()))
            //                  {
            //                     List<Object> itemsList = new Vector<Object>();
            //                     itemsList.addAll(itemsAsCol);
            //                     Comparator<Object> comp = new Comparator<Object>() {
            //
            //                        @Override
            //                        public int compare(Object o1, Object o2) {
            //                           if (agg.getDataClass().isAssignableFrom(o1.getClass()) && agg.getDataClass().isAssignableFrom(o2.getClass()))
            //                           {
            //                              try
            //                              {
            //                                 Comparable<Object> f1 = (Comparable<Object>)orderFldGetter.invoke(agg.getDataClass().cast(o1), (Object[])null);
            //                                 Comparable<Object> f2 = (Comparable<Object>)orderFldGetter.invoke(agg.getDataClass().cast(o2), (Object[])null);
            //                                 if (f1 != null)
            //                                 {
            //                                    return f1.compareTo(f2);
            //                                 }
            //                                 else if (f2 != null)
            //                                 {
            //                                    return -1;
            //                                 } 
            //                                 else
            //                                 {
            //                                    return 0;
            //                                 }
            //                              }  catch (InvocationTargetException tex)
            //                                 {
            //                                    return 0;
            //                                 }catch (IllegalAccessException acex)
            //                                 {
            //                                    return 0;
            //                                 }catch (IllegalArgumentException arex)
            //                                 {
            //                                    return 0;
            //                                 }
            //                           }
            //                           return 0;
            //                        }
            //                     };
            //                     Collections.sort(itemsList, comp);
            //                     itemsAsCol = itemsList;
            //                  }
            //               } catch (NoSuchMethodException mex)
            //               {
            //                  //sorting was a bad idea
            //               } catch (SecurityException sex)
            //               {
            //                  //sorting was a bad idea
            //               }            
            //            }
            /*if (items != null && items.size() > 0)
            {
            if (items.iterator().next() instanceof Comparable<?>)
            {
                List<Comparable<Object>> itemsList = new ArrayList<Comparable<Object>>();
                for (Object obj : items)
                {
                    itemsList.add((Comparable<Object>)obj);
                }
                Collections.sort(itemsList);
                itemsAsCol = itemsList;
            }
            }*/

            int count = 0;
            for (Object obj : itemsAsCol) {
                if (obj != null) {
                    // only add a separator after the first element
                    if (count > 0) {
                        aggStr.append(agg.getSeparator());
                    }

                    if (agg.useIdentity() && obj instanceof FormDataObjIFace) {
                        aggStr.append(((FormDataObjIFace) obj).getIdentityTitle());

                    } else {
                        aggStr.append(
                                formatInternal(getInstance().getDataFormatter(obj, agg.getFormatName()), obj));
                    }

                    //System.out.println(aggStr.toString());

                    int aggCount = (agg.getCount() != null) ? agg.getCount() : 0;
                    if (aggCount > 0 && count >= aggCount - 1) {
                        // add the ending string at the end of the aggregated string and quit loop
                        String endingStr = agg.getEnding();
                        if (StringUtils.isNotEmpty(endingStr)) {
                            aggStr.append(endingStr);
                        }
                        break;
                    }
                }
                count++;
            }
            return aggStr.toString();

        }
        // else
        log.error("Aggegrator was null.");
        return null;
    }

    /**
     * Format a data object using a named formatter.
     * @param dataObj the data object for which fields will be formatted for it
     * @param formatName the name of the formatter to use
     * @return the string result of the format
     */
    public String format(final Object dataObj, final String formatName) {
        if (domFound && StringUtils.isNotEmpty(formatName)) {
            DataObjSwitchFormatter sf = formatHash.get(formatName);
            if (sf != null) {
                DataObjDataFieldFormatIFace dff = sf.getDataFormatter(dataObj);
                if (dff != null) {
                    return formatInternal(dff, dataObj);

                }
                // else
                log.error("Couldn't find DataObjDataFieldFormat for [" + sf.getName() + "] value[" + dataObj + "]");
            } else {
                log.error("Couldn't find DataObjSwitchFormatter for class [" + formatName + "]");
            }
        }
        return null;
    }

    /**
     * Format a data object using a named formatter.
     * @param dataObj the data object for which fields will be formatted for it
     * @param dataClass the class for the data to be formatted
     * @return the string result of the format
     */
    public String format(final Object dataObj, final Class<?> dataClass) {
        if (dataObj == null) {
            return "";
        }

        if (domFound) {
            DataObjSwitchFormatter sf = formatClassHash.get(dataClass);
            if (sf != null) {
                DataObjDataFieldFormatIFace dff = sf.getDataFormatter(dataObj);
                if (dff != null) {
                    return formatInternal(dff, dataObj);
                }
                // else
                log.error("Couldn't find DataObjDataFieldFormat for [" + sf.getName() + "] value[" + dataObj + "]");
            }
            // else
            log.error("Couldn't find DataObjSwitchFormatter for class [" + dataClass.getName() + "]");
        }
        return null;
    }

    /**
     * Format a data object using a named formatter.
     * @param dataObjs the array data objects for which fields will be formatted for it
     * @param formatName the name of the formatter to use
     * @return the string result of the format
     */
    public static String format(@SuppressWarnings("unused") final Object[] dataObjs,
            @SuppressWarnings("unused") final String formatName) {
        throw new RuntimeException("OK, I am used, so come and fix me up!");
        //return instance.formatInternal(dataObjs, formatName);
    }

    /**
     * @param aggName
     * @return
     */
    public DataObjAggregator getAggregator(final String aggName) {
        return aggHash.get(aggName);
    }

    /**
     * Aggregates all the items in a Collection into a string given a formatter.
     * @param items the collection of items
     * @param aggName the name of the aggregator to use
     * @return a string representing a collection of all the objects 
     */
    public String aggregate(final Collection<?> items, final String aggName) {
        if (domFound) {
            if (items != null && items.size() > 0) {
                DataObjAggregator agg = aggHash.get(aggName);
                if (agg != null) {
                    return aggregateInternal(items, agg);

                }
                // else
                log.error("Couldn't find Aggegrator [" + aggName + "]");
            }
        }
        // else
        return "";
    }

    /**
     * Aggregates all the items in a Collection into a string given a formatter.
     * @param items the collection of items
     * @param aggName the name of the aggregator to use
     * @return a string representing a collection of all the objects 
     */
    public String aggregate(final Collection<?> items, final Class<?> dataClass) {
        if (!domFound || items == null || dataClass == null) {
            return "";
        }

        DataObjAggregator defAgg = null;
        for (Enumeration<DataObjAggregator> e = aggHash.elements(); e.hasMoreElements();) {
            DataObjAggregator agg = e.nextElement();
            if (dataClass == agg.getDataClass()) {
                if (agg.isDefault()) {
                    defAgg = agg;
                    break;

                } else if (defAgg == null) {
                    defAgg = agg;
                }
            }
        }

        if (defAgg != null) {
            return aggregateInternal(items, defAgg);

        }
        // else
        log.error("Could find aggregator of class [" + dataClass.getCanonicalName() + "]");
        return "";
    }

    /**
     * Returns a list of aggregators that match the class, the default (if there is one) is at the beginning of the list.
     * @param clazz the class of the data that the aggregator is used for.
     * @return return a list of aggregators that match the class
     */
    public List<DataObjAggregator> getAggregatorList(final Class<?> clazz) {
        Vector<DataObjAggregator> list = new Vector<DataObjAggregator>();
        DataObjAggregator defFormatter = null;

        for (Enumeration<DataObjAggregator> e = aggHash.elements(); e.hasMoreElements();) {
            DataObjAggregator f = e.nextElement();
            if (clazz == f.getDataClass()) {
                if (f.isDefault() && defFormatter == null) {
                    defFormatter = f;
                } else {
                    list.add(f);
                }
            }
        }
        if (defFormatter != null) {
            list.insertElementAt(defFormatter, 0);
        }
        return list;
    }

    /**
     * Generic method that creates a unique name for an object in a hash if it doesn't yet have one
     * @param <T>
     * @param prefix
     * @param separator
     * @param names
     * @return
     */
    protected static <T> String getUniqueName(final String prefix, final String separator,
            final Set<String> names) {
        // find a name that doesn't yet exist in the hash
        // name formation patter is prefix.i, where i is a counter
        int i = 1;
        String name = prefix + separator + Integer.toString(i);
        while (names.contains(name)) {
            name = prefix + separator + Integer.toString(++i);
        }
        return name;
    }

    /**
     * Adds a new aggregator.
     * @param aggregator the aggregator to add
     */
    public void addAggregator(final DataObjAggregator aggregator) {
        aggHash.put(aggregator.getName(), aggregator);
        aggClassHash.put(aggregator.getDataClass(), aggregator);
        hasChanged = true;
    }

    /**
     * Deletes a aggregator from the hashes
     * @param aggregator the aggregator to remove
     */
    public void removeAggregator(final DataObjAggregator aggregator) {
        aggHash.remove(aggregator.getName());
        aggClassHash.remove(aggregator.getName());
        hasChanged = true;
    }

    /**
     * Returns the instance to the singleton
     * @return  the instance to the singleton
     */
    public static DataObjFieldFormatMgr getInstance() {
        if (instance.get() != null) {
            return instance.get();
        }

        synchronized (instance) {
            if (StringUtils.isEmpty(factoryName)) {
                instance.set(new DataObjFieldFormatMgr());

            } else {

                // else
                String factoryNameStr = AccessController.doPrivileged(new java.security.PrivilegedAction<String>() {
                    public String run() {
                        return System.getProperty(factoryName);
                    }
                });

                if (StringUtils.isNotEmpty(factoryNameStr)) {
                    try {
                        instance.set((DataObjFieldFormatMgr) Class.forName(factoryNameStr).newInstance());

                    } catch (Exception e) {
                        edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                        edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(DataObjFieldFormatMgr.class,
                                e);
                        InternalError error = new InternalError(
                                "Can't instantiate DataObjFieldFormatMgr factory " + factoryNameStr);
                        error.initCause(e);
                        throw error;
                    }
                }

                if (instance.get() == null) {
                    instance.set(new DataObjFieldFormatMgr());
                }
            }

            // now that all formats have been loaded, set table/field/formatter
            // info\
            // must be executed after the instance is set
            for (DataObjSwitchFormatter format : instance.get().formatHash.values()) {
                format.setTableAndFieldInfo();
            }
        }
        return instance.get();
    }

    public Hashtable<String, DataObjSwitchFormatter> getFormatHash() {
        return formatHash;
    }

    public void setFormatHash(Hashtable<String, DataObjSwitchFormatter> formatHash) {
        this.formatHash = formatHash;
    }

    public Hashtable<Class<?>, DataObjSwitchFormatter> getFormatClassHash() {
        return formatClassHash;
    }

    public void setFormatClassHash(Hashtable<Class<?>, DataObjSwitchFormatter> formatClassHash) {
        this.formatClassHash = formatClassHash;
    }

    public Hashtable<String, DataObjAggregator> getAggHash() {
        return aggHash;
    }

    public void setAggHash(Hashtable<String, DataObjAggregator> aggHash) {
        this.aggHash = aggHash;
    }

    public Hashtable<Class<?>, DataObjAggregator> getAggClassHash() {
        return aggClassHash;
    }

    public void setAggClassHash(Hashtable<Class<?>, DataObjAggregator> aggClassHash) {
        this.aggClassHash = aggClassHash;
    }

    /**
     * Sets DataObjFieldFormatMgr instance to null. 
     * Next getInstance() call will force complete rebuild. 
     * 
     * Not sure if synchronization helps if getInstance() is not synchronized.
     * However, risk of concurrent access is minimal, because this is only called 
     * after Schema localization changes, which can only be performed when no other tasks are open.
     * 
     */
    public synchronized static void clear() {
        instance = null;
    }
}