From ae9d59f6c63450122b7a52dfbadd61ac1b2d80bc Mon Sep 17 00:00:00 2001 From: Thomas <thomas.musset@pasteur.fr> Date: Wed, 3 Jul 2024 15:44:15 +0200 Subject: [PATCH] updated pom to v4.0.0-a.1, fix classes accordingly to new architecture, added icon, updated .gitignore --- .gitignore | 44 +++++++++++-- pom.xml | 24 ++----- .../blocks/tools/io/FileToWorkbook.java | 15 +++-- .../blocks/tools/io/WorkbookToFile.java | 17 +++-- .../swing/PersistentColumnControlButton.java | 3 +- .../vars/gui/swing/WorkbookEditor.java | 56 +++++++++------- .../adufour/vars/lang/VarWorkbook.java | 5 +- .../adufour/workbooks/IcySpreadSheet.java | 20 ++++-- .../plugins/adufour/workbooks/Workbooks.java | 62 +++++++++++------- src/main/resources/workbooks.png | Bin 0 -> 12669 bytes 10 files changed, 154 insertions(+), 92 deletions(-) create mode 100644 src/main/resources/workbooks.png diff --git a/.gitignore b/.gitignore index b2f15ce..57f16fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,41 @@ -.idea/ -.settings/ -build/ +/build* +/workspace +setting.xml +release/ target/ -bin/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +icy.log + +### IntelliJ IDEA ### +.idea/ +*.iws *.iml -*.jar +*.ipr + +### Eclipse ### +.apt_generated .classpath +.factorypath .project -export.jardesc -**/.DS_Store \ No newline at end of file +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +**/.DS_Store +Icon? \ No newline at end of file diff --git a/pom.xml b/pom.xml index b9332fa..05f4fb1 100644 --- a/pom.xml +++ b/pom.xml @@ -7,46 +7,36 @@ <parent> <groupId>org.bioimageanalysis.icy</groupId> <artifactId>pom-icy</artifactId> - <version>2.2.0</version> + <version>3.0.0-a.1</version> </parent> <artifactId>workbooks</artifactId> - <version>4.0.0</version> - - <packaging>jar</packaging> + <version>4.0.0-a.1</version> <name>Workbooks</name> <description> Toolbox to create and manipulate result tables with plug-ins and protocols, and export as text file or to your favorite spreadsheet software </description> - <properties> - <artifact-to-extract>JMathPlot,poi,poi-ooxml,poi-ooxml-lite,log4j-api,log4j-core</artifact-to-extract> - </properties> - <dependencies> <dependency> <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>protocols</artifactId> + <artifactId>ezplug</artifactId> </dependency> - <dependency> <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>apache-poi</artifactId> + <artifactId>protocols</artifactId> </dependency> - <dependency> - <groupId>com.github.yannrichet</groupId> - <artifactId>JMathPlot</artifactId> - <version>1.0.1</version> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>icy-jmath</artifactId> </dependency> </dependencies> <repositories> <repository> <id>icy</id> - <name>Icy's Nexus</name> - <url>https://icy-nexus.pasteur.fr/repository/Icy/</url> + <url>https://nexus-icy.pasteur.cloud/repository/icy/</url> </repository> </repositories> </project> \ No newline at end of file diff --git a/src/main/java/plugins/adufour/blocks/tools/io/FileToWorkbook.java b/src/main/java/plugins/adufour/blocks/tools/io/FileToWorkbook.java index a571dc7..7a3c7c7 100644 --- a/src/main/java/plugins/adufour/blocks/tools/io/FileToWorkbook.java +++ b/src/main/java/plugins/adufour/blocks/tools/io/FileToWorkbook.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,8 +18,11 @@ package plugins.adufour.blocks.tools.io; -import icy.plugin.abstract_.Plugin; import org.apache.poi.ss.usermodel.*; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.jetbrains.annotations.NotNull; import plugins.adufour.blocks.tools.io.WorkbookToFile.MergePolicy; import plugins.adufour.blocks.util.VarList; import plugins.adufour.vars.lang.VarFile; @@ -36,6 +39,8 @@ import java.io.FileReader; * * @author Alexandre Dufour */ +@IcyPluginName("File to Workbook") +@IcyPluginIcon(path = "/workbooks.png") public class FileToWorkbook extends Plugin implements IOBlock { VarFile inputFile = new VarFile("input file", null); VarWorkbook workbook = new VarWorkbook("workbook", (Workbook) null); @@ -46,12 +51,12 @@ public class FileToWorkbook extends Plugin implements IOBlock { } @Override - public void declareInput(final VarList inputMap) { + public void declareInput(final @NotNull VarList inputMap) { inputMap.add("input file", inputFile); } @Override - public void declareOutput(final VarList outputMap) { + public void declareOutput(final @NotNull VarList outputMap) { outputMap.add("workbook", workbook); } @@ -70,7 +75,7 @@ public class FileToWorkbook extends Plugin implements IOBlock { * @param file input file * @return the workbook read from input file */ - public static Workbook readWorkbook(final File file) { + public static Workbook readWorkbook(final @NotNull File file) { if (!file.exists() || file.isDirectory()) throw new IllegalArgumentException("Cannot read a workbook from " + file.getPath()); diff --git a/src/main/java/plugins/adufour/blocks/tools/io/WorkbookToFile.java b/src/main/java/plugins/adufour/blocks/tools/io/WorkbookToFile.java index 7e22a20..cc1b522 100644 --- a/src/main/java/plugins/adufour/blocks/tools/io/WorkbookToFile.java +++ b/src/main/java/plugins/adufour/blocks/tools/io/WorkbookToFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,10 +18,13 @@ package plugins.adufour.blocks.tools.io; -import icy.file.FileUtil; -import icy.plugin.abstract_.Plugin; -import icy.system.IcyHandledException; import org.apache.poi.ss.usermodel.*; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.io.FileUtil; +import org.bioimageanalysis.icy.system.IcyHandledException; +import org.jetbrains.annotations.NotNull; import plugins.adufour.blocks.util.VarList; import plugins.adufour.vars.lang.VarEnum; import plugins.adufour.vars.lang.VarFile; @@ -36,6 +39,8 @@ import java.util.HashMap; * * @author Alexandre Dufour */ +@IcyPluginName("Workbook to File") +@IcyPluginIcon(path = "/workbooks.png") public class WorkbookToFile extends Plugin implements IOBlock { public enum WorkbookFormat { /** @@ -67,7 +72,7 @@ public class WorkbookToFile extends Plugin implements IOBlock { Merge_sheets___excluding_first_row; @Override - public String toString() { + public @NotNull String toString() { return super.toString().replace("__", ",").replace("_", " "); } } @@ -93,7 +98,7 @@ public class WorkbookToFile extends Plugin implements IOBlock { } @Override - public void declareInput(final VarList inputMap) { + public void declareInput(final @NotNull VarList inputMap) { inputMap.add("workbook", workbook); inputMap.add("file format", format); inputMap.add("output file", file); diff --git a/src/main/java/plugins/adufour/vars/gui/swing/PersistentColumnControlButton.java b/src/main/java/plugins/adufour/vars/gui/swing/PersistentColumnControlButton.java index 0c6e326..b709130 100644 --- a/src/main/java/plugins/adufour/vars/gui/swing/PersistentColumnControlButton.java +++ b/src/main/java/plugins/adufour/vars/gui/swing/PersistentColumnControlButton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -49,7 +49,6 @@ public class PersistentColumnControlButton extends ColumnControlButton { return new DefaultColumnControlPopup() { @Override public void addVisibilityActionItems(final List<? extends AbstractActionExt> actions) { - final ActionContainerFactory factory = new ActionContainerFactory(null); for (final Action action : actions) { final JMenuItem mi = factory.createMenuItem(action); diff --git a/src/main/java/plugins/adufour/vars/gui/swing/WorkbookEditor.java b/src/main/java/plugins/adufour/vars/gui/swing/WorkbookEditor.java index 9e357f6..af0cf9c 100644 --- a/src/main/java/plugins/adufour/vars/gui/swing/WorkbookEditor.java +++ b/src/main/java/plugins/adufour/vars/gui/swing/WorkbookEditor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,19 +18,23 @@ package plugins.adufour.vars.gui.swing; -import icy.gui.dialog.MessageDialog; -import icy.gui.dialog.OpenDialog; -import icy.gui.frame.IcyFrame; -import icy.math.ArrayMath; -import icy.resource.ResourceUtil; -import icy.system.IcyHandledException; -import icy.system.thread.ThreadUtil; import org.apache.poi.ss.formula.FormulaParseException; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.bioimageanalysis.icy.common.math.ArrayMath; +import org.bioimageanalysis.icy.gui.component.button.IcyButton; +import org.bioimageanalysis.icy.gui.component.icon.SVGIcon; +import org.bioimageanalysis.icy.gui.dialog.MessageDialog; +import org.bioimageanalysis.icy.gui.dialog.OpenDialog; +import org.bioimageanalysis.icy.gui.frame.IcyFrame; +import org.bioimageanalysis.icy.system.IcyHandledException; +import org.bioimageanalysis.icy.system.thread.ThreadUtil; import org.jdesktop.swingx.JXPanel; import org.jdesktop.swingx.JXTable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.math.plot.Plot2DPanel; import org.math.plot.Plot3DPanel; import org.math.plot.utils.Array; @@ -196,7 +200,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { this.readOnly.setValue(readOnly); } - private MergePolicy confirm() { + private @Nullable MergePolicy confirm() { final EzDialog confirmDialog = new EzDialog("Confirmation"); confirmDialog.addEzComponent(new EzLabel("This file already exists.")); @@ -249,7 +253,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { optionBar.setLayout(new BoxLayout(optionBar, BoxLayout.X_AXIS)); // Open - openButton = new JButton(ResourceUtil.getImageIcon(ResourceUtil.ICON_OPEN, 18)); + openButton = new IcyButton(SVGIcon.FILE_OPEN); openButton.setVisible(openButtonVisible.getValue()); openButton.setBorderPainted(false); openButton.setFocusable(false); @@ -270,7 +274,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { optionBar.add(openButton); // Export - exportButton = new JButton(ResourceUtil.getImageIcon(ResourceUtil.ICON_SAVE, 18)); + exportButton = new IcyButton(SVGIcon.SAVE); exportButton.setBorderPainted(false); exportButton.setFocusable(false); exportButton.setContentAreaFilled(false); @@ -320,7 +324,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { // Plot (START) - plotButton = new JButton(ResourceUtil.getImageIcon(ResourceUtil.getAlphaIconAsImage("align_left.png"), 18)); + plotButton = new IcyButton(SVGIcon.FORMAT_ALIGN_LEFT); plotButton.setBorderPainted(false); plotButton.setFocusable(false); plotButton.setContentAreaFilled(false); @@ -429,8 +433,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { continue; if (xValues.length != yValues.length) - throw new IcyHandledException( - "Cannot create scatter plot: datasets have different sizes"); + throw new IcyHandledException("Cannot create scatter plot: datasets have different sizes"); final double minY = ArrayMath.min(yValues); final double maxY = ArrayMath.max(yValues); @@ -747,7 +750,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { tabs.addTab("+", new JPanel()); } - private static void createSheet(final Workbook book) { + private static void createSheet(final @NotNull Workbook book) { book.createSheet("Sheet " + (book.getNumberOfSheets() + 1)); } @@ -782,8 +785,9 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { final VarString sheetName = new VarString("sheet name", sheet.getSheetName()); final JXTable table = new JXTable(tableModel) { + @Contract("_, _ -> new") @Override - public TableCellEditor getCellEditor(final int row, final int column) { + public @NotNull TableCellEditor getCellEditor(final int row, final int column) { return new DefaultCellEditor(new JTextField()) { @Override public Component getTableCellEditorComponent(final JTable theTable, final Object value, final boolean isSelected, int theRow, int theColumn) { @@ -800,8 +804,9 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { }; } + @Contract("_, _ -> new") @Override - public TableCellRenderer getCellRenderer(final int row, final int column) { + public @NotNull TableCellRenderer getCellRenderer(final int row, final int column) { return new DefaultTableCellRenderer() { @Override public Component getTableCellRendererComponent(final JTable theTable, final Object value, final boolean isSelected, final boolean hasFocus, int theRow, int theColumn) { @@ -865,13 +870,15 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { // http://stackoverflow.com/questions/6711877/jtable-use-row-numbers final AbstractTableModel headerModel = new AbstractTableModel() { + @Contract(pure = true) @Override public int getColumnCount() { return 1; } + @Contract(pure = true) @Override - public Object getValueAt(final int row, final int column) { + public @NotNull Object getValueAt(final int row, final int column) { return row + 1; } @@ -882,8 +889,9 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { }; final JXTable headerTable = new JXTable(headerModel) { + @Contract(value = "_, _ -> new", pure = true) @Override - public TableCellRenderer getCellRenderer(final int row, final int column) { + public @NotNull TableCellRenderer getCellRenderer(final int row, final int column) { return new TableCellRenderer() { private final TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer(); @@ -953,7 +961,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { class Listener implements ListSelectionListener, TableColumnModelListener { @Override - public void valueChanged(final ListSelectionEvent e) { + public void valueChanged(final @NotNull ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; @@ -969,18 +977,22 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { valueChanged(e); } + @Contract(pure = true) @Override public void columnRemoved(final TableColumnModelEvent e) { } + @Contract(pure = true) @Override public void columnMoved(final TableColumnModelEvent e) { } + @Contract(pure = true) @Override public void columnMarginChanged(final ChangeEvent e) { } + @Contract(pure = true) @Override public void columnAdded(final TableColumnModelEvent e) { } @@ -1077,9 +1089,7 @@ public class WorkbookEditor extends SwingVarEditor<Workbook> { @Override public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) { - if (aValue instanceof String) { - final String text = (String) aValue; - + if (aValue instanceof final String text) { if (text.isEmpty()) { sheet.deleteCell(rowIndex, columnIndex); } diff --git a/src/main/java/plugins/adufour/vars/lang/VarWorkbook.java b/src/main/java/plugins/adufour/vars/lang/VarWorkbook.java index 7f76ccf..e080b9f 100644 --- a/src/main/java/plugins/adufour/vars/lang/VarWorkbook.java +++ b/src/main/java/plugins/adufour/vars/lang/VarWorkbook.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -21,6 +21,7 @@ package plugins.adufour.vars.lang; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.jetbrains.annotations.NotNull; import plugins.adufour.vars.gui.VarEditor; import plugins.adufour.vars.gui.swing.WorkbookEditor; import plugins.adufour.vars.util.VarException; @@ -86,7 +87,7 @@ public class VarWorkbook extends Var<Workbook> { * @throws VarException if the workbook is <code>null</code> * @throws IOException if the file cannot be accessed */ - public void saveToDisk(final File folder, final String workbookName) throws VarException, IOException { + public void saveToDisk(final @NotNull File folder, final String workbookName) throws VarException, IOException { if (!folder.isDirectory()) throw new FilerException(folder + "is not a valid folder"); diff --git a/src/main/java/plugins/adufour/workbooks/IcySpreadSheet.java b/src/main/java/plugins/adufour/workbooks/IcySpreadSheet.java index 8495c69..a3dd6fc 100644 --- a/src/main/java/plugins/adufour/workbooks/IcySpreadSheet.java +++ b/src/main/java/plugins/adufour/workbooks/IcySpreadSheet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -27,6 +27,9 @@ import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFColor; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.xmlbeans.impl.values.XmlValueDisconnectedException; +import org.bioimageanalysis.icy.system.logging.IcyLogger; +import org.jetbrains.annotations.NotNull; import java.awt.Color; import java.lang.reflect.Array; @@ -48,7 +51,7 @@ public class IcySpreadSheet { private final Map<Color, CellStyle> colorStyleMap; - public IcySpreadSheet(final Sheet sheet) { + public IcySpreadSheet(final @NotNull Sheet sheet) { this.sheet = sheet; this.evaluator = sheet.getWorkbook().getCreationHelper().createFormulaEvaluator(); this.colorStyleMap = new HashMap<>(); @@ -130,7 +133,12 @@ public class IcySpreadSheet { final Cell cell = row.getCell(columnIndex); if (cell != null) { row.removeCell(cell); - evaluator.notifyDeleteCell(cell); + try { + evaluator.notifyDeleteCell(cell); + } + catch (final XmlValueDisconnectedException e) { + IcyLogger.warn(this.getClass(), e); // Should not appear + } } } @@ -411,9 +419,7 @@ public class IcySpreadSheet { // what is the value like? - if (value instanceof String) { - final String text = (String) value; - + if (value instanceof final String text) { try { value = Double.parseDouble(text); } @@ -513,7 +519,7 @@ public class IcySpreadSheet { * converted to string using {@link Object#toString()}, while <code>null</code> * values will result in an empty cell */ - public void setRow(final int rowIndex, final Object... values) { + public void setRow(final int rowIndex, final Object @NotNull ... values) { final Iterable<?> list; // special case: rowValues could contain a single array diff --git a/src/main/java/plugins/adufour/workbooks/Workbooks.java b/src/main/java/plugins/adufour/workbooks/Workbooks.java index 096be83..8122437 100644 --- a/src/main/java/plugins/adufour/workbooks/Workbooks.java +++ b/src/main/java/plugins/adufour/workbooks/Workbooks.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,14 +18,19 @@ package plugins.adufour.workbooks; -import icy.plugin.abstract_.PluginActionable; -import icy.plugin.interface_.PluginThreaded; -import icy.system.thread.ThreadUtil; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.WorkbookUtil; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.bioimageanalysis.icy.extension.plugin.abstract_.PluginActionable; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginThreaded; +import org.bioimageanalysis.icy.system.logging.IcyLogger; +import org.bioimageanalysis.icy.system.thread.ThreadUtil; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import plugins.adufour.blocks.tools.io.FileToWorkbook; import plugins.adufour.blocks.tools.io.WorkbookToFile; import plugins.adufour.blocks.tools.io.WorkbookToFile.MergePolicy; @@ -45,6 +50,8 @@ import java.util.List; * * @author Alexandre Dufour */ +@IcyPluginName("Workbooks") +@IcyPluginIcon(path = "/workbooks.png") public class Workbooks extends PluginActionable implements PluginThreaded { /** * List of supported workbook formats @@ -54,7 +61,9 @@ public class Workbooks extends PluginActionable implements PluginThreaded { public enum WorkbookFormat { /** * Legacy format (compatible with Excel <= 2004). Limited to 256 columns and 65536 rows + * Do not use it anymore */ + @Deprecated(forRemoval = true) XLS, /** @@ -62,24 +71,29 @@ public class Workbooks extends PluginActionable implements PluginThreaded { */ XLSX; + @Contract(" -> new") + @NotNull Workbook createEmptyWorkbook() { - switch (this) { - case XLS: - return new HSSFWorkbook(); - case XLSX: - return new XSSFWorkbook(); - default: - throw new UnsupportedOperationException("Unknown format: " + this); - } + return switch (this) { + case XLS -> { + IcyLogger.warn(this.getClass(), "Deprecated format, please use XLSX instead."); + yield new HSSFWorkbook(); + } + case XLSX -> new XSSFWorkbook(); + //default -> throw new UnsupportedOperationException("Unknown format: " + this); + }; } - public String getExtension() { + public @NotNull String getExtension() { return '.' + name().toLowerCase(); } + @Contract(value = "null -> fail", pure = true) public static WorkbookFormat getFormat(final Workbook workbook) { - if (workbook instanceof HSSFWorkbook) + if (workbook instanceof HSSFWorkbook) { + IcyLogger.warn(WorkbookFormat.class, "Deprecated format, please use XLSX instead."); return XLS; + } if (workbook instanceof XSSFWorkbook) return XLSX; @@ -87,7 +101,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { } } - private final String defaultTitle = "Icy Workbooks v." + getDescriptor().getVersion().getMajor() + "." + getDescriptor().getVersion().getMinor(); + private final String defaultTitle = "Icy Workbooks v." + getDescriptor().getVersion().toShortString(); @Override public void run() { @@ -97,7 +111,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { /** * @return A new empty workbook using the new XLSX format. */ - public static Workbook createEmptyWorkbook() { + public static @NotNull Workbook createEmptyWorkbook() { return createEmptyWorkbook(WorkbookFormat.XLSX); } @@ -109,7 +123,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { * @see WorkbookFormat * @see WorkbookFormat#XLS */ - public static Workbook createEmptyWorkbook(final WorkbookFormat format) { + public static @NotNull Workbook createEmptyWorkbook(final @NotNull WorkbookFormat format) { final Workbook workbook = format.createEmptyWorkbook(); workbook.setMissingCellPolicy(Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); return workbook; @@ -157,6 +171,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { * @param wb input {@link Workbook} * @return The format of this workbook (see the {@link WorkbookFormat} enumeration) */ + @Contract(value = "null -> fail", pure = true) public static WorkbookFormat getFormat(final Workbook wb) { return WorkbookFormat.getFormat(wb); } @@ -175,7 +190,8 @@ public class Workbooks extends PluginActionable implements PluginThreaded { * @return the sheet with the specified name, wrapped into a {@link IcySpreadSheet} object for * simplified manipulation */ - public static IcySpreadSheet getSheet(final Workbook workbook, String sheetName) { + @Contract("_, _ -> new") + public static @NotNull IcySpreadSheet getSheet(final @NotNull Workbook workbook, String sheetName) { Sheet sheet = workbook.getSheet(sheetName); if (sheet != null) return new IcySpreadSheet(sheet); @@ -190,7 +206,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { * @param workbook input {@link Workbook} * @return a collection of sheets contained in this workbook */ - public static Collection<IcySpreadSheet> getSheets(final Workbook workbook) { + public static @NotNull Collection<IcySpreadSheet> getSheets(final @NotNull Workbook workbook) { final int nSheets = workbook.getNumberOfSheets(); final List<IcySpreadSheet> sheets = new ArrayList<>(nSheets); for (int i = 0; i < nSheets; i++) @@ -205,7 +221,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { * @param srcCell source cell * @param dstCell destination cell */ - public static void copyCell(final Cell srcCell, final Cell dstCell) { + public static void copyCell(final @NotNull Cell srcCell, final @NotNull Cell dstCell) { dstCell.setCellComment(srcCell.getCellComment()); //dstCell.setCellType(srcCell.getCellType()); switch (srcCell.getCellType()) { @@ -234,7 +250,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { * @param srcStyle source style * @param dstStyle destination style */ - public static void copyStyle(final CellStyle srcStyle, final CellStyle dstStyle) { + public static void copyStyle(final @NotNull CellStyle srcStyle, final @NotNull CellStyle dstStyle) { dstStyle.setAlignment(srcStyle.getAlignment()); dstStyle.setBorderBottom(srcStyle.getBorderBottom()); dstStyle.setBorderLeft(srcStyle.getBorderLeft()); @@ -258,7 +274,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { * @param srcFont source font * @param dstFont destination font */ - public static void copyFont(final Font srcFont, final Font dstFont) { + public static void copyFont(final @NotNull Font srcFont, final @NotNull Font dstFont) { dstFont.setBold(srcFont.getBold()); dstFont.setCharSet(srcFont.getCharSet()); dstFont.setColor(srcFont.getColor()); @@ -280,7 +296,7 @@ public class Workbooks extends PluginActionable implements PluginThreaded { * @param sheetName the name of the sheet to search for * @return <code>true</code> if the sheet exists, <code>false</code> otherwise */ - public static boolean containsSheet(final Workbook workbook, String sheetName) { + public static boolean containsSheet(final @NotNull Workbook workbook, String sheetName) { sheetName = WorkbookUtil.createSafeSheetName(sheetName); return workbook.getSheet(sheetName) != null; } diff --git a/src/main/resources/workbooks.png b/src/main/resources/workbooks.png new file mode 100644 index 0000000000000000000000000000000000000000..275debf629d721e470b2759fdae3f67251b74dc4 GIT binary patch literal 12669 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 literal 0 HcmV?d00001 -- GitLab