Autopsy  4.10.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
DirectoryTreeTopComponent.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.directorytree;
20 
21 import java.awt.Cursor;
22 import java.awt.EventQueue;
23 import java.beans.PropertyChangeEvent;
24 import java.beans.PropertyChangeListener;
25 import java.beans.PropertyVetoException;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.EnumSet;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.concurrent.ExecutionException;
36 import java.util.logging.Level;
37 import java.util.prefs.PreferenceChangeEvent;
38 import java.util.prefs.PreferenceChangeListener;
39 import javax.swing.Action;
40 import javax.swing.SwingUtilities;
41 import javax.swing.SwingWorker;
42 import javax.swing.event.PopupMenuEvent;
43 import javax.swing.event.PopupMenuListener;
44 import javax.swing.event.TreeExpansionEvent;
45 import javax.swing.event.TreeExpansionListener;
46 import javax.swing.tree.TreeSelectionModel;
47 import org.apache.commons.lang3.StringUtils;
48 import org.openide.explorer.ExplorerManager;
49 import org.openide.explorer.ExplorerUtils;
50 import org.openide.explorer.view.Visualizer;
51 import org.openide.nodes.AbstractNode;
52 import org.openide.nodes.Children;
53 import org.openide.nodes.Node;
54 import org.openide.nodes.NodeNotFoundException;
55 import org.openide.nodes.NodeOp;
56 import org.openide.util.NbBundle;
57 import org.openide.util.NbBundle.Messages;
58 import org.openide.windows.TopComponent;
59 import org.openide.windows.WindowManager;
89 import org.sleuthkit.datamodel.Account;
90 import org.sleuthkit.datamodel.BlackboardArtifact;
91 import org.sleuthkit.datamodel.BlackboardAttribute;
92 import org.sleuthkit.datamodel.Content;
93 import org.sleuthkit.datamodel.TskCoreException;
94 
98 // Registered as a service provider for DataExplorer in layer.xml
99 @Messages({
100  "DirectoryTreeTopComponent.resultsView.title=Listing"
101 })
102 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
103 public final class DirectoryTreeTopComponent extends TopComponent implements DataExplorer, ExplorerManager.Provider {
104 
105  private final transient ExplorerManager em = new ExplorerManager();
107  private final DataResultTopComponent dataResult = new DataResultTopComponent(Bundle.DirectoryTreeTopComponent_resultsView_title());
108  private final ViewPreferencesPanel viewPreferencesPanel = new ViewPreferencesPanel(true);
109  private final LinkedList<String[]> backList;
110  private final LinkedList<String[]> forwardList;
111  private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS
112  private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName());
114  private Children autopsyTreeChildren;
116  private boolean showRejectedResults;
117  private static final long DEFAULT_DATASOURCE_GROUPING_THRESHOLD = 5; // Threshold for prompting the user about grouping by data source
118  private static final String GROUPING_THRESHOLD_NAME = "GroupDataSourceThreshold";
119  private static final String SETTINGS_FILE = "CasePreferences.properties"; //NON-NLS
120 
125  initComponents();
126 
127  // only allow one item to be selected at a time
128  getTree().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
129  //Hook into the JTree and pre-expand the Views Node and Results node when a user
130  //expands an item in the tree that makes these nodes visible.
131  getTree().addTreeExpansionListener(new TreeExpansionListener() {
132  @Override
133  public void treeExpanded(TreeExpansionEvent event) {
134  //Bail immediately if we are not in the Group By view.
135  //Assumption here is that the views are already expanded.
137  return;
138  }
139 
140  Node expandedNode = Visualizer.findNode(event.getPath().getLastPathComponent());
141  for (Node child : em.getRootContext().getChildren().getNodes()) {
142  if (child.equals(expandedNode)) {
143  preExpandNodes(child.getChildren());
144  }
145  }
146  }
147 
148  @Override
149  public void treeCollapsed(TreeExpansionEvent event) {
150  //Do nothing
151  }
152 
153  });
154  // remove the close button
155  putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.TRUE);
156  setName(NbBundle.getMessage(DirectoryTreeTopComponent.class, "CTL_DirectoryTreeTopComponent"));
157  setToolTipText(NbBundle.getMessage(DirectoryTreeTopComponent.class, "HINT_DirectoryTreeTopComponent"));
158 
159  subscribeToChangeEvents();
160  associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
161 
162  // set the back & forward list and also disable the back & forward button
163  this.backList = new LinkedList<>();
164  this.forwardList = new LinkedList<>();
165  backButton.setEnabled(false);
166  forwardButton.setEnabled(false);
167 
168  viewPreferencesPopupMenu.add(viewPreferencesPanel);
169  viewPreferencesPopupMenu.setSize(viewPreferencesPanel.getPreferredSize().width + 6, viewPreferencesPanel.getPreferredSize().height + 6);
170  viewPreferencesPopupMenu.addPopupMenuListener(new PopupMenuListener() {
171  @Override
172  public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
173  openViewPreferencesButton.setSelected(true);
174  }
175 
176  @Override
177  public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
178  openViewPreferencesButton.setSelected(false);
179  }
180 
181  @Override
182  public void popupMenuCanceled(PopupMenuEvent e) {
183  openViewPreferencesButton.setSelected(false);
184  }
185  });
186  }
187 
194  private void preExpandNodes(Children rootChildren) {
195  ExpansionBeanTreeView tree = getTree();
196 
197  Node results = rootChildren.findChild(ResultsNode.NAME);
198  if (!Objects.isNull(results)) {
199  tree.expandNode(results);
200  Children resultsChildren = results.getChildren();
201  Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode);
202  }
203 
204  Node views = rootChildren.findChild(ViewsNode.NAME);
205  if (!Objects.isNull(views)) {
206  tree.expandNode(views);
207  }
208  }
209 
213  private void subscribeToChangeEvents() {
214  UserPreferences.addChangeListener(new PreferenceChangeListener() {
215  @Override
216  public void preferenceChange(PreferenceChangeEvent evt) {
217  switch (evt.getKey()) {
225  refreshContentTreeSafe();
226  break;
228  refreshTagsTree();
229  break;
232  // TODO: Need a way to refresh the Views subtree alone.
233  refreshContentTreeSafe();
234  break;
235  }
236  }
237  });
238 
240  this.em.addPropertyChangeListener(this);
243  }
244 
246  this.dataResult.requestActive();
247  }
248 
249  public void openDirectoryListing() {
250  this.dataResult.open();
251  }
252 
254  return this.dataResult;
255  }
256 
262  public boolean getShowRejectedResults() {
263  return showRejectedResults;
264  }
265 
272  public void setShowRejectedResults(boolean showRejectedResults) {
273  this.showRejectedResults = showRejectedResults;
274  if (accounts != null) {
275  accounts.setShowRejected(showRejectedResults);
276  }
277  }
278 
284  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
285  private void initComponents() {
286 
287  viewPreferencesPopupMenu = new javax.swing.JPopupMenu();
288  treeView = new ExpansionBeanTreeView();
289  backButton = new javax.swing.JButton();
290  forwardButton = new javax.swing.JButton();
291  openViewPreferencesButton = new javax.swing.JButton();
292 
293  treeView.setBorder(null);
294 
295  backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N
296  org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.backButton.text")); // NOI18N
297  backButton.setBorderPainted(false);
298  backButton.setContentAreaFilled(false);
299  backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled.png"))); // NOI18N
300  backButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
301  backButton.setMaximumSize(new java.awt.Dimension(55, 100));
302  backButton.setMinimumSize(new java.awt.Dimension(5, 5));
303  backButton.setPreferredSize(new java.awt.Dimension(24, 24));
304  backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover.png"))); // NOI18N
305  backButton.addActionListener(new java.awt.event.ActionListener() {
306  public void actionPerformed(java.awt.event.ActionEvent evt) {
307  backButtonActionPerformed(evt);
308  }
309  });
310 
311  forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward.png"))); // NOI18N
312  org.openide.awt.Mnemonics.setLocalizedText(forwardButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.forwardButton.text")); // NOI18N
313  forwardButton.setBorderPainted(false);
314  forwardButton.setContentAreaFilled(false);
315  forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled.png"))); // NOI18N
316  forwardButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
317  forwardButton.setMaximumSize(new java.awt.Dimension(55, 100));
318  forwardButton.setMinimumSize(new java.awt.Dimension(5, 5));
319  forwardButton.setPreferredSize(new java.awt.Dimension(24, 24));
320  forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover.png"))); // NOI18N
321  forwardButton.addActionListener(new java.awt.event.ActionListener() {
322  public void actionPerformed(java.awt.event.ActionEvent evt) {
323  forwardButtonActionPerformed(evt);
324  }
325  });
326 
327  openViewPreferencesButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/view-preferences-23.png"))); // NOI18N
328  org.openide.awt.Mnemonics.setLocalizedText(openViewPreferencesButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.openViewPreferencesButton.text")); // NOI18N
329  openViewPreferencesButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
330  openViewPreferencesButton.setBorderPainted(false);
331  openViewPreferencesButton.setContentAreaFilled(false);
332  openViewPreferencesButton.setMaximumSize(new java.awt.Dimension(24, 24));
333  openViewPreferencesButton.setMinimumSize(new java.awt.Dimension(24, 24));
334  openViewPreferencesButton.setPreferredSize(new java.awt.Dimension(24, 24));
335  openViewPreferencesButton.addActionListener(new java.awt.event.ActionListener() {
336  public void actionPerformed(java.awt.event.ActionEvent evt) {
337  openViewPreferencesButtonActionPerformed(evt);
338  }
339  });
340 
341  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
342  this.setLayout(layout);
343  layout.setHorizontalGroup(
344  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
345  .addComponent(treeView)
346  .addGroup(layout.createSequentialGroup()
347  .addContainerGap()
348  .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
349  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
350  .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
351  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 140, Short.MAX_VALUE)
352  .addComponent(openViewPreferencesButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
353  .addContainerGap())
354  );
355  layout.setVerticalGroup(
356  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
357  .addGroup(layout.createSequentialGroup()
358  .addGap(0, 0, 0)
359  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
360  .addComponent(openViewPreferencesButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
361  .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
362  .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
363  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
364  .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 919, Short.MAX_VALUE))
365  );
366  }// </editor-fold>//GEN-END:initComponents
367 
368  private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_backButtonActionPerformed
369  // change the cursor to "waiting cursor" for this operation
370  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
371 
372  // the end is the current place,
373  String[] currentNodePath = backList.pollLast();
374  forwardList.addLast(currentNodePath);
375  forwardButton.setEnabled(true);
376 
377  /*
378  * We peek instead of poll because we use its existence in the list
379  * later on so that we do not reset the forward list after the selection
380  * occurs.
381  */
382  String[] newCurrentNodePath = backList.peekLast();
383 
384  // enable / disable the back and forward button
385  if (backList.size() > 1) {
386  backButton.setEnabled(true);
387  } else {
388  backButton.setEnabled(false);
389  }
390 
391  // update the selection on directory tree
392  setSelectedNode(newCurrentNodePath, null);
393 
394  this.setCursor(null);
395  }//GEN-LAST:event_backButtonActionPerformed
396 
397  private void forwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_forwardButtonActionPerformed
398  // change the cursor to "waiting cursor" for this operation
399  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
400 
401  String[] newCurrentNodePath = forwardList.pollLast();
402  if (!forwardList.isEmpty()) {
403  forwardButton.setEnabled(true);
404  } else {
405  forwardButton.setEnabled(false);
406  }
407 
408  backList.addLast(newCurrentNodePath);
409  backButton.setEnabled(true);
410 
411  // update the selection on directory tree
412  setSelectedNode(newCurrentNodePath, null);
413 
414  this.setCursor(null);
415  }//GEN-LAST:event_forwardButtonActionPerformed
416 
417  private void openViewPreferencesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openViewPreferencesButtonActionPerformed
418  viewPreferencesPanel.load();
419  viewPreferencesPopupMenu.show(openViewPreferencesButton, 0, openViewPreferencesButton.getHeight() - 1);
420  }//GEN-LAST:event_openViewPreferencesButtonActionPerformed
421 
422  // Variables declaration - do not modify//GEN-BEGIN:variables
423  private javax.swing.JButton backButton;
424  private javax.swing.JButton forwardButton;
425  private javax.swing.JButton openViewPreferencesButton;
426  private javax.swing.JScrollPane treeView;
427  private javax.swing.JPopupMenu viewPreferencesPopupMenu;
428  // End of variables declaration//GEN-END:variables
429 
438  public static synchronized DirectoryTreeTopComponent getDefault() {
439  if (instance == null) {
440  instance = new DirectoryTreeTopComponent();
441  }
442  return instance;
443  }
444 
451  public static synchronized DirectoryTreeTopComponent findInstance() {
452  WindowManager winManager = WindowManager.getDefault();
453  TopComponent win = winManager.findTopComponent(PREFERRED_ID);
454  if (win == null) {
455  LOGGER.warning(
456  "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system."); //NON-NLS
457  return getDefault();
458  }
459  if (win instanceof DirectoryTreeTopComponent) {
460  return (DirectoryTreeTopComponent) win;
461  }
462  LOGGER.warning(
463  "There seem to be multiple components with the '" + PREFERRED_ID //NON-NLS
464  + "' ID. That is a potential source of errors and unexpected behavior."); //NON-NLS
465  return getDefault();
466  }
467 
474  @Override
475  public int getPersistenceType() {
476  return TopComponent.PERSISTENCE_NEVER;
477  }
478 
485  private void promptForDataSourceGrouping(int dataSourceCount) {
487  GroupDataSourcesDialog dialog = new GroupDataSourcesDialog(dataSourceCount);
488  dialog.display();
489  if (dialog.groupByDataSourceSelected()) {
491  refreshContentTreeSafe();
492  } else {
494  }
495  }
496  }
497 
505  @NbBundle.Messages({"# {0} - dataSourceCount",
506  "DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contains {0} data sources. Would you like to group by data source for faster loading?",
507  "DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source?"})
508  @Override
509  public void componentOpened() {
510  // change the cursor to "waiting cursor" for this operation
511  this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
512  Case currentCase = null;
513  try {
514  currentCase = Case.getCurrentCaseThrows();
515  } catch (NoCurrentCaseException ex) {
516  // No open case.
517  }
518 
519  // close the top component if there's no image in this case
520  if (null == currentCase || currentCase.hasData() == false) {
521  getTree().setRootVisible(false); // hide the root
522  } else {
523  // If the case contains a lot of data sources, and they aren't already grouping
524  // by data source, give the user the option to do so before loading the tree.
526  long threshold = DEFAULT_DATASOURCE_GROUPING_THRESHOLD;
527  if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME)) {
528  try {
529  threshold = Long.parseLong(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME));
530  } catch (NumberFormatException ex) {
531  LOGGER.log(Level.SEVERE, "Group data sources threshold is not a number", ex);
532  }
533  } else {
534  ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME, String.valueOf(threshold));
535  }
536 
537  try {
538  int dataSourceCount = currentCase.getDataSources().size();
539  if (!Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)
540  && dataSourceCount > threshold) {
541  promptForDataSourceGrouping(dataSourceCount);
542  }
543  } catch (TskCoreException ex) {
544  LOGGER.log(Level.SEVERE, "Error loading data sources", ex);
545  }
546  }
547 
548  // if there's at least one image, load the image and open the top componen
549  autopsyTreeChildFactory = new AutopsyTreeChildFactory();
550  autopsyTreeChildren = Children.create(autopsyTreeChildFactory, true);
551  Node root = new AbstractNode(autopsyTreeChildren) {
552  //JIRA-2807: What is the point of these overrides?
557  @Override
558  public Action[] getActions(boolean popup) {
559  return new Action[]{};
560  }
561 
562  // Overide the AbstractNode use of DefaultHandle to return
563  // a handle which can be serialized without a parent
564  @Override
565  public Node.Handle getHandle() {
566  return new Node.Handle() {
567  @Override
568  public Node getNode() throws IOException {
569  return em.getRootContext();
570  }
571  };
572  }
573  };
574 
575  root = new DirectoryTreeFilterNode(root, true);
576 
577  em.setRootContext(root);
578  em.getRootContext().setName(currentCase.getName());
579  em.getRootContext().setDisplayName(currentCase.getName());
580  getTree().setRootVisible(false); // hide the root
581 
582  // Reset the forward and back lists because we're resetting the root context
583  resetHistory();
584  new SwingWorker<Node[], Void>() {
585  @Override
586  protected Node[] doInBackground() throws Exception {
587  Children rootChildren = em.getRootContext().getChildren();
588  preExpandNodes(rootChildren);
589  /*
590  * JIRA-2806: What is this supposed to do? Right now it
591  * selects the data sources node, but the comment seems to
592  * indicate it is supposed to select the first datasource.
593  */
594  // select the first image node, if there is one
595  // (this has to happen after dataResult is opened, because the event
596  // of changing the selected node fires a handler that tries to make
597  // dataResult active)
598  if (rootChildren.getNodesCount() > 0) {
599  return new Node[]{rootChildren.getNodeAt(0)};
600  }
601  return new Node[]{};
602  }
603 
604  @Override
605  protected void done() {
606  super.done();
607 
608  // if the dataResult is not opened
609  if (!dataResult.isOpened()) {
610  dataResult.open(); // open the data result top component as well when the directory tree is opened
611  }
612  /*
613  * JIRA-2806: What is this supposed to do?
614  */
615  // select the first image node, if there is one
616  // (this has to happen after dataResult is opened, because the event
617  // of changing the selected node fires a handler that tries to make
618  // dataResult active)
619  try {
620  Node[] selections = get();
621  if (selections != null && selections.length > 0) {
622  em.setSelectedNodes(selections);
623  }
624  } catch (PropertyVetoException ex) {
625  LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS
626  } catch (InterruptedException | ExecutionException ex) {
627  LOGGER.log(Level.SEVERE, "Error expanding tree to initial state.", ex); //NON-NLS
628  } finally {
629  setCursor(null);
630  }
631  }
632  }.execute();
633  }
634  }
635 
642  @Override
643  public void componentClosed() {
644  //@@@ push the selection node to null?
645  autopsyTreeChildren = null;
646  }
647 
648  void writeProperties(java.util.Properties p) {
649  // better to version settings since initial version as advocated at
650  // http://wiki.apidesign.org/wiki/PropertyFiles
651  p.setProperty("version", "1.0");
652  // TODO store your settings
653  }
654 
655  Object readProperties(java.util.Properties p) {
656  if (instance == null) {
657  instance = this;
658  }
659  instance.readPropertiesImpl(p);
660  return instance;
661  }
662 
663  private void readPropertiesImpl(java.util.Properties p) {
664  String version = p.getProperty("version");
665  // TODO read your settings according to their version
666  }
667 
673  @Override
674  protected String preferredID() {
675  return PREFERRED_ID;
676  }
677 
678  @Override
679  public boolean canClose() {
680  /*
681  * Only allow the main tree view in the left side of the main window to
682  * be closed if there is no opne case or the open case has no data
683  * sources.
684  */
685  try {
686  Case openCase = Case.getCurrentCaseThrows();
687  return openCase.hasData() == false;
688  } catch (NoCurrentCaseException ex) {
689  return true;
690  }
691  }
692 
698  @Override
699  public ExplorerManager getExplorerManager() {
700  return this.em;
701  }
702 
708  @Override
709  public Action[] getActions() {
710  return new Action[]{};
711  }
712 
718  public Node getSelectedNode() {
719  Node result = null;
720 
721  Node[] selectedNodes = this.getExplorerManager().getSelectedNodes();
722  if (selectedNodes.length > 0) {
723  result = selectedNodes[0];
724  }
725  return result;
726  }
727 
734  @Override
735  public void propertyChange(PropertyChangeEvent event) {
737  String changed = event.getPropertyName();
738  if (changed.equals(Case.Events.CURRENT_CASE.toString())) { // changed current case
739  // When a case is closed, the old value of this property is the
740  // closed Case object and the new value is null. When a case is
741  // opened, the old value is null and the new value is the new Case
742  // object.
743  // @@@ This needs to be revisited. Perhaps case closed and case
744  // opened events instead of property change events would be a better
745  // solution. Either way, more probably needs to be done to clean up
746  // data model objects when a case is closed.
747  if (event.getOldValue() != null && event.getNewValue() == null) {
748  // The current case has been closed. Reset the ExplorerManager.
749  SwingUtilities.invokeLater(() -> {
750  Node emptyNode = new AbstractNode(Children.LEAF);
751  em.setRootContext(emptyNode);
752  });
753  } else if (event.getNewValue() != null) {
754  // A new case has been opened. Reset the ExplorerManager.
755  Case newCase = (Case) event.getNewValue();
756  final String newCaseName = newCase.getName();
757  SwingUtilities.invokeLater(() -> {
758  em.getRootContext().setName(newCaseName);
759  em.getRootContext().setDisplayName(newCaseName);
760 
761  // Reset the forward and back
762  // buttons. Note that a call to CoreComponentControl.openCoreWindows()
763  // by the new Case object will lead to a componentOpened() call
764  // that will repopulate the tree.
765  // @@@ The repopulation of the tree in this fashion also merits
766  // reconsideration.
767  resetHistory();
768  });
769  }
770  } // if the image is added to the case
771  else if (changed.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
778  try {
780  /*
781  * In case the Case 'updateGUIForCaseOpened()' method hasn't
782  * already done so, open the tree and all other core
783  * windows.
784  *
785  * TODO: (JIRA-4053) DirectoryTreeTopComponent should not be
786  * responsible for opening core windows. Consider moving
787  * this elsewhere.
788  */
789  SwingUtilities.invokeLater(() -> {
790  if (!DirectoryTreeTopComponent.this.isOpened()) {
792  }
793  });
794  } catch (NoCurrentCaseException notUsed) {
798  }
799  } // change in node selection
800  else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
801  respondSelection((Node[]) event.getOldValue(), (Node[]) event.getNewValue());
802  } else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
803  // nothing to do here.
804  // all nodes should be listening for these events and update accordingly.
805  }
806  }
807  }
808 
817  @NbBundle.Messages("DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.")
818  void respondSelection(final Node[] oldNodes, final Node[] newNodes) {
819  if (!Case.isCaseOpen()) {
820  return;
821  }
822 
823  // Some lock that prevents certain Node operations is set during the
824  // ExplorerManager selection-change, so we must handle changes after the
825  // selection-change event is processed.
826  //TODO find a different way to refresh data result viewer, scheduling this
827  //to EDT breaks loading of nodes in the background
828  EventQueue.invokeLater(() -> {
829  // change the cursor to "waiting cursor" for this operation
830  DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
831  try {
832  Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode();
833  if (treeNode != null) {
834  Node originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal();
835  //set node, wrap in filter node first to filter out children
836  Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em);
837  // Create a TableFilterNode with knowledge of the node's type to allow for column order settings
838  if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) {
839  //Special case for when File Type Identification has not yet been run and
840  //there are no mime types to populate Files by Mime Type Tree
841  EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text());
842  dataResult.setNode(new TableFilterNode(emptyNode, true, "This Node Is Empty")); //NON-NLS
843  } else if (originNode instanceof DisplayableItemNode) {
844  dataResult.setNode(new TableFilterNode(drfn, true, ((DisplayableItemNode) originNode).getItemType()));
845  } else {
846  dataResult.setNode(new TableFilterNode(drfn, true));
847  }
848  String displayName = "";
849  Content content = originNode.getLookup().lookup(Content.class);
850  if (content != null) {
851  try {
852  displayName = content.getUniquePath();
853  } catch (TskCoreException ex) {
854  LOGGER.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: {0}", originNode); //NON-NLS
855  }
856  } else if (originNode.getLookup().lookup(String.class) != null) {
857  displayName = originNode.getLookup().lookup(String.class);
858  }
859  dataResult.setPath(displayName);
860  }
861  // set the directory listing to be active
862  if (oldNodes != null && newNodes != null
863  && (oldNodes.length == newNodes.length)) {
864  boolean sameNodes = true;
865  for (int i = 0; i < oldNodes.length; i++) {
866  sameNodes = sameNodes && oldNodes[i].getName().equals(newNodes[i].getName());
867  }
868  if (!sameNodes) {
869  dataResult.requestActive();
870  }
871  }
872  } finally {
873  setCursor(null);
874  }
875  });
876 
877  // update the back and forward list
878  updateHistory(em.getSelectedNodes());
879  }
880 
881  private void updateHistory(Node[] selectedNodes) {
882  if (selectedNodes.length == 0) {
883  return;
884  }
885 
886  Node selectedNode = selectedNodes[0];
887  String selectedNodeName = selectedNode.getName();
888 
889  /*
890  * get the previous entry to make sure we don't duplicate it. Motivation
891  * for this is also that if we used the back button, then we already
892  * added the 'current' node to 'back' and we will detect that and not
893  * reset the forward list.
894  */
895  String[] currentLast = backList.peekLast();
896  String lastNodeName = null;
897  if (currentLast != null && currentLast.length > 0) {
898  lastNodeName = currentLast[currentLast.length - 1];
899  }
900 
901  if (currentLast == null || !selectedNodeName.equals(lastNodeName)) {
902  //add to the list if the last if not the same as current
903  final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext());
904  backList.addLast(selectedPath); // add the node to the "backList"
905  if (backList.size() > 1) {
906  backButton.setEnabled(true);
907  } else {
908  backButton.setEnabled(false);
909  }
910 
911  forwardList.clear(); // clear the "forwardList"
912  forwardButton.setEnabled(false); // disable the forward Button
913  }
914  }
915 
920  private void resetHistory() {
921  // clear the back and forward list
922  backList.clear();
923  forwardList.clear();
924  backButton.setEnabled(false);
925  forwardButton.setEnabled(false);
926  }
927 
933  public ExpansionBeanTreeView getTree() {
934  return (ExpansionBeanTreeView) this.treeView;
935  }
936 
940  public void refreshContentTreeSafe() {
941  SwingUtilities.invokeLater(this::rebuildTree);
942  }
943 
947  private void refreshTagsTree() {
948  SwingUtilities.invokeLater(() -> {
949  // Ensure the component children have been created first.
950  if (autopsyTreeChildren == null) {
951  return;
952  }
953 
954  if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
955  for (Node dataSource : autopsyTreeChildren.getNodes()) {
956  Node tagsNode = dataSource.getChildren().findChild(Tags.getTagsDisplayName());
957  if (tagsNode != null) {
958  //Reports is at the same level as the data sources so we want to ignore it
959  ((Tags.RootNode) tagsNode).refresh();
960  }
961  }
962  } else {
963  Node tagsNode = autopsyTreeChildren.findChild(Tags.getTagsDisplayName());
964  if (tagsNode != null) {
965  ((Tags.RootNode) tagsNode).refresh();
966  }
967  }
968  });
969  }
970 
976  private void rebuildTree() {
977 
978  // if no open case or has no data then there is no tree to rebuild
979  Case currentCase;
980  try {
981  currentCase = Case.getCurrentCaseThrows();
982  } catch (NoCurrentCaseException ex) {
983  return;
984  }
985  if (null == currentCase || currentCase.hasData() == false) {
986  return;
987  }
988 
989  // refresh all children of the root.
990  autopsyTreeChildFactory.refreshChildren();
991 
992  // Select the first node and reset the selection history
993  // This should happen on the EDT once the tree has been rebuilt.
994  // hence the SwingWorker that does this in the done() method
995  new SwingWorker<Void, Void>() {
996 
997  @Override
998  protected Void doInBackground() throws Exception {
999  return null;
1000  }
1001 
1002  @Override
1003  protected void done() {
1004  super.done();
1005  try {
1006  get();
1007  resetHistory();
1008  preExpandNodes(em.getRootContext().getChildren());
1009  } catch (InterruptedException | ExecutionException ex) {
1010  LOGGER.log(Level.SEVERE, "Error selecting tree node.", ex); //NON-NLS
1011  } //NON-NLS
1012  }
1013  }.execute();
1014  }
1015 
1023  private void setSelectedNode(final String[] previouslySelectedNodePath, final String rootNodeName) {
1024  if (previouslySelectedNodePath == null) {
1025  return;
1026  }
1027  SwingUtilities.invokeLater(new Runnable() {
1028  @Override
1029  public void run() {
1030  if (previouslySelectedNodePath.length > 0 && (rootNodeName == null || previouslySelectedNodePath[0].equals(rootNodeName))) {
1031  Node selectedNode = null;
1032  ArrayList<String> selectedNodePath = new ArrayList<>(Arrays.asList(previouslySelectedNodePath));
1033  while (null == selectedNode && !selectedNodePath.isEmpty()) {
1034  try {
1035  selectedNode = NodeOp.findPath(em.getRootContext(), selectedNodePath.toArray(new String[selectedNodePath.size()]));
1036  } catch (NodeNotFoundException ex) {
1037  // The selected node may have been deleted (e.g., a deleted tag), so truncate the path and try again.
1038  if (selectedNodePath.size() > 1) {
1039  selectedNodePath.remove(selectedNodePath.size() - 1);
1040  } else {
1041  StringBuilder nodePath = new StringBuilder();
1042  for (int i = 0; i < previouslySelectedNodePath.length; ++i) {
1043  nodePath.append(previouslySelectedNodePath[i]).append("/");
1044  }
1045  LOGGER.log(Level.WARNING, "Failed to find any nodes to select on path " + nodePath.toString(), ex); //NON-NLS
1046  break;
1047  }
1048  }
1049  }
1050 
1051  if (null != selectedNode) {
1052  if (rootNodeName != null) {
1053  //called from tree auto refresh context
1054  //remove last from backlist, because auto select will result in duplication
1055  backList.pollLast();
1056  }
1057  try {
1058  em.setExploredContextAndSelection(selectedNode, new Node[]{selectedNode});
1059  } catch (PropertyVetoException ex) {
1060  LOGGER.log(Level.WARNING, "Property veto from ExplorerManager setting selection to " + selectedNode.getName(), ex); //NON-NLS
1061  }
1062  }
1063  }
1064  }
1065  });
1066  }
1067 
1068  @Override
1069  public TopComponent getTopComponent() {
1070  return this;
1071  }
1072 
1073  @Override
1074  public boolean hasMenuOpenAction() {
1075  return false;
1076  }
1077 
1078  public void viewArtifact(final BlackboardArtifact art) {
1079  int typeID = art.getArtifactTypeID();
1080  String typeName = art.getArtifactTypeName();
1081  Children rootChilds = em.getRootContext().getChildren();
1082  Node treeNode = null;
1083  Node resultsNode = rootChilds.findChild(ResultsNode.NAME);
1084  Children resultsChilds = resultsNode.getChildren();
1085  if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
1086  Node hashsetRootNode = resultsChilds.findChild(typeName);
1087  Children hashsetRootChilds = hashsetRootNode.getChildren();
1088  try {
1089  String setName = null;
1090  List<BlackboardAttribute> attributes = art.getAttributes();
1091  for (BlackboardAttribute att : attributes) {
1092  int typeId = att.getAttributeType().getTypeID();
1093  if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) {
1094  setName = att.getValueString();
1095  }
1096  }
1097  treeNode = hashsetRootChilds.findChild(setName);
1098  } catch (TskCoreException ex) {
1099  LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
1100  }
1101  } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
1102  Node keywordRootNode = resultsChilds.findChild(typeName);
1103  Children keywordRootChilds = keywordRootNode.getChildren();
1104  try {
1105  String listName = null;
1106  String keywordName = null;
1107  String regex = null;
1108  List<BlackboardAttribute> attributes = art.getAttributes();
1109  for (BlackboardAttribute att : attributes) {
1110  int typeId = att.getAttributeType().getTypeID();
1111  if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) {
1112  listName = att.getValueString();
1113  } else if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) {
1114  keywordName = att.getValueString();
1115  } else if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()) {
1116  regex = att.getValueString();
1117  }
1118  }
1119  if (listName == null) {
1120  if (regex == null) { //using same labels used for creation
1121  listName = NbBundle.getMessage(KeywordHits.class, "KeywordHits.simpleLiteralSearch.text");
1122  } else {
1123  listName = NbBundle.getMessage(KeywordHits.class, "KeywordHits.singleRegexSearch.text");
1124  }
1125  }
1126  Node listNode = keywordRootChilds.findChild(listName);
1127  if (listNode == null) {
1128  return;
1129  }
1130  Children listChildren = listNode.getChildren();
1131  if (listChildren == null) {
1132  return;
1133  }
1134  if (regex != null) { //For support of regex nodes such as URLs, IPs, Phone Numbers, and Email Addrs as they are down another level
1135  Node regexNode = listChildren.findChild(regex);
1136  if (regexNode == null) {
1137  return;
1138  }
1139  listChildren = regexNode.getChildren();
1140  if (listChildren == null) {
1141  return;
1142  }
1143  }
1144 
1145  treeNode = listChildren.findChild(keywordName);
1146 
1147  } catch (TskCoreException ex) {
1148  LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
1149  }
1150  } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID()
1151  || typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
1152  Node interestingItemsRootNode = resultsChilds.findChild(NbBundle
1153  .getMessage(InterestingHits.class, "InterestingHits.interestingItems.text"));
1154  Children interestingItemsRootChildren = interestingItemsRootNode.getChildren();
1155  try {
1156  String setName = null;
1157  List<BlackboardAttribute> attributes = art.getAttributes();
1158  for (BlackboardAttribute att : attributes) {
1159  int typeId = att.getAttributeType().getTypeID();
1160  if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) {
1161  setName = att.getValueString();
1162  }
1163  }
1164  Node setNode = interestingItemsRootChildren.findChild(setName);
1165  if (setNode == null) {
1166  return;
1167  }
1168  Children interestingChildren = setNode.getChildren();
1169  if (interestingChildren == null) {
1170  return;
1171  }
1172  treeNode = interestingChildren.findChild(art.getDisplayName());
1173  } catch (TskCoreException ex) {
1174  LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
1175  }
1176  } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) {
1177  Node emailMsgRootNode = resultsChilds.findChild(typeName);
1178  Children emailMsgRootChilds = emailMsgRootNode.getChildren();
1179  Map<String, String> parsedPath = null;
1180  try {
1181  List<BlackboardAttribute> attributes = art.getAttributes();
1182  for (BlackboardAttribute att : attributes) {
1183  int typeId = att.getAttributeType().getTypeID();
1184  if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID()) {
1185  parsedPath = EmailExtracted.parsePath(att.getValueString());
1186  break;
1187  }
1188  }
1189  if (parsedPath == null) {
1190  return;
1191  }
1192  Node defaultNode = emailMsgRootChilds.findChild(parsedPath.get(NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.defaultAcct.text")));
1193  Children defaultChildren = defaultNode.getChildren();
1194  treeNode = defaultChildren.findChild(parsedPath.get(NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.defaultFolder.text")));
1195  } catch (TskCoreException ex) {
1196  LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
1197  }
1198 
1199  } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
1200  Node accountRootNode = resultsChilds.findChild(art.getDisplayName());
1201  Children accountRootChilds = accountRootNode.getChildren();
1202  List<BlackboardAttribute> attributes;
1203  String accountType = null;
1204  String ccNumberName = null;
1205  try {
1206  attributes = art.getAttributes();
1207  for (BlackboardAttribute att : attributes) {
1208  int typeId = att.getAttributeType().getTypeID();
1209  if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()) {
1210  accountType = att.getValueString();
1211  }
1212  if (typeId == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID()) {
1213  ccNumberName = att.getValueString();
1214  }
1215  }
1216  if (accountType == null) {
1217  return;
1218  }
1219 
1220  if (accountType.equals(Account.Type.CREDIT_CARD.getTypeName())) {
1221  Node accountNode = accountRootChilds.findChild(Account.Type.CREDIT_CARD.getDisplayName());
1222  if (accountNode == null) {
1223  return;
1224  }
1225  Children accountChildren = accountNode.getChildren();
1226  if (accountChildren == null) {
1227  return;
1228  }
1229  Node binNode = accountChildren.findChild(NbBundle.getMessage(Accounts.class, "Accounts.ByBINNode.name"));
1230  if (binNode == null) {
1231  return;
1232  }
1233  Children binChildren = binNode.getChildren();
1234  if (ccNumberName == null) {
1235  return;
1236  }
1237  //right padded with 0s to 8 digits when single number
1238  //when a range of numbers, the first 6 digits are rightpadded with 0s to 8 digits then a dash then 3 digits, the 6,7,8, digits of the end number right padded with 9s
1239  String binName = StringUtils.rightPad(ccNumberName, 8, "0");
1240  binName = binName.substring(0, 8);
1241  int bin;
1242  try {
1243  bin = Integer.parseInt(binName);
1244  } catch (NumberFormatException ex) {
1245  LOGGER.log(Level.WARNING, "Unable to parseInt a BIN for node selection from string binName=" + binName, ex); //NON-NLS
1246  return;
1247  }
1249  if (binInfo != null) {
1250  int startBin = ((BINRange) binInfo).getBINstart();
1251  int endBin = ((BINRange) binInfo).getBINend();
1252  if (startBin != endBin) {
1253  binName = Integer.toString(startBin) + "-" + Integer.toString(endBin).substring(5); //if there is a range re-construct the name it appears as
1254  }
1255  }
1256  if (binName == null) {
1257  return;
1258  }
1259  treeNode = binChildren.findChild(binName);
1260  } else { //default account type
1261  treeNode = accountRootChilds.findChild(accountType);
1262  }
1263  } catch (TskCoreException ex) {
1264  LOGGER.log(Level.WARNING, "Error retrieving attributes", ex); //NON-NLS
1265  }
1266  } else {
1267  Node extractedContent = resultsChilds.findChild(ExtractedContent.NAME);
1268  Children extractedChilds = extractedContent.getChildren();
1269  if (extractedChilds == null) {
1270  return;
1271  }
1272  treeNode = extractedChilds.findChild(typeName);
1273  }
1274 
1275  if (treeNode == null) {
1276  return;
1277  }
1278 
1279  DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) treeNode).getOriginal();
1280  undecoratedParentNode.setChildNodeSelectionInfo(new ArtifactNodeSelectionInfo(art));
1281  getTree().expandNode(treeNode);
1282  if (this.getSelectedNode().equals(treeNode)) {
1283  this.setDirectoryListingActive();
1284  this.respondSelection(em.getSelectedNodes(), new Node[]{treeNode});
1285  } else {
1286  try {
1287  em.setExploredContextAndSelection(treeNode, new Node[]{treeNode});
1288  } catch (PropertyVetoException ex) {
1289  LOGGER.log(Level.WARNING, "Property Veto: ", ex); //NON-NLS
1290  }
1291  }
1292  // Another thread is needed because we have to wait for dataResult to populate
1293  }
1294 
1295  public void viewArtifactContent(BlackboardArtifact art) {
1296  new ViewContextAction(
1297  NbBundle.getMessage(this.getClass(), "DirectoryTreeTopComponent.action.viewArtContent.text"),
1298  new BlackboardArtifactNode(art)).actionPerformed(null);
1299  }
1300 
1301  public void addOnFinishedListener(PropertyChangeListener l) {
1302  DirectoryTreeTopComponent.this.addPropertyChangeListener(l);
1303  }
1304 
1305 }
static final Map< String, String > parsePath(String path)
static synchronized IngestManager getInstance()
void setSelectedNode(final String[] previouslySelectedNodePath, final String rootNodeName)
static String getTagsDisplayName()
Definition: Tags.java:79
static synchronized BankIdentificationNumber getBINInfo(int bin)
static void setGroupItemsInTreeByDataSource(boolean value)
void addIngestJobEventListener(final PropertyChangeListener listener)
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
static String getConfigSetting(String moduleName, String settingName)
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:437
static void addChangeListener(PreferenceChangeListener listener)
void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo)
static synchronized DirectoryTreeTopComponent findInstance()
static boolean settingExists(String moduleName, String settingName)
static final String HIDE_CENTRAL_REPO_COMMENTS_AND_OCCURRENCES

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.