diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ArtifactContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/ArtifactContentViewer.java new file mode 100644 index 0000000000..985ca96879 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ArtifactContentViewer.java @@ -0,0 +1,56 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit 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.contentviewers; + +import java.awt.Component; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Common interface implemented by artifact viewers. + * + * An artifact viewer displays the artifact in a custom + * layout panel suitable for the artifact type. + * + */ +public interface ArtifactContentViewer { + + /** + * Called to display the contents of the given artifact. + * + * @param artifact the artifact to display. + */ + void setArtifact(BlackboardArtifact artifact); + + /** + * Returns the panel. + * + * @return display panel. + */ + Component getComponent(); + + /** + * Checks whether the given artifact is supported by the viewer. + * + * @param artifact Artifact to check. + * + * @return True if the artifact can be displayed by the viewer, false otherwise. + */ + boolean isSupported(BlackboardArtifact artifact); + +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index ebc9a58e1a..60afbe4051 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -953,4 +953,6 @@ manager.properties.lafError =\ manager.properties.brokenProperty = Broken default property {0} value: {1} -manager.properties.missingProperty = Missing default property {0} value: {1} \ No newline at end of file +manager.properties.missingProperty = Missing default property {0} value: {1} +DefaultArtifactContentViewer.copyMenuItem.text=Copy +DefaultArtifactContentViewer.selectAllMenuItem.text=Select All diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle_ja.properties index f7b7844e60..18fdb5ce50 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle_ja.properties @@ -164,3 +164,5 @@ MediaViewImagePanel.tagsMenu.text_1=\u30bf\u30b0\u30e1\u30cb\u30e5\u30fc SQLiteViewer.readTable.errorText=\u6b21\u306e\u30c6\u30fc\u30d6\u30eb\u306e\u884c\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0} # {0} - tableName SQLiteViewer.selectTable.errorText=\u6b21\u306e\u30c6\u30fc\u30d6\u30eb\u306e\u884c\u30ab\u30a6\u30f3\u30c8\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0} +DefaultArtifactContentViewer.selectAllMenuItem.text=\u3059\u3079\u3066\u3092\u9078\u629e +DefaultArtifactContentViewer.copyMenuItem.text=\u30b3\u30d4\u30fc diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form new file mode 100644 index 0000000000..eb1f729611 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form @@ -0,0 +1,73 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java new file mode 100644 index 0000000000..d77ca887d8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java @@ -0,0 +1,524 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2020 Basis Technology Corp. + * Contact: carrier sleuthkit 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.contentviewers; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.datatransfer.StringSelection; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JMenuItem; +import javax.swing.JTextArea; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.TableColumnModelEvent; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumn; +import javax.swing.event.TableColumnModelListener; +import javax.swing.text.View; +import org.apache.commons.lang.StringUtils; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; +import org.netbeans.swing.etable.ETable; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonArray; +import java.util.Locale; +import java.util.Map; +import javax.swing.SwingUtilities; + +/** + * This class displays a Blackboard artifact as a table listing all it's + * attributes names and values. + */ + +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class DefaultArtifactContentViewer extends javax.swing.JPanel implements ArtifactContentViewer { + + @NbBundle.Messages({ + "DefaultArtifactContentViewer.attrsTableHeader.type=Type", + "DefaultArtifactContentViewer.attrsTableHeader.value=Value", + "DefaultArtifactContentViewer.attrsTableHeader.sources=Source(s)", + "DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database", + "DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database" + }) + + private final static Logger logger = Logger.getLogger(DefaultArtifactContentViewer.class.getName()); + + private static final long serialVersionUID = 1L; + + private static final String[] COLUMN_HEADERS = { + Bundle.DefaultArtifactContentViewer_attrsTableHeader_type(), + Bundle.DefaultArtifactContentViewer_attrsTableHeader_value(), + Bundle.DefaultArtifactContentViewer_attrsTableHeader_sources()}; + private static final int[] COLUMN_WIDTHS = {100, 800, 100}; + private static final int CELL_BOTTOM_MARGIN = 5; + private static final int CELL_RIGHT_MARGIN = 1; + + public DefaultArtifactContentViewer() { + initResultsTable(); + initComponents(); + resultsTableScrollPane.setViewportView(resultsTable); + customizeComponents(); + resetComponents(); + resultsTable.setDefaultRenderer(Object.class, new MultiLineTableCellRenderer()); + } + + private void initResultsTable() { + resultsTable = new ETable(); + resultsTable.setModel(new javax.swing.table.DefaultTableModel() { + private static final long serialVersionUID = 1L; + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + }); + resultsTable.setCellSelectionEnabled(true); + resultsTable.getTableHeader().setReorderingAllowed(false); + resultsTable.setColumnHidingAllowed(false); + resultsTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); + resultsTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() { + + @Override + public void columnAdded(TableColumnModelEvent e) { + // do nothing + } + + @Override + public void columnRemoved(TableColumnModelEvent e) { + // do nothing + } + + @Override + public void columnMoved(TableColumnModelEvent e) { + // do nothing + } + + @Override + public void columnMarginChanged(ChangeEvent e) { + updateRowHeights(); //When the user changes column width we may need to resize row height + } + + @Override + public void columnSelectionChanged(ListSelectionEvent e) { + // do nothing + } + }); + resultsTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN); + + } + + /** + * Sets the row heights to the heights of the content in their Value column. + */ + private void updateRowHeights() { + int valueColIndex = -1; + for (int col = 0; col < resultsTable.getColumnCount(); col++) { + if (resultsTable.getColumnName(col).equals(COLUMN_HEADERS[1])) { + valueColIndex = col; + } + } + if (valueColIndex != -1) { + for (int row = 0; row < resultsTable.getRowCount(); row++) { + Component comp = resultsTable.prepareRenderer( + resultsTable.getCellRenderer(row, valueColIndex), row, valueColIndex); + final int rowHeight; + if (comp instanceof JTextArea) { + final JTextArea tc = (JTextArea) comp; + final View rootView = tc.getUI().getRootView(tc); + java.awt.Insets i = tc.getInsets(); + rootView.setSize(resultsTable.getColumnModel().getColumn(valueColIndex) + .getWidth() - (i.left + i.right +CELL_RIGHT_MARGIN), //current width minus borders + Integer.MAX_VALUE); + rowHeight = (int) rootView.getPreferredSpan(View.Y_AXIS); + } else { + rowHeight = comp.getPreferredSize().height; + } + if (rowHeight > 0) { + resultsTable.setRowHeight(row, rowHeight + CELL_BOTTOM_MARGIN); + } + } + } + } + + /** + * Update the column widths so that the Value column has most of the space. + */ + private void updateColumnSizes() { + Enumeration columns = resultsTable.getColumnModel().getColumns(); + while (columns.hasMoreElements()) { + TableColumn col = columns.nextElement(); + if (col.getHeaderValue().equals(COLUMN_HEADERS[0])) { + col.setPreferredWidth(COLUMN_WIDTHS[0]); + } else if (col.getHeaderValue().equals(COLUMN_HEADERS[1])) { + col.setPreferredWidth(COLUMN_WIDTHS[1]); + } else if (col.getHeaderValue().equals(COLUMN_HEADERS[2])) { + col.setPreferredWidth(COLUMN_WIDTHS[2]); + } + } + } + + /** + * 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 + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + rightClickMenu = new javax.swing.JPopupMenu(); + copyMenuItem = new javax.swing.JMenuItem(); + selectAllMenuItem = new javax.swing.JMenuItem(); + resultsTableScrollPane = new javax.swing.JScrollPane(); + + copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.copyMenuItem.text")); // NOI18N + rightClickMenu.add(copyMenuItem); + + selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.selectAllMenuItem.text")); // NOI18N + rightClickMenu.add(selectAllMenuItem); + + setPreferredSize(new java.awt.Dimension(100, 58)); + + resultsTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + resultsTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(620, 34)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 58, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JMenuItem copyMenuItem; + private javax.swing.JScrollPane resultsTableScrollPane; + private javax.swing.JPopupMenu rightClickMenu; + private javax.swing.JMenuItem selectAllMenuItem; + // End of variables declaration//GEN-END:variables + private ETable resultsTable; + + private void customizeComponents() { + resultsTable.setComponentPopupMenu(rightClickMenu); + ActionListener actList = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JMenuItem jmi = (JMenuItem) e.getSource(); + if (jmi.equals(copyMenuItem)) { + StringBuilder selectedText = new StringBuilder(512); + for (int row : resultsTable.getSelectedRows()) { + for (int col : resultsTable.getSelectedColumns()) { + selectedText.append((String) resultsTable.getValueAt(row, col)); + selectedText.append('\t'); + } + //if its the last row selected don't add a new line + if (row != resultsTable.getSelectedRows()[resultsTable.getSelectedRows().length - 1]) { + selectedText.append(System.lineSeparator()); + } + } + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(selectedText.toString()), null); + } else if (jmi.equals(selectAllMenuItem)) { + resultsTable.selectAll(); + } + } + }; + copyMenuItem.addActionListener(actList); + + selectAllMenuItem.addActionListener(actList); + } + + /** + * Resets the components to an empty view state. + */ + private void resetComponents() { + + ((DefaultTableModel) resultsTable.getModel()).setRowCount(0); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void setArtifact(BlackboardArtifact artifact) { + try { + ResultsTableArtifact resultsTableArtifact = new ResultsTableArtifact(artifact, artifact.getParent()); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + updateView(resultsTableArtifact); + } + }); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting parent content for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + } + + } + + @Override + public boolean isSupported(BlackboardArtifact artifact) { + // This viewer supports all artifacts. + return true; + } + + /** + * This class is a container to hold the data necessary for the artifact + * being viewed. + */ + private class ResultsTableArtifact { + + private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); + private String[][] rowData = null; + private final String artifactDisplayName; + private final Content content; + + ResultsTableArtifact(BlackboardArtifact artifact, Content content) { + artifactDisplayName = artifact.getDisplayName(); + this.content = content; + addRows(artifact); + } + + ResultsTableArtifact(String errorMsg) { + artifactDisplayName = errorMsg; + rowData = new String[1][3]; + rowData[0] = new String[]{"", errorMsg, ""}; + content = null; + } + + private String[][] getRows() { + return rowData; + } + + private void addRows(BlackboardArtifact artifact) { + List rowsToAdd = new ArrayList<>(); + try { + /* + * Add rows for each attribute. + */ + for (BlackboardAttribute attr : artifact.getAttributes()) { + /* + * Attribute value column. + */ + String value; + switch (attr.getAttributeType().getValueType()) { + + // Use Autopsy date formatting settings, not TSK defaults + case DATETIME: + value = epochTimeToString(attr.getValueLong()); + break; + case JSON: + // Get the attribute's JSON value and convert to indented multiline display string + String jsonVal = attr.getValueString(); + JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(jsonVal).getAsJsonObject(); + + value = toJsonDisplayString(json, ""); + break; + + case STRING: + case INTEGER: + case LONG: + case DOUBLE: + case BYTE: + default: + value = attr.getDisplayString(); + break; + } + /* + * Attribute sources column. + */ + String sources = StringUtils.join(attr.getSources(), ", "); + rowsToAdd.add(new String[]{attr.getAttributeType().getDisplayName(), value, sources}); + } + /* + * Add a row for the source content path. + */ + String path = ""; + try { + if (null != content) { + path = content.getUniquePath(); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting source content path for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + path = Bundle.DataContentViewerArtifact_failedToGetSourcePath_message(); + } + rowsToAdd.add(new String[]{"Source File Path", path, ""}); + /* + * Add a row for the artifact id. + */ + rowsToAdd.add(new String[]{"Artifact ID", Long.toString(artifact.getArtifactID()), ""}); + } catch (TskCoreException ex) { + rowsToAdd.add(new String[]{"", Bundle.DataContentViewerArtifact_failedToGetAttributes_message(), ""}); + } + rowData = rowsToAdd.toArray(new String[0][0]); + } + + /** + * @return the artifactDisplayName + */ + String getArtifactDisplayName() { + return artifactDisplayName; + } + + private static final String INDENT_RIGHT = " "; + private static final String NEW_LINE = "\n"; + + /** + * Recursively converts a JSON element into an indented multi-line + * display string. + * + * @param element JSON element to convert + * @param startIndent Starting indentation for the element. + * + * @return A multi-line display string. + */ + private String toJsonDisplayString(JsonElement element, String startIndent) { + + StringBuilder sb = new StringBuilder(""); + JsonObject obj = element.getAsJsonObject(); + + for (Map.Entry entry : obj.entrySet()) { + appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb ); + } + + String returnString = sb.toString(); + if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) { + returnString = returnString.substring(NEW_LINE.length()); + } + return returnString; + } + + + /** + * Converts the given JSON element into string and appends to the given string builder. + * + * @param jsonKey + * @param jsonElement + * @param startIndent Starting indentation for the element. + * @param sb String builder to append to. + */ + private void appendJsonElementToString(String jsonKey, JsonElement jsonElement, String startIndent, StringBuilder sb) { + if (jsonElement.isJsonArray()) { + JsonArray jsonArray = jsonElement.getAsJsonArray(); + if (jsonArray.size() > 0) { + int count = 1; + sb.append(NEW_LINE).append(String.format("%s%s", startIndent, jsonKey)); + for (JsonElement arrayMember : jsonArray) { + sb.append(NEW_LINE).append(String.format("%s%d", startIndent.concat(INDENT_RIGHT), count)); + sb.append(toJsonDisplayString(arrayMember, startIndent.concat(INDENT_RIGHT).concat(INDENT_RIGHT))); + count++; + } + } + } else if (jsonElement.isJsonObject()) { + sb.append(NEW_LINE).append(String.format("%s%s %s", startIndent, jsonKey, toJsonDisplayString(jsonElement.getAsJsonObject(), startIndent + INDENT_RIGHT))); + } else if (jsonElement.isJsonPrimitive()) { + String attributeName = jsonKey; + String attributeValue; + if (attributeName.toUpperCase().contains("DATETIME")) { + attributeValue = epochTimeToString(Long.parseLong(jsonElement.getAsString())); + } else { + attributeValue = jsonElement.getAsString(); + } + sb.append(NEW_LINE).append(String.format("%s%s = %s", startIndent, attributeName, attributeValue)); + } else if (jsonElement.isJsonNull()) { + sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey)); + } + } + + /** + * Converts epoch time to readable string. + * + * @param epochTime epoch time value to be converted to string. + * @return String with human readable time. + */ + private String epochTimeToString(long epochTime) { + String dateTimeString = "0000-00-00 00:00:00"; + if (null != content && 0 != epochTime) { + dateFormatter.setTimeZone(ContentUtils.getTimeZone(content)); + dateTimeString = dateFormatter.format(new java.util.Date(epochTime * 1000)); + } + return dateTimeString; + } + + } + + /** + * Updates the table view with the given artifact data. + * + * It should be called on EDT. + * + * @param resultsTableArtifact Artifact data to display in the view. + */ + private void updateView(ResultsTableArtifact resultsTableArtifact) { + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel()); + tModel.setDataVector(resultsTableArtifact.getRows(), COLUMN_HEADERS); + updateColumnSizes(); + updateRowHeights(); + resultsTable.clearSelection(); + + this.setCursor(null); + } + + /** + * TableCellRenderer for displaying multiline text. + */ + private class MultiLineTableCellRenderer implements javax.swing.table.TableCellRenderer { + + @Override + public Component getTableCellRendererComponent(javax.swing.JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + javax.swing.JTextArea jtex = new javax.swing.JTextArea(); + if (value instanceof String) { + jtex.setText((String) value); + jtex.setLineWrap(true); + jtex.setWrapStyleWord(false); + } + //cell backgroud color when selected + if (isSelected) { + jtex.setBackground(javax.swing.UIManager.getColor("Table.selectionBackground")); + } else { + jtex.setBackground(javax.swing.UIManager.getColor("Table.background")); + } + return jtex; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 4571bcc3df..8834b0f5d7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -30,8 +30,6 @@ DataContentViewerArtifact.pageLabel2.text=Result DataContentViewerArtifact.nextPageButton.text= DataContentViewerArtifact.currentPageLabel.text=1 DataContentViewerArtifact.ofLabel.text=of -DataContentViewerArtifact.copyMenuItem.text=Copy -DataContentViewerArtifact.selectAllMenuItem.text=Select All DataContentViewerArtifact.pageLabel.text=Result: AdvancedConfigurationDialog.applyButton.text=OK DataContentViewerHex.goToPageTextField.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties index 6f941632c8..ec91946e79 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties @@ -82,8 +82,6 @@ DataContentViewerArtifact.pageLabel2.text=\u7d50\u679c DataContentViewerArtifact.nextPageButton.text= DataContentViewerArtifact.currentPageLabel.text=1 DataContentViewerArtifact.ofLabel.text=/ -DataContentViewerArtifact.copyMenuItem.text=\u30b3\u30d4\u30fc -DataContentViewerArtifact.selectAllMenuItem.text=\u3059\u3079\u3066\u3092\u9078\u629e DataContentViewerArtifact.pageLabel.text=\u7d50\u679c: AdvancedConfigurationDialog.applyButton.text=OK DataContentViewerHex.goToPageTextField.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form index 5827ce617f..c4928f5111 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form @@ -1,30 +1,6 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - + @@ -45,16 +21,16 @@ - - + + - - + + @@ -248,19 +224,34 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 78ccd6e4ec..23116a5d11 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -20,178 +20,67 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Component; import java.awt.Cursor; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.datatransfer.StringSelection; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.JMenuItem; -import javax.swing.JTextArea; import javax.swing.SwingWorker; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.TableColumnModelEvent; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableColumn; -import javax.swing.event.TableColumnModelListener; -import javax.swing.text.View; -import org.apache.commons.lang.StringUtils; import org.openide.nodes.Node; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; -import org.netbeans.swing.etable.ETable; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonArray; -import java.util.Map; +import java.util.Arrays; +import java.util.Collections; +import org.sleuthkit.autopsy.contentviewers.ArtifactContentViewer; +import org.sleuthkit.autopsy.contentviewers.DefaultArtifactContentViewer; /** * Instances of this class display the BlackboardArtifacts associated with the - * Content represented by a Node. Each BlackboardArtifact is rendered displayed - * in a JTable representation of its BlackboardAttributes. + * Content represented by a Node. + * + * It goes through a list of known ArtifactContentViewer to find a viewer that + * supports a given artifact and then hands it the artifact to display. */ @ServiceProvider(service = DataContentViewer.class, position = 7) @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataContentViewerArtifact extends javax.swing.JPanel implements DataContentViewer { + private static final long serialVersionUID = 1L; + @NbBundle.Messages({ - "DataContentViewerArtifact.attrsTableHeader.type=Type", - "DataContentViewerArtifact.attrsTableHeader.value=Value", - "DataContentViewerArtifact.attrsTableHeader.sources=Source(s)", "DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database", "DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database" }) private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName()); private final static String WAIT_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.waitText"); private final static String ERROR_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.errorText"); + private Node currentNode; // @@@ Remove this when the redundant setNode() calls problem is fixed. private int currentPage = 1; private final Object lock = new Object(); - private List artifactTableContents; // Accessed by multiple threads, use getArtifactContents() and setArtifactContents() - SwingWorker currentTask; // Accessed by multiple threads, use startNewTask() - private static final String[] COLUMN_HEADERS = { - Bundle.DataContentViewerArtifact_attrsTableHeader_type(), - Bundle.DataContentViewerArtifact_attrsTableHeader_value(), - Bundle.DataContentViewerArtifact_attrsTableHeader_sources()}; - private static final int[] COLUMN_WIDTHS = {100, 800, 100}; - private static final int CELL_BOTTOM_MARGIN = 5; - private static final int CELL_RIGHT_MARGIN = 1; + private List artifactTableContents; // Accessed by multiple threads, use getArtifactContents() and setArtifactContents() + private SwingWorker currentTask; // Accessed by multiple threads, use startNewTask() + + private final Collection KNOWN_ARTIFACT_VIEWERS = + Arrays.asList( + // TBD: populate with custom artifact viewers. + ); + public DataContentViewerArtifact() { - initResultsTable(); + initComponents(); - resultsTableScrollPane.setViewportView(resultsTable); - customizeComponents(); + resetComponents(); - resultsTable.setDefaultRenderer(Object.class, new MultiLineTableCellRenderer()); - } - - private void initResultsTable() { - resultsTable = new ETable(); - resultsTable.setModel(new javax.swing.table.DefaultTableModel() { - private static final long serialVersionUID = 1L; - - public boolean isCellEditable(int rowIndex, int columnIndex) { - return false; - } - }); - resultsTable.setCellSelectionEnabled(true); - resultsTable.getTableHeader().setReorderingAllowed(false); - resultsTable.setColumnHidingAllowed(false); - resultsTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); - resultsTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() { - - @Override - public void columnAdded(TableColumnModelEvent e) { - } - - @Override - public void columnRemoved(TableColumnModelEvent e) { - } - - @Override - public void columnMoved(TableColumnModelEvent e) { - - } - - @Override - public void columnMarginChanged(ChangeEvent e) { - updateRowHeights(); //When the user changes column width we may need to resize row height - } - - @Override - public void columnSelectionChanged(ListSelectionEvent e) { - } - }); - resultsTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN); - - } - - /** - * Sets the row heights to the heights of the content in their Value column. - */ - private void updateRowHeights() { - int valueColIndex = -1; - for (int col = 0; col < resultsTable.getColumnCount(); col++) { - if (resultsTable.getColumnName(col).equals(COLUMN_HEADERS[1])) { - valueColIndex = col; - } - } - if (valueColIndex != -1) { - for (int row = 0; row < resultsTable.getRowCount(); row++) { - Component comp = resultsTable.prepareRenderer( - resultsTable.getCellRenderer(row, valueColIndex), row, valueColIndex); - final int rowHeight; - if (comp instanceof JTextArea) { - final JTextArea tc = (JTextArea) comp; - final View rootView = tc.getUI().getRootView(tc); - java.awt.Insets i = tc.getInsets(); - rootView.setSize(resultsTable.getColumnModel().getColumn(valueColIndex) - .getWidth() - (i.left + i.right +CELL_RIGHT_MARGIN), //current width minus borders - Integer.MAX_VALUE); - rowHeight = (int) rootView.getPreferredSpan(View.Y_AXIS); - } else { - rowHeight = comp.getPreferredSize().height; - } - if (rowHeight > 0) { - resultsTable.setRowHeight(row, rowHeight + CELL_BOTTOM_MARGIN); - } - } - } - } - - /** - * Update the column widths so that the Value column has most of the space. - */ - private void updateColumnSizes() { - Enumeration columns = resultsTable.getColumnModel().getColumns(); - while (columns.hasMoreElements()) { - TableColumn col = columns.nextElement(); - if (col.getHeaderValue().equals(COLUMN_HEADERS[0])) { - col.setPreferredWidth(COLUMN_WIDTHS[0]); - } else if (col.getHeaderValue().equals(COLUMN_HEADERS[1])) { - col.setPreferredWidth(COLUMN_WIDTHS[1]); - } else if (col.getHeaderValue().equals(COLUMN_HEADERS[2])) { - col.setPreferredWidth(COLUMN_WIDTHS[2]); - } - } } /** @@ -204,9 +93,6 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; - rightClickMenu = new javax.swing.JPopupMenu(); - copyMenuItem = new javax.swing.JMenuItem(); - selectAllMenuItem = new javax.swing.JMenuItem(); jScrollPane1 = new javax.swing.JScrollPane(); jPanel1 = new javax.swing.JPanel(); totalPageLabel = new javax.swing.JLabel(); @@ -218,13 +104,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat prevPageButton = new javax.swing.JButton(); artifactLabel = new javax.swing.JLabel(); filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); - resultsTableScrollPane = new javax.swing.JScrollPane(); - - copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.copyMenuItem.text")); // NOI18N - rightClickMenu.add(copyMenuItem); - - selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.selectAllMenuItem.text")); // NOI18N - rightClickMenu.add(selectAllMenuItem); + artifactContentPanel = new javax.swing.JPanel(); setPreferredSize(new java.awt.Dimension(100, 58)); @@ -341,43 +221,41 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat jScrollPane1.setViewportView(jPanel1); - resultsTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - resultsTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(620, 34)); + artifactContentPanel.setLayout(new javax.swing.OverlayLayout(artifactContentPanel)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1) - .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 561, Short.MAX_VALUE) + .addComponent(artifactContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(artifactContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 397, Short.MAX_VALUE)) ); }// //GEN-END:initComponents private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed - currentPage = currentPage + 1; + currentPage += 1; currentPageLabel.setText(Integer.toString(currentPage)); - artifactLabel.setText(artifactTableContents.get(currentPage - 1).getArtifactDisplayName()); + artifactLabel.setText(artifactTableContents.get(currentPage - 1).getDisplayName()); startNewTask(new SelectedArtifactChangedTask(currentPage)); }//GEN-LAST:event_nextPageButtonActionPerformed private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed - currentPage = currentPage - 1; + currentPage -= 1; currentPageLabel.setText(Integer.toString(currentPage)); - artifactLabel.setText(artifactTableContents.get(currentPage - 1).getArtifactDisplayName()); + artifactLabel.setText(artifactTableContents.get(currentPage - 1).getDisplayName()); startNewTask(new SelectedArtifactChangedTask(currentPage)); }//GEN-LAST:event_prevPageButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel artifactContentPanel; private javax.swing.JLabel artifactLabel; - private javax.swing.JMenuItem copyMenuItem; private javax.swing.JLabel currentPageLabel; private javax.swing.Box.Filler filler1; private javax.swing.JPanel jPanel1; @@ -387,41 +265,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private javax.swing.JLabel pageLabel; private javax.swing.JLabel pageLabel2; private javax.swing.JButton prevPageButton; - private javax.swing.JScrollPane resultsTableScrollPane; - private javax.swing.JPopupMenu rightClickMenu; - private javax.swing.JMenuItem selectAllMenuItem; private javax.swing.JLabel totalPageLabel; // End of variables declaration//GEN-END:variables - private ETable resultsTable; - private void customizeComponents() { - resultsTable.setComponentPopupMenu(rightClickMenu); - ActionListener actList = new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JMenuItem jmi = (JMenuItem) e.getSource(); - if (jmi.equals(copyMenuItem)) { - StringBuilder selectedText = new StringBuilder(512); - for (int row : resultsTable.getSelectedRows()) { - for (int col : resultsTable.getSelectedColumns()) { - selectedText.append((String) resultsTable.getValueAt(row, col)); - selectedText.append("\t"); - } - //if its the last row selected don't add a new line - if (row != resultsTable.getSelectedRows()[resultsTable.getSelectedRows().length - 1]) { - selectedText.append(System.lineSeparator()); - } - } - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(selectedText.toString()), null); - } else if (jmi.equals(selectAllMenuItem)) { - resultsTable.selectAll(); - } - } - }; - copyMenuItem.addActionListener(actList); - - selectAllMenuItem.addActionListener(actList); - } /** * Resets the components to an empty view state. @@ -431,10 +277,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat currentPageLabel.setText(""); artifactLabel.setText(""); totalPageLabel.setText(""); - ((DefaultTableModel) resultsTable.getModel()).setRowCount(0); + prevPageButton.setEnabled(false); nextPageButton.setEnabled(false); currentNode = null; + + artifactContentPanel.removeAll(); } @Override @@ -521,183 +369,13 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } } - /** - * This class is a container to hold the data necessary for each of the - * result pages associated with file or artifact beivng viewed. - */ - private class ResultsTableArtifact { - - private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - private String[][] rowData = null; - private final String artifactDisplayName; - private final Content content; - - ResultsTableArtifact(BlackboardArtifact artifact, Content content) { - artifactDisplayName = artifact.getDisplayName(); - this.content = content; - addRows(artifact); - } - - ResultsTableArtifact(String errorMsg) { - artifactDisplayName = errorMsg; - rowData = new String[1][3]; - rowData[0] = new String[]{"", errorMsg, ""}; - content = null; - } - - private String[][] getRows() { - return rowData; - } - - private void addRows(BlackboardArtifact artifact) { - List rowsToAdd = new ArrayList<>(); - try { - /* - * Add rows for each attribute. - */ - for (BlackboardAttribute attr : artifact.getAttributes()) { - /* - * Attribute value column. - */ - String value = ""; - switch (attr.getAttributeType().getValueType()) { - case STRING: - case INTEGER: - case LONG: - case DOUBLE: - case BYTE: - default: - value = attr.getDisplayString(); - break; - // Use Autopsy date formatting settings, not TSK defaults - case DATETIME: - value = epochTimeToString(attr.getValueLong()); - break; - case JSON: - // Get the attribute's JSON value and convert to indented multiline display string - String jsonVal = attr.getValueString(); - JsonParser parser = new JsonParser(); - JsonObject json = parser.parse(jsonVal).getAsJsonObject(); - - value = toJsonDisplayString(json, ""); - break; - } - /* - * Attribute sources column. - */ - String sources = StringUtils.join(attr.getSources(), ", "); - rowsToAdd.add(new String[]{attr.getAttributeType().getDisplayName(), value, sources}); - } - /* - * Add a row for the source content path. - */ - String path = ""; - try { - if (null != content) { - path = content.getUniquePath(); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error getting source content path for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); - path = Bundle.DataContentViewerArtifact_failedToGetSourcePath_message(); - } - rowsToAdd.add(new String[]{"Source File Path", path, ""}); - /* - * Add a row for the artifact id. - */ - rowsToAdd.add(new String[]{"Artifact ID", Long.toString(artifact.getArtifactID()), ""}); - } catch (TskCoreException ex) { - rowsToAdd.add(new String[]{"", Bundle.DataContentViewerArtifact_failedToGetAttributes_message(), ""}); - } - rowData = rowsToAdd.toArray(new String[0][0]); - } - - /** - * @return the artifactDisplayName - */ - String getArtifactDisplayName() { - return artifactDisplayName; - } + private ArtifactContentViewer getSupportingViewer(BlackboardArtifact artifact) { - private static final String INDENT_RIGHT = " "; - private static final String NEW_LINE = "\n"; - - /** - * Recursively converts a JSON element into an indented multi-line - * display string. - * - * @param element JSON element to convert - * @param startIndent Starting indentation for the element. - * - * @return A multi-line display string. - */ - private String toJsonDisplayString(JsonElement element, String startIndent) { - - StringBuilder sb = new StringBuilder(""); - JsonObject obj = element.getAsJsonObject(); - - for (Map.Entry entry : obj.entrySet()) { - appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb ); - } - - String returnString = sb.toString(); - if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) { - returnString = returnString.substring(NEW_LINE.length()); - } - return returnString; - } - - - /** - * Converts the given JSON element into string and appends to the given string builder. - * - * @param jsonKey - * @param jsonElement - * @param startIndent Starting indentation for the element. - * @param sb String builder to append to. - */ - private void appendJsonElementToString(String jsonKey, JsonElement jsonElement, String startIndent, StringBuilder sb) { - if (jsonElement.isJsonArray()) { - JsonArray jsonArray = jsonElement.getAsJsonArray(); - if (jsonArray.size() > 0) { - int count = 1; - sb.append(NEW_LINE).append(String.format("%s%s", startIndent, jsonKey)); - for (JsonElement arrayMember : jsonArray) { - sb.append(NEW_LINE).append(String.format("%s%d", startIndent.concat(INDENT_RIGHT), count)); - sb.append(toJsonDisplayString(arrayMember, startIndent.concat(INDENT_RIGHT).concat(INDENT_RIGHT))); - count++; - } - } - } else if (jsonElement.isJsonObject()) { - sb.append(NEW_LINE).append(String.format("%s%s %s", startIndent, jsonKey, toJsonDisplayString(jsonElement.getAsJsonObject(), startIndent + INDENT_RIGHT))); - } else if (jsonElement.isJsonPrimitive()) { - String attributeName = jsonKey; - String attributeValue; - if (attributeName.toUpperCase().contains("DATETIME")) { - attributeValue = epochTimeToString(Long.parseLong(jsonElement.getAsString())); - } else { - attributeValue = jsonElement.getAsString(); - } - sb.append(NEW_LINE).append(String.format("%s%s = %s", startIndent, attributeName, attributeValue)); - } else if (jsonElement.isJsonNull()) { - sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey)); - } - } - - /** - * Converts epoch time to readable string. - * - * @param epochTime epoch time value to be converted to string. - * @return String with human readable time. - */ - private String epochTimeToString(long epochTime) { - String dateTimeString = "0000-00-00 00:00:00"; - if (null != content && 0 != epochTime) { - dateFormatter.setTimeZone(ContentUtils.getTimeZone(content)); - dateTimeString = dateFormatter.format(new java.util.Date(epochTime * 1000)); - } - return dateTimeString; - } - + return this.KNOWN_ARTIFACT_VIEWERS.stream() + .filter(knownViewer -> knownViewer.isSupported(artifact)) + .findAny() + .orElse(new DefaultArtifactContentViewer()); + } /** @@ -708,18 +386,21 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat int numberOfPages; int currentPage; - ResultsTableArtifact tableContents; + BlackboardArtifact artifact; + String errorMsg; - ViewUpdate(int numberOfPages, int currentPage, ResultsTableArtifact contents) { + ViewUpdate(int numberOfPages, int currentPage, BlackboardArtifact artifact) { this.currentPage = currentPage; this.numberOfPages = numberOfPages; - this.tableContents = contents; + this.artifact = artifact; + this.errorMsg = null; } ViewUpdate(int numberOfPages, int currentPage, String errorMsg) { this.currentPage = currentPage; this.numberOfPages = numberOfPages; - this.tableContents = new ResultsTableArtifact(errorMsg); + this.errorMsg = errorMsg; + this.artifact = null; } } @@ -738,14 +419,26 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat currentPage = viewUpdate.currentPage; totalPageLabel.setText(Integer.toString(viewUpdate.numberOfPages)); currentPageLabel.setText(Integer.toString(currentPage)); - artifactLabel.setText(viewUpdate.tableContents.getArtifactDisplayName()); - DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel()); - tModel.setDataVector(viewUpdate.tableContents.getRows(), COLUMN_HEADERS); - updateColumnSizes(); - updateRowHeights(); - resultsTable.clearSelection(); + + + artifactContentPanel.removeAll(); + + if (viewUpdate.artifact != null) { + artifactLabel.setText(viewUpdate.artifact.getDisplayName()); + BlackboardArtifact artifact = viewUpdate.artifact; + ArtifactContentViewer viewer = this.getSupportingViewer(artifact); + viewer.setArtifact(artifact); + + artifactContentPanel.add(viewer.getComponent()); + } else { + artifactLabel.setText(viewUpdate.errorMsg); + } + + artifactContentPanel.revalidate(); this.setCursor(null); + + this.revalidate(); } /** @@ -755,13 +448,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat * @param task A new SwingWorker object to execute as a background thread. */ private synchronized void startNewTask(SwingWorker task) { - String[][] waitRow = new String[1][3]; - waitRow[0] = new String[]{"", WAIT_TEXT, ""}; - DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel()); - tModel.setDataVector(waitRow, COLUMN_HEADERS); - updateColumnSizes(); - updateRowHeights(); - resultsTable.clearSelection(); + // The output of the previous task is no longer relevant. if (currentTask != null) { // This call sets a cancellation flag. It does not terminate the background thread running the task. @@ -775,12 +462,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } /** - * Populate the cache of artifact represented as ResultsTableArtifacts. + * Populate the cache of artifacts represented as ResultsTableArtifacts. * * @param artifactList A list of ResultsTableArtifact representations of * artifacts. */ - private void setArtifactContents(List artifactList) { + private void setArtifactContents(List artifactList) { synchronized (lock) { this.artifactTableContents = artifactList; } @@ -789,11 +476,11 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat /** * Retrieve the cache of artifact represented as ResultsTableArtifacts. * - * @return A list of ResultsTableArtifact representations of artifacts. + * @return A list of artifacts. */ - private List getArtifactContents() { + private List getArtifactContents() { synchronized (lock) { - return artifactTableContents; + return Collections.unmodifiableList(artifactTableContents); } } @@ -843,9 +530,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } // Build the new artifact contents cache. - ArrayList artifactContents = new ArrayList<>(); + ArrayList artifactContents = new ArrayList<>(); for (BlackboardArtifact artifact : artifacts) { - artifactContents.add(new ResultsTableArtifact(artifact, underlyingContent)); + artifactContents.add(artifact); } // If the node has an underlying blackboard artifact, show it. If not, @@ -932,14 +619,14 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat protected ViewUpdate doInBackground() { // Get the artifact content to display from the cache. Note that one must be subtracted from the // page index to get the corresponding artifact content index. - List artifactContents = getArtifactContents(); - ResultsTableArtifact artifactContent = artifactContents.get(pageIndex - 1); - + List artifactContents = getArtifactContents(); + // It may take a considerable amount of time to fetch the attributes of the selected artifact so check for cancellation. if (isCancelled()) { return null; } + BlackboardArtifact artifactContent = artifactContents.get(pageIndex - 1); return new ViewUpdate(artifactContents.size(), pageIndex, artifactContent); } @@ -957,27 +644,4 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } } } - - /** - * TableCellRenderer for displaying multiline text. - */ - private class MultiLineTableCellRenderer implements javax.swing.table.TableCellRenderer { - - @Override - public Component getTableCellRendererComponent(javax.swing.JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - javax.swing.JTextArea jtex = new javax.swing.JTextArea(); - if (value instanceof String) { - jtex.setText((String) value); - jtex.setLineWrap(true); - jtex.setWrapStyleWord(false); - } - //cell backgroud color when selected - if (isSelected) { - jtex.setBackground(javax.swing.UIManager.getColor("Table.selectionBackground")); - } else { - jtex.setBackground(javax.swing.UIManager.getColor("Table.background")); - } - return jtex; - } - } }