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.List;
 
   38 import java.util.concurrent.CancellationException;
 
   39 import java.util.concurrent.ExecutionException;
 
   40 import java.util.logging.Level;
 
   41 import javax.swing.SwingWorker;
 
   55 final class CommunicationsGraph 
extends mxGraph {
 
   57     private static final Logger logger = Logger.getLogger(CommunicationsGraph.class.getName());
 
   58     private static final URL MARKER_PIN_URL = CommunicationsGraph.class.getResource(
"/org/sleuthkit/autopsy/communications/images/marker--pin.png");
 
   59     private static final URL LOCK_URL = CommunicationsGraph.class.getResource(
"/org/sleuthkit/autopsy/communications/images/lock_large_locked.png");
 
   62     private final static Mustache labelMustache;
 
   65         final InputStream templateStream = CommunicationsGraph.class.getResourceAsStream(
"/org/sleuthkit/autopsy/communications/Vertex_Label_template.html");
 
   66         labelMustache = 
new DefaultMustacheFactory().compile(
new InputStreamReader(templateStream), 
"Vertex_Label");
 
   71     static final private mxStylesheet mxStylesheet = 
new mxStylesheet();
 
   75         mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
 
   76         mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_ELLIPSE);
 
   77         mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_FONTCOLOR, 
"000000");
 
   80         mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_NOLABEL, 
true);
 
   81         mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_PERIMETER_SPACING, 0);
 
   82         mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_ENDARROW, mxConstants.NONE);
 
   83         mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE);
 
   87     private final Map<String, mxCell> nodeMap = 
new HashMap<>();
 
   90     private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
 
   91     private final LockedVertexModel lockedVertexModel;
 
   93     private final PinnedAccountModel pinnedAccountModel;
 
   95     CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) {
 
   97         this.pinnedAccountModel = pinnedAccountModel;
 
   98         this.lockedVertexModel = lockedVertexModel;
 
  100         setAutoSizeCells(
true);
 
  101         setCellsCloneable(
false);
 
  102         setDropEnabled(
false);
 
  103         setCellsCloneable(
false);
 
  104         setCellsEditable(
false);
 
  105         setCellsResizable(
false);
 
  106         setCellsMovable(
true);
 
  107         setCellsDisconnectable(
false);
 
  108         setConnectableEdges(
false);
 
  109         setDisconnectOnMove(
false);
 
  110         setEdgeLabelsMovable(
false);
 
  111         setVertexLabelsMovable(
false);
 
  112         setAllowDanglingEdges(
false);
 
  113         setCellsBendable(
true);
 
  114         setKeepEdgesInBackground(
true);
 
  115         setResetEdgesOnMove(
true);
 
  124     LockedVertexModel getLockedVertexModel() {
 
  125         return lockedVertexModel;
 
  128     PinnedAccountModel getPinnedAccountModel() {
 
  129         return pinnedAccountModel;
 
  135         removeCells(getChildVertices(getDefaultParent()));
 
  139     public String convertValueToString(Object cell) {
 
  140         final StringWriter stringWriter = 
new StringWriter();
 
  141         HashMap<String, Object> scopes = 
new HashMap<>();
 
  143         Object value = getModel().getValue(cell);
 
  144         if (value instanceof AccountDeviceInstanceKey) {
 
  145             final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
 
  147             scopes.put(
"accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
 
  148             scopes.put(
"size", Math.round(Math.log(adiKey.getMessageCount()) + 5));
 
  149             scopes.put(
"iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
 
  150             scopes.put(
"pinned", pinnedAccountModel.isAccountPinned(adiKey));
 
  151             scopes.put(
"MARKER_PIN_URL", MARKER_PIN_URL);
 
  152             scopes.put(
"locked", lockedVertexModel.isVertexLocked((mxCell) cell));
 
  153             scopes.put(
"LOCK_URL", LOCK_URL);
 
  155             labelMustache.execute(stringWriter, scopes);
 
  157             return stringWriter.toString();
 
  164     public String getToolTipForCell(Object cell) {
 
  165         final StringWriter stringWriter = 
new StringWriter();
 
  166         HashMap<String, Object> scopes = 
new HashMap<>();
 
  168         Object value = getModel().getValue(cell);
 
  169         if (value instanceof AccountDeviceInstanceKey) {
 
  170             final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
 
  172             scopes.put(
"accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
 
  173             scopes.put(
"relationships", 12);
 
  174             scopes.put(
"iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
 
  175             scopes.put(
"pinned", pinnedAccountModel.isAccountPinned(adiKey));
 
  176             scopes.put(
"MARKER_PIN_URL", MARKER_PIN_URL);
 
  177             scopes.put(
"locked", lockedVertexModel.isVertexLocked((mxCell) cell));
 
  178             scopes.put(
"LOCK_URL", LOCK_URL);
 
  179             scopes.put(
"device_id", adiKey.getAccountDeviceInstance().getDeviceId());
 
  181             labelMustache.execute(stringWriter, scopes);
 
  183             return stringWriter.toString();
 
  185             final mxICell edge = (mxICell) cell;
 
  186             final long count = (long) edge.getValue();
 
  187             return "<html>" + edge.getId() + 
"<br>" + count + (count == 1 ? 
" relationship" : 
" relationships") + 
"</html>";
 
  191     SwingWorker<?, ?> rebuild(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
 
  192         return new RebuildWorker(progress, commsManager, currentFilter);
 
  197         getView().setScale(1);
 
  198         pinnedAccountModel.clear();
 
  199         lockedVertexModel.clear();
 
  202     private mxCell getOrCreateVertex(AccountDeviceInstanceKey accountDeviceInstanceKey) {
 
  203         final AccountDeviceInstance accountDeviceInstance = accountDeviceInstanceKey.getAccountDeviceInstance();
 
  204         final String name = accountDeviceInstance.getAccount().getTypeSpecificID();
 
  206         final mxCell vertex = nodeMap.computeIfAbsent(name + accountDeviceInstance.getDeviceId(), vertexName -> {
 
  207             double size = Math.sqrt(accountDeviceInstanceKey.getMessageCount()) + 10;
 
  209             mxCell newVertex = (mxCell) insertVertex(
 
  211                     name, accountDeviceInstanceKey,
 
  221     @SuppressWarnings(
"unchecked")
 
  222     private mxCell addOrUpdateEdge(
long relSources, AccountDeviceInstanceKey account1, AccountDeviceInstanceKey account2) {
 
  223         mxCell vertex1 = getOrCreateVertex(account1);
 
  224         mxCell vertex2 = getOrCreateVertex(account2);
 
  225         Object[] edgesBetween = getEdgesBetween(vertex1, vertex2);
 
  227         if (edgesBetween.length == 0) {
 
  228             final String edgeName = vertex1.getId() + 
" - " + vertex2.getId();
 
  229             edge = (mxCell) insertEdge(getDefaultParent(), edgeName, relSources, vertex1, vertex2,
 
  230                     "strokeWidth=" + (Math.log(relSources) + 1));
 
  232             edge = (mxCell) edgesBetween[0];
 
  233             edge.setStyle(
"strokeWidth=" + (Math.log(relSources) + 1));
 
  249             this.progressIndicator = progress;
 
  257             progressIndicator.
start(
"Loading accounts");
 
  258             int progressCounter = 0;
 
  263                 final Map<AccountDeviceInstance, AccountDeviceInstanceKey> relatedAccounts = 
new HashMap<>();
 
  264                 for (
final AccountDeviceInstanceKey adiKey : pinnedAccountModel.getPinnedAccounts()) {
 
  269                     final List<AccountDeviceInstance> relatedAccountDeviceInstances
 
  270                             = commsManager.getRelatedAccountDeviceInstances(adiKey.getAccountDeviceInstance(), 
currentFilter);
 
  271                     relatedAccounts.put(adiKey.getAccountDeviceInstance(), adiKey);
 
  272                     getOrCreateVertex(adiKey);
 
  274                     for (
final AccountDeviceInstance relatedADI : relatedAccountDeviceInstances) {
 
  275                         final long adiRelationshipsCount = commsManager.getRelationshipSourcesCount(relatedADI, currentFilter);
 
  276                         final AccountDeviceInstanceKey relatedADIKey = 
new AccountDeviceInstanceKey(relatedADI, currentFilter, adiRelationshipsCount);
 
  277                         relatedAccounts.put(relatedADI, relatedADIKey); 
 
  279                     progressIndicator.
progress(++progressCounter);
 
  282                 Set<AccountDeviceInstance> accounts = relatedAccounts.keySet();
 
  284                 Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(accounts, currentFilter);
 
  286                 int total = relationshipCounts.size();
 
  288                 String progressText = 
"";
 
  290                 for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) {
 
  291                     Long count = entry.getValue();
 
  292                     AccountPair relationshipKey = entry.getKey();
 
  293                     AccountDeviceInstanceKey account1 = relatedAccounts.get(relationshipKey.getFirst());
 
  294                     AccountDeviceInstanceKey account2 = relatedAccounts.get(relationshipKey.getSecond());
 
  296                     if (pinnedAccountModel.isAccountPinned(account1)
 
  297                             || pinnedAccountModel.isAccountPinned(account2)) {
 
  298                         mxCell addEdge = addOrUpdateEdge(count, account1, account2);
 
  299                         progressText = addEdge.getId();
 
  301                     progressIndicator.
progress(progressText, progress++);
 
  303             } 
catch (TskCoreException tskCoreException) {
 
  304                 logger.log(Level.SEVERE, 
"Error", tskCoreException);
 
  315             } 
catch (InterruptedException | ExecutionException ex) {
 
  316                 logger.log(Level.SEVERE, 
"Error building graph visualization. ", ex);
 
  317             } 
catch (CancellationException ex) {
 
  318                 logger.log(Level.INFO, 
"Graph visualization cancelled");
 
  320                 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)