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

Copyright © 2012-2019 Basis Technology. Generated on: Tue Jan 7 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.