0

in my current project I am working with a JTable and DefaultTableModel. Everything is working so far and currently for saving the TableModel I simply serialize the object to disk.

As this might be a reasonable way of saving and loading the data I don't like the fact that the data is completely "obfuscated" / in byte form. This makes data almost impossible to rescue if something bad happens. Another problem is the serialization UUID which makes it harder to update my program and don't make the data un-loadable.

The database will be filled over the years and will contain important information. To make data recoverable I wanted to parse the TableModel into an XML file but this fails because the XML Encoder cannot deal with the JTable / TableModel.

What are recommendations for saving data from a JTable in "clear text" form efficiently? Is simply iterating through columns & lines and saving this into a text file (with seperators between column data) line by line a good way? My concerns here are that the user might use a separator like ":" (which I might use) as table data and make the parser crash.

Thanks for your help.

Flatron
  • 1,375
  • 2
  • 12
  • 33
  • 4
    Database would be my first recommendation, may XML or JSON coming in as second. You could look at something like [Introduction to JAXB](https://docs.oracle.com/javase/tutorial/jaxb/intro/), which will allow you to define a series of annotations in your data object (POJO) which will allow the API do encode/decode it – MadProgrammer Sep 07 '15 at 08:10
  • A database also allows more flexibility, like SQL queries, changes to the database, textual dumps for other DBs/Excel and so on. You may use an embedded database like Derby or H2. I like @MadProgrammer's mention of JAXB with annotations, to have object classes for rows. – Joop Eggen Sep 07 '15 at 09:12

2 Answers2

3

What are recommendations for saving data from a

The API recommends an XMLEncoder

I wanted to parse the TableModel into an XML file but this fails because the XML Encoder cannot deal with the JTable / TableModel.

You need to create a custom encoder. Below gives two implementations for a DefaultTableModel.

//  Following code is a more complete version of:
//  http://stackoverflow.com/q/26250939/131872

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.util.Vector;
import javax.swing.*;
import javax.swing.table.*;

public class DefaultTableModelPersistenceDelegateTest
{
    private File file = new File("TableModel.xml");
    private final JTextArea textArea = new JTextArea();

    private final String[] columnNames = {"Column1", "Column2"};

    private final Object[][] data =
    {
        {"aaa", new Integer(1)},
        {"bbb\u2600", new Integer(2)}
    };

    private DefaultTableModel model = new DefaultTableModel(data, columnNames);
    private final JTable table = new JTable(model);

    public JComponent makeUI()
    {
        model.setColumnCount(5);
        JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
        sp.setResizeWeight(.3);
        sp.setTopComponent(new JScrollPane(table));
        sp.setBottomComponent(new JScrollPane(textArea));

        JPanel p = new JPanel();
        p.add(new JButton(new AbstractAction("XMLEncoder")
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
                    XMLEncoder xe = new XMLEncoder(os);
                    xe.setPersistenceDelegate(DefaultTableModel.class, new DefaultTableModelPersistenceDelegate());
                    xe.writeObject(model);
                    xe.close();

                    Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
                    textArea.read(r, null);
                }
                catch (IOException ex)
                {
                    ex.printStackTrace();
                }
            }
        }));

        p.add(new JButton(new AbstractAction("XMLDecoder")
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    InputStream is = new BufferedInputStream( new FileInputStream( file ));
                    XMLDecoder xd = new XMLDecoder(is);
                    model = (DefaultTableModel)xd.readObject();
                    table.setModel(model);
                }
                catch (IOException ex)
                {
                    ex.printStackTrace();
                }
            }
        }));

        p.add(new JButton(new AbstractAction("clear")
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                model = new DefaultTableModel();
                table.setModel(model);
            }
        }));

        JPanel pnl = new JPanel(new BorderLayout());
        pnl.add(sp);
        pnl.add(p, BorderLayout.SOUTH);
        return pnl;
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            @Override public void run()
            {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().add(new DefaultTableModelPersistenceDelegateTest().makeUI());
        f.setSize(420, 340);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

//  See following link for more information on Using XMLEncoder:
//  http://www.oracle.com/technetwork/java/persistence4-140124.html

class DefaultTableModelPersistenceDelegate extends DefaultPersistenceDelegate
{
    //  Initially creates an empty DefaultTableModel. The columns are created
    //  and finally each row of data is added to the model.

    @Override
    protected void initialize(Class<?> type, Object oldInstance, Object newInstance, Encoder encoder)
    {
        DefaultTableModel model= (DefaultTableModel)oldInstance;

        //  Create XML to restore the column names

        Vector<String> columnNames = new Vector<String>(model.getColumnCount());

        for (int i = 0; i < model.getColumnCount(); i++)
        {
            columnNames.add( model.getColumnName(i) );
        }

        Object[] columnNamesData = new Object[] { columnNames };
        encoder.writeStatement(new Statement(oldInstance, "setColumnIdentifiers", columnNamesData));

        //  Create XML to restore row data

        Vector row = model.getDataVector();

        for (int i = 0; i < model.getRowCount(); i++)
        {
            Object[] rowData = new Object[] { row.get(i) };
            encoder.writeStatement(new Statement(oldInstance, "addRow", rowData));
        }
    }
}

class DefaultTableModelPersistenceDelegate2 extends DefaultPersistenceDelegate
{
    //  Initially creates a DefaultTableModel with rows and columns. Then the
    //  columns are reset and proper names are used. Finally data is set for each
    //  cell in the model.

    @Override
    protected void initialize(Class<?> type, Object oldInstance, Object newInstance, Encoder encoder)
    {
        super.initialize(type, oldInstance, newInstance, encoder);

        DefaultTableModel model= (DefaultTableModel)oldInstance;

        //  Create XML to restore the column names

        Vector<String> columnNames = new Vector<String>(model.getColumnCount());

        for (int i = 0; i < model.getColumnCount(); i++)
        {
            columnNames.add( model.getColumnName(i) );
        }

        Object[] columnNamesData = new Object[] { columnNames };
        encoder.writeStatement(new Statement(oldInstance, "setColumnIdentifiers", columnNamesData));

        //  Create XML to reset the value of every cell to its value

        for (int row = 0; row < model.getRowCount(); row++)
        {
            for (int col = 0; col < model.getColumnCount(); col++)
            {
                Object[] o = new Object[] {model.getValueAt(row, col), row, col};
                encoder.writeStatement(new Statement(oldInstance, "setValueAt", o));
            }
        }
    }
}
camickr
  • 321,443
  • 19
  • 166
  • 288
0

You should use and serialize the model only and not the GUI objects (because of the way you're doing it, maybe serialization is not the best option for you). If you want to store the table "headers" (layout of the JTable columns) for this purpose is other Model (+/- Table Column Model - I'm without java environment).

EDIT: If this project is old or important, and you think its worth of investing the time, then maybe you should rethink your Model class?

Craig
  • 2,286
  • 3
  • 24
  • 37
Jacek Cz
  • 1,872
  • 1
  • 15
  • 22
  • Object serialization is not meant for the long term storage of objects and as the OP has pointed out has to many limitations. There a many better options available now days, JAXB been just one of them – MadProgrammer Sep 07 '15 at 09:14
  • Thanks for your answer. Of course I serialize the Model, not the GUI element. Serializing the JTable would have no effect because it stores **no** data. – Flatron Sep 07 '15 at 09:15