19 package org.sleuthkit.autopsy.keywordsearch.multicase;
 
   21 import com.google.common.eventbus.Subscribe;
 
   22 import com.google.common.eventbus.DeadEvent;
 
   23 import java.awt.Color;
 
   24 import java.awt.Component;
 
   25 import java.awt.Dimension;
 
   26 import java.io.BufferedWriter;
 
   28 import java.io.FileWriter;
 
   29 import java.io.IOException;
 
   30 import java.lang.reflect.InvocationTargetException;
 
   31 import java.util.ArrayList;
 
   32 import java.util.Collection;
 
   33 import java.util.Collections;
 
   34 import java.util.Enumeration;
 
   35 import java.util.HashMap;
 
   36 import java.util.HashSet;
 
   37 import java.util.List;
 
   39 import java.util.logging.Level;
 
   40 import java.util.stream.Collectors;
 
   41 import javax.swing.AbstractButton;
 
   42 import javax.swing.DefaultListModel;
 
   43 import javax.swing.DefaultListSelectionModel;
 
   44 import javax.swing.JCheckBox;
 
   45 import javax.swing.JFileChooser;
 
   46 import javax.swing.table.TableColumn;
 
   47 import javax.swing.JOptionPane;
 
   48 import javax.swing.JTable;
 
   49 import javax.swing.ListModel;
 
   50 import javax.swing.ListSelectionModel;
 
   51 import javax.swing.SwingUtilities;
 
   52 import javax.swing.filechooser.FileNameExtensionFilter;
 
   53 import javax.swing.table.TableCellRenderer;
 
   54 import org.netbeans.swing.outline.DefaultOutlineModel;
 
   55 import org.openide.explorer.ExplorerManager;
 
   56 import org.netbeans.swing.outline.Outline;
 
   57 import org.openide.nodes.Children;
 
   58 import org.openide.nodes.Node;
 
   59 import org.openide.util.NbBundle.Messages;
 
   60 import org.openide.windows.WindowManager;
 
   70 final class MultiCaseKeywordSearchPanel 
extends javax.swing.JPanel implements ExplorerManager.Provider {
 
   73         "MultiCaseKeywordSearchPanel.emptyNode.waitText=Please Wait..." 
   75     private static final long serialVersionUID = 1L;
 
   76     private volatile SearchThread searchThread = null;
 
   77     private final Outline outline;
 
   78     private final ExplorerManager em;
 
   79     private final org.openide.explorer.view.OutlineView outlineView;
 
   80     private static final Logger LOGGER = Logger.getLogger(MultiCaseKeywordSearchPanel.class.getName());
 
   81     private static final EmptyNode PLEASE_WAIT_NODE = 
new EmptyNode(Bundle.MultiCaseKeywordSearchPanel_emptyNode_waitText());
 
   82     private static final MultiCaseKeywordSearchNode NO_RESULTS_NODE = 
new MultiCaseKeywordSearchNode(
new ArrayList<>());
 
   83     private Collection<SearchHit> allSearchHits = 
new ArrayList<>();
 
   84     private Collection<MultiCaseSearcherException> searchExceptions = 
new ArrayList<>();
 
   85     private final SelectMultiUserCasesDialog caseSelectionDialog = SelectMultiUserCasesDialog.getInstance();
 
   86     private final Map<String, CaseNodeData> caseNameToCaseDataMap;
 
   87     private Node[] currentConfirmedSelections;
 
   92     MultiCaseKeywordSearchPanel() {
 
   93         em = 
new ExplorerManager();
 
   94         outlineView = 
new org.openide.explorer.view.OutlineView();
 
   95         outline = outlineView.getOutline();
 
   96         outlineView.setPropertyColumns(
 
   97                 Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(), Bundle.MultiCaseKeywordSearchNode_properties_caseDirectory(),
 
   98                 Bundle.MultiCaseKeywordSearchNode_properties_dataSource(), Bundle.MultiCaseKeywordSearchNode_properties_dataSource(),
 
   99                 Bundle.MultiCaseKeywordSearchNode_properties_path(), Bundle.MultiCaseKeywordSearchNode_properties_path(),
 
  100                 Bundle.MultiCaseKeywordSearchNode_properties_sourceType(), Bundle.MultiCaseKeywordSearchNode_properties_sourceType(),
 
  101                 Bundle.MultiCaseKeywordSearchNode_properties_source(), Bundle.MultiCaseKeywordSearchNode_properties_source());
 
  102         ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.MultiCaseKeywordSearchNode_properties_case());
 
  104         outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 
  105         outline.setRootVisible(
false);
 
  106         outlineView.setPreferredSize(resultsScrollPane.getPreferredSize());
 
  107         resultsScrollPane.setViewportView(outlineView);
 
  108         caseSelectionDialog.subscribeToNewCaseSelections(
new ChangeListener() {
 
  110             public void nodeSelectionChanged(Node[] selections, List<CaseNodeData> selectionCaseData) {
 
  111                 populateCasesList(selectionCaseData);
 
  112                 currentConfirmedSelections = selections;
 
  118         outline.setRowSelectionAllowed(
false);
 
  119         searchProgressBar.setVisible(
false);
 
  120         exportButton.setEnabled(
false);
 
  121         outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
 
  122         caseNameToCaseDataMap = 
new HashMap<>();
 
  126         caseSelectionList.setSelectionModel(
new DefaultListSelectionModel() {
 
  128             public void setSelectionInterval(
int index0, 
int index1) {
 
  129                 super.setSelectionInterval(-1, -1);
 
  147     @Messages({
"MultiCaseKeywordSearchPanel.countOfResults.label=Count: "})
 
  149     void subscribeToResults(Collection<SearchHit> hits) {
 
  150         allSearchHits.addAll(hits);
 
  151         if (allSearchHits.size() > 0) {
 
  152             MultiCaseKeywordSearchNode resultsNode = 
new MultiCaseKeywordSearchNode(allSearchHits);
 
  153             SwingUtilities.invokeLater(() -> {
 
  154                 em.setRootContext(resultsNode);
 
  155                 outline.setRowSelectionAllowed(
true);
 
  156                 resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + Integer.toString(outline.getRowCount()));
 
  159             em.setRootContext(NO_RESULTS_NODE);
 
  160             resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + 0);
 
  172     void subscribeToStrings(String stringReceived) {
 
  173         if (stringReceived.equals(MultiCaseSearcher.getSearchCompleteMessage())) {
 
  174             searchThread.unregisterWithSearcher(MultiCaseKeywordSearchPanel.this);
 
  177             if (!searchExceptions.isEmpty()) {
 
  178                 warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_errorsEncounter_text(searchExceptions.size()));
 
  180             if (!em.getRootContext().equals(PLEASE_WAIT_NODE) && !em.getRootContext().equals(NO_RESULTS_NODE)) {
 
  181                 exportButton.setEnabled(
true);
 
  182                 SwingUtilities.invokeLater(() -> {
 
  183                     exportButton.setEnabled(
true);
 
  189             LOGGER.log(Level.INFO, 
"String posted to MultiCaseKeywordSearchPanel EventBus with value of " + stringReceived);
 
  200     void subscribeToInterruptionExceptions(InterruptedException exception) {
 
  201         warningLabel.setText(exception.getMessage());
 
  203         if (em.getRootContext().equals(PLEASE_WAIT_NODE)) {
 
  204             em.setRootContext(NO_RESULTS_NODE);
 
  205             resultsCountLabel.setText(Bundle.MultiCaseKeywordSearchPanel_countOfResults_label() + 0);
 
  215     @Messages({
"# {0} - numberOfErrors",
 
  216         "MultiCaseKeywordSearchPanel.errorsEncounter.text={0} Error(s) encountered while performing search" 
  219     void subscribeToMultiCaseSearcherExceptions(MultiCaseSearcherException exception) {
 
  220         searchExceptions.add(exception);
 
  231     void subscribeToDeadEvents(DeadEvent deadEvent) {
 
  232         LOGGER.log(Level.INFO, 
"Dead Event posted to MultiCaseKeywordSearchPanel EventBus " + deadEvent.toString());
 
  235     private void displaySearchErrors() {
 
  236         if (!searchExceptions.isEmpty()) {
 
  237             StringBuilder strBuilder = 
new StringBuilder(
"");
 
  238             searchExceptions.forEach((exception) -> {
 
  239                 strBuilder.append(
"- ").append(exception.getMessage()).append(System.lineSeparator());
 
  241             SwingUtilities.invokeLater(() -> {
 
  242                 new MultiCaseKeywordSearchErrorDialog(strBuilder.toString());
 
  251     private void populateCasesList(List<CaseNodeData> selectedNodes) {
 
  252         caseSelectionList.removeAll();
 
  253         caseSelectionList.revalidate();
 
  254         caseSelectionList.repaint();
 
  255         caseNameToCaseDataMap.clear();
 
  256         DefaultListModel<String> listModel = 
new DefaultListModel<>();
 
  257         Collections.sort(selectedNodes, (CaseNodeData o1, CaseNodeData o2) -> {
 
  258             return o1.getName().toLowerCase()
 
  259                     .compareTo(o2.getName().toLowerCase()); 
 
  262         for (
int i = 0; i < selectedNodes.size(); i++) {
 
  263             CaseNodeData data = selectedNodes.get(i);
 
  264             String multiUserCaseName = data.getName();
 
  265             listModel.addElement(multiUserCaseName);
 
  270             caseNameToCaseDataMap.put(multiUserCaseName, data);
 
  272         caseSelectionList.setModel(listModel);
 
  276     public ExplorerManager getExplorerManager() {
 
  285     @SuppressWarnings(
"unchecked")
 
  287     private 
void initComponents() {
 
  289         searchTypeGroup = 
new javax.swing.ButtonGroup();
 
  290         searchButton = 
new javax.swing.JButton();
 
  291         substringRadioButton = 
new javax.swing.JRadioButton();
 
  292         keywordTextField = 
new javax.swing.JTextField();
 
  293         exactRadioButton = 
new javax.swing.JRadioButton();
 
  294         regexRadioButton = 
new javax.swing.JRadioButton();
 
  295         casesLabel = 
new javax.swing.JLabel();
 
  296         resultsLabel = 
new javax.swing.JLabel();
 
  297         toolDescriptionScrollPane = 
new javax.swing.JScrollPane();
 
  298         toolDescriptionTextArea = 
new javax.swing.JTextArea();
 
  299         resultsScrollPane = 
new javax.swing.JScrollPane();
 
  300         cancelButton = 
new javax.swing.JButton();
 
  301         searchProgressBar = 
new javax.swing.JProgressBar();
 
  302         warningLabel = 
new javax.swing.JLabel();
 
  303         exportButton = 
new javax.swing.JButton();
 
  304         resultsCountLabel = 
new javax.swing.JLabel();
 
  305         viewErrorsButton = 
new javax.swing.JButton();
 
  306         pickCasesButton = 
new javax.swing.JButton();
 
  307         jScrollPane1 = 
new javax.swing.JScrollPane();
 
  308         caseSelectionList = 
new javax.swing.JList<>();
 
  312         setPreferredSize(
new java.awt.Dimension(1000, 442));
 
  314         org.openide.awt.Mnemonics.setLocalizedText(searchButton, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.searchButton.text")); 
 
  315         searchButton.setMaximumSize(
new java.awt.Dimension(84, 23));
 
  316         searchButton.setMinimumSize(
new java.awt.Dimension(84, 23));
 
  317         searchButton.setPreferredSize(
new java.awt.Dimension(84, 23));
 
  318         searchButton.addActionListener(
new java.awt.event.ActionListener() {
 
  319             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  320                 searchButtonActionPerformed(evt);
 
  324         searchTypeGroup.add(substringRadioButton);
 
  325         org.openide.awt.Mnemonics.setLocalizedText(substringRadioButton, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.substringRadioButton.text_1")); 
 
  326         substringRadioButton.addActionListener(
new java.awt.event.ActionListener() {
 
  327             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  328                 substringRadioButtonActionPerformed(evt);
 
  332         keywordTextField.setFont(
new java.awt.Font(
"Monospaced", 0, 14)); 
 
  333         keywordTextField.setText(
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.keywordTextField.text_1")); 
 
  334         keywordTextField.setBorder(
new javax.swing.border.LineBorder(
new java.awt.Color(192, 192, 192), 1, 
true));
 
  335         keywordTextField.setMinimumSize(
new java.awt.Dimension(2, 25));
 
  336         keywordTextField.setPreferredSize(
new java.awt.Dimension(2, 25));
 
  338         searchTypeGroup.add(exactRadioButton);
 
  339         exactRadioButton.setSelected(
true);
 
  340         org.openide.awt.Mnemonics.setLocalizedText(exactRadioButton, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.exactRadioButton.text_1")); 
 
  342         searchTypeGroup.add(regexRadioButton);
 
  343         org.openide.awt.Mnemonics.setLocalizedText(regexRadioButton, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.regexRadioButton.text_1")); 
 
  345         org.openide.awt.Mnemonics.setLocalizedText(casesLabel, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.casesLabel.text_1")); 
 
  347         org.openide.awt.Mnemonics.setLocalizedText(resultsLabel, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.resultsLabel.text")); 
 
  349         toolDescriptionTextArea.setEditable(
false);
 
  350         toolDescriptionTextArea.setBackground(
new java.awt.Color(240, 240, 240));
 
  351         toolDescriptionTextArea.setColumns(20);
 
  352         toolDescriptionTextArea.setFont(
new java.awt.Font(
"Tahoma", 0, 11)); 
 
  353         toolDescriptionTextArea.setLineWrap(
true);
 
  354         toolDescriptionTextArea.setRows(3);
 
  355         toolDescriptionTextArea.setText(
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.toolDescriptionTextArea.text")); 
 
  356         toolDescriptionTextArea.setWrapStyleWord(
true);
 
  357         toolDescriptionTextArea.setFocusable(
false);
 
  358         toolDescriptionScrollPane.setViewportView(toolDescriptionTextArea);
 
  360         resultsScrollPane.setMinimumSize(
new java.awt.Dimension(100, 40));
 
  361         resultsScrollPane.setPreferredSize(
new java.awt.Dimension(200, 100));
 
  362         resultsScrollPane.setRequestFocusEnabled(
false);
 
  364         org.openide.awt.Mnemonics.setLocalizedText(cancelButton, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.cancelButton.text")); 
 
  365         cancelButton.setEnabled(
false);
 
  366         cancelButton.addActionListener(
new java.awt.event.ActionListener() {
 
  367             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  368                 cancelButtonActionPerformed(evt);
 
  372         warningLabel.setForeground(
new java.awt.Color(200, 0, 0));
 
  373         org.openide.awt.Mnemonics.setLocalizedText(warningLabel, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.warningLabel.text")); 
 
  374         warningLabel.setFocusable(
false);
 
  376         org.openide.awt.Mnemonics.setLocalizedText(exportButton, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.exportButton.text")); 
 
  377         exportButton.setMargin(
new java.awt.Insets(2, 2, 2, 2));
 
  378         exportButton.setMaximumSize(
new java.awt.Dimension(84, 23));
 
  379         exportButton.setMinimumSize(
new java.awt.Dimension(84, 23));
 
  380         exportButton.setPreferredSize(
new java.awt.Dimension(84, 23));
 
  381         exportButton.addActionListener(
new java.awt.event.ActionListener() {
 
  382             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  383                 exportButtonActionPerformed(evt);
 
  387         resultsCountLabel.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
 
  388         org.openide.awt.Mnemonics.setLocalizedText(resultsCountLabel, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.resultsCountLabel.text")); 
 
  390         org.openide.awt.Mnemonics.setLocalizedText(viewErrorsButton, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.viewErrorsButton.text")); 
 
  391         viewErrorsButton.setEnabled(
false);
 
  392         viewErrorsButton.addActionListener(
new java.awt.event.ActionListener() {
 
  393             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  394                 viewErrorsButtonActionPerformed(evt);
 
  398         org.openide.awt.Mnemonics.setLocalizedText(pickCasesButton, 
org.openide.util.NbBundle.getMessage(MultiCaseKeywordSearchPanel.class, 
"MultiCaseKeywordSearchPanel.pickCasesButton.text_1")); 
 
  399         pickCasesButton.addActionListener(
new java.awt.event.ActionListener() {
 
  400             public void actionPerformed(java.awt.event.ActionEvent evt) {
 
  401                 pickCasesButtonActionPerformed(evt);
 
  405         jScrollPane1.setViewportView(caseSelectionList);
 
  407         javax.swing.GroupLayout layout = 
new javax.swing.GroupLayout(
this);
 
  408         this.setLayout(layout);
 
  409         layout.setHorizontalGroup(
 
  410             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  411             .addGroup(layout.createSequentialGroup()
 
  413                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  414                     .addGroup(layout.createSequentialGroup()
 
  415                         .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  416                             .addGroup(layout.createSequentialGroup()
 
  417                                 .addComponent(exactRadioButton)
 
  418                                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
 
  419                                 .addComponent(substringRadioButton)
 
  420                                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  421                                 .addComponent(regexRadioButton))
 
  422                             .addComponent(keywordTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 591, Short.MAX_VALUE))
 
  423                         .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  424                         .addComponent(toolDescriptionScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 295, javax.swing.GroupLayout.PREFERRED_SIZE))
 
  425                     .addGroup(layout.createSequentialGroup()
 
  426                         .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, 
false)
 
  427                             .addComponent(casesLabel)
 
  428                             .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 174, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  429                             .addGroup(layout.createSequentialGroup()
 
  430                                 .addComponent(pickCasesButton, javax.swing.GroupLayout.PREFERRED_SIZE, 84, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  431                                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
 
  432                                 .addComponent(searchButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
 
  433                         .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
 
  434                         .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  435                             .addGroup(layout.createSequentialGroup()
 
  436                                 .addComponent(resultsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  437                                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
 
  438                                 .addComponent(resultsCountLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 98, javax.swing.GroupLayout.PREFERRED_SIZE))
 
  439                             .addComponent(resultsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
 
  440                             .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
 
  441                                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  442                                     .addComponent(viewErrorsButton)
 
  443                                     .addComponent(warningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 607, Short.MAX_VALUE))
 
  445                                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, 
false)
 
  446                                     .addComponent(exportButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
 
  447                                     .addComponent(cancelButton, javax.swing.GroupLayout.DEFAULT_SIZE, 87, Short.MAX_VALUE))))))
 
  449             .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  450                 .addGroup(layout.createSequentialGroup()
 
  451                     .addGap(196, 196, 196)
 
  452                     .addComponent(searchProgressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 608, Short.MAX_VALUE)
 
  453                     .addGap(108, 108, 108)))
 
  455         layout.setVerticalGroup(
 
  456             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  457             .addGroup(layout.createSequentialGroup()
 
  459                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, 
false)
 
  460                     .addGroup(layout.createSequentialGroup()
 
  461                         .addComponent(keywordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  462                         .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  463                         .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  464                             .addComponent(regexRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  465                             .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
 
  466                                 .addComponent(exactRadioButton)
 
  467                                 .addComponent(substringRadioButton))))
 
  468                     .addComponent(toolDescriptionScrollPane))
 
  469                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
 
  470                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  471                     .addComponent(casesLabel)
 
  472                     .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
 
  473                         .addComponent(resultsLabel)
 
  474                         .addComponent(resultsCountLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)))
 
  475                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  476                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  477                     .addComponent(resultsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 281, Short.MAX_VALUE)
 
  478                     .addComponent(jScrollPane1))
 
  479                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  480                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  481                     .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  482                     .addComponent(exportButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  483                     .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
 
  484                         .addComponent(pickCasesButton)
 
  485                         .addComponent(searchButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
 
  486                 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
 
  487                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  488                     .addComponent(viewErrorsButton)
 
  489                     .addComponent(cancelButton))
 
  491             .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
 
  492                 .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
 
  493                     .addContainerGap(433, Short.MAX_VALUE)
 
  494                     .addComponent(searchProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
 
  500         "MultiCaseKeywordSearchPanel.warningText.noCases=At least one case must be selected to perform a search.",
 
  501         "MultiCaseKeywordSearchPanel.warningText.emptySearch=You must enter something to search for in the text field." 
  507     private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  508         if (null == searchThread) {
 
  509             Collection<String> cases = getCases();
 
  510             String searchString = keywordTextField.getText();
 
  511             if (cases.isEmpty()) {
 
  512                 warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_warningText_noCases());
 
  513             } 
else if (searchString.isEmpty()) {
 
  514                 warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_warningText_emptySearch());
 
  517                 Collection<CaseNodeData> caseNodeData = cases.stream()
 
  518                         .map(c -> caseNameToCaseDataMap.get(c))
 
  519                         .collect(Collectors.toList());
 
  522                 warningLabel.setText(
"");
 
  523                 allSearchHits = 
new ArrayList<>();
 
  524                 searchExceptions = 
new ArrayList<>();
 
  525                 searchEnabled(
false);
 
  526                 exportButton.setEnabled(
false);
 
  527                 outline.setRowSelectionAllowed(
false);
 
  528                 SearchQuery kwsQuery = 
new SearchQuery(getQueryType(), searchString);
 
  529                 em.setRootContext(PLEASE_WAIT_NODE);
 
  530                 resultsCountLabel.setText(
"");
 
  531                 searchThread = 
new SearchThread(caseNodeData, kwsQuery);
 
  532                 searchThread.registerWithSearcher(MultiCaseKeywordSearchPanel.this);
 
  533                 searchThread.start();
 
  543     private Collection<String> getCases() {
 
  544         Collection<String> cases = 
new HashSet<>();
 
  545         ListModel<String> listModel = caseSelectionList.getModel();
 
  546         for(
int i = 0; i < listModel.getSize(); i++) {
 
  547             String caseName = listModel.getElementAt(i);
 
  558     private QueryType getQueryType() {
 
  559         String queryTypeText = 
"";
 
  560         Enumeration<AbstractButton> buttonGroup = searchTypeGroup.getElements();
 
  561         while (buttonGroup.hasMoreElements()) {
 
  562             AbstractButton dspButton = buttonGroup.nextElement();
 
  563             if (dspButton.isSelected()) {
 
  564                 queryTypeText = dspButton.getText();
 
  568         if (queryTypeText.equals(substringRadioButton.getText())) {
 
  569             return QueryType.SUBSTRING;
 
  570         } 
else if (queryTypeText.equals(regexRadioButton.getText())) {
 
  571             return QueryType.REGEX;
 
  574             return QueryType.EXACT_MATCH;
 
  582     private void setColumnWidths() {
 
  583         int widthLimit = 1000;
 
  586         for (
int col = 0; col < outline.getColumnCount(); col++) {
 
  588             int rowsToResize = Math.min(outline.getRowCount(), 100);
 
  589             for (
int row = 0; row < rowsToResize; row++) {
 
  590                 if (outline.getValueAt(row, col) != null) {
 
  591                     TableCellRenderer renderer = outline.getCellRenderer(row, col);
 
  592                     Component comp = outline.prepareRenderer(renderer, row, col);
 
  593                     width = Math.max(comp.getPreferredSize().width, width);
 
  597             width += 2 * margin + padding;
 
  598             width = Math.min(width, widthLimit);
 
  599             TableColumn column = outline.getColumnModel().getColumn(outline.convertColumnIndexToModel(col));
 
  600             column.setPreferredWidth(width);
 
  602         resultsScrollPane.setPreferredSize(
new Dimension(outline.getPreferredSize().width, resultsScrollPane.getPreferredSize().height));
 
  610     private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  618         "MultiCaseKeywordSearchPanel.searchThread.cancellingText=Cancelling search"})
 
  619     private void cancelSearch() {
 
  620         if (null != searchThread) {
 
  621             warningLabel.setText(Bundle.MultiCaseKeywordSearchPanel_searchThread_cancellingText());
 
  622             searchThread.interrupt();
 
  626     @Messages({
"MultiCaseKeywordSearchPanel.searchResultsExport.csvExtensionFilterlbl=Comma Separated Values File (csv)",
 
  627         "MultiCaseKeywordSearchPanel.searchResultsExport.featureName=Search Results Export",
 
  628         "MultiCaseKeywordSearchPanel.searchResultsExport.failedExportMsg=Export of search results failed" 
  634     private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  635         JFileChooser chooser = 
new JFileChooser();
 
  636         final String EXTENSION = 
"csv"; 
 
  637         FileNameExtensionFilter csvFilter = 
new FileNameExtensionFilter(
 
  638                 Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_csvExtensionFilterlbl(), EXTENSION);
 
  639         chooser.setFileFilter(csvFilter);
 
  640         chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
 
  641         chooser.setName(
"Choose file to export results to");
 
  642         chooser.setMultiSelectionEnabled(
false);
 
  643         int returnVal = chooser.showSaveDialog(
this);
 
  644         if (returnVal == JFileChooser.APPROVE_OPTION) {
 
  645             File selFile = chooser.getSelectedFile();
 
  646             if (selFile == null) {
 
  647                 JOptionPane.showMessageDialog(
this,
 
  648                         Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_failedExportMsg(),
 
  649                         Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
 
  650                         JOptionPane.WARNING_MESSAGE);
 
  651                 LOGGER.warning(
"Selected file was null, when trying to export search results");
 
  654             String fileAbs = selFile.getAbsolutePath();
 
  655             if (!fileAbs.endsWith(
"." + EXTENSION)) {
 
  656                 fileAbs = fileAbs + 
"." + EXTENSION;
 
  657                 selFile = 
new File(fileAbs);
 
  659             saveResultsAsTextFile(selFile);
 
  663     private void viewErrorsButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  664         displaySearchErrors();
 
  667     private void pickCasesButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  668         caseSelectionDialog.setVisible(
true);
 
  669         if (currentConfirmedSelections != null) {
 
  670             caseSelectionDialog.setNodeSelections(currentConfirmedSelections);
 
  675     private void substringRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {
 
  686     private void searchEnabled(
boolean canSearch) {
 
  687         searchButton.setEnabled(canSearch);
 
  688         cancelButton.setEnabled(!canSearch);
 
  689         viewErrorsButton.setEnabled(canSearch);
 
  690         viewErrorsButton.setVisible(!searchExceptions.isEmpty());
 
  693     @Messages({
"# {0} - file name",
 
  694         "MultiCaseKeywordSearchPanel.searchResultsExport.fileExistPrompt=File {0} exists, overwrite?",
 
  696         "MultiCaseKeywordSearchPanel.searchResultsExport.exportMsg=Search results exported to {0}" 
  701     private void saveResultsAsTextFile(File resultsFile) {
 
  702         if (resultsFile.exists()) {
 
  704             boolean shouldWrite = JOptionPane.showConfirmDialog(null,
 
  705                     Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_fileExistPrompt(resultsFile.getName()),
 
  706                     Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
 
  707                     JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)
 
  708                     == JOptionPane.YES_OPTION;
 
  714             BufferedWriter resultsWriter;
 
  715             resultsWriter = 
new BufferedWriter(
new FileWriter(resultsFile));
 
  718             while (col < outline.getColumnCount()) {
 
  720                 resultsWriter.write(outline.getColumnName(col));
 
  722                 if (col < outline.getColumnCount()) {
 
  723                     resultsWriter.write(
",");
 
  726             resultsWriter.write(System.lineSeparator());
 
  728             Children resultsChildren = em.getRootContext().getChildren();
 
  729             for (
int row = 0; row < resultsChildren.getNodesCount(); row++) {
 
  731                 while (col < outline.getColumnCount()) {
 
  732                     if (outline.getValueAt(row, col) instanceof Node.Property) {
 
  733                         resultsWriter.write(((Node.Property) outline.getValueAt(row, col)).getValue().toString());
 
  735                         resultsWriter.write(outline.getValueAt(row, col).toString());
 
  738                     if (col < outline.getColumnCount()) {
 
  739                         resultsWriter.write(
",");
 
  742                 resultsWriter.write(System.lineSeparator());
 
  744             resultsWriter.flush();
 
  745             resultsWriter.close();
 
  747             JOptionPane.showMessageDialog(
 
  748                     WindowManager.getDefault().getMainWindow(),
 
  749                     Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_exportMsg(resultsFile.getName()),
 
  750                     Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
 
  751                     JOptionPane.INFORMATION_MESSAGE);
 
  752         } 
catch (IllegalAccessException | IOException | InvocationTargetException ex) {
 
  753             JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
 
  754                     Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_failedExportMsg(),
 
  755                     Bundle.MultiCaseKeywordSearchPanel_searchResultsExport_featureName(),
 
  756                     JOptionPane.WARNING_MESSAGE);
 
  757             LOGGER.log(Level.WARNING, 
"Export of search results failed unable to write results csv file", ex);
 
  766         "MultiCaseKeywordSearchPanel.continueSearch.text=A search is currently being performed. " 
  767         + 
"Would you like the search to continue in the background while the search window is closed?",
 
  768         "MultiCaseKeywordSearchPanel.continueSearch.title=Closing multi-case search" 
  770     void closeSearchPanel() {
 
  771         if (cancelButton.isEnabled()) {
 
  772             boolean shouldContinueSearch = JOptionPane.showConfirmDialog(null,
 
  773                     Bundle.MultiCaseKeywordSearchPanel_continueSearch_text(),
 
  774                     Bundle.MultiCaseKeywordSearchPanel_continueSearch_title(),
 
  775                     JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)
 
  776                     == JOptionPane.YES_OPTION;
 
  777             if (!shouldContinueSearch) {
 
  784     private javax.swing.JButton cancelButton;
 
  785     private javax.swing.JList<String> caseSelectionList;
 
  786     private javax.swing.JLabel casesLabel;
 
  787     private javax.swing.JRadioButton exactRadioButton;
 
  788     private javax.swing.JButton exportButton;
 
  789     private javax.swing.JScrollPane jScrollPane1;
 
  790     private javax.swing.JTextField keywordTextField;
 
  791     private javax.swing.JButton pickCasesButton;
 
  792     private javax.swing.JRadioButton regexRadioButton;
 
  793     private javax.swing.JLabel resultsCountLabel;
 
  794     private javax.swing.JLabel resultsLabel;
 
  795     private javax.swing.JScrollPane resultsScrollPane;
 
  796     private javax.swing.JButton searchButton;
 
  797     private javax.swing.JProgressBar searchProgressBar;
 
  798     private javax.swing.ButtonGroup searchTypeGroup;
 
  799     private javax.swing.JRadioButton substringRadioButton;
 
  800     private javax.swing.JScrollPane toolDescriptionScrollPane;
 
  801     private javax.swing.JTextArea toolDescriptionTextArea;
 
  802     private javax.swing.JButton viewErrorsButton;
 
  803     private javax.swing.JLabel warningLabel;
 
  821         private SearchThread(Collection<CaseNodeData> caseNodes, SearchQuery searchQuery) {
 
  833             multiCaseSearcher.registerWithEventBus(
object);
 
  843             multiCaseSearcher.unregisterWithEventBus(
object);
 
  850             multiCaseSearcher.stopMultiCaseSearch();
 
  855             multiCaseSearcher.performKeywordSearch(caseNodes, searchQuery, 
new MultiCaseKeywordSearchProgressIndicator(searchProgressBar));
 
SearchThread(Collection< CaseNodeData > caseNodes, SearchQuery searchQuery)
final SearchQuery searchQuery
void registerWithSearcher(Object object)
final MultiCaseSearcher multiCaseSearcher
final Collection< CaseNodeData > caseNodes
void nodeSelectionChanged(Node[] selections, List< CaseNodeData > selectionCaseData)
void unregisterWithSearcher(Object object)