Linking the Handler and Reading an Excel file

In this section, we will use the defined Command Java class and will connect our application to external Excel files.

Starting Point

This section builds on the content from Building the Custom Data Command Class.

1. Linking the Handler (Phase 1: Skeleton Test)

Before parsing the Excel file, let’s verify our Command skeleton is hooked up correctly to our UI button.

Open your Handler class. Replace the placeholder action (that printed out the file path) with the following code to execute the command with an empty list.

ImportCustomDataHandler.java
OpenpitScenario currentScenario = (OpenpitScenario)appData.getScenario();

ImportCustomDataCommand command = new ImportCustomDataCommand(currentScenario, redrawRestrictionManager);
command.importCustomData(List.of()); // Passing an empty list for now

CommandsManager.getEditingDomain().getCommandStack().execute(command);

MessageBoxFactory.createMessageBox(shell, SWT.OK, "Success", "Data import command executed.");
Eclipse Quick Fix for Imports

If ImportCustomDataCommand is underlined in red in your Handler, click on the error and press Ctrl + 1 on Windows or Cmd + 1 on Mac. This brings up the Eclipse Quick Fix menu, which will automatically find and populate the correct import statement for you.

Full Code up to this point
ImportCustomDataCommand.java
import java.util.List;
import com.amalgamasimulation.desktop.binding.RedrawRestrictionManager;
import com.amalgamasimulation.minetwin.datamodel.MineArc;
import com.amalgamasimulation.minetwin.datamodel.MineArea;
import com.amalgamasimulation.minetwin.datamodel.MineSegment;
import com.amalgamasimulation.minetwin.datamodel.MiningType;
import com.amalgamasimulation.minetwin.datamodel.OreType;
import com.amalgamasimulation.minetwin.datamodel.Scenario;
import com.amalgamasimulation.minetwin.openpit.datamodel.openpit.Block;
import com.amalgamasimulation.minetwin.openpit.application.commands.OpenPitCompoundCommand;

public class ImportCustomDataCommand extends OpenPitCompoundCommand {

    // Define the structure of our Excel data
    public record BlockData(String id, String arcId, String mineAreaId, Double volume) {}

    // Constructor
    public ImportCustomDataCommand(Scenario scenario, RedrawRestrictionManager redrawRestrictionManager) {
        super(scenario, redrawRestrictionManager);
    }

    // Debug printing method
    public void importCustomData(List<BlockData> data) {
        System.out.println("Identifier : " + getScenario().getIdentifier());
        System.out.println("data records count : " + data.size());

        for( var r : data ) {
            System.out.println("id : " + r.id() + " arcId : " + r.arcId() + " volume : " + r.volume());
        }
    }
}
ImportCustomDataHandler.java
package com.~.application.handlers;

import java.util.List;

import org.eclipse.e4.core.di.annotations.CanExecute;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;

import com.amalgamasimulation.desktop.binding.RedrawRestrictionManager;
import com.amalgamasimulation.desktop.commands.CommandsManager;
import com.amalgamasimulation.desktop.ui.dialogs.MessageBoxFactory;
import com.amalgamasimulation.minetwin.openpit.datamodel.openpit.OpenpitScenario;
import com.~.application.commands.ImportCustomDataCommand;

// Note: Ensure your AppData import matches your specific project structure
import com.~.application.AppData;

public class ImportCustomDataHandler {

    @Execute
    public void execute(Shell shell, AppData appData, RedrawRestrictionManager redrawRestrictionManager) {

        // 1. Retrieve the current open scenario
        OpenpitScenario currentScenario = (OpenpitScenario) appData.getScenario();

        // 2. Instantiate the new command
        ImportCustomDataCommand command = new ImportCustomDataCommand(currentScenario, redrawRestrictionManager);

        // 3. Pass an empty list (Skeleton phase)
        command.importCustomData(List.of());

        // 4. Execute the command via the Command Stack
        CommandsManager.getEditingDomain().getCommandStack().execute(command);

        // 5. Show success message
        MessageBoxFactory.createMessageBox(shell, SWT.OK, "Success", "Data import command executed.");
    }

    @CanExecute
    public boolean canExecute(AppData appData) {
        // Only allow execution if a scenario is actually loaded
        return appData != null && appData.getScenario() != null;
    }
}

1.1. Testing the Skeleton

  1. Run the application and import this default template.

    MineTwin Openpit software menu with file options; \"Import scenario from template\" has a submenu open showing \"Other\".
  2. Save the scenario to a convenient location so it can be easily reloaded later.

  3. Click your custom import data button and select any .xlsx file.

A pop-up should appear with blank statements.

A dialog box with "Title" as the header, "Message" text, an "OK" button, and a close button at the top right.

The Eclipse Console should also show:

Identifier : New Scenario MineTwin OpenPit 0
data records count : 0

This proves our Handler can see the file and execute the Command, even though we haven’t parsed the records yet.

2. Reading Data with ExcelFile

Now, we replace the List.of() test code in the handler with the actual logic to read the Excel file.

Update your Handler execution block to use the ExcelFile utility inside a try-with-resources block.

ImportCustomDataHandler.java
try (ExcelFile excelFile = new ExcelFile(selectedPath)) {
    List<Object> issues = new ArrayList<>();    // list for collecting errors

    // Parse Excel data into a List of BlockData records
    List<BlockData> dataRecords = excelFile.readRecords("BlockData", 2, BlockData.class)
                                           .peekValue(issues::addAll)
                                           .orElse(List.of());

    // Create command and pass parsed records to it
    ImportCustomDataCommand command = new ImportCustomDataCommand(currentScenario, redrawRestrictionManager);
    command.importCustomData(dataRecords);

    // Execute command via the CommandStack
    CommandsManager.getEditingDomain().getCommandStack().execute(command);

    MessageBoxFactory.createMessageBox(shell, SWT.OK, "Success", "Data imported successfully.");
} catch (Exception e) {
    MessageBoxFactory.createMessageBox(shell, SWT.ERROR, "Import Error", "Failed to load data.");
}

Remember to import BlockData into your Handler

import com.eyftraining.application.commands.ImportCustomDataCommand.BlockData;. Use Ctrl + 1.

2.1. Understanding the Implementation

Let’s break down exactly what is happening in this execution block:

  • The try-with-resources Block: Notice how we declare ExcelFile excelFile = new ExcelFile(selectedPath) inside the parentheses of the try statement. This is a modern Java feature called try-with-resources. It guarantees that the Excel file is automatically closed as soon as the block finishes executing, preventing memory leaks or file locks, even if an error occurs.

  • Parsing the Excel File (readRecords): The readRecords("BlockData", 2, BlockData.class) method is doing the heavy lifting of converting rows and columns into Java objects.

    • "BlockData" refers to the specific name of the sheet inside the Excel workbook.

    • 2 tells the parser to start reading at row 2, allowing us to safely skip the header row.

    • BlockData.class is the target Java Record we want to map the data into.

  • Functional Error Handling: The .peekValue(issues::addAll) line is a functional programming approach to capture any non-fatal parsing issues (like a malformed cell) without crashing the entire application. The .orElse(List.of()) ensures that if the parsing fails completely, it returns a safe, empty list rather than throwing a NullPointerException.

  • Executing via the Command Stack: We do not just call command.execute(). By passing our command into CommandsManager.getEditingDomain().getCommandStack().execute(command), we are registering this data import with MineTwin’s core architecture. This is what magically allows the user to press Ctrl+Z (Undo) to reverse the import if they loaded the wrong file!

  • User Feedback (MessageBoxFactory): Finally, we use SWT (Standard Widget Toolkit) dialogs to inform the user whether the operation was a Success or an ERROR, providing immediate visual feedback for the action.