gda.data.metadata.NXMetaDataProvider.java Source code

Java tutorial

Introduction

Here is the source code for gda.data.metadata.NXMetaDataProvider.java

Source

/*-
 * Copyright  2013 Diamond Light Source Ltd.
 *
 * This file is part of GDA.
 *
 * GDA is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License version 3 as published by the Free
 * Software Foundation.
 *
 * GDA 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 GDA. If not, see <http://www.gnu.org/licenses/>.
 */

package gda.data.metadata;

import gda.data.PlottableDetectorData;
import gda.data.nexus.extractor.NexusExtractor;
import gda.data.nexus.extractor.NexusGroupData;
import gda.data.nexus.tree.INexusTree;
import gda.data.nexus.tree.NexusTreeAppender;
import gda.data.nexus.tree.NexusTreeNode;
import gda.data.scan.datawriter.NexusDataWriter;
import gda.device.Detector;
import gda.device.DeviceException;
import gda.device.Scannable;
import gda.device.ScannableMotionUnits;
import gda.device.scannable.ScannableUtils;
import gda.device.scannable.scannablegroup.ScannableGroup;
import gda.factory.Findable;
import gda.jython.InterfaceProvider;
import gda.jython.JythonServerFacade;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.math3.util.Pair;
import org.nexusformat.NexusFile;
import org.python.core.PyException;
import org.python.core.PyFloat;
import org.python.core.PyInteger;
import org.python.core.PyList;
import org.python.core.PyNone;
import org.python.core.PyObject;
import org.python.core.PySequence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

public class NXMetaDataProvider implements NexusTreeAppender, Map<String, Object>, Findable {

    private String name;

    private static final String GROUP_ITEM_SEPARATOR = "."; // single dot
    private static final String FIELD_ITEM_SEPARATOR = "."; // single dot
    private static final String PREAMBLE = "meta:\n";
    private static final String LS_NEXT_ITEM_SEPARATOR = "\n"; // single new line
    private static final String LL_NEXT_ITEM_SEPARATOR = "\n"; // single new line
    private static final String LL_MID_CONNECTOR = " = ";
    private static final String LL_UNITS_SEPARATOR = " "; // single white space

    private static final String LL_ARRAY_OPEN = "[";
    private static final String LL_ARRAY_CLOSE = "]";
    private static final String LL_ARRAY_ITEM_SEPARATOR = ", "; // single coma followed by single white space
    private static final String LL_FLOAT_ARRAY_FORMAT = "%5.3f";
    private static final String LL_INT_ARRAY_FORMAT = "%d";

    public String groupItemSeparator = GROUP_ITEM_SEPARATOR;
    public String fieldItemSeparator = FIELD_ITEM_SEPARATOR;
    public String preamble = PREAMBLE;
    public String lsNextItemSeparator = LS_NEXT_ITEM_SEPARATOR;
    public String llNextItemSeparator = LL_NEXT_ITEM_SEPARATOR;
    public String llMidConnector = LL_MID_CONNECTOR;
    public String llUnitsSeparator = LL_UNITS_SEPARATOR;

    public String llArrayOpen = LL_ARRAY_OPEN;
    public String llArrayClose = LL_ARRAY_CLOSE;
    public String llArrayItemSeparator = LL_ARRAY_ITEM_SEPARATOR;
    public String llFloatArrayFormat = LL_FLOAT_ARRAY_FORMAT;
    public String llIntArrayFormat = LL_INT_ARRAY_FORMAT;

    private static final Map<String, String> defaultFormattingMap = new HashMap<String, String>();
    private Map<String, String> formattingMap;

    private static final String ATTRIBUTE_KEY_FOR_UNITS = "units";
    private static final String ATTRIBUTE_KEY_FOR_FORMAT = "format";

    private static final String ATTRIBUTE_KEY_FOR_METADATA_TYPE = "metadata_type";
    private static final String ATTRIBUTE_VALUE_FOR_METADATA_TYPE_SUPPLIED = "text";
    private static final String ATTRIBUTE_VALUE_FOR_METADATA_TYPE_SCANNABLE = "scannable";
    private static final String ATTRIBUTE_VALUE_FOR_METADATA_TYPE_SCANNABLE_GROUP = ATTRIBUTE_VALUE_FOR_METADATA_TYPE_SCANNABLE;

    private static final String ATTRIBUTE_KEY_FOR_FIELD_TYPE = "field_type";
    private static final String ATTRIBUTE_VALUE_FOR_FIELD_TYPE_INPUT = "input";
    private static final String ATTRIBUTE_VALUE_FOR_FIELD_TYPE_EXTRA = "extra";

    Map<String, Object> metaTextualMap;
    //List<Scannable> metaScannables = new Vector<Scannable>();

    private boolean withScannables = false;

    private static final Logger logger = LoggerFactory.getLogger(NXMetaDataProvider.class);

    public NXMetaDataProvider() {
        super();
        defaultFormattingMap.put("PREAMBLE", "meta:\n"); // 
        defaultFormattingMap.put("LS_NEXT_ITEM_SEPARATOR", "\n"); // single new line
        defaultFormattingMap.put("LL_MID_CONNECTOR", " = ");
        defaultFormattingMap.put("LL_NEXT_ITEM_SEPARATOR", "\n"); // single new line
        defaultFormattingMap.put("LL_UNITS_SEPARATOR", " "); // single space
        defaultFormattingMap.put("LL_ARRAY_OPEN", "[");
        defaultFormattingMap.put("LL_ARRAY_CLOSE", "]");
        defaultFormattingMap.put("LL_ARRAY_ITEM_SEPARATOR", ", "); // single coma followed by single space
        defaultFormattingMap.put("LL_FLOAT_ARRAY_FORMAT", "%5.3f");
        defaultFormattingMap.put("LL_INT_ARRAY_FORMAT", "%d");

        reset();
    }

    @Override
    public void appendToTopNode(INexusTree topNode) {
        for (Entry<String, Object> e : metaTextualMap.entrySet()) {
            INexusTree childNode = createChildNodeForTextualMetaEntry(e, topNode);
            if (childNode != null) {
                topNode.addChildNode(childNode);
            } else {
                logger.debug("Nexus tree child node is null for " + e.getKey());
            }
        }

        if (withScannables) {
            //for (Scannable scn : metaScannables) {
            List<Scannable> metaScannableList = new Vector<Scannable>();
            Set<String> metaScannableSet = NexusDataWriter.getMetadatascannables();
            for (String scannableName : metaScannableSet) {
                Scannable scannable = (Scannable) InterfaceProvider.getJythonNamespace()
                        .getFromJythonNamespace(scannableName);
                if (scannable == null) {
                    throw new IllegalStateException(
                            "could not find scannable '" + scannableName + "' in Jython namespace.");
                }
                metaScannableList.add(scannable);
            }
            for (Scannable scn : metaScannableList) {
                //            System.out.println("getNexusTree: scannable = " + scn.getName());
                try {
                    Map<String, Object> scannableMap = createMetaScannableMap(scn);
                    //System.out.println("\t scannableMap = " + scannableMap.toString());
                    INexusTree childNode = createChildNodeForScannableMetaEntry(scn, topNode, scannableMap); //TODO Change name
                    if (childNode != null) {
                        topNode.addChildNode(childNode);
                    } else {
                        logger.debug("Nexus tree child node is null for " + scn.getName());
                    }
                } catch (DeviceException e1) {
                    logger.error("Error creating metadata for scannable" + scn.getName(), e1);
                }

            }
        }
    }

    public void reset() {
        this.metaTextualMap = new HashMap<String, Object>();
        this.formattingMap = new HashMap<String, String>();

        formattingMap.put("preamble", defaultFormattingMap.get("PREAMBLE"));
        formattingMap.put("lsNextItemSeparator", defaultFormattingMap.get("LS_NEXT_ITEM_SEPARATOR"));
        formattingMap.put("llMidConnector", defaultFormattingMap.get("LL_MID_CONNECTOR"));
        formattingMap.put("llNextItemSeparator", defaultFormattingMap.get("LL_NEXT_ITEM_SEPARATOR"));
        formattingMap.put("llUnitsSeparator", defaultFormattingMap.get("LL_UNITS_SEPARATOR"));
        formattingMap.put("llArrayOpen", defaultFormattingMap.get("LL_ARRAY_OPEN"));
        formattingMap.put("llArrayClose", defaultFormattingMap.get("LL_ARRAY_CLOSE"));
        formattingMap.put("llArrayItemSeparator", defaultFormattingMap.get("LL_ARRAY_ITEM_SEPARATOR"));
        formattingMap.put("llFloatArrayFormat", defaultFormattingMap.get("LL_FLOAT_ARRAY_FORMAT"));
        formattingMap.put("llIntArrayFormat", defaultFormattingMap.get("LL_INT_ARRAY_FORMAT"));

        //modifyFormattingMap(defaultFormattingMap);
    }

    public String listAsString(String fmt, String delimiter) {
        // TODO Auto-generated method stub
        String total = "";
        for (Entry<String, Object> e : metaTextualMap.entrySet()) {
            total += "," + e.getKey() + ":" + e.getValue();
        }
        // String x = concatenateKeyAndValueForListAsString("%s:%x", ",");
        String x = concatenateKeyAndValueForListAsString(fmt, delimiter);
        System.out.println("***x = " + x);
        total = x;
        return total;
    }

    public String concatenateKeyAndValueForListAsString(String fmt, String delimiter) {
        String concatenated = "";
        int sanityCheck = StringUtils.countOccurrencesOf(fmt, "%s");
        System.out.println("***sanityCheck = " + sanityCheck);
        if (sanityCheck == 2) {
            for (Entry<String, Object> e : metaTextualMap.entrySet()) {
                // concatenated += "," + e.getKey() + ":" + e.getValue();
                concatenated += String.format(fmt, e.getKey(), e.getValue());
                concatenated += delimiter;
            }
            // remove the unnecessary last delimiter
            concatenated = concatenated.substring(0, concatenated.length() - 1);
        } else {
            String defaultFmt = "%s:%s";
            String defaultDelimiter = ",";
            logger.warn("Bad input format: " + "\"" + fmt + "\"" + " is " + "replaced by default format: " + "\""
                    + defaultFmt + "\"");
            concatenated = concatenateKeyAndValueForListAsString(defaultFmt, defaultDelimiter);
        }
        // remove the unnecessary last delimiter, if present
        // if ( concatenated.lastIndexOf(delimiter)==(concatenated.length()-1)){
        // concatenated = concatenated.substring(0, concatenated.length()-1);
        // }
        return concatenated;
    }

    public void setMetaTexts(Map<String, String> metaTexts) {
        for (String key : metaTexts.keySet()) {
            add(key, metaTexts.get(key));
        }
    }

    public Map<String, Object> getMetaTexts() {
        Map<String, Object> outMap = new HashMap<String, Object>(metaTextualMap);
        return outMap;
    }

    public void add(String key, Object value, String units) {
        Pair<Object, String> valueWithUnits = new Pair<Object, String>(value, units);
        put(key, valueWithUnits);
        //System.out.println("adding user-supplied given 3 args");
    }

    public void add(MetaDataUserSuppliedItem userSupplied) {
        Pair<Object, String> valueWithUnits = new Pair<Object, String>(userSupplied.getValue(),
                userSupplied.getUnits());
        put(userSupplied.getKey(), valueWithUnits);
        //System.out.println("adding user-supplied given 1 MetaDataUserSuppliedItem arg");
    }

    @Override
    public int size() {
        return metaTextualMap.size();
    }

    @Override
    public boolean isEmpty() {
        return metaTextualMap.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return metaTextualMap.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return metaTextualMap.containsValue(value);
    }

    @Override
    public Object get(Object key) {
        return metaTextualMap.get(key);
    }

    @Override
    public Object put(String key, Object value) {
        return metaTextualMap.put(key, value);
    }

    @Override
    public Object remove(Object key) {
        //String msg = "remove key = " + key;
        //logger.debug(msg);
        //System.out.println(msg);
        return metaTextualMap.remove(key);
    }

    @Override
    public void putAll(Map<? extends String, ? extends Object> m) {
        metaTextualMap.putAll(m);
    }

    @Override
    public void clear() {
        metaTextualMap.clear();
    }

    @Override
    public Set<String> keySet() {
        return metaTextualMap.keySet();
    }

    @Override
    public Collection<Object> values() {
        return metaTextualMap.values();
    }

    @Override
    public Set<java.util.Map.Entry<String, Object>> entrySet() {
        return metaTextualMap.entrySet();
    }

    @Override
    public boolean equals(Object o) {
        return metaTextualMap.equals(o);
    }

    @Override
    public int hashCode() {
        return metaTextualMap.hashCode();
    }

    public void setMetaScannables(List<Scannable> metaScannables) {
        //this.metaScannables.addAll(metaScannables);
        for (Scannable scn : metaScannables) {
            NexusDataWriter.getMetadatascannables().add(scn.getName());
        }
    }

    public List<Scannable> getMetaScannables() {
        //List<Scannable> outLst = new Vector<Scannable>(this.metaScannables);
        //return outLst;
        List<Scannable> metaScannableList = new Vector<Scannable>();
        Set<String> metaScannableSet = NexusDataWriter.getMetadatascannables();
        for (String scannableName : metaScannableSet) {
            Scannable scannable = (Scannable) InterfaceProvider.getJythonNamespace()
                    .getFromJythonNamespace(scannableName);
            metaScannableList.add(scannable);
        }
        return metaScannableList;
    }

    /*
     * To be called by meata_ls command
     */
    public String list(boolean withValues) {
        withScannables = true;
        return concatenateContentsForList(withValues, preamble, lsNextItemSeparator, llMidConnector,
                llNextItemSeparator);
    }

    public String concatenateContentsForList(boolean withValues, String preamble, String lsNextItemSeparator,
            String llMidConnector, String llNextItemSeparator) {

        INexusTree listTree = new NexusTreeNode("list", NexusExtractor.NXCollectionClassName, null);
        appendToTopNode(listTree);
        NexusTreeStringDump treeDump = new NexusTreeStringDump(listTree);

        String strOut = "";
        if (preamble == null) {
            strOut += PREAMBLE;
        } else {
            strOut += preamble;
        }
        String lsNextItemSeparatorUsed = "";
        if (lsNextItemSeparator == null) {
            lsNextItemSeparatorUsed += LS_NEXT_ITEM_SEPARATOR;
        } else {
            lsNextItemSeparatorUsed += lsNextItemSeparator;
        }

        String llMidConnectorUsed = "";
        if (llMidConnector == null) {
            llMidConnectorUsed += LL_MID_CONNECTOR;
        } else {
            llMidConnectorUsed += llMidConnector;
        }

        String llNextItemSeparatorUsed = "";
        if (llNextItemSeparator == null) {
            llNextItemSeparatorUsed += LL_NEXT_ITEM_SEPARATOR;
        } else {
            llNextItemSeparatorUsed += llNextItemSeparator;
        }

        List<DatumForJythonList> alphabeticalOut = new Vector<DatumForJythonList>();

        NexusGroupData ngdFieldType = null;
        for (Pair<String, NexusDumpItem> e : treeDump.getDumpList()) {
            String name = e.getFirst();
            String value = llMidConnectorUsed + e.getSecond().toString() + llNextItemSeparator;
            String field_type = "";
            ngdFieldType = e.getSecond().getFieldType();
            if (ngdFieldType != null) {
                field_type = ngdFieldType.toString();
            }
            alphabeticalOut.add(new DatumForJythonList(name, value, field_type));
        }

        Collections.sort(alphabeticalOut, new Comparator<DatumForJythonList>() {
            @Override
            public int compare(final DatumForJythonList o1, final DatumForJythonList o2) {

                int out = 0;
                String name1 = o1.datumName;
                String fieldType1 = o1.datumFieldType;

                String name2 = o2.datumName;
                String fieldType2 = o2.datumFieldType;

                String splitOn = FIELD_ITEM_SEPARATOR;
                if (splitOn.equals(".")) {
                    splitOn = "\\" + splitOn;
                }
                String[] split1 = name1.split("\\.", -1);
                String[] split2 = name2.split("\\.", -1);
                int count1 = split1.length - 1;
                int count2 = split2.length - 1;

                String root1 = name1;
                int lastIdx1 = name1.lastIndexOf("\\.");
                if (lastIdx1 >= 0) {
                    root1 = name1.substring(0, lastIdx1);
                }

                String root2 = name2;
                int lastIdx2 = name2.lastIndexOf("\\.");
                if (lastIdx2 >= 0) {
                    root2 = name2.substring(0, lastIdx2);
                }

                if (count1 != count2) {
                    out = name1.toLowerCase().compareTo(name2.toLowerCase());
                    //System.out.println("count1 != count2: out = " + out );
                } else {
                    // if belong to the same scannable
                    if (root1.equals(root2)) {
                        //System.out.println("CASE split1[count1] == split2[count2]: out = " + out );
                        if (fieldType1 == ATTRIBUTE_VALUE_FOR_FIELD_TYPE_INPUT
                                && fieldType2 == ATTRIBUTE_VALUE_FOR_FIELD_TYPE_EXTRA) {
                            // input before extra
                            out = 1;
                            //System.out.println("input before extra: out = " + out );
                        } else if (fieldType1 == ATTRIBUTE_VALUE_FOR_FIELD_TYPE_EXTRA
                                && fieldType2 == ATTRIBUTE_VALUE_FOR_FIELD_TYPE_INPUT) {
                            // input before extra
                            out = -1;
                            //System.out.println("extra after input: out = " + out );
                        } else {
                            out = name1.toLowerCase().compareTo(name2.toLowerCase());
                            //System.out.println("same field types: out = " + out);
                        }
                    } else {
                        out = name1.toLowerCase().compareTo(name2.toLowerCase());
                        //System.out.println("CASE split1[count1] != split2[count2]: out = " + out );
                    }
                }
                //System.out.println("END: out = " + out );
                return out;
            }
        });

        if (withValues) {
            for (DatumForJythonList d : alphabeticalOut) {
                strOut += d.datumName;
                //strOut += llMidConnectorUsed;         //already included
                strOut += d.datumValue;
                //strOut += llNextItemSeparatorUsed;   //already included
            }
            int substringLen = strOut.length() - llNextItemSeparatorUsed.length();
            if (substringLen >= 0) {
                strOut = strOut.substring(0, substringLen);
            }

            return strOut;
        }

        for (DatumForJythonList d : alphabeticalOut) {
            strOut += d.datumName + lsNextItemSeparatorUsed;
        }
        int strOutLen = strOut.length();
        int lsNextItemSeparatorUsedLen = lsNextItemSeparatorUsed.length();
        if (strOutLen >= lsNextItemSeparatorUsedLen) {
            strOut = strOut.substring(0, strOutLen - lsNextItemSeparatorUsedLen);
        }
        withScannables = false;
        return strOut;
    }

    public void add(Object... args) {
        if (args[0] instanceof Scannable && args.length == 1) {
            add((Scannable) args[0]);
        } else if (args[0] instanceof String && args.length == 2) {
            add((String) args[0], args[1], null);
        } else if (args[0] instanceof String && args.length == 3 && args[2] instanceof String) {
            add((String) args[0], args[1], (String) args[2]);
        } else {
            throw new IllegalArgumentException("Invalid arguments");
        }
    }

    public void add(Scannable scannable) {
        logger.debug("add called on scannable = " + scannable.getName());
        String scannableName = scannable.getName();
        while (NexusDataWriter.getMetadatascannables().contains(scannableName)) {
            NexusDataWriter.getMetadatascannables().remove(scannableName);
        }
        NexusDataWriter.getMetadatascannables().add(scannableName);
    }

    public void remove(Object... args) {
        for (Object arg : args) {
            if (arg instanceof Scannable) {
                remove((Scannable) arg);
            } else if (arg instanceof String) {
                remove(arg);
            } else {
                throw new IllegalArgumentException("Invalid arguments");
            }
        }
    }

    public void remove(Scannable scannable) {
        logger.debug("remove called on scannable = " + scannable.getName());
        //metaScannables.remove(scannable);
        NexusDataWriter.getMetadatascannables().remove(scannable.getName());
    }

    public void createMetaScannableMap() throws DeviceException {
        //for (Scannable scn : metaScannables) {
        List<Scannable> metaScannableList = new Vector<Scannable>();
        Set<String> metaScannableSet = NexusDataWriter.getMetadatascannables();
        for (String scannableName : metaScannableSet) {
            Scannable scannable = (Scannable) InterfaceProvider.getJythonNamespace()
                    .getFromJythonNamespace(scannableName);
            metaScannableList.add(scannable);
        }
        for (Scannable scn : metaScannableList) {
            List<ScannableMetaEntry> metas = new Vector<ScannableMetaEntry>();

            List<String> scnNames = new Vector<String>();

            int len = scn.getInputNames().length;
            String[] inputNames = scn.getInputNames();
            for (int i = 0; i < len; i++) {
                scnNames.add(inputNames[i]);
            }

            len = scn.getExtraNames().length;
            String[] extraNames = scn.getExtraNames();
            for (int i = 0; i < len; i++) {
                scnNames.add(extraNames[i]);
            }

            List<String> scnFormats = new Vector<String>();
            len = scn.getOutputFormat().length;
            String[] outFormats = scn.getOutputFormat();
            for (int i = 0; i < len; i++) {
                scnFormats.add(outFormats[i]);
            }

            String[] formattedCurrentPositionArray = ScannableUtils.getFormattedCurrentPositionArray(scn);

            List<String> scannableFieldNames = getScannableFieldNames(scn);

            for (int i = 0; i < scannableFieldNames.size(); i++) {
                metas.add(new ScannableMetaEntry(scannableFieldNames.get(i), formattedCurrentPositionArray[i]));
            }

        }
    }

    public Map<String, Object> createMetaScannableMap(Scannable scn) throws DeviceException {
        Map<String, Object> metaScannableMapObj = new HashMap<String, Object>();

        List<ScannableMetaEntryObj> metasObj = new Vector<ScannableMetaEntryObj>();

        Object scnPos = null;
        try {
            scnPos = scn.getPosition();
        } catch (PyException e) {
            throw new DeviceException(
                    "Error calling getPosition on scannable " + scn.getName() + ":" + e.toString());
        } catch (Exception e) {
            throw new DeviceException(
                    "Error calling getPosition on scannable " + scn.getName() + ": " + e.getMessage(), e);
        }

        if (scnPos == null) {
            // something's wrong in the scannable! log this and call the metadata "null"
            logger.info("Null returned when asking " + scn.getName() + " for its position");
            scnPos = "null";
        }

        Object[] elementalGetPosObjects = separateGetPositionOutputIntoElementalPosObjects(scnPos);

        List<String> scannableFieldNames = getScannableFieldNames(scn);

        for (int i = 0; i < scannableFieldNames.size(); i++) {
            metasObj.add(new ScannableMetaEntryObj(scannableFieldNames.get(i), elementalGetPosObjects[i]));
        }

        for (ScannableMetaEntryObj e : metasObj) {
            metaScannableMapObj.put(e.key, e.value);
        }

        return metaScannableMapObj;
    }

    class ScannableMetaEntry {
        public String key;
        public String value;

        public ScannableMetaEntry(String key, String value) {
            super();
            this.key = key;
            this.value = value;
        }

    }

    class ScannableMetaEntryObj {
        public String key;
        public Object value;

        public ScannableMetaEntryObj(String key, Object value) {
            super();
            this.key = key;
            this.value = value;
        }

    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        if ((this.name == null || this.name.isEmpty())) {
            logger.warn(
                    "getName() called on NXMetaDataProvider when the name has not been set. This may cause problems in the system and should be fixed.");
        }
        return this.name;
    }

    public List<String> getScannableFieldNames(Scannable scannable) {
        Vector<String> fieldNames = new Vector<String>();

        // if detector the inputNames are not returned in ScanDataPoint so do not add
        String[] extraNames = scannable.getExtraNames();
        if (scannable instanceof Detector) {
            if (extraNames.length > 0) {
                fieldNames.addAll(Arrays.asList(extraNames));
            } else {
                fieldNames.add(scannable.getName());
            }
        } else {
            fieldNames.addAll(Arrays.asList(scannable.getInputNames()));
            fieldNames.addAll(Arrays.asList(extraNames));
        }
        return fieldNames;
    }

    public List<String> getScannableInputNames(Scannable scannable) {
        Vector<String> outNames = new Vector<String>();

        // if detector the inputNames are not returned in ScanDataPoint so do not add
        if (!(scannable instanceof Detector)) {
            outNames.addAll(Arrays.asList(scannable.getInputNames()));
        }
        return outNames;
    }

    public List<String> getScannableExtraNames(Scannable scannable) {
        Vector<String> outNames = new Vector<String>();

        String[] extraNames = scannable.getExtraNames();
        if (scannable instanceof Detector) {
            if (extraNames.length > 0) {
                outNames.addAll(Arrays.asList(extraNames));
            } else {
                outNames.add(scannable.getName());
            }
        } else {
            outNames.addAll(Arrays.asList(extraNames));
        }
        return outNames;
    }

    public INexusTree createChildNodeForTextualMetaEntry(Entry<String, Object> e, INexusTree parentNode) {
        String nxClass = NexusExtractor.SDSClassName;
        String childNodeName = e.getKey();
        Object object = e.getValue();
        String units = "placeholder units";
        if (object instanceof Pair) {
            Pair<?, ?> valueWithUnits = (Pair<?, ?>) e.getValue();
            object = valueWithUnits.getFirst();
            units = (String) valueWithUnits.getSecond();
        }
        NexusGroupData groupData = null;
        groupData = createNexusGroupData(object);

        INexusTree node = new NexusTreeNode(childNodeName, nxClass, parentNode, groupData);

        node.addChildNode(new NexusTreeNode(ATTRIBUTE_KEY_FOR_METADATA_TYPE, NexusExtractor.AttrClassName, node,
                new NexusGroupData(ATTRIBUTE_VALUE_FOR_METADATA_TYPE_SUPPLIED)));

        if (units != null) {
            node.addChildNode(new NexusTreeNode(ATTRIBUTE_KEY_FOR_UNITS, NexusExtractor.AttrClassName, node,
                    new NexusGroupData(units)));
        }
        return node;
    }

    public INexusTree createChildNodeForScannableMetaEntry(Scannable scn, INexusTree parentNode,
            Map<String, Object> scannableMap) {
        INexusTree node = null;

        List<String> fieldNames = ScannableUtils.getScannableFieldNames(Arrays.asList(new Scannable[] { scn }));
        List<String> inputNames = new Vector<String>();
        inputNames = getScannableInputNames(scn);

        List<String> extraNames = new Vector<String>();
        extraNames = getScannableExtraNames(scn);

        int inputSize = inputNames.size();
        int extraSize = extraNames.size();
        int fieldSize = fieldNames.size();

        if (inputSize + extraSize != fieldSize) {
            String msg = "input names + extra names != field names (" + Integer.toString(inputSize) + " + "
                    + Integer.toString(extraSize) + " != " + Integer.toString(fieldSize);

            System.out.println(msg);
            //throw new DeviceException("input names + extra names != field names ("inputNames.size());
        }

        if (scn instanceof ScannableGroup) {

            node = new NexusTreeNode(scn.getName(), NexusExtractor.NXCollectionClassName, parentNode);
            node.addChildNode(new NexusTreeNode(ATTRIBUTE_KEY_FOR_METADATA_TYPE, NexusExtractor.AttrClassName, node,
                    new NexusGroupData(ATTRIBUTE_VALUE_FOR_METADATA_TYPE_SCANNABLE_GROUP)));

            for (Scannable s : ((ScannableGroup) scn).getGroupMembers()) {
                INexusTree sNode = createChildNodeForScannableMetaEntry(s, node, scannableMap);
                if (sNode != null) {
                    node.addChildNode(sNode);
                }
            }
        } else if (hasGenuineMultipleFieldNames(scn)) {
            node = new NexusTreeNode(scn.getName(), NexusExtractor.NXCollectionClassName, parentNode);

            node.addChildNode(new NexusTreeNode(ATTRIBUTE_KEY_FOR_METADATA_TYPE, NexusExtractor.AttrClassName, node,
                    new NexusGroupData(ATTRIBUTE_VALUE_FOR_METADATA_TYPE_SCANNABLE)));

            String[] outputFormat = null;
            outputFormat = scn.getOutputFormat();

            int fieldIdx = 0;
            for (String field : inputNames) {
                String key = field;
                Object posObj = scannableMap.get(key);
                String units = null;

                if (posObj != null) {
                    try {
                        units = getScannableUnit(scn);
                    } catch (DeviceException e1) {
                        logger.error("Error getting scannable unit", e1);
                    }

                    NexusGroupData groupData = null;
                    groupData = createNexusGroupData(posObj);
                    if (groupData != null) {
                        NexusTreeNode fieldNode = new NexusTreeNode(field, NexusExtractor.SDSClassName, node,
                                groupData);
                        fieldNode.addChildNode(
                                new NexusTreeNode(ATTRIBUTE_KEY_FOR_FIELD_TYPE, NexusExtractor.AttrClassName,
                                        fieldNode, new NexusGroupData(ATTRIBUTE_VALUE_FOR_FIELD_TYPE_INPUT)));

                        if (units != null) {
                            fieldNode.addChildNode(new NexusTreeNode(ATTRIBUTE_KEY_FOR_UNITS,
                                    NexusExtractor.AttrClassName, fieldNode, new NexusGroupData(units)));
                        }
                        if (outputFormat != null && outputFormat[fieldIdx] != null) {
                            fieldNode.addChildNode(
                                    new NexusTreeNode(ATTRIBUTE_KEY_FOR_FORMAT, NexusExtractor.AttrClassName,
                                            fieldNode, new NexusGroupData(outputFormat[fieldIdx])));
                        }
                        node.addChildNode(fieldNode);
                    } else {
                        logger.warn("GroupData is null!");
                    }
                }
                fieldIdx += 1;
            }

            for (String field : extraNames) {
                String key = field;
                Object posObj = scannableMap.get(key);
                String units = null;

                if (posObj != null) {
                    try {
                        units = getScannableUnit(scn);
                    } catch (DeviceException e1) {
                        // TODO Auto-generated catch block
                        logger.error("TODO put description of error here", e1);
                    }

                    NexusGroupData groupData = null;
                    groupData = createNexusGroupData(posObj);
                    if (groupData != null) {
                        NexusTreeNode fieldNode = new NexusTreeNode(field, NexusExtractor.SDSClassName, node,
                                groupData);
                        fieldNode.addChildNode(
                                new NexusTreeNode(ATTRIBUTE_KEY_FOR_FIELD_TYPE, NexusExtractor.AttrClassName,
                                        fieldNode, new NexusGroupData(ATTRIBUTE_VALUE_FOR_FIELD_TYPE_EXTRA)));
                        if (units != null) {
                            fieldNode.addChildNode(new NexusTreeNode(ATTRIBUTE_KEY_FOR_UNITS,
                                    NexusExtractor.AttrClassName, fieldNode, new NexusGroupData(units)));
                        }
                        if (outputFormat != null && outputFormat[fieldIdx] != null) {
                            fieldNode.addChildNode(
                                    new NexusTreeNode(ATTRIBUTE_KEY_FOR_FORMAT, NexusExtractor.AttrClassName,
                                            fieldNode, new NexusGroupData(outputFormat[fieldIdx])));
                        }
                        node.addChildNode(fieldNode);
                    } else {
                        logger.warn("GroupData is null!");
                    }
                }
                fieldIdx += 1;
            }

        } else {
            String key = null;
            int fieldIdx = 0;
            String whoami = "";
            if (inputSize == 1) {
                key = inputNames.get(fieldIdx);
                whoami = "input";
            } else if (extraSize == 1) {
                key = extraNames.get(fieldIdx);
                whoami = "extra";
            } else {
                key = scn.getName();
            }

            String[] outputFormat = null;
            outputFormat = scn.getOutputFormat();

            Object posObj = scannableMap.get(key);
            String units = null;

            if (posObj != null) {
                try {
                    units = getScannableUnit(scn);
                } catch (DeviceException e1) {
                    logger.error("TODO put description of error here", e1);
                }

                NexusGroupData groupData = null;
                groupData = createNexusGroupData(posObj);
                if (groupData != null) {
                    node = new NexusTreeNode(key, NexusExtractor.SDSClassName, parentNode, groupData);

                    if (parentNode.getAttribute(ATTRIBUTE_KEY_FOR_METADATA_TYPE) == null) {
                        node.addChildNode(
                                new NexusTreeNode(ATTRIBUTE_KEY_FOR_METADATA_TYPE, NexusExtractor.AttrClassName,
                                        node, new NexusGroupData(ATTRIBUTE_VALUE_FOR_METADATA_TYPE_SCANNABLE)));
                    } else {
                        System.out.println("Metadata type already set on the parent");
                    }

                    if (whoami.equals("input")) {
                        node.addChildNode(
                                new NexusTreeNode(ATTRIBUTE_KEY_FOR_FIELD_TYPE, NexusExtractor.AttrClassName, node,
                                        new NexusGroupData(ATTRIBUTE_VALUE_FOR_FIELD_TYPE_INPUT)));
                    } else if (whoami.equals("extra")) {
                        node.addChildNode(
                                new NexusTreeNode(ATTRIBUTE_KEY_FOR_FIELD_TYPE, NexusExtractor.AttrClassName, node,
                                        new NexusGroupData(ATTRIBUTE_VALUE_FOR_FIELD_TYPE_EXTRA)));
                    }

                    if (units != null && units.length() > 0) {
                        node.addChildNode(new NexusTreeNode(ATTRIBUTE_KEY_FOR_UNITS, NexusExtractor.AttrClassName,
                                node, new NexusGroupData(units)));

                        if (outputFormat != null && outputFormat[fieldIdx] != null) {
                            //System.out.println("\t\t output format = " + outputFormat[fieldIdx]);
                            node.addChildNode(
                                    new NexusTreeNode(ATTRIBUTE_KEY_FOR_FORMAT, NexusExtractor.AttrClassName, node,
                                            new NexusGroupData(outputFormat[fieldIdx])));
                        }

                    }
                } else {
                    System.out.println("***NEW goupData is null!");
                }
            } else {
                System.out.println("NOT FOUND!!! key = " + key);
                System.out.println("\t scannableMap = " + scannableMap.toString());
            }
        }
        return node;
    }

    public boolean hasGenuineMultipleFieldNames(Scannable scn) {
        List<String> inputNames = new Vector<String>();
        inputNames = getScannableInputNames(scn);
        List<String> extraNames = new Vector<String>();
        extraNames = getScannableExtraNames(scn);

        int inputSize = inputNames.size();
        int extraSize = extraNames.size();

        boolean hasRedundantSingleInputName = (inputSize == 1 && extraSize == 0
                && (scn.getName().equals(inputNames.get(0))
                        || inputNames.get(0).equals(Scannable.DEFAULT_INPUT_NAME)));
        boolean hasRedundanSingleExtraName = (inputSize == 0 && extraSize == 1
                && (scn.getName().equals(extraNames.get(0))
                        || extraNames.get(0).equals(Scannable.DEFAULT_INPUT_NAME)));
        boolean hasRedundantSingleFieldName = (hasRedundantSingleInputName || hasRedundanSingleExtraName);

        return !hasRedundantSingleFieldName;
    }

    public NexusGroupData createNexusGroupData(Object object) {
        NexusGroupData groupData = null;

        if (object instanceof String) {
            groupData = new NexusGroupData((String) object);
        } else if (object instanceof Integer) {
            groupData = new NexusGroupData((Integer) object);
        } else if (object instanceof Long) {
            Double dblValue = ((Number) object).doubleValue();
            double[] dblData = new double[] { dblValue };
            int[] dims = new int[] { dblData.length };
            int type = NexusFile.NX_FLOAT64;
            groupData = new NexusGroupData(dims, type, dblData);
        } else if (object instanceof Number) {
            Double dblValue = ((Number) object).doubleValue();
            double[] dblData = new double[] { dblValue };
            int[] dims = new int[] { dblData.length };
            int type = NexusFile.NX_FLOAT64;
            groupData = new NexusGroupData(dims, type, dblData);
        } else if (object instanceof double[]) {
            double[] data = (double[]) object;
            int[] dims = new int[] { data.length };
            int type = NexusFile.NX_FLOAT64;
            groupData = new NexusGroupData(dims, type, data);
        } else if (object instanceof int[]) {
            int[] data = (int[]) object;
            int[] dims = new int[] { data.length };
            int type = NexusFile.NX_INT32;
            groupData = new NexusGroupData(dims, type, data);
        } else if (object instanceof PyFloat) {
            double[] data = new double[] { ((PyFloat) object).asDouble() };
            int[] dims = new int[] { 1 };
            int type = NexusFile.NX_FLOAT64;
            groupData = new NexusGroupData(dims, type, data);
        } else if (object instanceof PyInteger) {
            //store as NX_FLOAT64 since a lot of things may pass an int for an expect a double on readback
            groupData = new NexusGroupData((double) ((PyInteger) object).getValue());
        } else if (object instanceof long[]) {
            long[] data = (long[]) object;
            int dataLen = data.length;
            double[] dblData = new double[dataLen];
            for (int i = 0; i < dataLen; i++) {
                dblData[i] = data[i];
            }
            int[] dims = new int[] { dblData.length };
            int type = NexusFile.NX_FLOAT64;
            groupData = new NexusGroupData(dims, type, data);
        } else if (object instanceof Number[]) {
            Number[] data = (Number[]) object;
            int dataLen = data.length;
            double[] dblData = new double[dataLen];
            for (int i = 0; i < dataLen; i++) {
                dblData[i] = data[i].doubleValue();
            }
            int[] dims = new int[] { dblData.length };
            int type = NexusFile.NX_FLOAT64;
            groupData = new NexusGroupData(dims, type, dblData);
        } else if (object instanceof PyList) {
            // coerce PyList into double array.
            int dataLen = ((PyList) object).__len__();
            double[] dblData = new double[dataLen];
            for (int i = 0; i < dataLen; i++) {
                // dblData[i] = Double.valueOf(((PyList) object).__getitem__(i).toString());
                PyObject item = ((PyList) object).__finditem__(i);

                if (item instanceof PyNone) {
                    dblData[i] = Double.NaN;
                } else {
                    dblData[i] = Double.valueOf(item.toString());
                }
            }
            int[] dims = new int[] { dblData.length };
            int type = NexusFile.NX_FLOAT64;
            groupData = new NexusGroupData(dims, type, dblData);
        } else if (object instanceof PySequence) {
            // coerce PySequence into double array.
            int dataLen = ((PySequence) object).__len__();
            double[] dblData = new double[dataLen];
            for (int i = 0; i < dataLen; i++) {
                // dblData[i] = Double.valueOf(((PySequence) object).__getitem__(i).toString());
                PyObject item = ((PySequence) object).__finditem__(i);

                if (item instanceof PyNone) {
                    dblData[i] = Double.NaN;
                } else {
                    dblData[i] = Double.valueOf(item.toString());
                }
            }
            int[] dims = new int[] { dblData.length };
            int type = NexusFile.NX_FLOAT64;
            groupData = new NexusGroupData(dims, type, dblData);
        } else {
            logger.error("unhandled data type: " + object.getClass().getName()
                    + " - this dataset might not have been written correctly to Nexus file.");
            groupData = new NexusGroupData(object.toString());
        }
        return groupData;
    }

    public Object[] separateGetPositionOutputIntoElementalPosObjects(Object scannableGetPositionOut) {
        if (scannableGetPositionOut == null)
            return new Object[] {};

        Object[] elements = new Object[] { scannableGetPositionOut };
        if (scannableGetPositionOut instanceof Object[]) {
            elements = (Object[]) scannableGetPositionOut;
        } else if (scannableGetPositionOut instanceof PySequence) {
            PySequence seq = (PySequence) scannableGetPositionOut;
            int len = seq.__len__();
            elements = new Object[len];
            for (int i = 0; i < len; i++) {
                elements[i] = seq.__finditem__(i);
            }
        } else if (scannableGetPositionOut instanceof PyList) {
            PyList seq = (PyList) scannableGetPositionOut;
            int len = seq.__len__();
            elements = new Object[len];
            for (int i = 0; i < len; i++) {
                elements[i] = seq.__finditem__(i);
            }
        } else if (scannableGetPositionOut.getClass().isArray()) {
            int len = ArrayUtils.getLength(scannableGetPositionOut);
            elements = new Object[len];
            for (int i = 0; i < len; i++) {
                elements[i] = Array.get(scannableGetPositionOut, i);
            }
        } else if (scannableGetPositionOut instanceof PlottableDetectorData) {
            elements = ((PlottableDetectorData) scannableGetPositionOut).getDoubleVals();
        }
        return elements;
    }

    public static String getScannableUnit(Scannable s) throws DeviceException {
        String unit = null;
        if (s instanceof ScannableMotionUnits) {
            unit = ((ScannableMotionUnits) s).getUserUnits();
        } else {
            Object attribute = s.getAttribute(ScannableMotionUnits.USERUNITS);
            if (attribute != null) {
                unit = attribute.toString();
            }
        }
        return unit;
    }

    public INexusTree createChildNodeForMetaTextEntry(String name, String nxClass, INexusTree parentNode,
            NexusGroupData groupData) {

        INexusTree outTree = new NexusTreeNode(name, nxClass, parentNode, groupData);
        return outTree;
    }

    public void modifyFormattingMap(Map<String, String> modificationsMap) {
        //this.formattingMap = formattingMap;
        for (Entry<String, String> e : modificationsMap.entrySet()) {
            this.formattingMap.put(e.getKey(), e.getValue());
            //System.out.println("modifyFormattingMap: key = " + e.getKey() + ", val = " + e.getValue()); 
        }
    }

}

/**
 * class to create a map of entries from the nexus tree
 * 
 * A key is a concatenation of nexus groups names, separated by a dot, followed by the item name 
 * the entry is the SDS item  plus the format attribute as a String
 */
class NexusTreeStringDump {
    private static final String KEY_SEPARATOR = ".";
    INexusTree tree;
    private Map<String, NexusDumpItem> dumpMap;
    private List<Pair<String, NexusDumpItem>> dumpList;

    public NexusTreeStringDump(INexusTree tree) {
        super();
        this.tree = tree;
        this.dumpMap = new HashMap<String, NexusDumpItem>();
        this.dumpList = new Vector<Pair<String, NexusDumpItem>>();
        Traverse();
    }

    @Override
    public String toString() {
        return "NexusTreeStringDump [tree=" + tree + "]";
    }

    private void Traverse() {
        if (this.tree != null) {
            int nNodes = this.tree.getNumberOfChildNodes();
            String key = "";
            for (int i = 0; i < nNodes; i++) {
                INexusTree node = this.tree.getChildNode(i);
                Traverse(node, key);
            }
        }
    }

    private void Traverse(INexusTree tree, String key) {
        if (tree != null) {
            if (isToBeTraversed(tree)) {
                int nNodes = tree.getNumberOfChildNodes();
                key += tree.getName() + KEY_SEPARATOR;
                if (nNodes > 0) {
                    for (int i = 0; i < nNodes; i++) {
                        INexusTree node = tree.getChildNode(i);
                        Traverse(node, key);
                    }
                }
            } else {
                if (isToBeHarvested(tree)) {
                    key += tree.getName();
                    int nNodes = tree.getNumberOfChildNodes();

                    NexusGroupData ngdData = tree.getData();

                    Map<String, NexusGroupData> ngdMap = new HashMap<String, NexusGroupData>();
                    for (int i = 0; i < nNodes; i++) {
                        INexusTree node = tree.getChildNode(i);
                        ngdMap.put(node.getName(), node.getData());
                    }

                    NexusGroupData ngdUnits = ngdMap.get("units");
                    NexusGroupData ngdFormat = ngdMap.get("format");
                    NexusGroupData ngdFieldType = ngdMap.get("field_type");

                    NexusDumpItem item = new NexusDumpItem(ngdData, ngdUnits, ngdFormat, ngdFieldType);
                    dumpMap.put(key, new NexusDumpItem(ngdData, ngdUnits, ngdFormat, ngdFieldType));
                    Pair<String, NexusDumpItem> e = new Pair<String, NexusDumpItem>(key, item);
                    dumpList.add(e);
                }
            }
        }
    }

    public Map<String, NexusDumpItem> getDumpMap() {
        return dumpMap;
    }

    public List<Pair<String, NexusDumpItem>> getDumpList() {
        return dumpList;
    }

    public boolean isToBeTraversed(INexusTree tree) {
        int nNodes = tree.getNumberOfChildNodes();
        boolean out = (nNodes > 0);
        HashMap<String, Serializable> attributes = tree.getAttributes();
        if (attributes != null && attributes.size() == nNodes) {

            Serializable units = attributes.get("units");
            Serializable format = attributes.get("format");
            Serializable field_t = attributes.get("field_type");
            Serializable metadata_t = attributes.get("metadata_type");

            int nodesToBeTraversed = nNodes;
            if (units != null) {
                nodesToBeTraversed -= 1;
            }
            if (format != null) {
                nodesToBeTraversed -= 1;
            }
            if (field_t != null) {
                nodesToBeTraversed -= 1;
            }
            if (metadata_t != null) {
                nodesToBeTraversed -= 1;
            }
            out = (nodesToBeTraversed > 0);
        }
        return out;
    }

    public boolean isToBeHarvested(INexusTree tree) {
        int nNodes = tree.getNumberOfChildNodes();
        boolean out = (nNodes > 0);
        HashMap<String, Serializable> attributes = tree.getAttributes();
        if (attributes != null && attributes.size() == nNodes) {

            Serializable units = attributes.get("units");
            Serializable format = attributes.get("format");
            Serializable field_t = attributes.get("field_type");
            Serializable metadata_t = attributes.get("metadata_type");

            int nodesRemaining = nNodes;
            if (units != null) {
                nodesRemaining -= 1;
            }
            if (format != null) {
                nodesRemaining -= 1;
            }
            if (field_t != null) {
                nodesRemaining -= 1;
            }
            if (metadata_t != null) {
                nodesRemaining -= 1;
            }
            out = (nodesRemaining == 0);
        }
        return out;
    }

}

class DatumForJythonList {
    String datumName; // ls part
    String datumValue; // ll extension
    String datumFieldType; // input or extra (for scannable)

    public DatumForJythonList(String name, String value, String field_type) {
        super();
        this.datumName = name;
        this.datumValue = value;
        this.datumFieldType = field_type;
    }
}

/*
 * Class to create a user friendly string representation of an item
 */
class NexusDumpItem {
    NexusGroupData data;
    NexusGroupData units;
    NexusGroupData format;
    private NexusGroupData field_type;

    @Override
    public String toString() {
        Object targetVal = null;

        String out = "";
        if (data != null) {
            Object val = data.dimensions.length == 1 && data.dimensions[0] == 1 ? data.getFirstValue()
                    : data.getBuffer();
            if (format != null) {
                out = String.format(format.dataToTxt(false, true, false), val);
            } else {
                String defaultFormat = "";
                if (val instanceof Integer) {
                    defaultFormat = "%d";
                    targetVal = val;
                } else if (val instanceof Double) {
                    defaultFormat = "%5.3f";
                    targetVal = val;
                } else if (val instanceof String) {
                    defaultFormat = "%s";
                    targetVal = val;
                } else if (val instanceof byte[]) {
                    defaultFormat = "%s";
                    String sVal = new String((byte[]) val);
                    targetVal = sVal;
                } else if (val instanceof int[]) {
                    int[] intVal = (int[]) val;
                    int intValLen = intVal.length;
                    Integer[] intTargetVal = new Integer[intValLen];
                    intTargetVal = new Integer[intValLen];
                    for (int i = 0; i < intValLen; i++) {
                        intTargetVal[i] = intVal[i];
                    }
                    val = targetVal;

                    defaultFormat = createIntArrayFormat((Object[]) intTargetVal);
                    targetVal = intTargetVal;
                } else if (val instanceof double[]) {
                    double[] dblVal = (double[]) val;
                    int dblValLen = dblVal.length;
                    Double[] dblTargetVal = new Double[dblValLen];
                    dblTargetVal = new Double[dblValLen];
                    for (int i = 0; i < dblValLen; i++) {
                        dblTargetVal[i] = dblVal[i];
                    }

                    val = targetVal;

                    defaultFormat = createFloatArrayFormat((Object[]) dblTargetVal);
                    targetVal = dblTargetVal;
                }

                if (targetVal instanceof Object[]) {
                    out = String.format(defaultFormat, (Object[]) targetVal);
                } else {
                    out = String.format(defaultFormat, targetVal);
                }
            }
        }

        if (units != null) {
            out += units.dataToTxt(false, true, false);
        }
        return out;
    }

    public NexusDumpItem(NexusGroupData data, NexusGroupData units, NexusGroupData format,
            NexusGroupData field_type) {
        super();
        this.data = data;
        this.units = units;
        this.format = format;
        this.field_type = field_type;
    }

    public static String formatIntArray(Object... args) {

        String itemSep = ", ";
        String itemFormat = "%d" + itemSep;
        String format = new String(new char[args.length]).replace("\0", itemFormat);
        format = "[" + format;

        int formatLen = format.length();
        int itemSepLen = itemSep.length();
        if (formatLen >= itemSepLen) {
            format = format.substring(0, formatLen - itemSepLen);
        }
        format += "]";
        return String.format(format, args);
    }

    public static String createIntArrayFormat(Object... args) {

        String itemSep = ", ";
        String itemFormat = "%d" + itemSep;
        String format = new String(new char[args.length]).replace("\0", itemFormat);
        format = "[" + format;

        int formatLen = format.length();
        int itemSepLen = itemSep.length();
        if (formatLen >= itemSepLen) {
            format = format.substring(0, formatLen - itemSepLen);
        }
        format += "]";
        return format;
    }

    public static String createArrayFormat(Object... args) {
        String itemSep = ", ";
        String itemFormat = "";
        if (args instanceof Double[]) {
            itemFormat = "%5.3f";
        } else if (args instanceof Integer[]) {
            itemFormat = "%d";
        }
        itemFormat += itemSep;

        String format = new String(new char[args.length]).replace("\0", itemFormat);
        format = "[" + format;

        int formatLen = format.length();
        int itemSepLen = itemSep.length();
        if (formatLen >= itemSepLen) {
            format = format.substring(0, formatLen - itemSepLen);
        }
        format += "]";
        return format;
    }

    public static String formatFloatArray(Object... args) {
        String itemSep = ", ";
        String itemFormat = "%5.3f" + itemSep;
        String format = new String(new char[args.length]).replace("\0", itemFormat);
        format = "[" + format;

        int formatLen = format.length();
        int itemSepLen = itemSep.length();
        if (formatLen >= itemSepLen) {
            format = format.substring(0, formatLen - itemSepLen);
        }
        format += "]";
        //System.out.println("formatFloatArray: format used = " + format);
        return String.format(format, args);
    }

    public static String formatArray(Object... args) {
        String itemSep = ", ";
        String itemFormat = "";
        if (args instanceof Double[]) {
            itemFormat = "%5.3f";
        } else if (args instanceof Integer[]) {
            itemFormat = "%d";
        }
        itemFormat += itemSep;
        String format = new String(new char[args.length]).replace("\0", itemFormat);
        format = "[" + format;

        int formatLen = format.length();
        int itemSepLen = itemSep.length();
        if (formatLen >= itemSepLen) {
            format = format.substring(0, formatLen - itemSepLen);
        }
        format += "]";
        return String.format(format, args);
    }

    public static String createFloatArrayFormat(Object... args) {
        String itemSep = ", ";
        String itemFormat = "%5.3f" + itemSep;
        String format = new String(new char[args.length]).replace("\0", itemFormat);
        format = "[" + format;

        int formatLen = format.length();
        int itemSepLen = itemSep.length();
        if (formatLen >= itemSepLen) {
            format = format.substring(0, formatLen - itemSepLen);
        }
        format += "]";
        return format;
    }

    public NexusGroupData getFieldType() {
        return field_type;
    }

    public void setFieldType(NexusGroupData field_type) {
        this.field_type = field_type;
    }
}