How to read Excel files with Java using the IntelliJ IDEA plugin

How to read Excel files in Java with real efficiency using SCell

Table of Contents

There are plenty of possibilities for managing xlsx files. You can use Java to read Excel files, basically without any limitations. You can easily create anything using the right Java tools. To describe them, let’s create a plugin for IntelliJ that will display the xlsx file. 

To fulfill this task:

  1. We’ll create the “base” of the plugin using the IntelliJ devkit.
  2. We’ll integrate and use an appropriate Java tool to read and display the Excel file.

 

Creating the base for the IntelliJ plugin to read Excel files using Java

The first step is to create a new Gradle project having enabled the “Intellij devkit” library. The “IntelliJ devkit” allows you to extend and modify default IntelliJ possibilities. In our case, we have decided to display the created Excel / xlsx file in the “tool window” by double clicking it in the project tree.

Using Java to read Excel files - done the right way
Using Java to read Excel files – done the right way

To handle a double click, let’s create the IntelliJ “AnAction” class and bind the xlsx file extension to it.

public class FilePopupAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        final Project project = e.getData(CommonDataKeys.PROJECT);
        final VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
        if (project == null || virtualFile == null) {
            return;
        }
 
        SCellService scellService = project.getService(SCellService.class);
        scellService.load(virtualFile);
    }
 
    @Override
    public void update(@NotNull AnActionEvent e) {
        e.getPresentation().setEnabledAndVisible(available(e));
    }
 
    private static boolean available(AnActionEvent e) {
        VirtualFile data = e.getData(CommonDataKeys.VIRTUAL_FILE);
        if (data == null) {
            return false;
        }
        String extension = data.getExtension();
        return Arrays.asList(XlsxFileType.SUPPORTED_EXTENSIONS).contains(extension);
    }
}

This class provides you with the “actionPerformed” method, that is called when one of IntelliJ actions is invoked. To specify the place in which you want to invoke this method, modify the “yourProject\src\main\resources\META-INF\plugin.xml”:

<idea-plugin>
    <depends>com.intellij.modules.platform</depends>
    <depends>com.intellij.javafx</depends>
    <depends>com.intellij.java</depends>
 
    <extensions defaultExtensionNs="com.intellij">
       <projectService serviceImplementation="com.intechcore.scell.ideaplugin.platform.SCellService"/>
        <fileType
                name="Spreadsheet file"
                implementationClass="com.intechcore.scell.ideaplugin.platform.XlsxFileType"
                fieldName="INSTANCE"
                extensions="xlsx"/>
 
     <toolWindow id="XLSXReader" anchor="bottom" icon="/icons/scellIcon16x16.svg"                   factoryClass="com.intechcore.scell.ideaplugin.toolwindow.SpreadsheetToolWindowFactory"/>
    </extensions>
 
    <actions>
        <action id="XLSXReader" class="com.intechcore.scell.ideaplugin.FilePopupAction" text="Open with XLSXReader"
                description="The tool you can read the xlsx files in IDEA.">
            <add-to-group group-id="ProjectViewPopupMenu" anchor="first"/>
        </action>
    </actions>
</idea-plugin>

This section subscribes the “FilePopupAction” class provided to a double click on any file in the project tree and creates the new project view popup option by clicking the right button:

<action id="XLSXReader" class="com.intechcore.scell.ideaplugin.FilePopupAction" text="Open with XLSXReader" 
                description="The tool you can read the xlsx files in IDEA."> 
            <add-to-group group-id="ProjectViewPopupMenu" anchor="first"/> 
        </action>

Now let’s focus on the “update” method.

Our Action should only be invoked when the xlsx file is clicked, this can be achieved by implementing the “update method”. The contents of this method will verify if the file has the xlsx extension. Depending on the result, the IntelliJ will call the “actionPerformed” method, which will pass the obtained file to our service class and will run it.

The “XlsxFileType” class describes desired file:

public class XlsxFileType implements INativeFileType {
    public static final INativeFileType INSTANCE = new XlsxFileType();
    public static final Icon SCELL_FILE_ICON = IconLoader.getIcon("/icons/scellIcon16x16.svg", XlsxFileType.class);
    public static final String[] SUPPORTED_EXTENSIONS = new String[] { "xlsx" };
 
    @Override
    public boolean openFileInAssociatedApplication(Project project, @NotNull VirtualFile file) {
        IdeFrame lastFocusedFrame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
        if (lastFocusedFrame == null) {
            return false;
        }
 
        SCellService scellService = project.getService(SCellService.class);
        scellService.load(file);
        return false;
    }
 
    @Override
    public boolean useNativeIcon() {
        return false;
    }
 
    @Override
    public @NonNls @NotNull String getName() {
        return "Spreadsheet file";
    }
 
    @Override
    public @NlsContexts.Label @NotNull String getDescription() {
        return "Excel";
    }
 
    @Override
    public @NlsSafe @NotNull String getDefaultExtension() {
        return "xlsx";
    }
 
    @Override
    public Icon getIcon() {
        return SCELL_FILE_ICON;
    }
 
    @Override
    public boolean isBinary() {
        return true;
    }
}

To reserve this file type for our plugin we need to add this to the plugin.xml:

<extensions defaultExtensionNs="com.intellij">
    <fileType
            name="Spreadsheet file"
            implementationClass="com.intechcore.scell.ideaplugin.platform.XlsxFileType"
            fieldName="INSTANCE"
            extensions="xlsx"/>
 
</extensions>

It will also be convenient to create the start window in the tool bar. The window will ask you to select a file if it has not been selected when the window was opened. 

Using Java to create Excel files easily with SCell

The following class will do what we need:

public class SpreadsheetToolWindowFactory implements ToolWindowFactory, DumbAware {
    @Override
    public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
        if (ApplicationManager.getApplication().isUnitTestMode()) {
            return;
        }
 
        JBPanelWithEmptyText myMessagePanel = new JBPanelWithEmptyText().withEmptyText("Double click the xlsx-file inside the filetree to open it here");
        ContentFactory contentFactory = ContentFactory.getInstance();
        Content content = contentFactory.createContent(myMessagePanel, "Usage", false);
        toolWindow.getContentManager().addContent(content);
 
        SCellService scellService = project.getService(SCellService.class);
        scellService.initToolWindow((ToolWindowEx)toolWindow);
    }
}

The IntelliJ “ToolWindowFactory” interface provides access to the toolbar window for further modifications through the “createToolWindowContent” method. To display this window, we need to add it to the extensions in the plugin.xml: 

<toolWindow id="XLSXReader" anchor="bottom" icon="/icons/scellIcon16x16.svg" factoryClass="com.intechcore.scell.ideaplugin.toolwindow.SpreadsheetToolWindowFactory"/>

From this point we can proceed to the second step of our plan.

 

Integrating the Java library to read and work with Excel files and to display the xlsx file in IntellJ IDEA

SCell

The SCell is a Java library which is suitable for our task to read and work Excel files. It is designed for embedding Excel spreadsheets into other applications, it can open, and even edit and save the xlsx files. What is more important, it can render spreadsheets for us.

Let’s start by adding the SCell dependency into our project.

First, add this to the “yourProject/build.graddle” file:

repositories {
  jcenter()
  maven {
    url "${SCELL_REPO_URL}"
    credentials {
      username "${SCELL_REPO_USER}"
      password "${SCELL_REPO_PASS}"
    }
  }
}

dependencies {
  implementation "com.intechcore.scomponents.scell:api-interfaces-core:${SCELL_API_VERSION}"
  runtimeOnly "com.intechcore.scomponents.scell:api-impl-core:${SCELL_API_VERSION}"
  implementation "com.intechcore.scomponents.scell:api-interfaces-ui:${SCELL_API_VERSION}"
  runtimeOnly "com.intechcore.scomponents.scell:api-impl-ui:${SCELL_API_VERSION}"
}

Then create the “yourProject/graddle.properties” file:
SCELL_API_VERSION = 1.3.5-java11-DEMO
SCELL_REPO_URL = https://nexus.intechcore.online/repository/maven-scomponents-demo
SCELL_REPO_USER = demo
SCELL_REPO_PASS = demo

After this the SCell is ready to work.

Then we register a service, that will process the xlsx file, so that other IDE components could have access it. To register this service, create it and add the following changes to the extensions of the plugin.xml:

<projectService serviceImplementation="com.intechcore.scell.ideaplugin.platform.SCellService"/>

Now we can load the xlsx file with the help of SCell, to do this, we’ve added the “load” method to the Service, which is called in our Action: 

public class SCellService {
 
    public static final String TOOL_WINDOW_ID = "XLSXReader";
 
    private final Project project;
 
    private CompletableFuture<IScellApiResolver> scellResolverFuture;
 
    private ToolWindowEx toolWindow;
    private ScellControl scellControl;
 
    public SCellService(Project project) {
        this.project = project;
    }
 
    public void initToolWindow(ToolWindowEx toolWindow) {
        if (this.toolWindow != null) {
            return;
        }
        this.toolWindow = toolWindow;
        toolWindow.setAutoHide(false);
        toolWindow.setToHideOnEmptyContent(true);
    }
 
    public ToolWindowEx getOrInitToolWindow() {
        ToolWindowEx localToolWindow = this.toolWindow;
        if (localToolWindow == null) {
            localToolWindow = (ToolWindowEx)ToolWindowManager.getInstance(this.project).getToolWindow(TOOL_WINDOW_ID);
            this.initToolWindow(localToolWindow);
        }
        return localToolWindow;
    }
 
    public void load(VirtualFile virtualFile) {
        if (this.scellResolverFuture == null) {
            this.scellResolverFuture =
                    ScellApiEntryPoint.getApiResolverAsync(this.getClass().getClassLoader());
        }

        ToolWindowEx output = this.getOrInitToolWindow();

        if (this.scellControl == null) {
            this.scellControl = new ScellControl(this.scellResolverFuture);
        }
 
        output.getContentManager().removeAllContents(false);
        Content content = createContent(virtualFile.getName(), this.scellControl);
        output.getContentManager().addContent(content);
        this.scellControl.addNotify();
        this.scellControl.loadFile(virtualFile);
        output.show();
    }
 
    private static Content createContent(String fileName, JComponent component) {
        String tabName = ObjectUtils.notNull(fileName, "New File");
        return ContentFactory.getInstance().createContent(component, tabName, false);
    }
}

The first thing you’ll get from SCell is the ScellApiEntryPoint, from which you can get any API component.

If it’s the first call of this method, it initializes the SCell API and passes it to the ScellControl:

public class ScellControl extends JFXPanel implements Disposable {
    private final CompletableFuture<IScellApiResolver> scellResolverFuture;
    private CompletableFuture<IScellUiApi<Node>> uiApiFuture;
    private BorderPane root;
 
    public ScellControl(CompletableFuture<IScellApiResolver> scellResolverFuture) {
        this.scellResolverFuture = scellResolverFuture;
        this.setLayout(new BorderLayout());
        this.root = new BorderPane(new Label("Initializing ..."));
        Platform.runLater(() -> {
            Scene scene = new Scene(this.root);
            this.setScene(scene);
        });
    }
 
    public CompletableFuture<Void> loadFile(VirtualFile file) {
        if (this.uiApiFuture == null) {
            this.uiApiFuture = this.createScellUi(file);
            this.uiApiFuture.thenApplyAsync(IScellUiApi::getControl).whenCompleteAsync((node, ex) -> {
                if (ex != null) {
                    node = new Label("Failed to init: " + ex.getCause().getMessage());
                }
 
                this.root.setCenter(node);
            }, Platform::runLater);
 
            return this.uiApiFuture.thenAccept(unused -> { });
        }
 
        return this.uiApiFuture.thenAcceptAsync(uiApi -> {
            try {
                uiApi.getContentManager().load(file.getInputStream(), file.getPath());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }
 
    private CompletableFuture<IScellUiApi<Node>> createScellUi(VirtualFile file) {
        return this.scellResolverFuture.thenApplyAsync(resolver -> {
            IScellCoreApiFactory apiFactory = IScellCoreApiFactory.resolve(resolver);
 
            IScellUiApiBuilder<Node> uiBuilder = IScellUiFxApiBuilder.resolve(resolver);
 
            return uiBuilder.readOnly(true).create(CompletableFuture.supplyAsync(() -> {
                try {
                    return apiFactory.load(file.getInputStream(), () -> "", file.getPath());
                } catch (IOException e) {
                    return null;
                }
            }));
        });
    }
 
    @Override
    public void dispose() {
        Platform.runLater(() -> {
            if (this.root.getCenter() != null) {
                this.root.setCenter(null);
            }
        });
    }
}

Note that SCell uses JavaFX while Intellij uses swing for rendering.

Swing can host the JavaFx scene with its own UI control. To do this we inherit the “JFXPanel” and pass the JavaFX scene with the spreadsheet to the “setScene” method.

The “loadFile” method creates a node that contains the loaded spreadsheet and passes it to the scene.

The “ISCellUiApiBuilder” interface implementation creates the UI node and the “IScellCoreApiFactory” implementation actually loads the file:

private CompletableFuture<IScellUiApi<Node>> createScellUi(VirtualFile file) {
       return this.scellResolverFuture.thenApplyAsync(resolver -> {
           IScellCoreApiFactory apiFactory = IScellCoreApiFactory.resolve(resolver);

           IScellUiApiBuilder<Node> uiBuilder = IScellUiFxApiBuilder.resolve(resolver);

           return uiBuilder.readOnly(true).create(CompletableFuture.supplyAsync(() -> {
               try {
                   return apiFactory.load(file.getInputStream(), () -> "", file.getPath());
               } catch (IOException e) {
                   return null;
               }
           }));
       });
   }

The “IScellCoreApiFactory.load(InputStream inputStream, Supplier<String> passwordSupplier, String title)” can load password protected files. Since in our example, we load a common xlsx, we pass an empty string as a password (in the case of loading password protected files, the password receiving menu is required). We also set the spreadsheet to read-only mode using the “ISCellUiApiBuilder” because our task is simply to read the xlsx file.

These two instances can be obtained from the “CompletableFuture<IScellApiResolver> scellResolverFuture” that we have passed earlier from the “ScellServise”.

Now let’s get back to the “SCellService”:

public void load(VirtualFile virtualFile) {
     if (this.scellResolverFuture == null) {
         this.scellResolverFuture =
                 ScellApiEntryPoint.getApiResolverAsync(this.getClass().getClassLoader());
     }
 
     ToolWindowEx output = this.getOrInitToolWindow();
 
     if (this.scellControl == null) {
         this.scellControl = new ScellControl(this.scellResolverFuture);
     }
 
     output.getContentManager().removeAllContents(false);
     Content content = createContent(virtualFile.getName(), this.scellControl);
     output.getContentManager().addContent(content);
     this.scellControl.addNotify();
     this.scellControl.loadFile(virtualFile);
     output.show();
 }

After initializing the “ScellControl” we get the tool window that has been created earlier. Then we create the content for the tool window from “scellControl” using the “createContent” method. After that we call the “loadFile”, which creates the spreadsheet for us, and displays the spreadsheet in the tool window by calling the “output.show()” method.

Congrats, the job is done, we can open and display the xlsx spreadsheet in the IntellJ.

Please also check the short IntelliJ video: Integration into IntelliJ IDEA – YouTube and subscribe to our YouTube channel.

 

Invitation: learn more about our Java components that create, read and edit Excel files 

If you want to use Java to read Excel files and also want to learn how to use other features of the SCell, such as editing or saving the xlsx files, or how to install it to the Maven project, visit our article “Work and create Excel files in Java: Unleashing SCell” or the official site or the JavaDock documentation page.

Here you will definitely find some helpful information. Feel free to reach out to us on our contact page

Share:

Follow us:

More Posts

Scroll to Top