org.n52.ifgicopter.spf.output.GpxOutputPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.n52.ifgicopter.spf.output.GpxOutputPlugin.java

Source

/**
 * Copyright (C) 2012
 * by 52 North Initiative for Geospatial Open Source Software GmbH
 *
 * Contact: Andreas Wytzisk
 * 52 North Initiative for Geospatial Open Source Software GmbH
 * Martin-Luther-King-Weg 24
 * 48155 Muenster, Germany
 * info@52north.org
 *
 * This program is free software; you can redistribute and/or modify it under
 * the terms of the GNU General Public License serviceVersion 2 as published by the
 * Free Software Foundation.
 *
 * This program is distributed WITHOUT ANY WARRANTY; even without 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 (see gnu-gpl v2.txt). If not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA or
 * visit the Free Software Foundation web page, http://www.fsf.org.
 */
package org.n52.ifgicopter.spf.output;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileFilter;

import net.boplicity.xmleditor.XmlTextPane;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlOptions;
import org.n52.ifgicopter.spf.common.IModule;
import org.n52.ifgicopter.spf.gui.ModuleGUI;
import org.n52.ifgicopter.spf.output.IOutputPlugin;
import org.n52.ifgicopter.spf.xml.Location;
import org.n52.ifgicopter.spf.xml.Plugin;

import com.topografix.gpx.x1.x1.GpxDocument;
import com.topografix.gpx.x1.x1.WptType;

/**
 * 
 * http://de.wikipedia.org/wiki/GPS_Exchange_Format
 * 
 * @author Daniel Nst (d.nuest@52north.org)
 * 
 */
public class GpxOutputPlugin implements IOutputPlugin {

    /**
     * 
     * http://www.java2s.com/Code/Java/Swing-JFC/AverticallayoutmanagersimilartojavaawtFlowLayout.htm
     * 
     * A vertical layout manager similar to java.awt.FlowLayout. Like FlowLayout components do not expand to
     * fill available space except when the horizontal alignment is <code>BOTH</code> in which case components
     * are stretched horizontally. Unlike FlowLayout, components will not wrap to form another column if there
     * isn't enough space vertically. VerticalLayout can optionally anchor components to the top or bottom of
     * the display area or center them between the top and bottom.
     * 
     * Revision date 12th July 2001
     * 
     * @author Colin Mummery e-mail: colin_mummery@yahoo.com Homepage:www.kagi.com/equitysoft - Based on
     *         'FlexLayout' in Java class libraries Vol 2 Chan/Lee Addison-Wesley 1998
     * 
     *         THIS PROGRAM IS PROVIDED "AS IS" WITHOUT ANY WARRANTIES (OR CONDITIONS), EXPRESS OR IMPLIED
     *         WITH RESPECT TO THE PROGRAM, INCLUDING THE IMPLIED WARRANTIES (OR CONDITIONS) OF
     *         MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF USE OR
     *         PERFORMANCE OF THE PROGRAM AND DOCUMENTATION REMAINS WITH THE USER.
     */
    protected class VerticalLayout implements LayoutManager {

        /**
         * The horizontal alignment constant that designates stretching the component horizontally.
         */
        public final static int BOTH = 3;
        /**
         * The anchoring constant that designates anchoring to the bottom of the display area
         */
        public final static int BOTTOM = 2;
        /**
         * The horizontal alignment constant that designates centering. Also used to designate center
         * anchoring.
         */
        public final static int CENTER = 0;
        /**
         * The horizontal alignment constant that designates left justification.
         */
        public final static int LEFT = 2;

        /**
         * The horizontal alignment constant that designates right justification.
         */
        public final static int RIGHT = 1;
        /**
         * The anchoring constant that designates anchoring to the top of the display area
         */
        public final static int TOP = 1;
        private int alignment; // LEFT, RIGHT, CENTER or BOTH...how the components are justified
        private int anchor; // TOP, BOTTOM or CENTER ...where are the components positioned in an overlarge
                            // space
        private int vgap; // the vertical vgap between components...defaults to 5

        // private Hashtable comps;

        /**
         * Constructs an instance of VerticalLayout with a vertical vgap of 5 pixels, horizontal centering and
         * anchored to the top of the display area.
         */
        public VerticalLayout() {
            this(5, CENTER, TOP);
        }

        /**
         * Constructs a VerticalLayout instance with horizontal centering, anchored to the top with the
         * specified vgap
         * 
         * @param vgap
         *        An int value indicating the vertical seperation of the components
         */
        public VerticalLayout(int vgap) {
            this(vgap, CENTER, TOP);
        }

        /**
         * Constructs a VerticalLayout instance anchored to the top with the specified vgap and horizontal
         * alignment
         * 
         * @param vgap
         *        An int value indicating the vertical seperation of the components
         * @param alignment
         *        An int value which is one of <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal
         *        alignment.
         */
        public VerticalLayout(int vgap, int alignment) {
            this(vgap, alignment, TOP);
        }

        /**
         * Constructs a VerticalLayout instance with the specified vgap, horizontal alignment and anchoring
         * 
         * @param vgap
         *        An int value indicating the vertical seperation of the components
         * @param alignment
         *        An int value which is one of <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal
         *        alignment.
         * @param anchor
         *        An int value which is one of <code>TOP, BOTTOM, CENTER</code> indicating where the
         *        components are to appear if the display area exceeds the minimum necessary.
         */
        public VerticalLayout(int vgap, int alignment, int anchor) {
            this.vgap = vgap;
            this.alignment = alignment;
            this.anchor = anchor;
        }

        /*
         * Not used by this class
         * 
         * (non-Javadoc)
         * 
         * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String, java.awt.Component)
         */
        @Override
        public void addLayoutComponent(String name, Component comp) {
            //
        }

        // -----------------------------------------------------------------------------
        /**
         * Lays out the container.
         */
        @Override
        public void layoutContainer(Container parent) {
            Insets insets = parent.getInsets();
            synchronized (parent.getTreeLock()) {
                int n = parent.getComponentCount();
                Dimension pd = parent.getSize();
                int y = 0;
                // work out the total size
                for (int i = 0; i < n; i++) {
                    Component c = parent.getComponent(i);
                    Dimension d = c.getPreferredSize();
                    y += d.height + this.vgap;
                }
                y -= this.vgap; // otherwise there's a vgap too many
                // Work out the anchor paint
                if (this.anchor == TOP)
                    y = insets.top;
                else if (this.anchor == CENTER)
                    y = (pd.height - y) / 2;
                else
                    y = pd.height - y - insets.bottom;
                // do layout
                for (int i = 0; i < n; i++) {
                    Component c = parent.getComponent(i);
                    Dimension d = c.getPreferredSize();
                    int x = insets.left;
                    int wid = d.width;
                    if (this.alignment == CENTER)
                        x = (pd.width - d.width) / 2;
                    else if (this.alignment == RIGHT)
                        x = pd.width - d.width - insets.right;
                    else if (this.alignment == BOTH)
                        wid = pd.width - insets.left - insets.right;
                    c.setBounds(x, y, wid, d.height);
                    y += d.height + this.vgap;
                }
            }
        }

        // ----------------------------------------------------------------------------
        private Dimension layoutSize(Container parent, boolean minimum) {
            Dimension dim = new Dimension(0, 0);
            Dimension d;
            synchronized (parent.getTreeLock()) {
                int n = parent.getComponentCount();
                for (int i = 0; i < n; i++) {
                    Component c = parent.getComponent(i);
                    if (c.isVisible()) {
                        d = minimum ? c.getMinimumSize() : c.getPreferredSize();
                        dim.width = Math.max(dim.width, d.width);
                        dim.height += d.height;
                        if (i > 0)
                            dim.height += this.vgap;
                    }
                }
            }
            Insets insets = parent.getInsets();
            dim.width += insets.left + insets.right;
            dim.height += insets.top + insets.bottom + this.vgap + this.vgap;
            return dim;
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
         */
        @Override
        public Dimension minimumLayoutSize(Container parent) {
            return layoutSize(parent, false);
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
         */
        @Override
        public Dimension preferredLayoutSize(Container parent) {
            return layoutSize(parent, false);
        }

        /*
         * Not used by this class
         * 
         * (non-Javadoc)
         * 
         * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
         */
        @Override
        public void removeLayoutComponent(Component comp) {
            //
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return getClass().getName() + "[vgap=" + this.vgap + " align=" + this.alignment + " anchor="
                    + this.anchor + "]";
        }
    }

    private static final String DEFAULT_GPX_FILE_PATH = "data/out.gpx";

    private static final int DEFAULT_INDENT = 4;

    public static final XmlOptions GPX_OPTIONS = new XmlOptions();

    protected static Log log = LogFactory.getLog(GpxOutputPlugin.class);

    private static final String NO_DATA_LIST_ELEMENT = "[NO DATA]";

    private static final String PLUGIN_NAME = "GPX Output";

    private static final String SAVE_CURRENT_FILE_TEXT = "Save Current File";

    private static final String SELECT_GPX_FILE_TEXT = "Select GPX File";

    static {
        GPX_OPTIONS.setSavePrettyPrint();
        GPX_OPTIONS.setSavePrettyPrintIndent(DEFAULT_INDENT);
        HashMap<String, String> suggestedPrefixes = new HashMap<String, String>();
        suggestedPrefixes.put("http://www.topografix.com/GPX/1/1", "gpx");

        GPX_OPTIONS.setSaveSuggestedPrefixes(suggestedPrefixes);
    }

    protected Set<String> availableDataKeys = new HashSet<String>();

    private int counter = 0;

    private GpxDocument gpx;

    private String gpxFilePath = null;

    private ModuleGUI gui;

    private JMenu menu;

    protected JLabel outputFileLabel;

    private JPanel panel;

    protected XmlTextPane textPane;

    protected Collection<String> valuesToSave = new ArrayList<String>();

    protected JList valuesToSaveList;

    /**
     * 
     */
    @ConstructorParameters({})
    public GpxOutputPlugin() {
        this(DEFAULT_GPX_FILE_PATH);
    }

    /**
     * 
     */
    @ConstructorParameters({
            "The output file (including path), where to store the generated GPX file. Defaults to '"
                    + DEFAULT_GPX_FILE_PATH + "'." })
    public GpxOutputPlugin(String outputPath) {
        log.info("NEW " + this);
        setGpxFilePath(outputPath);
    }

    /**
     * @return the gpxFilePath
     */
    public String getGpxFilePath() {
        return this.gpxFilePath;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.output.IOutputPlugin#getName()
     */
    @Override
    public String getName() {
        return PLUGIN_NAME;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.output.IOutputPlugin#getStatus()
     */
    @Override
    public int getStatus() {
        return IModule.STATUS_RUNNING;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.output.IOutputPlugin#getStatusString()
     */
    @Override
    public String getStatusString() {
        return "Target file: " + this.gpxFilePath;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.output.IOutputPlugin#getUserInterface()
     */
    @Override
    public ModuleGUI getUserInterface() {
        return this.gui;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.common.IModule#init()
     */
    @Override
    public void init() throws Exception {
        this.gpx = GpxDocument.Factory.newInstance();
        this.gpx.addNewGpx();

        this.gui = new ModuleGUI();

        this.gui.setMenu(makeMenu());
        this.gui.setGui(makeGUI());

        setGpxFilePath(DEFAULT_GPX_FILE_PATH);

        updateGUI();
    }

    /**
     * 
     * @return
     */
    private JPanel makeGUI() {
        if (this.panel == null) {
            this.panel = new JPanel(new BorderLayout());
            JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
            JButton selectFileButton = new JButton(SELECT_GPX_FILE_TEXT);
            selectFileButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    selectGpxFileAction();
                }
            });
            controlPanel.add(selectFileButton);

            this.outputFileLabel = new JLabel();
            controlPanel.add(this.outputFileLabel);

            JButton saveFileButton = new JButton(SAVE_CURRENT_FILE_TEXT);
            saveFileButton.setToolTipText("Does also save manual changes undless model changes were made.");
            saveFileButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    saveCurrentFile();
                }
            });
            controlPanel.add(saveFileButton);

            // JCheckBox scrollLockCheckBox = new JCheckBox("scroll lock");
            // scrollLockCheckBox.setSelected(true);
            // scrollLockCheckBox.addActionListener(new ActionListener() {
            // @Override
            // public void actionPerformed(final ActionEvent e) {
            // EventQueue.invokeLater(new Runnable() {
            //
            // @Override
            // public void run() {
            // JCheckBox source = (JCheckBox) e.getSource();
            // // GpxOutputPlugin.this.textPane.setAutoscrolls(source.isSelected());
            // }
            // });
            // }
            // });
            // controlPanel.add(scrollLockCheckBox);

            this.panel.add(controlPanel, BorderLayout.NORTH);

            JPanel valuesPanel = new JPanel(new VerticalLayout(5, VerticalLayout.LEFT));
            DefaultListModel listModel = new DefaultListModel();
            listModel.addElement(NO_DATA_LIST_ELEMENT);
            this.valuesToSaveList = new JList(listModel);
            this.valuesToSaveList.setVisibleRowCount(2);
            this.valuesToSaveList
                    .setToolTipText("Select (multiple) values to include in the GPX file [Multiple using <Ctrl>].");
            this.valuesToSaveList.addListSelectionListener(new ListSelectionListener() {

                @Override
                public void valueChanged(final ListSelectionEvent e) {
                    EventQueue.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            JList list = (JList) e.getSource();
                            // DefaultListModel listModel = (DefaultListModel) list.getModel();
                            Object[] selectedValues = list.getSelectedValues();
                            log.debug("New selection in value list:" + Arrays.toString(selectedValues));

                            GpxOutputPlugin.this.valuesToSave.clear();

                            for (Object o : selectedValues) {
                                if (o instanceof String) {
                                    String s = (String) o;
                                    if (!GpxOutputPlugin.this.valuesToSave.contains(s))
                                        GpxOutputPlugin.this.valuesToSave.add(s);
                                }
                            }
                        }
                    });
                }
            });
            this.valuesToSaveList.setSize(200, 22);
            this.valuesToSaveList.setAlignmentY(Component.CENTER_ALIGNMENT);
            valuesPanel.add(new JLabel("Saved attributes:"));
            valuesPanel.add(this.valuesToSaveList);
            valuesPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
            this.panel.add(valuesPanel, BorderLayout.EAST);

            this.textPane = new XmlTextPane();
            this.textPane.setEditable(true);
            this.textPane.setFont(Font.getFont(Font.MONOSPACED));

            JScrollPane scrollPane = new JScrollPane(this.textPane);
            this.panel.add(scrollPane, BorderLayout.CENTER);
            JButton refreshTextFieldButton = new JButton("Update");
            refreshTextFieldButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    EventQueue.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            updateGUI();
                        }
                    });
                }
            });
            this.panel.add(refreshTextFieldButton, BorderLayout.SOUTH);

            updateGUI();
        }

        return this.panel;
    }

    /**
     * 
     * @return
     */
    private JMenu makeMenu() {
        if (this.menu == null) {
            this.menu = new JMenu();
            JMenuItem selectFile = new JMenuItem(SELECT_GPX_FILE_TEXT);
            selectFile.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    selectGpxFileAction();
                }
            });
            this.menu.add(selectFile);
            JMenuItem saveFile = new JMenuItem(SAVE_CURRENT_FILE_TEXT);
            saveFile.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    saveCurrentFile();
                }
            });
            this.menu.add(saveFile);
        }

        return this.menu;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.output.IOutputPlugin#processData(java.util.Map,
     * org.n52.ifgicopter.spf.xml.Plugin)
     */
    @Override
    public int processData(Map<Long, Map<String, Object>> data, Plugin plugin) {
        for (Entry<Long, Map<String, Object>> entry : data.entrySet()) {
            int i = processSingleData(entry.getValue(), entry.getKey(), plugin);
            if (i != IModule.STATUS_RUNNING)
                log.warn("Processing of data had error: " + entry);
        }

        return IModule.STATUS_RUNNING;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.output.IOutputPlugin#processSingleData(java.util.Map, java.lang.Long,
     * org.n52.ifgicopter.spf.xml.Plugin)
     */
    @Override
    public int processSingleData(Map<String, Object> data, Long timestamp, Plugin plugin) {
        if (log.isDebugEnabled())
            log.debug("New data for GPX output: " + data + " @ " + timestamp);

        this.availableDataKeys.addAll(data.keySet());

        WptType newWpt = this.gpx.getGpx().addNewWpt();

        newWpt.setName(plugin.getName() + " [" + Integer.toString(this.counter) + "]");
        this.counter++;

        Date d = new Date(timestamp.longValue());
        Calendar cal = Calendar.getInstance();
        cal.setTime(d);
        newWpt.setTime(cal);

        // use plugin.getLocation() instead of hard coded keys for the data map
        Location location = plugin.getLocation();
        Double lat = (Double) data.get(location.getFirstCoordinateName());
        Double lon = (Double) data.get(location.getSecondCoordinateName());
        Double alt = (Double) data.get(location.getAltitudeName());
        newWpt.setLat(new BigDecimal(lat.toString()));
        newWpt.setLon(new BigDecimal(lon.toString()));
        newWpt.setEle(new BigDecimal(alt.toString()));

        HashMap<String, Object> dataToSave = new HashMap<String, Object>();
        for (Entry<String, Object> entry : data.entrySet()) {
            if (this.valuesToSave.contains(entry.getKey()))
                dataToSave.put(entry.getKey(), entry.getValue());
        }

        String values = encodeDataString(dataToSave);
        newWpt.setDesc(values);

        updateGUI();
        return IModule.STATUS_RUNNING;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.output.IOutputPlugin#restart()
     */
    @Override
    public void restart() throws Exception {
        log.info("Creating new empty document because of restart!");
        this.gpx = GpxDocument.Factory.newInstance();
    }

    /**
     * @throws IOException
     */
    protected void saveCurrentFile() {
        File f = new File(this.gpxFilePath);

        try {
            if (!f.exists()) {
                f.mkdirs();
                f.createNewFile();
            }

            String text = this.textPane.getText();

            GpxDocument gpxToSave = GpxDocument.Factory.parse(text);

            boolean valid = this.gpx.validate();
            if (log.isDebugEnabled()) {
                log.debug("Created GPX file from text pane, is valid=" + valid);
            }

            gpxToSave.save(f, GPX_OPTIONS);
        } catch (IOException e) {
            log.error("Could not save GPX output file " + f.getAbsolutePath(), e);
        } catch (XmlException e) {
            log.error("Could not save GPX output file " + f.getAbsolutePath(), e);
        }
    }

    /**
     * 
     */
    protected void selectGpxFileAction() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFileChooser fc = new JFileChooser();
                fc.setDialogTitle("Select GPX File");
                fc.setFileFilter(new FileFilter() {

                    @Override
                    public boolean accept(File f) {
                        if (f.getName().toLowerCase().endsWith("gpx") || f.getName().toLowerCase().endsWith("xml"))
                            return true;
                        if (f.isDirectory())
                            return true;

                        return false;
                    }

                    @Override
                    public String getDescription() {
                        return "GPX file";
                    }
                });

                File gpxfile = new File(getGpxFilePath());
                if (gpxfile.isFile()) {
                    fc.setCurrentDirectory(gpxfile.getParentFile());
                }

                int returnVal = fc.showOpenDialog(getUserInterface().getGui());

                if (returnVal == JFileChooser.APPROVE_OPTION) {
                    File file = fc.getSelectedFile();
                    if (!file.getName().endsWith(".gpx") || !file.getName().endsWith(".xml"))
                        setGpxFilePath(file.getAbsolutePath() + ".gpx");
                    else
                        setGpxFilePath(file.getAbsolutePath());
                } else {
                    log.debug("Open command cancelled by user.");
                }

                fc = null;
            }
        });
    }

    /**
     * @param gpxFilePath
     *        the gpxFilePath to set
     */
    public void setGpxFilePath(String gpxFilePath) {
        this.gpxFilePath = gpxFilePath;
        if (log.isDebugEnabled())
            log.debug("New output file path " + this.gpxFilePath);

        updateGUI();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.n52.ifgicopter.spf.common.IModule#shutdown()
     */
    @Override
    public void shutdown() throws Exception {
        saveCurrentFile();
        this.gpx = null;
    }

    /**
     * 
     */
    protected void updateGUI() {
        if (this.gpx == null)
            return;

        final String t = this.gpx.xmlText(GPX_OPTIONS);
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                GpxOutputPlugin.this.textPane.setText(t);

                GpxOutputPlugin.this.outputFileLabel
                        .setText("<html><b>Output file: </b>" + getGpxFilePath() + "</html>");

                DefaultListModel model = (DefaultListModel) GpxOutputPlugin.this.valuesToSaveList.getModel();
                // update list
                if (model.getSize() < GpxOutputPlugin.this.availableDataKeys.size()) {
                    if (model.contains(NO_DATA_LIST_ELEMENT))
                        model.remove(model.indexOf(NO_DATA_LIST_ELEMENT));

                    for (String s : GpxOutputPlugin.this.availableDataKeys) {
                        if (!model.contains(s)) {
                            model.addElement(s);
                        }
                    }
                }
            }
        });
    }

    private String encodeDataString(Map<String, Object> data) {
        StringBuilder values = new StringBuilder();
        for (Entry<String, Object> entry : data.entrySet()) {
            values.append(entry.getKey());
            values.append("=");
            values.append(entry.getValue().toString());
            values.append(";");
        }

        return values.toString();
    }

}