/******************************************************************************* * Copyright (c) 2012, 2017 Original authors and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Original authors and others - initial API and implementation * Uwe Peuker <dev@upeuker.net> - Bug 500788 ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.export; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.nebula.widgets.nattable.Messages; import org.eclipse.nebula.widgets.nattable.NatTable; import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; import org.eclipse.nebula.widgets.nattable.formula.command.DisableFormulaEvaluationCommand; import org.eclipse.nebula.widgets.nattable.formula.command.EnableFormulaEvaluationCommand; import org.eclipse.nebula.widgets.nattable.layer.ILayer; import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; import org.eclipse.nebula.widgets.nattable.print.command.PrintEntireGridCommand; import org.eclipse.nebula.widgets.nattable.print.command.TurnViewportOffCommand; import org.eclipse.nebula.widgets.nattable.print.command.TurnViewportOnCommand; import org.eclipse.nebula.widgets.nattable.resize.AutoResizeHelper; import org.eclipse.nebula.widgets.nattable.style.DisplayMode; import org.eclipse.nebula.widgets.nattable.summaryrow.command.CalculateSummaryRowValuesCommand; import org.eclipse.nebula.widgets.nattable.ui.ExceptionDialog; import org.eclipse.nebula.widgets.nattable.util.IClientAreaProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.program.Program; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.Shell; /** * This class is used to perform exports of a NatTable or {@link ILayer} in a * NatTable composition. The exporter to use can be configured via * {@link IConfigRegistry} or directly given as method parameter. * * @see ExportConfigAttributes#EXPORTER * @see ExportConfigAttributes#TABLE_EXPORTER */ public class NatExporter { private static final Log LOG = LogFactory.getLog(NatExporter.class); /** * The {@link Shell} that should be used to open sub-dialogs and perform * export operations in a background thread. * * @since 1.5 */ protected final Shell shell; /** * Flag that indicates if the created export result should be opened after * the export is finished. * * @since 1.5 */ protected boolean openResult = true; /** * Flag that indicates that the export succeeded. Used to determine whether * the export result can be opened or not. * * @since 1.5 */ protected boolean exportSucceeded = true; /** * Flag to configure whether in-memory pre-rendering is enabled or not. This * is necessary in case content painters are used that are configured for * content based auto-resizing. * * @since 1.5 */ protected boolean preRender = true; /** * Create a new {@link NatExporter}. * * @param shell * The {@link Shell} that should be used to open sub-dialogs and * perform export operations in a background thread. Can be * <code>null</code> but could lead to * {@link NullPointerException}s if {@link IExporter} are * configured, that use a {@link FileOutputStreamProvider}. */ public NatExporter(Shell shell) { this.shell = shell; } /** * Exports a single {@link ILayer} using the {@link ILayerExporter} * registered in the {@link IConfigRegistry} for the key * {@link ExportConfigAttributes#EXPORTER}. * * @param layer * The {@link ILayer} to export, usually a NatTable instance. * @param configRegistry * The {@link IConfigRegistry} of the NatTable instance to * export, that contains the necessary export configurations. */ public void exportSingleLayer( final ILayer layer, final IConfigRegistry configRegistry) { ILayerExporter exporter = configRegistry.getConfigAttribute( ExportConfigAttributes.EXPORTER, DisplayMode.NORMAL); exportSingleLayer(exporter, layer, configRegistry); } /** * Exports a single {@link ILayer} using the given {@link ILayerExporter}. * * @param exporter * The {@link ILayerExporter} to use for exporting. * @param layer * The {@link ILayer} to export, usually a NatTable instance. * @param configRegistry * The {@link IConfigRegistry} of the NatTable instance to * export, that contains the necessary export configurations. * * @since 1.5 */ public void exportSingleLayer( final ILayerExporter exporter, final ILayer layer, final IConfigRegistry configRegistry) { exportSingle(exporter, new BiConsumer<ILayerExporter, OutputStream>() { @Override public void apply(ILayerExporter exporter, OutputStream outputStream) { try { exporter.exportBegin(outputStream); exportLayer(exporter, outputStream, "", layer, configRegistry); //$NON-NLS-1$ exporter.exportEnd(outputStream); } catch (IOException e) { // exception is handled in the caller throw new RuntimeException(e); } } }); } /** * Exports a single {@link ILayer} using the {@link ILayerExporter} * registered in the {@link IConfigRegistry} for the key * {@link ExportConfigAttributes#EXPORTER}. * * @param layer * The {@link ILayer} to export, usually a NatTable instance. * @param configRegistry * The {@link IConfigRegistry} of the NatTable instance to * export, that contains the necessary export configurations. * * @since 1.5 */ public void exportSingleTable( final ILayer layer, final IConfigRegistry configRegistry) { final ITableExporter exporter = configRegistry.getConfigAttribute( ExportConfigAttributes.TABLE_EXPORTER, DisplayMode.NORMAL); exportSingleTable(exporter, layer, configRegistry); } /** * Exports a single {@link ILayer} using the given {@link ITableExporter}. * * @param exporter * The {@link ITableExporter} to use for exporting. * @param layer * The {@link ILayer} to export, usually a NatTable instance. * @param configRegistry * The {@link IConfigRegistry} of the NatTable instance to * export, that contains the necessary export configurations. * * @since 1.5 */ public void exportSingleTable( final ITableExporter exporter, final ILayer layer, final IConfigRegistry configRegistry) { exportSingle(exporter, new BiConsumer<ITableExporter, OutputStream>() { @Override public void apply(ITableExporter exporter, OutputStream outputStream) { exportLayer(exporter, outputStream, layer, configRegistry); } }); } /** * Functional interface used to specify how the export should be performed * for different exporter interface implementations. Can be removed once the * source API level is updated to Java 1.8 * * @param <T> * @param <U> */ private interface BiConsumer<T, U> { void apply(T t, U u); } /** * * @param exporter * The {@link IExporter} to use for exporting. * @param executable * The consumer implementation that is used to execute the export * and produce the output to an {@link OutputStream} */ private <T extends IExporter> void exportSingle(final T exporter, final BiConsumer<T, OutputStream> executable) { final OutputStream outputStream = getOutputStream(exporter); if (outputStream == null) { return; } Runnable exportRunnable = new Runnable() { @Override public void run() { try { executable.apply(exporter, outputStream); NatExporter.this.exportSucceeded = true; } catch (Exception e) { NatExporter.this.exportSucceeded = false; handleExportException(e); } finally { try { outputStream.close(); } catch (IOException e) { LOG.error("Failed to close the output stream", e); //$NON-NLS-1$ } } openExport(exporter); } }; if (this.shell != null) { // Run with the SWT display so that the progress bar can paint this.shell.getDisplay().asyncExec(exportRunnable); } else { // execute in the current thread exportRunnable.run(); } } /** * Export multiple NatTable instances to one file by using the given * ILayerExporter. * * @param exporter * The ILayerExporter to use for exporting. * @param natTablesMap * The NatTable instances to export. They keys in the map will be * used as sheet titles while the values are the instances to * export. */ public void exportMultipleNatTables( final ILayerExporter exporter, final Map<String, NatTable> natTablesMap) { exportMultipleNatTables(exporter, natTablesMap, false, null); } /** * Export multiple NatTable instances to one file by using the given * ILayerExporter. * * @param exporter * The ILayerExporter to use for exporting. * @param natTablesMap * The NatTable instances to export. They keys in the map will be * used as sheet titles while the values are the instances to * export. * @param exportOnSameSheet * Flag to configure whether multiple NatTable instances should * be exported on the same sheet or not. * @param sheetName * The sheet name that should be used in case of exporting * multiple NatTables on a single sheet. * @since 1.5 */ public void exportMultipleNatTables( final ILayerExporter exporter, final Map<String, NatTable> natTablesMap, final boolean exportOnSameSheet, final String sheetName) { final OutputStream outputStream = getOutputStream(exporter); if (outputStream == null) { return; } Runnable exportRunnable = new Runnable() { @Override public void run() { try { exporter.exportBegin(outputStream); if (exportOnSameSheet) { exporter.exportLayerBegin(outputStream, sheetName); } for (String name : natTablesMap.keySet()) { NatTable natTable = natTablesMap.get(name); exportLayer(exporter, outputStream, name, natTable, natTable.getConfigRegistry(), !exportOnSameSheet); } if (exportOnSameSheet) { exporter.exportLayerEnd(outputStream, sheetName); } exporter.exportEnd(outputStream); NatExporter.this.exportSucceeded = true; } catch (Exception e) { NatExporter.this.exportSucceeded = false; handleExportException(e); } finally { try { outputStream.close(); } catch (IOException e) { LOG.error("Failed to close the output stream", e); //$NON-NLS-1$ } } openExport(exporter); } }; if (this.shell != null) { // Run with the SWT display so that the progress bar can paint this.shell.getDisplay().asyncExec(exportRunnable); } else { exportRunnable.run(); } } /** * Exports the given layer to the outputStream using the provided exporter. * The {@link ILayerExporter#exportBegin(OutputStream)} method should be * called before this method is invoked, and * {@link ILayerExporter#exportEnd(OutputStream)} should be called after * this method returns. If multiple layers are being exported as part of a * single logical export operation, then * {@link ILayerExporter#exportBegin(OutputStream)} will be called once at * the very beginning, followed by n calls to this method, and finally * followed by {@link ILayerExporter#exportEnd(OutputStream)}. * * <p> * <b>Note:</b> This method calls * {@link #exportLayer(ILayerExporter, OutputStream, String, ILayer, IConfigRegistry, boolean)} * with the parameter <i>initExportLayer</i> set to <code>true</code>. * </p> * * @param exporter * The {@link ILayerExporter} that should be used for exporting. * @param outputStream * The {@link OutputStream} that should be used to write the * export to. * @param layerName * The name that should be set as sheet name of the export. * @param layer * The {@link ILayer} that should be exported. * @param configRegistry * The {@link IConfigRegistry} needed to retrieve the export * configurations. */ protected void exportLayer( final ILayerExporter exporter, final OutputStream outputStream, final String layerName, final ILayer layer, final IConfigRegistry configRegistry) { exportLayer(exporter, outputStream, layerName, layer, configRegistry, true); } /** * Exports the given layer to the outputStream using the provided exporter. * The {@link ILayerExporter#exportBegin(OutputStream)} method should be * called before this method is invoked, and * {@link ILayerExporter#exportEnd(OutputStream)} should be called after * this method returns. If multiple layers are being exported as part of a * single logical export operation, then * {@link ILayerExporter#exportBegin(OutputStream)} will be called once at * the very beginning, followed by n calls to this method, and finally * followed by {@link ILayerExporter#exportEnd(OutputStream)}. * * @param exporter * The {@link ILayerExporter} that should be used for exporting. * @param outputStream * The {@link OutputStream} that should be used to write the * export to. * @param layerName * The name that should be set as sheet name of the export. * @param layer * The {@link ILayer} that should be exported. * @param configRegistry * The {@link IConfigRegistry} needed to retrieve the export * configurations. * @param initExportLayer * flag to configure whether * {@link ILayerExporter#exportLayerBegin(OutputStream, String)} * and * {@link ILayerExporter#exportLayerEnd(OutputStream, String)} * should be called or not. Should be set to <code>true</code> if * multiple NatTable instances should be exported on the same * sheet. * @since 1.5 */ protected void exportLayer( final ILayerExporter exporter, final OutputStream outputStream, final String layerName, final ILayer layer, final IConfigRegistry configRegistry, final boolean initExportLayer) { exportLayer(new ITableExporter() { @Override public void exportTable(Shell shell, ProgressBar progressBar, OutputStream outputStream, ILayer layer, IConfigRegistry configRegistry) throws IOException { if (initExportLayer) { exporter.exportLayerBegin(outputStream, layerName); } for (int rowPosition = 0; rowPosition < layer.getRowCount(); rowPosition++) { exporter.exportRowBegin(outputStream, rowPosition); if (progressBar != null) { progressBar.setSelection(rowPosition); } for (int columnPosition = 0; columnPosition < layer.getColumnCount(); columnPosition++) { ILayerCell cell = layer.getCellByPosition(columnPosition, rowPosition); IExportFormatter exportFormatter = configRegistry.getConfigAttribute( ExportConfigAttributes.EXPORT_FORMATTER, cell.getDisplayMode(), cell.getConfigLabels().getLabels()); Object exportDisplayValue = exportFormatter.formatForExport(cell, configRegistry); exporter.exportCell(outputStream, exportDisplayValue, cell, configRegistry); } exporter.exportRowEnd(outputStream, rowPosition); } if (initExportLayer) { exporter.exportLayerEnd(outputStream, layerName); } } @Override public OutputStream getOutputStream(Shell shell) { return exporter.getOutputStream(shell); } @Override public Object getResult() { return exporter.getResult(); } }, outputStream, layer, configRegistry); } /** * Exports the given {@link ILayer} to the given {@link OutputStream} using * the provided {@link ITableExporter}. * * @param exporter * The {@link ITableExporter} that should be used for exporting. * @param outputStream * The {@link OutputStream} that should be used to write the * export to. * @param layer * The {@link ILayer} that should be exported. * @param configRegistry * The {@link IConfigRegistry} needed to retrieve the export * configurations. * * @since 1.5 */ protected void exportLayer( final ITableExporter exporter, final OutputStream outputStream, final ILayer layer, final IConfigRegistry configRegistry) { if (this.preRender) { AutoResizeHelper.autoResize(layer, configRegistry); } IClientAreaProvider originalClientAreaProvider = layer.getClientAreaProvider(); // This needs to be done so that the layer can return all the cells // not just the ones visible in the viewport layer.doCommand(new TurnViewportOffCommand()); setClientAreaToMaximum(layer); // if a SummaryRowLayer is in the layer stack, we need to ensure that // the values are calculated layer.doCommand(new CalculateSummaryRowValuesCommand()); // if a FormulaDataProvider is involved, we need to ensure that the // formula evaluation is disabled so the formula itself is exported // instead of the calculated value layer.doCommand(new DisableFormulaEvaluationCommand()); ProgressBar progressBar = null; if (this.shell != null) { Shell childShell = new Shell(this.shell.getDisplay(), SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); childShell.setText(Messages.getString("NatExporter.exporting")); //$NON-NLS-1$ int endRow = layer.getRowCount() - 1; progressBar = new ProgressBar(childShell, SWT.SMOOTH); progressBar.setMinimum(0); progressBar.setMaximum(endRow); progressBar.setBounds(0, 0, 400, 25); progressBar.setFocus(); childShell.pack(); childShell.open(); } try { exporter.exportTable(this.shell, progressBar, outputStream, layer, configRegistry); } catch (Exception e) { throw new RuntimeException(e); } finally { // These must be fired at the end of the thread execution layer.setClientAreaProvider(originalClientAreaProvider); layer.doCommand(new TurnViewportOnCommand()); layer.doCommand(new EnableFormulaEvaluationCommand()); if (progressBar != null) { Shell childShell = progressBar.getShell(); progressBar.dispose(); childShell.dispose(); } } } /** * Increase the client area so it can include the whole {@link ILayer}. * * @param layer * The {@link ILayer} for which the client area should be * maximized. * * @since 1.5 */ protected void setClientAreaToMaximum(ILayer layer) { final Rectangle maxClientArea = new Rectangle(0, 0, layer.getWidth(), layer.getHeight()); layer.setClientAreaProvider(new IClientAreaProvider() { @Override public Rectangle getClientArea() { return maxClientArea; } }); layer.doCommand(new PrintEntireGridCommand()); } /** * Open the export result in the matching application. * * @param exporter * The {@link IExporter} that was used to perform the export. * Needed to access the export result. * * @since 1.5 */ protected void openExport(IExporter exporter) { if (this.exportSucceeded && this.openResult && exporter.getResult() != null && exporter.getResult() instanceof File) { Program.launch(((File) exporter.getResult()).getAbsolutePath()); } } /** * Sets the behavior after finishing the export. * * The default is opening the created export file with the associated * application. You can prevent the opening by setting openResult to * <code>false</code>. * * @param openResult * set to <code>true</code> to open the created export file, * <code>false</code> otherwise * * @since 1.5 */ public void setOpenResult(boolean openResult) { this.openResult = openResult; } /** * Method that is used to retrieve the {@link OutputStream} to write the * export to in a safe way. Any occurring exception will be handled inside. * * @param exporter * The {@link ILayerExporter} that should be used * @return The {@link OutputStream} that is used to write the export to or * <code>null</code> if an error occurs. * * @since 1.5 */ protected OutputStream getOutputStream(IExporter exporter) { OutputStream outputStream = null; try { outputStream = exporter.getOutputStream(this.shell); } catch (Exception e) { handleExportException(e); } return outputStream; } /** * Method that is used to handle exceptions that are raised while processing * the export. * * @param e * The exception that should be handled. * @since 1.5 */ protected void handleExportException(Exception e) { LOG.error("Failed to export.", e); //$NON-NLS-1$ ExceptionDialog.open( this.shell, Messages.getString("ErrorDialog.title"), //$NON-NLS-1$ Messages.getString("NatExporter.errorMessagePrefix", e.getLocalizedMessage()), //$NON-NLS-1$ e); } /** * Enable in-memory pre-rendering. This is necessary in case content * painters are used that are configured for content based auto-resizing. * * @since 1.5 */ public void enablePreRendering() { this.preRender = true; } /** * Disable in-memory pre-rendering. You should consider to disable * pre-rendering if no content painters are used that are configured for * content based auto-resizing. * * @since 1.5 */ public void disablePreRendering() { this.preRender = false; } }