blob: 2dab5b2bf6bf281e1f6efd4a99b72be362acbc8c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2020 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;
/**
* Flag to configure whether the export should be performed asynchronously
* or synchronously. By default this flag is set to <code>true</code> and
* the decision whether the execution should be performed synchronously or
* not is made based on whether a {@link Shell} is set or not. If a
* {@link Shell} is set and this flag is set to <code>false</code> the
* execution is performed synchronously.
*
* @since 1.6
*/
private boolean runAsynchronously = 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, false);
}
/**
* 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}.
* @param executeSynchronously
* Configure whether the export should be performed
* asynchronously or synchronously. By default the decision
* whether the execution should be performed synchronously or not
* is made based on whether a {@link Shell} is set or not. If a
* {@link Shell} is set and this flag is set to <code>true</code>
* the execution is performed synchronously.
*
* @since 1.6
*/
public NatExporter(Shell shell, boolean executeSynchronously) {
this.shell = shell;
this.runAsynchronously = !executeSynchronously;
}
/**
* 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) {
Runnable exportRunnable = new Runnable() {
@Override
public void run() {
final OutputStream outputStream = getOutputStream(exporter);
if (outputStream != null) {
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
if (this.runAsynchronously) {
this.shell.getDisplay().asyncExec(exportRunnable);
} else {
this.shell.getDisplay().syncExec(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) {
Runnable exportRunnable = new Runnable() {
@Override
public void run() {
final OutputStream outputStream = getOutputStream(exporter);
if (outputStream != null) {
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
if (this.runAsynchronously) {
this.shell.getDisplay().asyncExec(exportRunnable);
} else {
this.shell.getDisplay().syncExec(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);
}
int layerHeight = layer.getHeight();
for (int rowPosition = 0; rowPosition < layer.getRowCount(); rowPosition++) {
if (layer.getRowHeightByPosition(rowPosition) > 0
&& layer.getStartYOfRowPosition(rowPosition) < layerHeight) {
exporter.exportRowBegin(outputStream, rowPosition);
if (progressBar != null) {
progressBar.setSelection(rowPosition);
}
for (int columnPosition = 0; columnPosition < layer.getColumnCount(); columnPosition++) {
ILayerCell cell = layer.getCellByPosition(columnPosition, rowPosition);
if (cell != null) {
IExportFormatter exportFormatter = configRegistry.getConfigAttribute(
ExportConfigAttributes.EXPORT_FORMATTER,
cell.getDisplayMode(),
cell.getConfigLabels());
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;
}
}