Merge pull request #6795 from gdicristofaro/7368-dssSpecialFormat

7368 dss special format
This commit is contained in:
Richard Cordovano 2021-03-19 09:21:20 -04:00 committed by GitHub
commit 5edbf07d7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1399 additions and 273 deletions

View File

@ -47,7 +47,7 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.EventUpdateHandler;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.DefaultMenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;

View File

@ -6,7 +6,21 @@ AnalysisPanel_keywordHits_tabName=Keyword Hits
AnalysisPanel_keywordSearchModuleName=Keyword Search
BaseDataSourceSummaryPanel_goToArtifact=View Source Result
BaseDataSourceSummaryPanel_goToFile=View Source File in Directory
ContainerPanel_export_acquisitionDetails=Acquisition Details:
ContainerPanel_export_deviceId=Device ID:
ContainerPanel_export_displayName=Display Name:
ContainerPanel_export_filePaths=File Paths:
ContainerPanel_export_imageType=Image Type:
ContainerPanel_export_md5=MD5:
ContainerPanel_export_originalName=Name:
ContainerPanel_export_sectorSize=Sector Size:
ContainerPanel_export_sha1=SHA1:
ContainerPanel_export_sha256=SHA256:
ContainerPanel_export_size=Size:
ContainerPanel_export_timeZone=Time Zone:
ContainerPanel_export_unallocatedSize=Unallocated Space:
ContainerPanel_setFieldsForNonImageDataSource_na=N/A
ContainerPanel_tabName=Container
CTL_DataSourceSummaryAction=Data Source Summary
DataSourceSummaryDialog.closeButton.text=Close
ContainerPanel.displayNameLabel.text=Display Name:
@ -73,6 +87,12 @@ GeolocationPanel_mostCommon_tabName=Most Common Cities
GeolocationPanel_mostRecent_tabName=Most Recent Cities
GeolocationPanel_onNoCrIngest_message=No results will be shown because the GPX Parser was not run.
GeolocationPanel_unknownRow_title=Unknown
IngestJobExcelExport_endTimeColumn=End Time
IngestJobExcelExport_ingestStatusTimeColumn=Ingest Status
IngestJobExcelExport_moduleNameTimeColumn=Module Name
IngestJobExcelExport_sheetName=Ingest History
IngestJobExcelExport_startTimeColumn=Start Time
IngestJobExcelExport_versionColumn=Module Version
PastCasesPanel_caseColumn_title=Case
PastCasesPanel_countColumn_title=Count
PastCasesPanel_notableFileTable_tabName=Cases with Common Notable
@ -87,18 +107,19 @@ RecentFilesPanel_attachmentsTable_tabName=Recent Attachments
RecentFilesPanel_col_head_date=Date
RecentFilesPanel_docsTable_tabName=Recently Opened Documents
RecentFilesPanel_downloadsTable_tabName=Recently Downloads
SizeRepresentationUtil_units_bytes=\ bytes
SizeRepresentationUtil_units_gigabytes=\ GB
SizeRepresentationUtil_units_kilobytes=\ kB
SizeRepresentationUtil_units_megabytes=\ MB
SizeRepresentationUtil_units_petabytes=\ PB
SizeRepresentationUtil_units_terabytes=\ TB
SizeRepresentationUtil_units_bytes=bytes
SizeRepresentationUtil_units_gigabytes=GB
SizeRepresentationUtil_units_kilobytes=KB
SizeRepresentationUtil_units_megabytes=MB
SizeRepresentationUtil_units_petabytes=PB
SizeRepresentationUtil_units_terabytes=TB
TimelinePanel_earliestLabel_title=Earliest
TimelinePanel_latestLabel_title=Latest
TimlinePanel_last30DaysChart_artifactEvts_title=Result Events
TimlinePanel_last30DaysChart_fileEvts_title=File Events
TimlinePanel_last30DaysChart_title=Last 30 Days
TypesPanel_artifactsTypesPieChart_title=Artifact Types
TypesPanel_excelTabName=Types
TypesPanel_fileMimeTypesChart_audio_title=Audio
TypesPanel_fileMimeTypesChart_documents_title=Documents
TypesPanel_fileMimeTypesChart_executables_title=Executables

View File

@ -19,21 +19,35 @@
package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.beans.PropertyChangeEvent;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.table.DefaultTableModel;
import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultUpdateGovernor;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ExcelItemExportable;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.KeyValueItemExportable;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.SingleCellExportable;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.TitledExportable;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Image;
@ -42,39 +56,184 @@ import org.sleuthkit.datamodel.TskCoreException;
/**
* Panel to display additional details associated with a specific DataSource
*/
@Messages({
"ContainerPanel_tabName=Container"
})
class ContainerPanel extends BaseDataSourceSummaryPanel {
/**
* Data payload for the Container panel.
* View model data for data source images.
*/
private static class ContainerPanelData {
private static class ImageViewModel {
private final DataSource dataSource;
private final Long unallocatedFilesSize;
private final long unallocatedSize;
private final long size;
private final long sectorSize;
private final String timeZone;
private final String imageType;
private final List<String> paths;
private final String md5Hash;
private final String sha1Hash;
private final String sha256Hash;
/**
* Main constructor.
*
* @param dataSource The original datasource.
* @param unallocatedFilesSize The unallocated file size.
* @param unallocatedSize Size in bytes of unallocated space.
* @param size Total size in bytes.
* @param sectorSize Sector size in bytes.
* @param timeZone The time zone.
* @param imageType The type of image.
* @param paths The source paths for the image.
* @param md5Hash The md5 hash or null.
* @param sha1Hash The sha1 hash or null.
* @param sha256Hash The sha256 hash or null.
*/
ContainerPanelData(DataSource dataSource, Long unallocatedFilesSize) {
this.dataSource = dataSource;
this.unallocatedFilesSize = unallocatedFilesSize;
ImageViewModel(long unallocatedSize, long size, long sectorSize,
String timeZone, String imageType, List<String> paths, String md5Hash,
String sha1Hash, String sha256Hash) {
this.unallocatedSize = unallocatedSize;
this.size = size;
this.sectorSize = sectorSize;
this.timeZone = timeZone;
this.imageType = imageType;
this.paths = paths == null ? Collections.emptyList() : new ArrayList<>(paths);
this.md5Hash = md5Hash;
this.sha1Hash = sha1Hash;
this.sha256Hash = sha256Hash;
}
/**
* @return The original datasource.
* @return Size in bytes of unallocated space.
*/
DataSource getDataSource() {
return dataSource;
long getUnallocatedSize() {
return unallocatedSize;
}
/**
* @return The unallocated file size.
* @return Total size in bytes.
*/
Long getUnallocatedFilesSize() {
return unallocatedFilesSize;
long getSize() {
return size;
}
/**
* @return Sector size in bytes.
*/
long getSectorSize() {
return sectorSize;
}
/**
* @return The time zone.
*/
String getTimeZone() {
return timeZone;
}
/**
* @return The type of image.
*/
String getImageType() {
return imageType;
}
/**
* @return The source paths for the image.
*/
List<String> getPaths() {
return paths;
}
/**
* @return The md5 hash or null.
*/
String getMd5Hash() {
return md5Hash;
}
/**
* @return The sha1 hash or null.
*/
String getSha1Hash() {
return sha1Hash;
}
/**
* @return The sha256 hash or null.
*/
String getSha256Hash() {
return sha256Hash;
}
}
/**
* View model for container data.
*/
private static class ContainerViewModel {
private final String displayName;
private final String originalName;
private final String deviceIdValue;
private final String acquisitionDetails;
private final ImageViewModel imageViewModel;
/**
* Main constructor.
*
* @param displayName The display name for this data source.
* @param originalName The original name for this data source.
* @param deviceIdValue The device id value for this data source.
* @param acquisitionDetails The acquisition details for this data
* source or null.
* @param imageViewModel If the data source is an image, the image view
* model for this data source or null if non-image.
*/
ContainerViewModel(String displayName, String originalName, String deviceIdValue,
String acquisitionDetails, ImageViewModel imageViewModel) {
this.displayName = displayName;
this.originalName = originalName;
this.deviceIdValue = deviceIdValue;
this.acquisitionDetails = acquisitionDetails;
this.imageViewModel = imageViewModel;
}
/**
* @return The display name for this data source.
*/
String getDisplayName() {
return displayName;
}
/**
* @return The original name for this data source.
*/
String getOriginalName() {
return originalName;
}
/**
* @return The device id value for this data source.
*/
String getDeviceId() {
return deviceIdValue;
}
/**
* @return The acquisition details for this data source or null.
*/
String getAcquisitionDetails() {
return acquisitionDetails;
}
/**
* @return If the data source is an image, the image view model for this
* data source or null if non-image.
*/
ImageViewModel getImageViewModel() {
return imageViewModel;
}
}
@ -103,6 +262,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
private static final Logger logger = Logger.getLogger(ContainerPanel.class.getName());
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private final DataFetcher<DataSource, ContainerViewModel> containerDataFetcher;
/**
* Creates a new form ContainerPanel.
@ -117,21 +277,15 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
ContainerPanel(ContainerSummary containerSummary) {
super(containerSummary, CONTAINER_UPDATES);
containerDataFetcher = (dataSource) -> getContainerViewModel(containerSummary, dataSource);
dataFetchComponents = Arrays.asList(
new DataFetchComponents<>(
(dataSource) -> {
return new ContainerPanelData(
dataSource,
containerSummary.getSizeOfUnallocatedFiles(dataSource)
);
},
containerDataFetcher,
(result) -> {
if (result != null && result.getResultType() == ResultType.SUCCESS) {
ContainerPanelData data = result.getData();
DataSource dataSource = (data == null) ? null : data.getDataSource();
Long unallocatedFileSize = (data == null) ? null : data.getUnallocatedFilesSize();
updateDetailsPanelData(dataSource, unallocatedFileSize);
ContainerViewModel data = result.getData();
updateDetailsPanelData(data);
} else {
if (result == null) {
logger.log(Level.WARNING, "No data fetch result was provided to the ContainerPanel.");
@ -139,8 +293,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
logger.log(Level.WARNING, "An exception occurred while attempting to fetch data for the ContainerPanel.",
result.getException());
}
updateDetailsPanelData(null, null);
updateDetailsPanelData(null);
}
}
)
@ -161,33 +314,113 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
}
/**
* Update which DataSource this panel should display details about
*
* @param selectedDataSource the DataSource to display details about.
* A means of retrieving data that could potentially throw an exception.
*/
private void updateDetailsPanelData(DataSource selectedDataSource, Long unallocatedFilesSize) {
private interface Retriever<O> {
/**
* Retrieves data of a certain type and possibly throws an exception.
*
* @return The data type.
* @throws TskCoreException
* @throws SleuthkitCaseProviderException
* @throws SQLException
*/
O retrieve() throws TskCoreException, SleuthkitCaseProviderException, SQLException;
}
/**
* Retrieves data of a particular type and handles any exceptions that may
* be thrown by logging.
*
* @param retriever The retrieving function.
* @return The retrieved data.
*/
private static <O> O retrieve(Retriever<O> retriever) {
try {
return retriever.retrieve();
} catch (TskCoreException | SleuthkitCaseProviderException | SQLException ex) {
logger.log(Level.WARNING, "Error while retrieving data.", ex);
return null;
}
}
/**
* Generates a container view model object containing data to display about
* the data source.
*
* @param containerSummary The service providing data about the data source.
* @param ds The data source.
* @return The generated view model.
*/
private static ContainerViewModel getContainerViewModel(ContainerSummary containerSummary, DataSource ds) {
if (ds == null) {
return null;
}
return new ContainerViewModel(
ds.getName(),
ds.getName(),
ds.getDeviceId(),
retrieve(() -> ds.getAcquisitionDetails()),
ds instanceof Image ? getImageViewModel(containerSummary, (Image) ds) : null
);
}
/**
* Generates an image view model object containing data to display about the
* image.
*
* @param containerSummary The service providing data about the image.
* @param image The image.
* @return The generated view model.
*/
private static ImageViewModel getImageViewModel(ContainerSummary containerSummary, Image image) {
if (image == null) {
return null;
}
Long unallocSize = retrieve(() -> containerSummary.getSizeOfUnallocatedFiles(image));
String imageType = image.getType().getName();
Long size = image.getSize();
Long sectorSize = image.getSsize();
String timeZone = image.getTimeZone();
List<String> paths = image.getPaths() == null ? Collections.emptyList() : Arrays.asList(image.getPaths());
String md5 = retrieve(() -> image.getMd5());
String sha1 = retrieve(() -> image.getSha1());
String sha256 = retrieve(() -> image.getSha256());
return new ImageViewModel(unallocSize, size, sectorSize, timeZone, imageType, paths, md5, sha1, sha256);
}
/**
* Update the swing components with fetched data.
*
* @param viewModel The data source view model data.
*/
private void updateDetailsPanelData(ContainerViewModel viewModel) {
clearTableValues();
if (selectedDataSource != null) {
displayNameValue.setText(selectedDataSource.getName());
originalNameValue.setText(selectedDataSource.getName());
deviceIdValue.setText(selectedDataSource.getDeviceId());
if (viewModel == null) {
return;
}
try {
acquisitionDetailsTextArea.setText(selectedDataSource.getAcquisitionDetails());
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get acquisition details for selected data source", ex);
}
displayNameValue.setText(viewModel.getDisplayName());
originalNameValue.setText(viewModel.getOriginalName());
deviceIdValue.setText(viewModel.getDeviceId());
acquisitionDetailsTextArea.setText(viewModel.getAcquisitionDetails());
if (selectedDataSource instanceof Image) {
setFieldsForImage((Image) selectedDataSource, unallocatedFilesSize);
} else {
setFieldsForNonImageDataSource();
}
if (viewModel.getImageViewModel() != null) {
setFieldsForImage(viewModel.getImageViewModel());
} else {
setFieldsForNonImageDataSource();
}
this.repaint();
}
/**
* Sets image-only fields to N/A.
*/
@Messages({
"ContainerPanel_setFieldsForNonImageDataSource_na=N/A"
})
@ -208,54 +441,24 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
}
/**
* Sets text fields for an image. This should be called after
* clearTableValues and before updateFieldVisibility to ensure the proper
* rendering.
* Sets fields for images.
*
* @param selectedImage The selected image.
* @param unallocatedFilesSize Unallocated file size in bytes.
* @param viewModel The image view model data.
*/
private void setFieldsForImage(Image selectedImage, Long unallocatedFilesSize) {
unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(unallocatedFilesSize));
imageTypeValue.setText(selectedImage.getType().getName());
sizeValue.setText(SizeRepresentationUtil.getSizeString(selectedImage.getSize()));
sectorSizeValue.setText(SizeRepresentationUtil.getSizeString(selectedImage.getSsize()));
timeZoneValue.setText(selectedImage.getTimeZone());
private void setFieldsForImage(ImageViewModel viewModel) {
unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(viewModel.getUnallocatedSize()));
imageTypeValue.setText(viewModel.getImageType());
sizeValue.setText(SizeRepresentationUtil.getSizeString(viewModel.getSize()));
sectorSizeValue.setText(SizeRepresentationUtil.getSizeString(viewModel.getSectorSize()));
timeZoneValue.setText(viewModel.getTimeZone());
for (String path : selectedImage.getPaths()) {
for (String path : viewModel.getPaths()) {
((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path});
}
try {
//older databases may have null as the hash values
String md5String = selectedImage.getMd5();
if (md5String == null) {
md5String = "";
}
md5HashValue.setText(md5String);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get MD5 for selected data source", ex);
}
try {
String sha1String = selectedImage.getSha1();
if (sha1String == null) {
sha1String = "";
}
sha1HashValue.setText(sha1String);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get SHA1 for selected data source", ex);
}
try {
String sha256String = selectedImage.getSha256();
if (sha256String == null) {
sha256String = "";
}
sha256HashValue.setText(sha256String);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get SHA256 for selected data source", ex);
}
md5HashValue.setText(viewModel.getMd5Hash());
sha1HashValue.setText(viewModel.getSha1Hash());
sha256HashValue.setText(viewModel.getSha256Hash());
}
/**
@ -277,9 +480,82 @@ class ContainerPanel extends BaseDataSourceSummaryPanel {
((DefaultTableModel) filePathsTable.getModel()).setRowCount(0);
}
/**
* Divides acquisition details into key/value pairs to be displayed in
* separate cells in an excel export.
*
* @param acquisitionDetails The acquisition details.
* @return The list of key value pairs that can be incorporated into the
* excel export.
*/
private static List<? extends ExcelItemExportable> getAcquisitionDetails(String acquisitionDetails) {
if (StringUtils.isBlank(acquisitionDetails)) {
return Collections.emptyList();
} else {
return Stream.of(acquisitionDetails.split("\\r?\\n"))
.map((line) -> (StringUtils.isBlank(line)) ? null : new SingleCellExportable(line))
.filter(item -> item != null)
.collect(Collectors.toList());
}
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource ds) {
return Collections.emptyList();
@Messages({
"ContainerPanel_export_displayName=Display Name:",
"ContainerPanel_export_originalName=Name:",
"ContainerPanel_export_deviceId=Device ID:",
"ContainerPanel_export_timeZone=Time Zone:",
"ContainerPanel_export_acquisitionDetails=Acquisition Details:",
"ContainerPanel_export_imageType=Image Type:",
"ContainerPanel_export_size=Size:",
"ContainerPanel_export_sectorSize=Sector Size:",
"ContainerPanel_export_md5=MD5:",
"ContainerPanel_export_sha1=SHA1:",
"ContainerPanel_export_sha256=SHA256:",
"ContainerPanel_export_unallocatedSize=Unallocated Space:",
"ContainerPanel_export_filePaths=File Paths:",})
protected List<ExcelSheetExport> getExports(DataSource ds) {
ContainerViewModel result = getFetchResult(containerDataFetcher, "Container sheets", ds);
if (ds == null || result == null) {
return Collections.emptyList();
}
String NA = Bundle.ContainerPanel_setFieldsForNonImageDataSource_na();
DefaultCellModel<?> NACell = new DefaultCellModel<>(NA);
ImageViewModel imageModel = result.getImageViewModel();
boolean hasImage = imageModel != null;
DefaultCellModel<?> timeZone = hasImage ? new DefaultCellModel<>(imageModel.getTimeZone()) : NACell;
DefaultCellModel<?> imageType = hasImage ? new DefaultCellModel<>(imageModel.getImageType()) : NACell;
DefaultCellModel<?> size = hasImage ? SizeRepresentationUtil.getBytesCell(imageModel.getSize()) : NACell;
DefaultCellModel<?> sectorSize = hasImage ? SizeRepresentationUtil.getBytesCell(imageModel.getSectorSize()) : NACell;
DefaultCellModel<?> md5 = hasImage ? new DefaultCellModel<>(imageModel.getMd5Hash()) : NACell;
DefaultCellModel<?> sha1 = hasImage ? new DefaultCellModel<>(imageModel.getSha1Hash()) : NACell;
DefaultCellModel<?> sha256 = hasImage ? new DefaultCellModel<>(imageModel.getSha256Hash()) : NACell;
DefaultCellModel<?> unallocatedSize = hasImage ? SizeRepresentationUtil.getBytesCell(imageModel.getUnallocatedSize()) : NACell;
List<String> paths = result.getImageViewModel() == null ? Collections.singletonList(NA) : result.getImageViewModel().getPaths();
List<SingleCellExportable> cellPaths = paths.stream()
.map(SingleCellExportable::new)
.collect(Collectors.toList());
return Arrays.asList(
new ExcelSpecialFormatExport(Bundle.ContainerPanel_tabName(), Arrays.asList(
new KeyValueItemExportable(Bundle.ContainerPanel_export_displayName(), new DefaultCellModel<>(result.getDisplayName())),
new KeyValueItemExportable(Bundle.ContainerPanel_export_originalName(), new DefaultCellModel<>(result.getOriginalName())),
new KeyValueItemExportable(Bundle.ContainerPanel_export_deviceId(), new DefaultCellModel<>(result.getDeviceId())),
new KeyValueItemExportable(Bundle.ContainerPanel_export_timeZone(), timeZone),
new TitledExportable(Bundle.ContainerPanel_export_acquisitionDetails(), getAcquisitionDetails(result.getAcquisitionDetails())),
new KeyValueItemExportable(Bundle.ContainerPanel_export_imageType(), imageType),
new KeyValueItemExportable(Bundle.ContainerPanel_export_size(), size),
new KeyValueItemExportable(Bundle.ContainerPanel_export_sectorSize(), sectorSize),
new KeyValueItemExportable(Bundle.ContainerPanel_export_md5(), md5),
new KeyValueItemExportable(Bundle.ContainerPanel_export_sha1(), sha1),
new KeyValueItemExportable(Bundle.ContainerPanel_export_sha256(), sha256),
new KeyValueItemExportable(Bundle.ContainerPanel_export_unallocatedSize(), unallocatedSize),
new TitledExportable(Bundle.ContainerPanel_export_filePaths(), cellPaths)
)));
}
/**

View File

@ -68,7 +68,6 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
*
* @param tabTitle The title of the tab.
* @param panel The component to be displayed in the tab.
* @param notifyParentClose Notifies parent to trigger a close.
*/
DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) {
this(tabTitle, panel, panel::setDataSource, panel::getExports, panel::close);
@ -138,10 +137,10 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
private Runnable notifyParentClose = null;
private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel();
// create an export panel whose button triggers the export to XLSX action
private final ExportPanel exportPanel = new ExportPanel();
private final List<DataSourceTab> tabs = Arrays.asList(
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_typesTab_title(), new TypesPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_userActivityTab_title(), new UserActivityPanel()),
@ -155,7 +154,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(),
ingestHistoryPanel,
ingestHistoryPanel::setDataSource,
null,
IngestJobExcelExport::getExports,
null),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_detailsTab_title(), new ContainerPanel()),
new DataSourceTab(
@ -165,10 +164,10 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
null,
null)
);
// the action that does the export
private final ExcelExportAction exportAction = new ExcelExportAction(tabs);
private DataSource dataSource = null;
private CardLayout cardLayout;
@ -222,7 +221,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
// set this to no datasource initially
cardLayout.show(this, NO_DATASOURCE_PANE);
// set action for when user requests xlsx export
exportPanel.setXlsxExportAction(() -> exportAction.accept(getDataSource()));
}
@ -270,7 +269,6 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), caseEventsListener);
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always

View File

@ -0,0 +1,253 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ColumnModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.IngestModuleInfo;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Class that handles exporting information in IngestJobInfoPanel to excel.
*/
@Messages({
"IngestJobExcelExport_startTimeColumn=Start Time",
"IngestJobExcelExport_endTimeColumn=End Time",
"IngestJobExcelExport_ingestStatusTimeColumn=Ingest Status",
"IngestJobExcelExport_moduleNameTimeColumn=Module Name",
"IngestJobExcelExport_versionColumn=Module Version",
"IngestJobExcelExport_sheetName=Ingest History"
})
class IngestJobExcelExport {
/**
* An entry to display in an excel export.
*/
private static class IngestJobEntry {
private final Date startTime;
private final Date endTime;
private final String status;
private final String ingestModule;
private final String ingestModuleVersion;
/**
* Main constructor.
*
* @param startTime The ingest start time.
* @param endTime The ingest stop time.
* @param status The ingest status.
* @param ingestModule The ingest module.
* @param ingestModuleVersion The ingest module version.
*/
IngestJobEntry(Date startTime, Date endTime, String status, String ingestModule, String ingestModuleVersion) {
this.startTime = startTime;
this.endTime = endTime;
this.status = status;
this.ingestModule = ingestModule;
this.ingestModuleVersion = ingestModuleVersion;
}
/**
* @return The ingest start time.
*/
Date getStartTime() {
return startTime;
}
/**
* @return The ingest stop time.
*/
Date getEndTime() {
return endTime;
}
/**
* @return The ingest status.
*/
String getStatus() {
return status;
}
/**
* @return The ingest module.
*/
String getIngestModule() {
return ingestModule;
}
/**
* @return The ingest module version.
*/
String getIngestModuleVersion() {
return ingestModuleVersion;
}
}
private static final Logger logger = Logger.getLogger(IngestJobExcelExport.class.getName());
private static final String DATETIME_FORMAT_STR = "yyyy/MM/dd HH:mm:ss";
private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat(DATETIME_FORMAT_STR, Locale.getDefault());
// columns in the excel export table to be created.
private static final List<ColumnModel<IngestJobEntry, DefaultCellModel<?>>> COLUMNS = Arrays.asList(
new ColumnModel<>(
Bundle.IngestJobExcelExport_startTimeColumn(),
(entry) -> getDateCell(entry.getStartTime())),
new ColumnModel<>(
Bundle.IngestJobExcelExport_endTimeColumn(),
(entry) -> getDateCell(entry.getEndTime())),
new ColumnModel<>(
Bundle.IngestJobExcelExport_ingestStatusTimeColumn(),
(entry) -> new DefaultCellModel<>(entry.getStatus())),
new ColumnModel<>(
Bundle.IngestJobExcelExport_moduleNameTimeColumn(),
(entry) -> new DefaultCellModel<>(entry.getIngestModule())),
new ColumnModel<>(
Bundle.IngestJobExcelExport_versionColumn(),
(entry) -> new DefaultCellModel<>(entry.getIngestModuleVersion()))
);
/**
* Retrieves data for a date cell.
*
* @param date The date.
* @return The data cell to be used in the excel export.
*/
private static DefaultCellModel<?> getDateCell(Date date) {
Function<Date, String> dateParser = (dt) -> dt == null ? "" : DATETIME_FORMAT.format(dt);
return new DefaultCellModel<>(date, dateParser, DATETIME_FORMAT_STR);
}
/**
* Retrieves all the ingest job modules and versions for a job.
*
* @param job The ingest job.
* @return All of the corresponding entries sorted by module name.
*/
private static List<IngestJobEntry> getEntries(IngestJobInfo job) {
List<IngestModuleInfo> infoList = job.getIngestModuleInfo();
if (infoList == null) {
return Collections.emptyList();
} else {
Date startTime = job.getStartDateTime();
Date endTime = job.getEndDateTime();
String status = job.getStatus().getDisplayName();
return infoList.stream()
.filter(info -> info != null)
.map(info -> new IngestJobEntry(startTime, endTime, status, info.getDisplayName(), info.getVersion()))
.sorted((a, b) -> {
boolean aIsNull = a == null || a.getIngestModule() == null;
boolean bIsNull = b == null || b.getIngestModule() == null;
if (aIsNull || bIsNull) {
return Boolean.compare(aIsNull, bIsNull);
} else {
return a.getIngestModule().compareTo(b.getIngestModule());
}
})
.collect(Collectors.toList());
}
}
/**
* For output, show ingest job details in first row present. Otherwise, set
* to null.
*
* @param list The list of entries for an ingest job.
* @return The stream of entries to be displayed.
*/
private static Stream<IngestJobEntry> showFirstRowOnly(List<IngestJobEntry> list) {
return IntStream.range(0, list.size())
.mapToObj(idx -> {
IngestJobEntry entry = list.get(idx);
if (entry == null || idx == 0) {
return entry;
} else {
return new IngestJobEntry(null, null, null, entry.getIngestModule(), entry.getIngestModuleVersion());
}
});
}
/**
* Returns a list of sheets to be exported for the Ingest History tab.
*
* @param dataSource The data source.
* @return The list of sheets to be included in an export.
*/
static List<ExcelSheetExport> getExports(DataSource dataSource) {
if (dataSource == null) {
return Collections.emptyList();
}
List<IngestJobInfo> info = null;
try {
info = Case.getCurrentCaseThrows().getSleuthkitCase().getIngestJobs();
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.WARNING, "There was an error fetching ingest jobs", ex);
}
if (info == null) {
info = Collections.emptyList();
}
List<IngestJobEntry> toDisplay = info.stream()
.filter(job -> job != null && dataSource.getId() == job.getObjectId())
.sorted((a, b) -> {
// sort ingest jobs by time.
boolean aIsNull = a.getStartDateTime() == null;
boolean bIsNull = b.getStartDateTime() == null;
if (aIsNull || bIsNull) {
return Boolean.compare(aIsNull, bIsNull);
} else {
return a.getStartDateTime().compareTo(b.getStartDateTime());
}
})
.map((job) -> getEntries(job))
.filter(lst -> lst != null)
.flatMap((lst) -> showFirstRowOnly(lst))
.filter(item -> item != null)
.collect(Collectors.toList());
return Arrays.asList(new ExcelTableExport<>(Bundle.IngestJobExcelExport_sheetName(), COLUMNS, toDisplay));
}
private IngestJobExcelExport() {
}
}

View File

@ -22,6 +22,7 @@ import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.List;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
/**
* This class provides utilities for representing storage size in most relevant
@ -32,14 +33,64 @@ public final class SizeRepresentationUtil {
private static final int SIZE_CONVERSION_CONSTANT = 1000;
private static final DecimalFormat APPROXIMATE_SIZE_FORMAT = new DecimalFormat("#.##");
private static List<String> UNITS = Arrays.asList(
Bundle.SizeRepresentationUtil_units_bytes(),
Bundle.SizeRepresentationUtil_units_kilobytes(),
Bundle.SizeRepresentationUtil_units_megabytes(),
Bundle.SizeRepresentationUtil_units_gigabytes(),
Bundle.SizeRepresentationUtil_units_terabytes(),
Bundle.SizeRepresentationUtil_units_petabytes()
);
/**
* A size unit corresponding to orders of magnitude of bytes (kilobyte, gigabytes, etc.).
*/
@NbBundle.Messages({
"SizeRepresentationUtil_units_bytes=bytes",
"SizeRepresentationUtil_units_kilobytes=KB",
"SizeRepresentationUtil_units_megabytes=MB",
"SizeRepresentationUtil_units_gigabytes=GB",
"SizeRepresentationUtil_units_terabytes=TB",
"SizeRepresentationUtil_units_petabytes=PB"
})
enum SizeUnit {
BYTES(Bundle.SizeRepresentationUtil_units_bytes(), "#", 0),
KB(Bundle.SizeRepresentationUtil_units_kilobytes(), "#,##0.00,", 1),
MB(Bundle.SizeRepresentationUtil_units_megabytes(), "#,##0.00,,", 2),
GB(Bundle.SizeRepresentationUtil_units_gigabytes(), "#,##0.00,,,", 3),
TB(Bundle.SizeRepresentationUtil_units_terabytes(), "#,##0.00,,,,", 4),
PB(Bundle.SizeRepresentationUtil_units_petabytes(), "#,##0.00,,,,,", 5);
private final String suffix;
private final String excelFormatString;
private final long divisor;
/**
* Main constructor.
* @param suffix The string suffix to use for size unit.
* @param excelFormatString The excel format string to use for this size unit.
* @param power The power of 1000 of bytes for this size unit.
*/
SizeUnit(String suffix, String excelFormatString, int power) {
this.suffix = suffix;
// based on https://www.mrexcel.com/board/threads/how-do-i-format-cells-to-show-gb-mb-kb.140135/
this.excelFormatString = String.format("%s \"%s\"", excelFormatString, suffix);
this.divisor = (long) Math.pow(SIZE_CONVERSION_CONSTANT, power);
}
/**
* @return The string suffix to use for size unit.
*/
public String getSuffix() {
return suffix;
}
/**
* @return The excel format string to use for this size unit.
*/
public String getExcelFormatString() {
return excelFormatString;
}
/**
* @return The divisor to convert from bytes to this unit.
*/
public long getDivisor() {
return divisor;
}
}
/**
* Get a long size in bytes as a string formated to be read by users.
@ -47,49 +98,59 @@ public final class SizeRepresentationUtil {
* @param size Long value representing a size in bytes.
*
* @return Return a string formated with a user friendly version of the size
* as a string, returns empty String when provided empty size.
* as a string, returns empty String when provided empty size.
*/
public static String getSizeString(Long size) {
static String getSizeString(Long size) {
return getSizeString(size, APPROXIMATE_SIZE_FORMAT, true);
}
/**
* Determines the relevant size unit that should be used for a particular size.
* @param size The size in bytes.
* @return The relevant size unit.
*/
static SizeUnit getSizeUnit(Long size) {
if (size == null) {
return SizeUnit.values()[0];
}
for (int unitsIndex = 0; unitsIndex < SizeUnit.values().length; unitsIndex++) {
SizeUnit unit = SizeUnit.values()[unitsIndex];
long result = size / unit.getDivisor();
if (result < SIZE_CONVERSION_CONSTANT) {
return unit;
}
}
return SizeUnit.values()[SizeUnit.values().length - 1];
}
/**
* Get a long size in bytes as a string formated to be read by users.
*
* @param size Long value representing a size in byte.s
* @param format The means of formatting the number.
* @param size Long value representing a size in byte.s
* @param format The means of formatting the number.
* @param showFullSize Optionally show the number of bytes in the
* datasource.
* datasource.
*
* @return Return a string formated with a user friendly version of the size
* as a string, returns empty String when provided empty size.
* as a string, returns empty String when provided empty size.
*/
@NbBundle.Messages({
"SizeRepresentationUtil_units_bytes= bytes",
"SizeRepresentationUtil_units_kilobytes= kB",
"SizeRepresentationUtil_units_megabytes= MB",
"SizeRepresentationUtil_units_gigabytes= GB",
"SizeRepresentationUtil_units_terabytes= TB",
"SizeRepresentationUtil_units_petabytes= PB"
})
public static String getSizeString(Long size, DecimalFormat format, boolean showFullSize) {
static String getSizeString(Long size, DecimalFormat format, boolean showFullSize) {
if (size == null) {
return "";
}
double approximateSize = size;
int unitsIndex = 0;
for (; unitsIndex < UNITS.size(); unitsIndex++) {
if (approximateSize < SIZE_CONVERSION_CONSTANT) {
break;
} else {
approximateSize /= SIZE_CONVERSION_CONSTANT;
}
SizeUnit sizeUnit = getSizeUnit(size);
if (sizeUnit == null) {
sizeUnit = SizeUnit.BYTES;
}
String fullSize = size + UNITS.get(0);
String closestUnitSize = format.format(approximateSize) + UNITS.get(unitsIndex);
if (unitsIndex == 0) {
String closestUnitSize = String.format("%s %s",
format.format(((double) size) / sizeUnit.getDivisor()), sizeUnit.getSuffix());
String fullSize = String.format("%d %s", size, SizeUnit.BYTES.getSuffix());
if (sizeUnit.equals(SizeUnit.BYTES)) {
return fullSize;
} else if (showFullSize) {
return String.format("%s (%s)", closestUnitSize, fullSize);
@ -97,6 +158,24 @@ public final class SizeRepresentationUtil {
return closestUnitSize;
}
}
/**
* Returns a default cell model using size units.
* @param bytes The number of bytes.
* @return The default cell model.
*/
static DefaultCellModel<?> getBytesCell(Long bytes) {
if (bytes == null) {
return new DefaultCellModel<>("");
} else {
SizeUnit unit = SizeRepresentationUtil.getSizeUnit(bytes);
if (unit == null) {
unit = SizeUnit.BYTES;
}
return new DefaultCellModel<Long>(bytes, SizeRepresentationUtil::getSizeString, unit.getExcelFormatString());
}
}
private SizeRepresentationUtil() {
}

View File

@ -31,7 +31,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TypesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.MimeTypeSummary;
@ -40,13 +39,16 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.KeyValueItemExportable;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel.PieChartItem;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
@ -72,7 +74,8 @@ import org.sleuthkit.datamodel.TskCoreException;
"TypesPanel_fileMimeTypesChart_notAnalyzed_title=Not Analyzed",
"TypesPanel_usageLabel_title=Usage",
"TypesPanel_osLabel_title=OS",
"TypesPanel_sizeLabel_title=Size"})
"TypesPanel_sizeLabel_title=Size",
"TypesPanel_excelTabName=Types"})
class TypesPanel extends BaseDataSourceSummaryPanel {
/**
@ -167,10 +170,9 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
private static final DecimalFormat INTEGER_SIZE_FORMAT = new DecimalFormat("#");
private static final DecimalFormat COMMA_FORMATTER = new DecimalFormat("#,###");
private static final String FILE_TYPE_FACTORY = FileTypeIdModuleFactory.class.getCanonicalName();
private static final String FILE_TYPE_MODULE_NAME = FileTypeIdModuleFactory.getModuleName();
private static final Logger logger = Logger.getLogger(TypesPanel.class.getName());
private static final String COMMA_FORMAT_STR = "#,###";
private static final DecimalFormat COMMA_FORMATTER = new DecimalFormat(COMMA_FORMAT_STR);
private static final Color IMAGES_COLOR = new Color(156, 39, 176);
private static final Color VIDEOS_COLOR = Color.YELLOW;
@ -191,6 +193,15 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
new TypesPieCategory(Bundle.TypesPanel_fileMimeTypesChart_unknown_title(), new HashSet<>(Arrays.asList("application/octet-stream")), UNKNOWN_COLOR)
);
private final DataFetcher<DataSource, String> usageFetcher;
private final DataFetcher<DataSource, String> osFetcher;
private final DataFetcher<DataSource, Long> sizeFetcher;
private final DataFetcher<DataSource, Long> allocatedFetcher;
private final DataFetcher<DataSource, Long> unallocatedFetcher;
private final DataFetcher<DataSource, Long> slackFetcher;
private final DataFetcher<DataSource, Long> directoriesFetcher;
private final LoadableLabel usageLabel = new LoadableLabel(Bundle.TypesPanel_usageLabel_title());
private final LoadableLabel osLabel = new LoadableLabel(Bundle.TypesPanel_osLabel_title());
private final LoadableLabel sizeLabel = new LoadableLabel(Bundle.TypesPanel_sizeLabel_title());
@ -246,42 +257,34 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
super(mimeTypeData, typeData, containerData);
this.usageFetcher = containerData::getDataSourceType;
this.osFetcher = containerData::getOperatingSystems;
this.sizeFetcher = (dataSource) -> dataSource == null ? null : dataSource.getSize();
this.allocatedFetcher = (dataSource) -> typeData.getCountOfAllocatedFiles(dataSource);
this.unallocatedFetcher = (dataSource) -> typeData.getCountOfUnallocatedFiles(dataSource);
this.slackFetcher = (dataSource) -> typeData.getCountOfSlackFiles(dataSource);
this.directoriesFetcher = (dataSource) -> typeData.getCountOfDirectories(dataSource);
this.dataFetchComponents = Arrays.asList(
// usage label worker
new DataFetchWorker.DataFetchComponents<>(
containerData::getDataSourceType,
(result) -> usageLabel.showDataFetchResult(result)),
// os label worker
new DataFetchWorker.DataFetchComponents<>(
containerData::getOperatingSystems,
(result) -> osLabel.showDataFetchResult(result)),
// size label worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> {
Long size = dataSource == null ? null : dataSource.getSize();
return SizeRepresentationUtil.getSizeString(size, INTEGER_SIZE_FORMAT, false);
},
sizeLabel::showDataFetchResult),
// file types worker
new DataFetchWorker.DataFetchComponents<>(usageFetcher, usageLabel::showDataFetchResult),
new DataFetchWorker.DataFetchComponents<>(osFetcher, osLabel::showDataFetchResult),
new DataFetchWorker.DataFetchComponents<>(sizeFetcher,
(sizeResult) -> sizeLabel.showDataFetchResult(
DataFetchResult.getSubResult(sizeResult,
size -> SizeRepresentationUtil.getSizeString(size, INTEGER_SIZE_FORMAT, false)))),
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getMimeTypeCategoriesModel(mimeTypeData, dataSource),
this::showMimeTypeCategories),
// allocated files worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getStringOrZero(typeData.getCountOfAllocatedFiles(dataSource)),
allocatedLabel::showDataFetchResult),
// unallocated files worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getStringOrZero(typeData.getCountOfUnallocatedFiles(dataSource)),
unallocatedLabel::showDataFetchResult),
// slack files worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getStringOrZero(typeData.getCountOfSlackFiles(dataSource)),
slackLabel::showDataFetchResult),
// directories worker
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> getStringOrZero(typeData.getCountOfDirectories(dataSource)),
directoriesLabel::showDataFetchResult)
new DataFetchWorker.DataFetchComponents<>(allocatedFetcher,
countRes -> allocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count)))),
new DataFetchWorker.DataFetchComponents<>(unallocatedFetcher,
countRes -> unallocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count)))),
new DataFetchWorker.DataFetchComponents<>(slackFetcher,
countRes -> slackLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count)))),
new DataFetchWorker.DataFetchComponents<>(directoriesFetcher,
countRes -> directoriesLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count))))
);
initComponents();
@ -406,9 +409,53 @@ class TypesPanel extends BaseDataSourceSummaryPanel {
return longVal == null ? "0" : COMMA_FORMATTER.format(longVal);
}
/**
* Returns a key value pair to be exported in a sheet.
*
* @param fetcher The means of fetching the data.
* @param key The key to use.
* @param dataSource The data source containing the data.
* @return The key value pair to be exported.
*/
private static KeyValueItemExportable getStrExportable(DataFetcher<DataSource, String> fetcher, String key, DataSource dataSource) {
String result = getFetchResult(fetcher, "Types", dataSource);
return (result == null) ? null : new KeyValueItemExportable(key, new DefaultCellModel<>(result));
}
/**
* Returns a key value pair to be exported in a sheet formatting the long
* with commas separated by orders of 1000.
*
* @param fetcher The means of fetching the data.
* @param key The string key for this key value pair.
* @param dataSource The data source.
* @return The key value pair.
*/
private static KeyValueItemExportable getCountExportable(DataFetcher<DataSource, Long> fetcher, String key, DataSource dataSource) {
Long count = getFetchResult(fetcher, "Types", dataSource);
return (count == null) ? null : new KeyValueItemExportable(key,
new DefaultCellModel<Long>(count, COMMA_FORMATTER::format, COMMA_FORMAT_STR));
}
@Override
List<ExcelExport.ExcelSheetExport> getExports(DataSource dataSource) {
return Collections.emptyList();
if (dataSource == null) {
return Collections.emptyList();
}
return Arrays.asList(new ExcelSpecialFormatExport(Bundle.TypesPanel_excelTabName(),
Stream.of(
getStrExportable(usageFetcher, Bundle.TypesPanel_usageLabel_title(), dataSource),
getStrExportable(osFetcher, Bundle.TypesPanel_osLabel_title(), dataSource),
new KeyValueItemExportable(Bundle.TypesPanel_sizeLabel_title(),
SizeRepresentationUtil.getBytesCell(getFetchResult(sizeFetcher, "Types", dataSource))),
getCountExportable(allocatedFetcher, Bundle.TypesPanel_filesByCategoryTable_allocatedRow_title(), dataSource),
getCountExportable(unallocatedFetcher, Bundle.TypesPanel_filesByCategoryTable_unallocatedRow_title(), dataSource),
getCountExportable(slackFetcher, Bundle.TypesPanel_filesByCategoryTable_slackRow_title(), dataSource),
getCountExportable(directoriesFetcher, Bundle.TypesPanel_filesByCategoryTable_directoryRow_title(), dataSource))
.filter(sheet -> sheet != null)
.collect(Collectors.toList())
));
}
/**

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import javax.swing.JLabel;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
/**
* Basic interface for a cell model.
@ -29,20 +30,23 @@ public interface CellModel {
* Describes the horizontal alignment.
*/
public enum HorizontalAlign {
LEFT(JLabel.LEFT),
CENTER(JLabel.CENTER),
RIGHT(JLabel.RIGHT);
LEFT(JLabel.LEFT, HorizontalAlignment.LEFT),
CENTER(JLabel.CENTER, HorizontalAlignment.CENTER),
RIGHT(JLabel.RIGHT, HorizontalAlignment.RIGHT);
private final int jlabelAlignment;
private final HorizontalAlignment poiAlignment;
/**
* Constructor for a HorizontalAlign enum.
*
* @param jlabelAlignment The corresponding JLabel horizontal alignment
* number.
* @param poiAlignment Horizontal alignment for Apache POI.
*/
HorizontalAlign(int jlabelAlignment) {
HorizontalAlign(int jlabelAlignment, HorizontalAlignment poiAlignment) {
this.jlabelAlignment = jlabelAlignment;
this.poiAlignment = poiAlignment;
}
/**
@ -52,6 +56,13 @@ public interface CellModel {
int getJLabelAlignment() {
return this.jlabelAlignment;
}
/**
* @return Horizontal alignment for Apache POI.
*/
HorizontalAlignment getPoiAlignment() {
return poiAlignment;
}
}
/**

View File

@ -24,21 +24,21 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelCellModel;
/**
* The default cell model.
*/
public class DefaultCellModel<T> implements GuiCellModel, ExcelCellModel {
private final T data;
private final Function<T, String> stringConverter;
final T data;
final Function<T, String> stringConverter;
String tooltip;
CellModel.HorizontalAlign horizontalAlignment;
Insets insets;
List<MenuItem> popupMenu;
Supplier<List<MenuItem>> menuItemSupplier;
private final String excelFormatString;
final String excelFormatString;
/**
* Main constructor.
@ -76,6 +76,7 @@ public class DefaultCellModel<T> implements GuiCellModel, ExcelCellModel {
this.data = data;
this.stringConverter = stringConverter;
this.excelFormatString = excelFormatString;
this.tooltip = getText();
}
@Override

View File

@ -0,0 +1,32 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.uiutils;
/**
* Basic interface for a cell model.
*/
public interface ExcelCellModel extends CellModel {
/**
* @return The format string to be used with Apache POI during excel
* export or null if none necessary.
*/
String getExcelFormatString();
}

View File

@ -21,13 +21,23 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModel.HorizontalAlign;
/**
* Class for handling Excel exporting.
@ -59,6 +69,87 @@ public class ExcelExport {
}
}
/**
* A cell style key that can be used with the WorksheetEnv to generate a
* cell style to be used in a POI excel document.
*/
static class CellStyleKey {
private final String formatString;
private final CellStyle cellStyle;
private final HorizontalAlign alignment;
/**
* Main constructor.
*
* @param formatString The format string or null if no special
* formatting.
* @param cellStyle The base cell style or null if default is to be
* used.
* @param alignment The horizontal alignment or null if default is to be
* used.
*/
CellStyleKey(String formatString, CellStyle cellStyle, HorizontalAlign alignment) {
this.formatString = formatString;
this.cellStyle = cellStyle;
this.alignment = alignment;
}
/**
* @return The format string or null if no special formatting.
*/
String getFormatString() {
return formatString;
}
/**
* @return The base cell style or null if default is to be used.
*/
CellStyle getCellStyle() {
return cellStyle;
}
/**
* @return The horizontal alignment or null if default is to be used.
*/
HorizontalAlign getAlignment() {
return alignment;
}
@Override
public int hashCode() {
int hash = 7;
hash = 29 * hash + Objects.hashCode(this.formatString);
hash = 29 * hash + Objects.hashCode(this.cellStyle);
hash = 29 * hash + Objects.hashCode(this.alignment);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final CellStyleKey other = (CellStyleKey) obj;
if (!Objects.equals(this.formatString, other.formatString)) {
return false;
}
if (!Objects.equals(this.cellStyle, other.cellStyle)) {
return false;
}
if (this.alignment != other.alignment) {
return false;
}
return true;
}
}
/**
* Class detailing aspects of the worksheet.
*/
@ -66,18 +157,47 @@ public class ExcelExport {
private final CellStyle headerStyle;
private final Workbook parentWorkbook;
private final CellStyle defaultStyle;
// maps a data format string / original cell style combination to a created cell style
private final Map<CellStyleKey, CellStyle> cellStyleCache = new HashMap<>();
/**
* Main constructor.
*
* @param headerStyle The cell style to use for headers.
* @param defaultStyle The cell style to use as a default.
* @param parentWorkbook The parent workbook.
*/
WorksheetEnv(CellStyle headerStyle, Workbook parentWorkbook) {
WorksheetEnv(CellStyle headerStyle, CellStyle defaultStyle, Workbook parentWorkbook) {
this.headerStyle = headerStyle;
this.defaultStyle = defaultStyle;
this.parentWorkbook = parentWorkbook;
}
/**
* Returns a cell style signified by the given cell style key. If the
* key is already present, a cached version is returned.
*
* @param cellStyleKey The key.
* @return The cell style representing this key.
*/
public CellStyle getCellStyle(CellStyleKey cellStyleKey) {
return cellStyleCache.computeIfAbsent(cellStyleKey, (pair) -> {
CellStyle computed = this.parentWorkbook.createCellStyle();
computed.cloneStyleFrom(cellStyleKey.getCellStyle() == null ? defaultStyle : cellStyleKey.getCellStyle());
if (cellStyleKey.getAlignment() != null) {
computed.setAlignment(cellStyleKey.getAlignment().getPoiAlignment());
}
if (cellStyleKey.getFormatString() != null) {
computed.setDataFormat(this.parentWorkbook.getCreationHelper().createDataFormat().getFormat(cellStyleKey.getFormatString()));
}
return computed;
});
}
/**
* Returns the cell style to use for headers.
*
@ -87,6 +207,15 @@ public class ExcelExport {
return headerStyle;
}
/**
* Returns the cell style for default items.
*
* @return The cell style for default items.
*/
public CellStyle getDefaultCellStyle() {
return defaultStyle;
}
/**
* Returns the parent workbook.
*
@ -125,6 +254,7 @@ public class ExcelExport {
/**
* Retrieves a singleton instance of this class.
*
* @return The instance.
*/
public static ExcelExport getInstance() {
@ -141,10 +271,11 @@ public class ExcelExport {
/**
* Writes the exports to a workbook.
*
* @param exports The sheets to export.
* @param path The path to the output file.
* @throws IOException
* @throws ExcelExportException
* @throws ExcelExportException
*/
@Messages({
"# {0} - sheetNumber",
@ -160,10 +291,15 @@ public class ExcelExport {
//headerFont.setFontHeightInPoints((short) 14);
// Create a CellStyle with the font
HorizontalAlignment alignment = HorizontalAlignment.LEFT;
CellStyle headerCellStyle = workbook.createCellStyle();
headerCellStyle.setFont(headerFont);
headerCellStyle.setAlignment(alignment);
WorksheetEnv env = new WorksheetEnv(headerCellStyle, workbook);
CellStyle defaultCellStyle = workbook.createCellStyle();
defaultCellStyle.setAlignment(alignment);
WorksheetEnv env = new WorksheetEnv(headerCellStyle, defaultCellStyle, workbook);
if (exports != null) {
for (int i = 0; i < exports.size(); i++) {
@ -190,4 +326,46 @@ public class ExcelExport {
// Closing the workbook
workbook.close();
}
/**
* Creates an excel cell given the model.
*
* @param env The work sheet environment including the workbook.
* @param row The row in the excel document.
* @param colNum The column number (not zero-indexed).
* @param cellModel The model for the cell.
* @param cellStyle The style to use.
* @return The created cell.
*/
static Cell createCell(WorksheetEnv env, Row row, int colNum, ExcelCellModel cellModel, Optional<CellStyle> cellStyle) {
CellStyle cellStyleToUse = cellStyle.orElse(env.getDefaultCellStyle());
if (cellModel.getExcelFormatString() != null || cellModel.getHorizontalAlignment() != null) {
cellStyleToUse = env.getCellStyle(new CellStyleKey(cellModel.getExcelFormatString(), cellStyleToUse, cellModel.getHorizontalAlignment()));
}
Object cellData = cellModel.getData();
Cell cell = row.createCell(colNum);
if (cellData instanceof Calendar) {
cell.setCellValue((Calendar) cellData);
} else if (cellData instanceof Date) {
cell.setCellValue((Date) cellData);
} else if (cellData instanceof Double) {
cell.setCellValue((Double) cellData);
} else if (cellData instanceof String) {
cell.setCellValue((String) cellData);
} else if (cellData instanceof Short) {
cell.setCellValue((Short) cellData);
} else if (cellData instanceof Integer) {
cell.setCellValue((Integer) cellData);
} else if (cellData instanceof Long) {
cell.setCellValue((Long) cellData);
} else if (cellData instanceof Float) {
cell.setCellValue((Float) cellData);
} else {
cell.setCellValue(cellModel.getText());
}
cell.setCellStyle(cellStyleToUse);
return cell;
}
}

View File

@ -0,0 +1,265 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException;
/**
* An excel export that has special row-by-row formatting.
*/
public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport {
/**
* The dimensions consumed by an item in an ExcelSpecialFormatExport list of
* items to be rendered.
*/
public static class ItemDimensions {
private final int rowStart;
private final int rowEnd;
private final int colStart;
private final int colEnd;
/**
* Main constructor.
*
* @param rowStart The starting excel row of the item.
* @param colStart The starting excel column of the item.
* @param rowEnd The last excel row of the the item.
* @param colEnd The last excel column of the item.
*/
public ItemDimensions(int rowStart, int colStart, int rowEnd, int colEnd) {
this.rowStart = rowStart;
this.colStart = colStart;
this.rowEnd = rowEnd;
this.colEnd = colEnd;
}
/**
* @return The starting excel row of the item.
*/
public int getRowStart() {
return rowStart;
}
/**
* @return The last excel row of the the item.
*/
public int getRowEnd() {
return rowEnd;
}
/**
* @return The starting excel column of the item.
*/
public int getColStart() {
return colStart;
}
/**
* @return The last excel column of the item.
*/
public int getColEnd() {
return colEnd;
}
}
/**
* An item to be exported in a specially formatted excel export.
*/
public interface ExcelItemExportable {
/**
* Writes the item to the sheet in the special format export sheet.
*
* @param sheet The sheet.
* @param rowStart The starting row to start writing.
* @param colStart The starting column to start writing.
* @param env The excel export context.
* @return The dimensions of what has been written.
* @throws ExcelExportException
*/
ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException;
}
/**
* Writes a string to a single cell in a specially formatted excel export.
*/
public static class SingleCellExportable implements ExcelItemExportable {
private final ExcelCellModel item;
/**
* Main constructor.
*
* @param key The text to be written.
*/
public SingleCellExportable(String key) {
this(new DefaultCellModel<>(key));
}
/**
* Main constructor.
*
* @param item The cell model to be written.
*/
public SingleCellExportable(ExcelCellModel item) {
this.item = item;
}
@Override
public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException {
Row row = sheet.createRow(rowStart);
ExcelExport.createCell(env, row, colStart, item, Optional.empty());
return new ItemDimensions(rowStart, colStart, rowStart, colStart);
}
}
/**
* Writes a row consisting of first column as a key and second column as a
* value.
*/
public static class KeyValueItemExportable implements ExcelItemExportable {
private final ExcelCellModel key;
private final ExcelCellModel value;
/**
* Main constructor.
*
* @param key The string key to be exported.
* @param value The cell model to be exported.
*/
public KeyValueItemExportable(String key, ExcelCellModel value) {
this(new DefaultCellModel<>(key), value);
}
/**
* Main constructor.
*
* @param key The cell key to be exported.
* @param value The cell model to be exported.
*/
public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value) {
this.key = key;
this.value = value;
}
@Override
public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException {
Row row = sheet.createRow(rowStart);
ExcelExport.createCell(env, row, colStart, key, Optional.of(env.getHeaderStyle()));
ExcelExport.createCell(env, row, colStart + 1, value, Optional.empty());
return new ItemDimensions(rowStart, colStart, rowStart, colStart + 1);
}
}
/**
* A special format excel export item that shows a title and a list of items
* indented one column.
*
* i.e.
* <pre>
* title
* item 1
* item 2
* </pre>
*/
public static class TitledExportable implements ExcelItemExportable {
private static final int DEFAULT_INDENT = 1;
private final String title;
private final List<? extends ExcelItemExportable> children;
/**
* Main constructor.
*
* @param title The title for the export.
* @param children The children to be indented and enumerated.
*/
public TitledExportable(String title, List<? extends ExcelItemExportable> children) {
this.title = title;
this.children = children;
}
@Override
public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException {
ExcelExport.createCell(env, sheet.createRow(rowStart), colStart, new DefaultCellModel<>(title), Optional.of(env.getHeaderStyle()));
int curRow = rowStart + 1;
int maxCol = colStart;
for (ExcelItemExportable export : children) {
if (export == null) {
continue;
}
ItemDimensions thisItemDim = export.write(sheet, curRow, colStart + DEFAULT_INDENT, env);
curRow = thisItemDim.getRowEnd() + 1;
maxCol = Math.max(thisItemDim.getColEnd(), maxCol);
}
return new ItemDimensions(rowStart, colStart, curRow - 1, maxCol);
}
}
private final String sheetName;
private final List<ExcelItemExportable> exports;
/**
* Main constructor.
*
* @param sheetName The name of the sheet.
* @param exports The row-by-row items to be exported.
*/
public ExcelSpecialFormatExport(String sheetName, List<ExcelItemExportable> exports) {
this.sheetName = sheetName;
this.exports = exports == null ? Collections.emptyList() : exports;
}
@Override
public String getSheetName() {
return sheetName;
}
@Override
public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv env) throws ExcelExportException {
int rowStart = 0;
int maxCol = 0;
for (ExcelItemExportable export : exports) {
if (export == null) {
continue;
}
ItemDimensions dimensions = export.write(sheet, rowStart, 0, env);
rowStart = dimensions.getRowEnd() + 1;
maxCol = Math.max(maxCol, dimensions.getColEnd());
}
// Resize all columns to fit the content size
for (int i = 0; i <= maxCol; i++) {
sheet.autoSizeColumn(i);
}
}
}

View File

@ -18,40 +18,26 @@
*/
package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ExcelItemExportable;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ItemDimensions;
/**
* An excel sheet export of table data.
*/
public class ExcelTableExport<T, C extends ExcelCellModel> implements ExcelExport.ExcelSheetExport {
/**
* Basic interface for a cell model.
*/
public interface ExcelCellModel extends CellModel {
/**
* @return The format string to be used with Apache POI during excel
* export or null if none necessary.
*/
String getExcelFormatString();
}
public class ExcelTableExport<T, C extends ExcelCellModel> implements ExcelSheetExport, ExcelItemExportable {
private final String sheetName;
private final List<ColumnModel<T, C>> columns;
private final List<T> data;
private final int columnIndent;
/**
* Main constructor.
@ -62,9 +48,23 @@ public class ExcelTableExport<T, C extends ExcelCellModel> implements ExcelExpor
* @param data The data to export.
*/
public ExcelTableExport(String sheetName, List<ColumnModel<T, C>> columns, List<T> data) {
this(sheetName, columns, data, 0);
}
/**
* Main constructor.
*
* @param sheetName The name of the sheet. NOTE: There can be no duplicates
* in a workbook.
* @param columns The columns of the table.
* @param data The data to export.
* @param columnIndent The column indent.
*/
public ExcelTableExport(String sheetName, List<ColumnModel<T, C>> columns, List<T> data, int columnIndent) {
this.sheetName = sheetName;
this.columns = columns;
this.data = data;
this.columnIndent = columnIndent;
}
@Override
@ -74,11 +74,20 @@ public class ExcelTableExport<T, C extends ExcelCellModel> implements ExcelExpor
@Override
public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv style) throws ExcelExport.ExcelExportException {
renderSheet(sheet, style, columns, data);
renderSheet(sheet, style, 0, columnIndent, columns, data);
// Resize all columns to fit the content size
for (int i = 0; i < columns.size(); i++) {
sheet.autoSizeColumn(i);
}
}
@Override
public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException {
int columnStart = columnIndent + colStart;
int rowsWritten = renderSheet(sheet, env, rowStart, columnStart, columns, data);
return new ItemDimensions(rowStart, columnStart, rowStart + rowsWritten - 1, this.columns == null ? columnStart : columnStart + this.columns.size());
}
/**
@ -86,88 +95,44 @@ public class ExcelTableExport<T, C extends ExcelCellModel> implements ExcelExpor
*
* @param sheet The sheet.
* @param worksheetEnv The worksheet environment and preferences.
* @param rowStart The row to start in.
* @param colStart The column to start in.
* @param columns The columns.
* @param data The data.
* @throws ExcelExportException
* @return The number of rows (including the header) written.
*/
private static <T, C extends ExcelCellModel> void renderSheet(
Sheet sheet, ExcelExport.WorksheetEnv worksheetEnv, List<ColumnModel<T, C>> columns, List<T> data)
private static <T, C extends ExcelCellModel> int renderSheet(
Sheet sheet,
ExcelExport.WorksheetEnv worksheetEnv,
int rowStart,
int colStart,
List<ColumnModel<T, C>> columns, List<T> data)
throws ExcelExport.ExcelExportException {
List<T> safeData = data == null ? Collections.emptyList() : data;
// Create a header row
Row headerRow = sheet.createRow(0);
Row headerRow = sheet.createRow(rowStart);
// Create header cells
for (int i = 0; i < columns.size(); i++) {
Cell cell = headerRow.createCell(i);
Cell cell = headerRow.createCell(i + colStart);
cell.setCellValue(columns.get(i).getHeaderTitle());
cell.setCellStyle(worksheetEnv.getHeaderStyle());
}
// freeze header row
sheet.createFreezePane(0, 1);
// Create Cell Style for each column (if one is needed)
Map<String, CellStyle> cellStyles = new HashMap<>();
for (int rowNum = 0; rowNum < safeData.size(); rowNum++) {
T rowData = safeData.get(rowNum);
Row row = sheet.createRow(rowNum + 1);
Row row = sheet.createRow(rowNum + rowStart + 1);
for (int colNum = 0; colNum < columns.size(); colNum++) {
ColumnModel<T, ? extends ExcelCellModel> colModel = columns.get(colNum);
ExcelCellModel cellModel = colModel.getCellRenderer().apply(rowData);
String formatString = cellModel.getExcelFormatString();
Optional<CellStyle> cellStyle = (formatString == null)
? Optional.empty()
: Optional.of(cellStyles.computeIfAbsent(formatString, k -> createCellStyle(worksheetEnv.getParentWorkbook(), formatString)));
createCell(row, colNum, cellModel, cellStyle);
ExcelExport.createCell(worksheetEnv, row, colNum + colStart, cellModel, Optional.empty());
}
}
}
/**
* Create a cell style in the workbook with the given format string.
*
* @param workbook The workbook.
* @param formatString The format string.
* @return The cell style.
*/
private static <T> CellStyle createCellStyle(Workbook workbook, String formatString) {
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat(formatString));
return cellStyle;
return safeData.size() + 1;
}
/**
* Creates an excel cell given the model.
*
* @param row The row in the excel document.
* @param colNum The column number (not zero-indexed).
* @param cellModel The model for the cell.
* @param cellStyle The style to use.
* @return The created cell.
*/
private static Cell createCell(Row row, int colNum, ExcelCellModel cellModel, Optional<CellStyle> cellStyle) {
Object cellData = cellModel.getData();
Cell cell = row.createCell(colNum);
if (cellData instanceof Calendar) {
cell.setCellValue((Calendar) cellData);
} else if (cellData instanceof Date) {
cell.setCellValue((Date) cellData);
} else if (cellData instanceof Double) {
cell.setCellValue((Double) cellData);
} else if (cellData instanceof String) {
cell.setCellValue((String) cellData);
} else if (cellData instanceof Short) {
cell.setCellValue((Short) cellData);
} else if (cellData instanceof Integer) {
cell.setCellValue((Integer) cellData);
} else if (cellData instanceof Long) {
cell.setCellValue((Long) cellData);
} else if (cellData instanceof Float) {
cell.setCellValue((Float) cellData);
} else {
cell.setCellValue(cellModel.getText());
}
cellStyle.ifPresent(cs -> cell.setCellStyle(cs));
return cell;
}
}