Custom File Types in Netbeans Platform
If your desktop Java application needs to deal with very particular or special kinds of files, we can make use of Netbeans Platform's custom File Type feature to define a file that our application can recognize, and display visual and editing components to view and edit the file's data.
Moreover, we can also make use of Netbeans Platform's Favorites pane, which displays a tree view of a file system, where we can open the special files we need to work with.
Creating a New Module
For this article, we are going to create a new file type for CSV (Comma Separated Value) files. When we open these files, our application will display a visual representation in the form of an editable JTable.
All this functionaltiy will live in its own Netbeans Platform Module. Meaning that it can be plugged in into different applications in a modular fashion.
We are going to create the module and build it using Maven, since we are also going to be adding some functionalities that depend on external libraries that we can easily include in our project with Maven. But you can also just create the module without Maven as well. To create the module go to New Project > Maven > Netbeans Module
We will name our module CSVFileSupport
.Before creating the module, make sure to uncheck the Allow OSGi bundles as dependencies option.
Creating a New File Type: CSV File
At this point we have a brand new blank Maven module. To begin creating the new file type, right click on the module and select New > File Type option to start the wizard.
In the wizard's first screen we need to enter the file's MIME Type and a space-separated list of its possible extensions:
Lastly we will use Csv
as the class name prefix, select an appropriate icon for the file type, and make sure that the Use MultiView option is checked.
Once the wizard is finished, a DataObject for our file type and a VisualElement
file for our file type will be created. Since we do not really need the Source panel in our view, you can go ahead and delete this code from CsvDataObject
file:
@MultiViewElement.Registration(
displayName = "#LBL_Abc_EDITOR",
iconBase = "com/elibro/csvfilesupport/books.png",
mimeType = "text/abc",
persistenceType = TopComponent.PERSISTENCE_ONLY_OPENED,
preferredID = "Abc",
position = 1000
)
@Messages("LBL_Abc_EDITOR=Source")
public static MultiViewEditorElement createEditor(Lookup lkp) {
return new MultiViewEditorElement(lkp);
}
We can then add this module to a Maven Netbeans Platform application to see it in action. We can use the Favorites pane to browse some CSV files, they should be displayed with the icon we defined:
If you open the file and check the Visual pane to the right you will see that it's blank. We will go ahead and add some cool way to represent the data in the CSV file later.
Notice that you can open multiple files using the Favorites explorer. A new pane will be opened for each file. Let's go ahead and set the name of each file in its corresponding pane. Open the CsvVisualElement
object and select the Source pane. We are going to create a private variable inside the CsvVisualElement
class to store the current file, and then set it in the class's constructor:
public final class CsvVisualElement extends JPanel implements MultiViewElement {
// ...
private final File csvFile;
public CsvVisualElement(Lookup lkp) throws IOException {
obj = lkp.lookup(CsvDataObject.class);
assert obj != null;
initComponents();
csvFile = FileUtil.toFile(lkp.lookup(DataObject.class).getPrimaryFile());
}
}
We can then set the file's name as the display name in the setMultiViewCallback
method of the class:
@Override
public void setMultiViewCallback(MultiViewElementCallback callback) {
this.callback = callback;
callback.getTopComponent().setDisplayName(csvFile.getName());
}
And it should look like this:
Displaying the CSV Data
Before we get into the real visual stuff, let's first go ahead and implement the parsing functionality of the file. We will go ahead and create a new Java Class called CSVManager
in the same package as the rest of the files. This class will take the file in its constructor, and it will contain many methods that work with the file.
public class CSVManager {
private File csvFile;
public CSVManager(File file) {
csvFile = file;
}
}
Since we will want to display the data in a table, let's create a method that parses the CSV file and returns a table model of the data. To parse the file we will use the OpenCSV library:
public DefaultTableModel CSVToTableModel() throws IOException {
// The RFC4180ParserBuilder allows us to parse '\' characters.
CSVReader csvReader = new CSVReaderBuilder(new FileReader(csvFile))
.withCSVParser(new RFC4180ParserBuilder().build())
.build();
List<String[]> csvData = csvReader.readAll();
Object[] headers = (String[]) csvData.get(0);
csvData.remove(0);
String[][] rowData = csvData.toArray(new String[0][]);
return new DefaultTableModel(rowData, headers);
}
=> In the example above we are using a DefaultTableModel
, but you will probably want to implement your own custom table model by extending AbstractTableModel
.
We can then create class local variables in CsvDataObject
to store the manager and the table model, initialize them in the constructor, and create the corresponding getter methods for each:
public class CsvDataObject extends MultiDataObject {
private final CSVManager csvManager;
private final DefaultTableModel csvTableModel;
public CsvDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
super(pf, loader);
registerEditor("text/csv", true);
csvManager = new CSVManager(FileUtil.toFile(this.getPrimaryFile()));
csvTableModel = csvManager.CSVToTableModel();
}
public CSVManager getCSVManager() {
return this.csvManager;
}
public DefaultTableModel getTableModel() {
return this.csvTableModel;
}
// ...
}
Open the CsvVisualElement
file again and in the Design pane, drag a new JScrollPane
component into the panel. Then drag a JTable
component inside the scroll pane. Now go to the file's source and in the getVisualRepresentation()
method we will set the new model to the table:
@Override
public JComponent getVisualRepresentation() {
dataTable = new JTable(obj.getTableModel());
return this;
}
If we run the application again and select a CSV file that contains actual data, you should see the rendered table with the file's data:
Implementing a Save Feature
We will want to be able to save the contents of the current file that is selected in the editor window. We can make use of Netbeans Platform's Savable
interface to easily achieve this. First, make the CsvDataObject
class implement the Savable
interface:
public class CsvDataObject extends MultiDataObject implements Savable {
// ...
}
And lastly add your custom implementation of the save()
method. For our CSV example, I am gonna add a method in the CSVManager
that writes the data in the table model to the file in CSV format:
public class CSVManager {
// ...
public void TableModelToCSVFile(TableModel tableModel) {
try (FileWriter fileWriter = new FileWriter(csvFile)) {
// Write the headers
List<String> headers = new ArrayList();
for (int i = 0; i < tableModel.getColumnCount(); i++) {
headers.add(tableModel.getColumnName(i));
}
fileWriter.write(String.join(",", headers));
fileWriter.write("\n");
// Write the data
for (int i = 0; i < tableModel.getRowCount(); i++) {
List<String> row = new ArrayList();
for (int j = 0; j < tableModel.getColumnCount(); j++) {
Object val = tableModel.getValueAt(i, j);
if (val == null) {
row.add("");
}
else {
row.add(tableModel.getValueAt(i, j).toString());
}
}
fileWriter.write(String.join(",", row));
fileWriter.write("\n");
}
}
catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
And then we can use that in the save()
method of CsvDataObject
:
@Override
public void save() throws IOException {
this.csvManager.TableModelToCSVFile(this.csvTableModel);
}