Autopsy  4.18.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
VisualizationPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2017-2021 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.communications;
20 
21 import com.google.common.eventbus.Subscribe;
22 import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
23 import com.mxgraph.layout.mxCircleLayout;
24 import com.mxgraph.layout.mxFastOrganicLayout;
25 import com.mxgraph.layout.mxIGraphLayout;
26 import com.mxgraph.layout.mxOrganicLayout;
27 import com.mxgraph.model.mxCell;
28 import com.mxgraph.model.mxICell;
29 import com.mxgraph.swing.handler.mxRubberband;
30 import com.mxgraph.swing.mxGraphComponent;
31 import com.mxgraph.util.mxCellRenderer;
32 import com.mxgraph.util.mxEvent;
33 import com.mxgraph.util.mxEventObject;
34 import com.mxgraph.util.mxEventSource;
35 import com.mxgraph.util.mxPoint;
36 import com.mxgraph.util.mxRectangle;
37 import com.mxgraph.util.mxUndoManager;
38 import com.mxgraph.util.mxUndoableEdit;
39 import com.mxgraph.view.mxCellState;
40 import com.mxgraph.view.mxGraph;
41 import com.mxgraph.view.mxGraphView;
42 import java.awt.BorderLayout;
43 import java.awt.Color;
44 import java.awt.Desktop;
45 import java.awt.Dimension;
46 import java.awt.Font;
47 import java.awt.Frame;
48 import java.awt.Graphics;
49 import java.awt.GridBagConstraints;
50 import java.awt.GridBagLayout;
51 import java.awt.GridLayout;
52 import java.awt.Insets;
53 import java.awt.event.ActionEvent;
54 import java.awt.event.ActionListener;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.MouseWheelEvent;
58 import java.awt.image.BufferedImage;
59 import java.beans.PropertyChangeEvent;
60 import java.io.IOException;
61 import java.nio.file.Files;
62 import java.nio.file.Path;
63 import java.nio.file.Paths;
64 import java.text.DecimalFormat;
65 import java.text.SimpleDateFormat;
66 import java.util.Arrays;
67 import java.util.Date;
68 import java.util.EnumSet;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.Map;
72 import java.util.Set;
73 import java.util.concurrent.Future;
74 import java.util.function.BiConsumer;
75 import java.util.logging.Level;
76 import java.util.stream.Collectors;
77 import java.util.stream.Stream;
78 import javafx.application.Platform;
79 import javafx.embed.swing.JFXPanel;
80 import javafx.scene.Scene;
81 import javafx.scene.layout.Pane;
82 import javax.swing.AbstractAction;
83 import javax.swing.ImageIcon;
84 import javax.swing.JButton;
85 import javax.swing.JLabel;
86 import javax.swing.JMenuItem;
87 import javax.swing.JOptionPane;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JTextField;
91 import javax.swing.JTextPane;
92 import javax.swing.JToolBar;
93 import javax.swing.SwingConstants;
94 import javax.swing.SwingUtilities;
95 import javax.swing.SwingWorker;
96 import org.apache.commons.lang3.StringUtils;
97 import org.controlsfx.control.Notifications;
98 import org.openide.util.NbBundle;
99 import org.openide.windows.WindowManager;
109 import org.sleuthkit.datamodel.AccountDeviceInstance;
110 import org.sleuthkit.datamodel.CommunicationsFilter;
111 import org.sleuthkit.datamodel.CommunicationsManager;
112 import org.sleuthkit.datamodel.TskCoreException;
124 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
125 final public class VisualizationPanel extends JPanel {
126 
127  private static final long serialVersionUID = 1L;
128  private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName());
129  private static final String BASE_IMAGE_PATH = "/org/sleuthkit/autopsy/communications/images";
130  static final private ImageIcon unlockIcon
131  = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_unlocked.png"));
132  static final private ImageIcon lockIcon
133  = new ImageIcon(VisualizationPanel.class.getResource(BASE_IMAGE_PATH + "/lock_large_locked.png"));
134 
135  @NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel")
136  private static final String CANCEL = Bundle.VisualizationPanel_cancelButton_text();
137 
138  private Frame windowAncestor;
139 
140  private CommunicationsManager commsManager;
141  private CommunicationsFilter currentFilter;
142 
143  private final mxGraphComponent graphComponent;
144  private final CommunicationsGraph graph;
145 
146  private final mxUndoManager undoManager = new mxUndoManager();
147  private final mxRubberband rubberband; //NOPMD We keep a referenec as insurance to prevent garbage collection
148 
150  private SwingWorker<?, ?> worker;
151  private final PinnedAccountModel pinnedAccountModel = new PinnedAccountModel();
152  private final LockedVertexModel lockedVertexModel = new LockedVertexModel();
153 
154  private final Map<NamedGraphLayout, JButton> layoutButtons = new HashMap<>();
155  private NamedGraphLayout currentLayout;
156 
157  private final RelationshipBrowser relationshipBrowser;
158 
159  private final StateManager stateManager;
160 
161  @NbBundle.Messages("VisalizationPanel.paintingError=Problem painting visualization.")
162  public VisualizationPanel(RelationshipBrowser relationshipBrowser) {
163  this.relationshipBrowser = relationshipBrowser;
164  initComponents();
165  //initialize invisible JFXPanel that is used to show JFXNotifications over this window.
166  notificationsJFXPanel.setScene(new Scene(new Pane()));
167 
168  graph = new CommunicationsGraph(pinnedAccountModel, lockedVertexModel);
169 
170  /*
171  * custom implementation of mxGraphComponent that uses... a custom
172  * implementation of mxGraphControl ... that overrides paint so we can
173  * catch the NPEs we are getting and deal with them. For now that means
174  * just ignoring them.
175  */
176  graphComponent = new mxGraphComponent(graph) {
177  @Override
178  protected mxGraphComponent.mxGraphControl createGraphControl() {
179 
180  return new mxGraphControl() {
181 
182  @Override
183  public void paint(Graphics graphics) {
184  try {
185  super.paint(graphics);
186  } catch (NullPointerException ex) { //NOPMD
187  /* We can't find the underlying cause of the NPE in
188  * jgraphx, but it doesn't seem to cause any
189  * noticeable problems, so we are just logging it
190  * and moving on.
191  */
192  logger.log(Level.WARNING, "There was a NPE while painting the VisualizationPanel", ex);
193  }
194  }
195 
196  };
197  }
198  };
199  graphComponent.setAutoExtend(true);
200  graphComponent.setAutoScroll(true);
201  graphComponent.setAutoscrolls(true);
202  graphComponent.setConnectable(false);
203  graphComponent.setDragEnabled(false);
204  graphComponent.setKeepSelectionVisibleOnZoom(true);
205  graphComponent.setOpaque(true);
206  graphComponent.setToolTips(true);
207  graphComponent.setBackground(Color.WHITE);
208  borderLayoutPanel.add(graphComponent, BorderLayout.CENTER);
209 
210  //install rubber band other handlers
211  rubberband = new mxRubberband(graphComponent);
212 
213  lockedVertexModel.registerhandler(this);
214 
215  final mxEventSource.mxIEventListener scaleListener = (Object sender, mxEventObject evt)
216  -> zoomPercentLabel.setText(DecimalFormat.getPercentInstance().format(graph.getView().getScale()));
217  graph.getView().addListener(mxEvent.SCALE, scaleListener);
218  graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, scaleListener);
219 
220  final GraphMouseListener graphMouseListener = new GraphMouseListener();
221  graphComponent.getGraphControl().addMouseWheelListener(graphMouseListener);
222  graphComponent.getGraphControl().addMouseListener(graphMouseListener);
223 
224  //feed selection to explorermanager
225  graph.getSelectionModel().addListener(mxEvent.CHANGE, new SelectionListener());
226  final mxEventSource.mxIEventListener undoListener = (Object sender, mxEventObject evt)
227  -> undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit"));
228 
229  graph.getModel().addListener(mxEvent.UNDO, undoListener);
230  graph.getView().addListener(mxEvent.UNDO, undoListener);
231 
232  FastOrganicLayoutImpl fastOrganicLayout = new FastOrganicLayoutImpl(graph);
233 
234  //local method to configure layout buttons
235  BiConsumer<JButton, NamedGraphLayout> configure = (layoutButton, layout) -> {
236  layoutButtons.put(layout, layoutButton);
237  layoutButton.addActionListener(event -> applyLayout(layout));
238  };
239  //configure layout buttons.
240  configure.accept(fastOrganicLayoutButton, fastOrganicLayout);
241 
242  applyLayout(fastOrganicLayout);
243 
244  stateManager = new StateManager(pinnedAccountModel);
245 
246  setStateButtonsEnabled();
247 
248  toolbar.setLayout(new WrapLayout());
249  }
250 
251  @Subscribe
252  void handle(LockedVertexModel.VertexLockEvent event) {
253  final Set<mxCell> vertices = event.getVertices();
254  mxGraphView view = graph.getView();
255  vertices.forEach(vertex -> {
256  final mxCellState state = view.getState(vertex, true);
257  view.updateLabel(state);
258  view.updateLabelBounds(state);
259  view.updateBoundingBox(state);
260  graphComponent.redraw(state);
261  });
262  }
263 
264  @Subscribe
265  void handle(final CVTEvents.UnpinAccountsEvent pinEvent) {
266  graph.getModel().beginUpdate();
267  pinnedAccountModel.unpinAccount(pinEvent.getAccountDeviceInstances());
268  graph.clear();
269  rebuildGraph();
270  // Updates the display
271  graph.getModel().endUpdate();
272 
273  setStateButtonsEnabled();
274  }
275 
276  @Subscribe
277  void handle(final CVTEvents.PinAccountsEvent pinEvent) {
278  graph.getModel().beginUpdate();
279  if (pinEvent.isReplace()) {
280  graph.resetGraph();
281  }
282  pinnedAccountModel.pinAccount(pinEvent.getAccountDeviceInstances());
283  rebuildGraph();
284  // Updates the display
285  graph.getModel().endUpdate();
286 
287  setStateButtonsEnabled();
288  }
289 
290  @Subscribe
291  void handle(final CVTEvents.FilterChangeEvent filterChangeEvent) {
292  graph.getModel().beginUpdate();
293  graph.clear();
294  currentFilter = filterChangeEvent.getNewFilter();
295  rebuildGraph();
296  // Updates the display
297  graph.getModel().endUpdate();
298 
299  setStateButtonsEnabled();
300  }
301 
302  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
303  private void rebuildGraph() {
304  if (pinnedAccountModel.isEmpty()) {
305  borderLayoutPanel.remove(graphComponent);
306  borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER);
307  repaint();
308  } else {
309  borderLayoutPanel.remove(placeHolderPanel);
310  borderLayoutPanel.add(graphComponent, BorderLayout.CENTER);
311  if (worker != null) {
312  worker.cancel(true);
313  }
314 
315  final CancelationListener cancelationListener = new CancelationListener();
316  final ModalDialogProgressIndicator progress = new ModalDialogProgressIndicator(windowAncestor, "Loading Visualization", new String[]{CANCEL}, CANCEL, cancelationListener);
317  worker = graph.rebuild(progress, commsManager, currentFilter);
318  cancelationListener.configure(worker, progress);
319  worker.addPropertyChangeListener((final PropertyChangeEvent evt) -> {
320  if (worker.isDone()) {
321  if (worker.isCancelled()) {
322  graph.resetGraph();
323  rebuildGraph();
324  }
325  applyLayout(currentLayout);
326  }
327  });
328 
329  worker.execute();
330  }
331  }
332 
333  @Override
334  public void addNotify() {
335  super.addNotify();
336  windowAncestor = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, this);
337 
338  try {
339  commsManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager();
340  } catch (TskCoreException ex) {
341  logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex); //NON-NLS
342  } catch (NoCurrentCaseException ex) {
343  logger.log(Level.SEVERE, "Can't get CommunicationsManager when there is no case open.", ex); //NON-NLS
344  }
345 
346  Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> {
347  graph.getModel().beginUpdate();
348  try {
349  graph.resetGraph();
350  } finally {
351  graph.getModel().endUpdate();
352  }
353  if (evt.getNewValue() == null) {
354  commsManager = null;
355  } else {
356  Case currentCase = (Case) evt.getNewValue();
357  try {
358  commsManager = currentCase.getSleuthkitCase().getCommunicationsManager();
359  } catch (TskCoreException ex) {
360  logger.log(Level.SEVERE, "Error getting CommunicationsManager for the current case.", ex); //NON-NLS
361  }
362  }
363  });
364  }
365 
371  @SuppressWarnings("unchecked")
372  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
373  private void initComponents() {
374  GridBagConstraints gridBagConstraints;
375 
376  borderLayoutPanel = new JPanel();
377  placeHolderPanel = new JPanel();
378  jTextPane1 = new JTextPane();
379  notificationsJFXPanel = new JFXPanel();
380  toolbar = new JToolBar();
381  backButton = new JButton();
382  forwardButton = new JButton();
383  jSeparator3 = new JToolBar.Separator();
384  clearVizButton = new JButton();
385  fastOrganicLayoutButton = new JButton();
386  jSeparator2 = new JToolBar.Separator();
387  zoomLabel = new JLabel();
388  zoomPercentLabel = new JLabel();
389  zoomOutButton = new JButton();
390  fitZoomButton = new JButton();
391  zoomActualButton = new JButton();
392  zoomInButton = new JButton();
393  jSeparator1 = new JToolBar.Separator();
394  snapshotButton = new JButton();
395 
396  setLayout(new BorderLayout());
397 
398  borderLayoutPanel.setLayout(new BorderLayout());
399 
400  placeHolderPanel.setLayout(new GridBagLayout());
401 
402  jTextPane1.setEditable(false);
403  jTextPane1.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextPane1.text")); // NOI18N
404  jTextPane1.setOpaque(false);
405  gridBagConstraints = new GridBagConstraints();
406  gridBagConstraints.anchor = GridBagConstraints.NORTH;
407  gridBagConstraints.weighty = 1.0;
408  gridBagConstraints.insets = new Insets(50, 0, 0, 0);
409  placeHolderPanel.add(jTextPane1, gridBagConstraints);
410 
411  borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER);
412  borderLayoutPanel.add(notificationsJFXPanel, BorderLayout.PAGE_END);
413 
414  add(borderLayoutPanel, BorderLayout.CENTER);
415 
416  toolbar.setRollover(true);
417 
418  backButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_previous.png"))); // NOI18N
419  backButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.text_1")); // NOI18N
420  backButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.toolTipText")); // NOI18N
421  backButton.setFocusable(false);
422  backButton.setHorizontalTextPosition(SwingConstants.CENTER);
423  backButton.setVerticalTextPosition(SwingConstants.BOTTOM);
424  backButton.addActionListener(new ActionListener() {
425  public void actionPerformed(ActionEvent evt) {
426  backButtonActionPerformed(evt);
427  }
428  });
429  toolbar.add(backButton);
430 
431  forwardButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_next.png"))); // NOI18N
432  forwardButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.text")); // NOI18N
433  forwardButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.toolTipText")); // NOI18N
434  forwardButton.setFocusable(false);
435  forwardButton.setHorizontalTextPosition(SwingConstants.CENTER);
436  forwardButton.setVerticalTextPosition(SwingConstants.BOTTOM);
437  forwardButton.addActionListener(new ActionListener() {
438  public void actionPerformed(ActionEvent evt) {
439  forwardButtonActionPerformed(evt);
440  }
441  });
442  toolbar.add(forwardButton);
443  toolbar.add(jSeparator3);
444 
445  clearVizButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/broom.png"))); // NOI18N
446  clearVizButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.text_1")); // NOI18N
447  clearVizButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.toolTipText")); // NOI18N
448  clearVizButton.setActionCommand(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.actionCommand")); // NOI18N
449  clearVizButton.setFocusable(false);
450  clearVizButton.setHorizontalTextPosition(SwingConstants.CENTER);
451  clearVizButton.setVerticalTextPosition(SwingConstants.BOTTOM);
452  clearVizButton.addActionListener(new ActionListener() {
453  public void actionPerformed(ActionEvent evt) {
454  clearVizButtonActionPerformed(evt);
455  }
456  });
457  toolbar.add(clearVizButton);
458 
459  fastOrganicLayoutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N
460  fastOrganicLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.text")); // NOI18N
461  fastOrganicLayoutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.toolTipText")); // NOI18N
462  fastOrganicLayoutButton.setFocusable(false);
463  fastOrganicLayoutButton.setHorizontalTextPosition(SwingConstants.CENTER);
464  fastOrganicLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
465  fastOrganicLayoutButton.addActionListener(new ActionListener() {
466  public void actionPerformed(ActionEvent evt) {
467  fastOrganicLayoutButtonActionPerformed(evt);
468  }
469  });
470  toolbar.add(fastOrganicLayoutButton);
471  toolbar.add(jSeparator2);
472 
473  zoomLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomLabel.text")); // NOI18N
474  toolbar.add(zoomLabel);
475 
476  zoomPercentLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomPercentLabel.text")); // NOI18N
477  toolbar.add(zoomPercentLabel);
478 
479  zoomOutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png"))); // NOI18N
480  zoomOutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.text")); // NOI18N
481  zoomOutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N
482  zoomOutButton.setFocusable(false);
483  zoomOutButton.setHorizontalTextPosition(SwingConstants.CENTER);
484  zoomOutButton.setVerticalTextPosition(SwingConstants.BOTTOM);
485  zoomOutButton.addActionListener(new ActionListener() {
486  public void actionPerformed(ActionEvent evt) {
487  zoomOutButtonActionPerformed(evt);
488  }
489  });
490  toolbar.add(zoomOutButton);
491 
492  fitZoomButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png"))); // NOI18N
493  fitZoomButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N
494  fitZoomButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N
495  fitZoomButton.setFocusable(false);
496  fitZoomButton.setHorizontalTextPosition(SwingConstants.CENTER);
497  fitZoomButton.setVerticalTextPosition(SwingConstants.BOTTOM);
498  fitZoomButton.addActionListener(new ActionListener() {
499  public void actionPerformed(ActionEvent evt) {
500  fitZoomButtonActionPerformed(evt);
501  }
502  });
503  toolbar.add(fitZoomButton);
504 
505  zoomActualButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png"))); // NOI18N
506  zoomActualButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N
507  zoomActualButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N
508  zoomActualButton.setFocusable(false);
509  zoomActualButton.setHorizontalTextPosition(SwingConstants.CENTER);
510  zoomActualButton.setVerticalTextPosition(SwingConstants.BOTTOM);
511  zoomActualButton.addActionListener(new ActionListener() {
512  public void actionPerformed(ActionEvent evt) {
513  zoomActualButtonActionPerformed(evt);
514  }
515  });
516  toolbar.add(zoomActualButton);
517 
518  zoomInButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png"))); // NOI18N
519  zoomInButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N
520  zoomInButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N
521  zoomInButton.setFocusable(false);
522  zoomInButton.setHorizontalTextPosition(SwingConstants.CENTER);
523  zoomInButton.setVerticalTextPosition(SwingConstants.BOTTOM);
524  zoomInButton.addActionListener(new ActionListener() {
525  public void actionPerformed(ActionEvent evt) {
526  zoomInButtonActionPerformed(evt);
527  }
528  });
529  toolbar.add(zoomInButton);
530  toolbar.add(jSeparator1);
531 
532  snapshotButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N
533  snapshotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N
534  snapshotButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.toolTipText")); // NOI18N
535  snapshotButton.setFocusable(false);
536  snapshotButton.setHorizontalTextPosition(SwingConstants.CENTER);
537  snapshotButton.setVerticalTextPosition(SwingConstants.BOTTOM);
538  snapshotButton.addActionListener(new ActionListener() {
539  public void actionPerformed(ActionEvent evt) {
540  snapshotButtonActionPerformed(evt);
541  }
542  });
543  toolbar.add(snapshotButton);
544 
545  add(toolbar, BorderLayout.NORTH);
546  }// </editor-fold>//GEN-END:initComponents
547 
548  private void fitZoomButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fitZoomButtonActionPerformed
549  fitGraph();
550  }//GEN-LAST:event_fitZoomButtonActionPerformed
551 
552  private void zoomActualButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_zoomActualButtonActionPerformed
553  graphComponent.zoomActual();
554  CVTEvents.getCVTEventBus().post(new CVTEvents.ScaleChangeEvent(graph.getView().getScale()));
555  }//GEN-LAST:event_zoomActualButtonActionPerformed
556 
557  private void zoomInButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_zoomInButtonActionPerformed
558  graphComponent.zoomIn();
559  CVTEvents.getCVTEventBus().post(new CVTEvents.ScaleChangeEvent(graph.getView().getScale()));
560  }//GEN-LAST:event_zoomInButtonActionPerformed
561 
562  private void zoomOutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_zoomOutButtonActionPerformed
563  graphComponent.zoomOut();
564  CVTEvents.getCVTEventBus().post(new CVTEvents.ScaleChangeEvent(graph.getView().getScale()));
565  }//GEN-LAST:event_zoomOutButtonActionPerformed
566 
573  @NbBundle.Messages({"VisualizationPanel.computingLayout=Computing Layout",
574  "# {0} - layout name",
575  "VisualizationPanel.layoutFailWithLockedVertices.text={0} layout failed with locked vertices. Unlock some vertices or try a different layout.",
576  "# {0} - layout name",
577  "VisualizationPanel.layoutFail.text={0} layout failed. Try a different layout."})
578  private void applyLayout(NamedGraphLayout layout) {
579  currentLayout = layout;
580  layoutButtons.forEach((layoutKey, button)
581  -> button.setFont(button.getFont().deriveFont(layoutKey == layout ? Font.BOLD : Font.PLAIN)));
582 
583  ModalDialogProgressIndicator progressIndicator = new ModalDialogProgressIndicator(windowAncestor, Bundle.VisualizationPanel_computingLayout());
584  progressIndicator.start(Bundle.VisualizationPanel_computingLayout());
585  graph.getModel().beginUpdate();
586  try {
587  layout.execute(graph.getDefaultParent());
588  fitGraph();
589  } finally {
590  graph.getModel().endUpdate();
591  progressIndicator.finish();
592  }
593  }
594 
595  private void clearVizButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_clearVizButtonActionPerformed
596  CVTEvents.getCVTEventBus().post(new CVTEvents.UnpinAccountsEvent(pinnedAccountModel.getPinnedAccounts()));
597  }//GEN-LAST:event_clearVizButtonActionPerformed
598 
599  private void forwardButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_forwardButtonActionPerformed
600  handleStateChange(stateManager.advance());
601  }//GEN-LAST:event_forwardButtonActionPerformed
602 
603  private void backButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_backButtonActionPerformed
604  handleStateChange(stateManager.retreat());
605  }//GEN-LAST:event_backButtonActionPerformed
606 
612  private void handleStateChange(StateManager.CommunicationsState newState ){
613  if(newState == null) {
614  return;
615  }
616 
617  // If the zoom was changed, only change the zoom.
618  if(newState.isZoomChange()) {
619  graph.getView().setScale(newState.getZoomValue());
620  return;
621  }
622 
623  // This will cause the FilterPane to update its controls
624  CVTEvents.getCVTEventBus().post(new CVTEvents.StateChangeEvent(newState));
625  setStateButtonsEnabled();
626 
627  graph.getModel().beginUpdate();
628  graph.resetGraph();
629 
630  if(newState.getPinnedList() != null) {
631  pinnedAccountModel.pinAccount(newState.getPinnedList());
632  } else {
633  pinnedAccountModel.clear();
634  }
635 
636  currentFilter = newState.getCommunicationsFilter();
637 
638  rebuildGraph();
639  // Updates the display
640  graph.getModel().endUpdate();
641 
642  fitGraph();
643 
644  }
645 
646  private void setStateButtonsEnabled() {
647  backButton.setEnabled(stateManager.canRetreat());
648  forwardButton.setEnabled(stateManager.canAdvance());
649  }
650 
651  @NbBundle.Messages({
652  "VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation."
653  })
654  private void snapshotButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_snapshotButtonActionPerformed
655  try {
656  handleSnapshotEvent();
657  } catch (NoCurrentCaseException | IOException ex) {
658  logger.log(Level.SEVERE, "Unable to create communications snapsot report", ex); //NON-NLS
659 
660  Platform.runLater(()
661  -> Notifications.create().owner(notificationsJFXPanel.getScene().getWindow())
662  .text(Bundle.VisualizationPanel_snapshot_report_failure())
663  .showWarning());
664  } catch( TskCoreException ex) {
665  logger.log(Level.WARNING, "Unable to add report to currenct case", ex); //NON-NLS
666  }
667  }//GEN-LAST:event_snapshotButtonActionPerformed
668 
669  private void fastOrganicLayoutButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fastOrganicLayoutButtonActionPerformed
670  // TODO add your handling code here:
671  }//GEN-LAST:event_fastOrganicLayoutButtonActionPerformed
672 
673  private void fitGraph() {
674  graphComponent.zoomTo(1, true);
675  mxPoint translate = graph.getView().getTranslate();
676  if (translate == null || Double.isNaN(translate.getX()) || Double.isNaN(translate.getY())) {
677  translate = new mxPoint();
678  }
679 
680  mxRectangle boundsForCells = graph.getCellBounds(graph.getDefaultParent(), true, true, true);
681  if (boundsForCells == null || Double.isNaN(boundsForCells.getWidth()) || Double.isNaN(boundsForCells.getHeight())) {
682  boundsForCells = new mxRectangle(0, 0, 1, 1);
683  }
684  final mxPoint mxPoint = new mxPoint(translate.getX() - boundsForCells.getX(), translate.getY() - boundsForCells.getY());
685 
686  graph.cellsMoved(graph.getChildCells(graph.getDefaultParent()), mxPoint.getX(), mxPoint.getY(), false, false);
687 
688  boundsForCells = graph.getCellBounds(graph.getDefaultParent(), true, true, true);
689  if (boundsForCells == null || Double.isNaN(boundsForCells.getWidth()) || Double.isNaN(boundsForCells.getHeight())) {
690  boundsForCells = new mxRectangle(0, 0, 1, 1);
691  }
692 
693  final Dimension size = graphComponent.getSize();
694  final double widthFactor = size.getWidth() / boundsForCells.getWidth();
695  final double heightFactor = size.getHeight() / boundsForCells.getHeight();
696 
697  graphComponent.zoom((heightFactor + widthFactor) / 2.0);
698  }
699 
706  @NbBundle.Messages({
707  "VisualizationPanel_action_dialogs_title=Communications",
708  "VisualizationPanel_module_name=Communications",
709  "VisualizationPanel_action_name_text=Snapshot Report",
710  "VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report:",
711  "VisualizationPane_reportName=Communications Snapshot",
712  "# {0} - default name",
713  "VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0}",
714  "VisualizationPane_blank_report_title=Blank Report Name",
715  "# {0} - report name",
716  "VisualizationPane_overrite_exiting=Overwrite existing report?\n{0}"
717  })
718  private void handleSnapshotEvent() throws NoCurrentCaseException, IOException, TskCoreException {
719  Case currentCase = Case.getCurrentCaseThrows();
720  Date generationDate = new Date();
721 
722  final String defaultReportName = FileUtil.escapeFileName(currentCase.getDisplayName() + " " + new SimpleDateFormat("MMddyyyyHHmmss").format(generationDate)); //NON_NLS
723 
724  final JTextField text = new JTextField(50);
725  final JPanel panel = new JPanel(new GridLayout(2, 1));
726  panel.add(new JLabel(Bundle.VisualizationPane_fileName_prompt()));
727  panel.add(text);
728 
729  text.setText(defaultReportName);
730 
731  int result = JOptionPane.showConfirmDialog(graphComponent, panel,
732  Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION);
733 
734  if (result == JOptionPane.OK_OPTION) {
735  String enteredReportName = text.getText();
736 
737  if(enteredReportName.trim().isEmpty()){
738  result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_accept_defaultName(defaultReportName), Bundle.VisualizationPane_blank_report_title(), JOptionPane.OK_CANCEL_OPTION);
739  if(result != JOptionPane.OK_OPTION) {
740  return;
741  }
742  }
743 
744  String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName);
745  Path reportPath = Paths.get(currentCase.getReportDirectory(), reportName);
746  if (Files.exists(reportPath)) {
747  result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_overrite_exiting(reportName),
748  Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION);
749 
750  if (result == JOptionPane.OK_OPTION) {
751  FileUtil.deleteFileDir(reportPath.toFile());
752  createReport(currentCase, reportName);
753  }
754  } else {
755  createReport(currentCase, reportName);
756  currentCase.addReport(reportPath.toString(), Bundle.VisualizationPanel_module_name(), reportName);
757 
758  }
759  }
760  }
761 
770  @NbBundle.Messages({
771  "VisualizationPane_DisplayName=Open Report",
772  "VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.",
773  "VisualizationPane_MessageBoxTitle=Open Report Failure",
774  "VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.",
775  "VisualizationPane_MissingReportFileMessage=The report file no longer exists.",
776  "VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.",
777  "# {0} - report path",
778  "VisualizationPane_Report_Success=Report Successfully create at:\n{0}",
779  "VisualizationPane_Report_OK_Button=OK",
780  "VisualizationPane_Open_Report=Open Report",})
781  private void createReport(Case currentCase, String reportName) throws IOException {
782 
783  // Create the report.
784  Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName, Bundle.VisualizationPane_reportName()); //NON_NLS
785  BufferedImage image = mxCellRenderer.createBufferedImage(graph, null, graph.getView().getScale(), Color.WHITE, true, null);
786  Path reportPath = new CommSnapShotReportWriter(currentCase, reportFolderPath, reportName, new Date(), image, currentFilter).writeReport();
787 
788  // Report success to the user and offer to open the report.
789  String message = Bundle.VisualizationPane_Report_Success(reportPath.toAbsolutePath());
790  String[] buttons = {Bundle.VisualizationPane_Open_Report(), Bundle.VisualizationPane_Report_OK_Button()};
791 
792  int result = JOptionPane.showOptionDialog(graphComponent, message,
793  Bundle.VisualizationPanel_action_dialogs_title(),
794  JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE,
795  null, buttons, buttons[1]);
796  if (result == JOptionPane.YES_NO_OPTION) {
797  try {
798  Desktop.getDesktop().open(reportPath.toFile());
799  } catch (IOException ex) {
800  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
801  Bundle.VisualizationPane_NoAssociatedEditorMessage(),
802  Bundle.VisualizationPane_MessageBoxTitle(),
803  JOptionPane.ERROR_MESSAGE);
804  } catch (UnsupportedOperationException ex) {
805  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
806  Bundle.VisualizationPane_NoOpenInEditorSupportMessage(),
807  Bundle.VisualizationPane_MessageBoxTitle(),
808  JOptionPane.ERROR_MESSAGE);
809  } catch (IllegalArgumentException ex) {
810  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
811  Bundle.VisualizationPane_MissingReportFileMessage(),
812  Bundle.VisualizationPane_MessageBoxTitle(),
813  JOptionPane.ERROR_MESSAGE);
814  } catch (SecurityException ex) {
815  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
816  Bundle.VisualizationPane_ReportFileOpenPermissionDeniedMessage(),
817  Bundle.VisualizationPane_MessageBoxTitle(),
818  JOptionPane.ERROR_MESSAGE);
819  }
820  }
821  }
822 
823  // Variables declaration - do not modify//GEN-BEGIN:variables
824  private JButton backButton;
825  private JPanel borderLayoutPanel;
826  private JButton clearVizButton;
827  private JButton fastOrganicLayoutButton;
828  private JButton fitZoomButton;
829  private JButton forwardButton;
830  private JToolBar.Separator jSeparator1;
831  private JToolBar.Separator jSeparator2;
832  private JToolBar.Separator jSeparator3;
833  private JTextPane jTextPane1;
834  private JFXPanel notificationsJFXPanel;
835  private JPanel placeHolderPanel;
836  private JButton snapshotButton;
837  private JToolBar toolbar;
838  private JButton zoomActualButton;
839  private JButton zoomInButton;
840  private JLabel zoomLabel;
841  private JButton zoomOutButton;
842  private JLabel zoomPercentLabel;
843  // End of variables declaration//GEN-END:variables
844 
849  final private class SelectionListener implements mxEventSource.mxIEventListener {
850 
851  @SuppressWarnings("unchecked")
852  @Override
853  public void invoke(Object sender, mxEventObject evt) {
854  Object[] selectionCells = graph.getSelectionCells();
855  if (selectionCells.length > 0) {
856  mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]);
857  HashSet<AccountDeviceInstance> selectedNodes = new HashSet<>();
858  HashSet<SelectionInfo.GraphEdge> selectedEdges = new HashSet<>();
859  for (mxICell cell : selectedCells) {
860  if (cell.isEdge()) {
861  mxICell source = (mxICell) graph.getModel().getTerminal(cell, true);
862  mxICell target = (mxICell) graph.getModel().getTerminal(cell, false);
863 
864  selectedEdges.add(new SelectionInfo.GraphEdge(((AccountDeviceInstanceKey) source.getValue()).getAccountDeviceInstance(),
865  ((AccountDeviceInstanceKey) target.getValue()).getAccountDeviceInstance()));
866 
867  } else if (cell.isVertex()) {
868  selectedNodes.add(((AccountDeviceInstanceKey) cell.getValue()).getAccountDeviceInstance());
869  }
870  }
871 
872  relationshipBrowser.setSelectionInfo(new SelectionInfo(selectedNodes, selectedEdges, currentFilter));
873  } else {
874  relationshipBrowser.setSelectionInfo(new SelectionInfo(new HashSet<>(), new HashSet<>(), currentFilter));
875  }
876  }
877  }
878 
882  private interface NamedGraphLayout extends mxIGraphLayout {
883 
884  String getDisplayName();
885  }
886 
890  final private class FastOrganicLayoutImpl extends mxFastOrganicLayout implements NamedGraphLayout {
891 
892  FastOrganicLayoutImpl(mxGraph graph) {
893  super(graph);
894  }
895 
896  @Override
897  public boolean isVertexIgnored(Object vertex) {
898  return super.isVertexIgnored(vertex)
899  || lockedVertexModel.isVertexLocked((mxCell) vertex);
900  }
901 
902  @Override
903  public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
904  if (isVertexIgnored(vertex)) {
905  return getVertexBounds(vertex);
906  } else {
907  return super.setVertexLocation(vertex, x, y);
908  }
909  }
910 
911  @Override
912  public String getDisplayName() {
913  return "Fast Organic";
914  }
915  }
916 
920  final private class CircleLayoutImpl extends mxCircleLayout implements NamedGraphLayout {
921 
922  CircleLayoutImpl(mxGraph graph) {
923  super(graph);
924  setResetEdges(true);
925  }
926 
927  @Override
928  public boolean isVertexIgnored(Object vertex) {
929  return super.isVertexIgnored(vertex)
930  || lockedVertexModel.isVertexLocked((mxCell) vertex);
931  }
932 
933  @Override
934  public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
935  if (isVertexIgnored(vertex)) {
936  return getVertexBounds(vertex);
937  } else {
938  return super.setVertexLocation(vertex, x, y);
939  }
940  }
941 
942  @Override
943  public String getDisplayName() {
944  return "Circle";
945  }
946  }
947 
951  final private class OrganicLayoutImpl extends mxOrganicLayout implements NamedGraphLayout {
952 
953  OrganicLayoutImpl(mxGraph graph) {
954  super(graph);
955  setResetEdges(true);
956  }
957 
958  @Override
959  public boolean isVertexIgnored(Object vertex) {
960  return super.isVertexIgnored(vertex)
961  || lockedVertexModel.isVertexLocked((mxCell) vertex);
962  }
963 
964  @Override
965  public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
966  if (isVertexIgnored(vertex)) {
967  return getVertexBounds(vertex);
968  } else {
969  return super.setVertexLocation(vertex, x, y);
970  }
971  }
972 
973  @Override
974  public String getDisplayName() {
975  return "Organic";
976  }
977  }
978 
982  final private class HierarchicalLayoutImpl extends mxHierarchicalLayout implements NamedGraphLayout {
983 
984  HierarchicalLayoutImpl(mxGraph graph) {
985  super(graph);
986  }
987 
988  @Override
989  public boolean isVertexIgnored(Object vertex) {
990  return super.isVertexIgnored(vertex)
991  || lockedVertexModel.isVertexLocked((mxCell) vertex);
992  }
993 
994  @Override
995  public mxRectangle setVertexLocation(Object vertex, double x, double y) { //NOPMD x, y are standard coordinate names
996  if (isVertexIgnored(vertex)) {
997  return getVertexBounds(vertex);
998  } else {
999  return super.setVertexLocation(vertex, x, y);
1000  }
1001  }
1002 
1003  @Override
1004  public String getDisplayName() {
1005  return "Hierarchical";
1006  }
1007  }
1008 
1013  private class CancelationListener implements ActionListener {
1014 
1015  private Future<?> cancellable;
1017 
1018  void configure(Future<?> cancellable, ModalDialogProgressIndicator progress) {
1019  this.cancellable = cancellable;
1020  this.progress = progress;
1021  }
1022 
1023  @Override
1024  public void actionPerformed(ActionEvent event) {
1025  progress.setCancelling("Cancelling...");
1026  cancellable.cancel(true);
1027  progress.finish();
1028  }
1029  }
1030 
1035  private class GraphMouseListener extends MouseAdapter {
1036 
1042  @Override
1043  public void mouseWheelMoved(final MouseWheelEvent event) {
1044  super.mouseWheelMoved(event);
1045  if (event.getPreciseWheelRotation() < 0) {
1046  graphComponent.zoomIn();
1047  } else if (event.getPreciseWheelRotation() > 0) {
1048  graphComponent.zoomOut();
1049  }
1050 
1051  CVTEvents.getCVTEventBus().post(new CVTEvents.ScaleChangeEvent(graph.getView().getScale()));
1052  }
1053 
1059  @Override
1060  public void mouseClicked(final MouseEvent event) {
1061  super.mouseClicked(event);
1062  if (SwingUtilities.isRightMouseButton(event)) {
1063  final mxCell cellAt = (mxCell) graphComponent.getCellAt(event.getX(), event.getY());
1064  if (cellAt != null && cellAt.isVertex()) {
1065  final JPopupMenu jPopupMenu = new JPopupMenu();
1066  final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) cellAt.getValue();
1067 
1068  Set<mxCell> selectedVertices
1069  = Stream.of(graph.getSelectionModel().getCells())
1070  .map(mxCell.class::cast)
1071  .filter(mxCell::isVertex)
1072  .collect(Collectors.toSet());
1073 
1074  if (lockedVertexModel.isVertexLocked(cellAt)) {
1075  jPopupMenu.add(new JMenuItem(new UnlockAction(selectedVertices)));
1076  } else {
1077  jPopupMenu.add(new JMenuItem(new LockAction(selectedVertices)));
1078  }
1079  if (pinnedAccountModel.isAccountPinned(adiKey.getAccountDeviceInstance())) {
1080  jPopupMenu.add(UnpinAccountsAction.getInstance().getPopupPresenter());
1081  } else {
1082  jPopupMenu.add(PinAccountsAction.getInstance().getPopupPresenter());
1083  jPopupMenu.add(ResetAndPinAccountsAction.getInstance().getPopupPresenter());
1084  }
1085  jPopupMenu.show(graphComponent.getGraphControl(), event.getX(), event.getY());
1086  }
1087  }
1088  }
1089  }
1090 
1094  @NbBundle.Messages({
1095  "VisualizationPanel.unlockAction.singularText=Unlock Selected Account",
1096  "VisualizationPanel.unlockAction.pluralText=Unlock Selected Accounts",})
1097  private final class UnlockAction extends AbstractAction {
1098 
1099  private final Set<mxCell> selectedVertices;
1100 
1101  UnlockAction(Set<mxCell> selectedVertices) {
1102  super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_unlockAction_pluralText() : Bundle.VisualizationPanel_unlockAction_singularText(),
1103  unlockIcon);
1104  this.selectedVertices = selectedVertices;
1105  }
1106 
1107  @Override
1108 
1109  public void actionPerformed(final ActionEvent event) {
1110  lockedVertexModel.unlock(selectedVertices);
1111  }
1112  }
1113 
1117  @NbBundle.Messages({
1118  "VisualizationPanel.lockAction.singularText=Lock Selected Account",
1119  "VisualizationPanel.lockAction.pluralText=Lock Selected Accounts"})
1120  private final class LockAction extends AbstractAction {
1121 
1122  private final Set<mxCell> selectedVertices;
1123 
1124  LockAction(Set<mxCell> selectedVertices) {
1125  super(selectedVertices.size() > 1 ? Bundle.VisualizationPanel_lockAction_pluralText() : Bundle.VisualizationPanel_lockAction_singularText(),
1126  lockIcon);
1127  this.selectedVertices = selectedVertices;
1128  }
1129 
1130  @Override
1131  public void actionPerformed(final ActionEvent event) {
1132  lockedVertexModel.lock(selectedVertices);
1133  }
1134  }
1135 }
void createReport(Case currentCase, String reportName)
void handleStateChange(StateManager.CommunicationsState newState)
static boolean deleteFileDir(File path)
Definition: FileUtil.java:87
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1891
synchronized void start(String message, int totalWorkUnits)
static String escapeFileName(String fileName)
Definition: FileUtil.java:169
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:676

Copyright © 2012-2021 Basis Technology. Generated on: Thu Jul 8 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.