19 package org.sleuthkit.autopsy.communications;
 
   21 import com.github.mustachejava.DefaultMustacheFactory;
 
   22 import com.github.mustachejava.Mustache;
 
   23 import com.google.common.collect.Multimap;
 
   24 import com.google.common.collect.MultimapBuilder;
 
   25 import com.mxgraph.model.mxCell;
 
   26 import com.mxgraph.model.mxICell;
 
   27 import com.mxgraph.util.mxConstants;
 
   28 import com.mxgraph.view.mxGraph;
 
   29 import com.mxgraph.view.mxStylesheet;
 
   30 import java.io.InputStream;
 
   31 import java.io.InputStreamReader;
 
   32 import java.io.StringWriter;
 
   34 import java.util.HashMap;
 
   35 import java.util.HashSet;
 
   36 import java.util.List;
 
   39 import java.util.concurrent.CancellationException;
 
   40 import java.util.concurrent.ExecutionException;
 
   41 import java.util.logging.Level;
 
   42 import javax.swing.SwingWorker;
 
   56 final class CommunicationsGraph 
extends mxGraph {
 
   58     private static final Logger logger = Logger.getLogger(CommunicationsGraph.class.getName());
 
   59     private static final URL MARKER_PIN_URL = CommunicationsGraph.class.getResource(
"/org/sleuthkit/autopsy/communications/images/marker--pin.png");
 
   60     private static final URL LOCK_URL = CommunicationsGraph.class.getResource(
"/org/sleuthkit/autopsy/communications/images/lock_large_locked.png");
 
   63     private final static Mustache labelMustache;
 
   66         final InputStream templateStream = CommunicationsGraph.class.getResourceAsStream(
"/org/sleuthkit/autopsy/communications/Vertex_Label_template.html");
 
   67         labelMustache = 
new DefaultMustacheFactory().compile(
new InputStreamReader(templateStream), 
"Vertex_Label");
 
   72     static final private mxStylesheet mxStylesheet = 
new mxStylesheet();
 
   76         mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
 
   77         mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_ELLIPSE);
 
   78         mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_FONTCOLOR, 
"000000");
 
   81         mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_NOLABEL, 
true);
 
   82         mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_PERIMETER_SPACING, 0);
 
   83         mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_ENDARROW, mxConstants.NONE);
 
   84         mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE);
 
   88     private final Map<String, mxCell> nodeMap = 
new HashMap<>();
 
   91     private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
 
   92     private final LockedVertexModel lockedVertexModel;
 
   94     private final PinnedAccountModel pinnedAccountModel;
 
   96     CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) {
 
   98         this.pinnedAccountModel = pinnedAccountModel;
 
   99         this.lockedVertexModel = lockedVertexModel;
 
  101         setAutoSizeCells(
true);
 
  102         setCellsCloneable(
false);
 
  103         setDropEnabled(
false);
 
  104         setCellsCloneable(
false);
 
  105         setCellsEditable(
false);
 
  106         setCellsResizable(
false);
 
  107         setCellsMovable(
true);
 
  108         setCellsDisconnectable(
false);
 
  109         setConnectableEdges(
false);
 
  110         setDisconnectOnMove(
false);
 
  111         setEdgeLabelsMovable(
false);
 
  112         setVertexLabelsMovable(
false);
 
  113         setAllowDanglingEdges(
false);
 
  114         setCellsBendable(
true);
 
  115         setKeepEdgesInBackground(
true);
 
  116         setResetEdgesOnMove(
true);
 
  125     LockedVertexModel getLockedVertexModel() {
 
  126         return lockedVertexModel;
 
  129     PinnedAccountModel getPinnedAccountModel() {
 
  130         return pinnedAccountModel;
 
  136         removeCells(getChildVertices(getDefaultParent()));
 
  140     public String convertValueToString(Object cell) {
 
  141         final StringWriter stringWriter = 
new StringWriter();
 
  142         HashMap<String, Object> scopes = 
new HashMap<>();
 
  144         Object value = getModel().getValue(cell);
 
  145         if (value instanceof AccountDeviceInstanceKey) {
 
  146             final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
 
  148             scopes.put(
"accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
 
  149             scopes.put(
"size", Math.round(Math.log(adiKey.getMessageCount()) + 5));
 
  150             scopes.put(
"iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
 
  151             scopes.put(
"pinned", pinnedAccountModel.isAccountPinned(adiKey.getAccountDeviceInstance()));
 
  152             scopes.put(
"MARKER_PIN_URL", MARKER_PIN_URL);
 
  153             scopes.put(
"locked", lockedVertexModel.isVertexLocked((mxCell) cell));
 
  154             scopes.put(
"LOCK_URL", LOCK_URL);
 
  156             labelMustache.execute(stringWriter, scopes);
 
  158             return stringWriter.toString();
 
  165     public String getToolTipForCell(Object cell) {
 
  166         final StringWriter stringWriter = 
new StringWriter();
 
  167         HashMap<String, Object> scopes = 
new HashMap<>();
 
  169         Object value = getModel().getValue(cell);
 
  170         if (value instanceof AccountDeviceInstanceKey) {
 
  171             final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
 
  173             scopes.put(
"accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
 
  174             scopes.put(
"relationships", 12);
 
  175             scopes.put(
"iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
 
  176             scopes.put(
"pinned", pinnedAccountModel.isAccountPinned(adiKey.getAccountDeviceInstance()));
 
  177             scopes.put(
"MARKER_PIN_URL", MARKER_PIN_URL);
 
  178             scopes.put(
"locked", lockedVertexModel.isVertexLocked((mxCell) cell));
 
  179             scopes.put(
"LOCK_URL", LOCK_URL);
 
  180             scopes.put(
"device_id", adiKey.getAccountDeviceInstance().getDeviceId());
 
  182             labelMustache.execute(stringWriter, scopes);
 
  184             return stringWriter.toString();
 
  186             final mxICell edge = (mxICell) cell;
 
  187             final long count = (long) edge.getValue();
 
  188             return "<html>" + edge.getId() + 
"<br>" + count + (count == 1 ? 
" relationship" : 
" relationships") + 
"</html>";
 
  192     SwingWorker<?, ?> rebuild(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
 
  193         return new RebuildWorker(progress, commsManager, currentFilter);
 
  198         getView().setScale(1);
 
  199         pinnedAccountModel.clear();
 
  200         lockedVertexModel.clear();
 
  203     private mxCell getOrCreateVertex(AccountDeviceInstance adi, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
 
  204         final AccountDeviceInstance accountDeviceInstance = adi;
 
  205         final String name = accountDeviceInstance.getAccount().getTypeSpecificID();
 
  207         final mxCell vertex = nodeMap.computeIfAbsent(name + accountDeviceInstance.getDeviceId(), vertexName -> {
 
  208             long adiRelationshipsCount = 1;
 
  210                 adiRelationshipsCount = commsManager.getRelationshipSourcesCount(accountDeviceInstance, currentFilter);
 
  211             } 
catch (TskCoreException tskCoreException) {
 
  212                 logger.log(Level.SEVERE, 
"There was an error fetching relationships for the node: " + accountDeviceInstance, tskCoreException);
 
  215             double size = Math.sqrt(adiRelationshipsCount) + 10;
 
  216             AccountDeviceInstanceKey adiKey = 
new AccountDeviceInstanceKey(adi, currentFilter, adiRelationshipsCount);
 
  218             mxCell newVertex = (mxCell) insertVertex(
 
  230     @SuppressWarnings(
"unchecked")
 
  231     private mxCell addOrUpdateEdge(
long relSources, 
 
  232             AccountDeviceInstance account1, AccountDeviceInstance account2, 
 
  233             CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
 
  234         mxCell vertex1 = getOrCreateVertex(account1, commsManager, currentFilter);
 
  235         mxCell vertex2 = getOrCreateVertex(account2, commsManager, currentFilter);
 
  236         Object[] edgesBetween = getEdgesBetween(vertex1, vertex2);
 
  238         if (edgesBetween.length == 0) {
 
  239             final String edgeName = vertex1.getId() + 
" - " + vertex2.getId();
 
  240             edge = (mxCell) insertEdge(getDefaultParent(), edgeName, relSources, vertex1, vertex2,
 
  241                     "strokeWidth=" + (Math.log(relSources) + 1));
 
  243             edge = (mxCell) edgesBetween[0];
 
  244             edge.setStyle(
"strokeWidth=" + (Math.log(relSources) + 1));
 
  260             this.progressIndicator = progress;
 
  268             progressIndicator.
start(
"Loading accounts");
 
  269             int progressCounter = 0;
 
  274                 final Set<AccountDeviceInstance> relatedAccounts = 
new HashSet<>();
 
  275                 for (
final AccountDeviceInstance adi : pinnedAccountModel.getPinnedAccounts()) {
 
  280                     final List<AccountDeviceInstance> relatedAccountDeviceInstances
 
  281                             = commsManager.getRelatedAccountDeviceInstances(adi, currentFilter);
 
  282                     relatedAccounts.add(adi);
 
  283                     getOrCreateVertex(adi, commsManager, currentFilter);
 
  285                     for (
final AccountDeviceInstance relatedADI : relatedAccountDeviceInstances)
 
  286                         relatedAccounts.add(relatedADI);
 
  288                     progressIndicator.
progress(++progressCounter);
 
  291                 Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(relatedAccounts, currentFilter);
 
  293                 int total = relationshipCounts.size();
 
  295                 String progressText = 
"";
 
  297                 for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) {
 
  298                     Long count = entry.getValue();
 
  299                     AccountPair relationshipKey = entry.getKey();
 
  300                     AccountDeviceInstance account1 = relationshipKey.getFirst();
 
  301                     AccountDeviceInstance account2 = relationshipKey.getSecond();
 
  303                     if (pinnedAccountModel.isAccountPinned(account1)
 
  304                             || pinnedAccountModel.isAccountPinned(account2)) {
 
  305                         mxCell addEdge = addOrUpdateEdge(count, account1, account2, commsManager, currentFilter);
 
  306                         progressText = addEdge.getId();
 
  308                     progressIndicator.
progress(progressText, progress++);
 
  310             } 
catch (TskCoreException tskCoreException) {
 
  311                 logger.log(Level.SEVERE, 
"Error", tskCoreException);
 
  322             } 
catch (InterruptedException | ExecutionException ex) {
 
  323                 logger.log(Level.SEVERE, 
"Error building graph visualization. ", ex);
 
  324             } 
catch (CancellationException ex) {
 
  325                 logger.log(Level.INFO, 
"Graph visualization cancelled");
 
  327                 progressIndicator.
finish();
 
void start(String message, int totalWorkUnits)
final ProgressIndicator progressIndicator
final CommunicationsManager commsManager
final CommunicationsFilter currentFilter
void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits)
void progress(String message)