/* package */ void updateDepGraphCells(Set<WebGridCell> newCells) {
    Set<WebGridCell> currentCells = _depGraphGrids.keySet();
    Set<WebGridCell> cellsToRemove = Sets.difference(currentCells, newCells);
    Set<WebGridCell> cellsToAdd = Sets.difference(newCells, currentCells);

    for (WebGridCell cell : cellsToRemove) {
      _depGraphGrids.remove(cell);
    }
    for (WebGridCell cell : cellsToAdd) {
      String gridName = getName() + ".depgraph-" + cell.getRowId() + "-" + cell.getColumnId();
      OperationTimer timer = new OperationTimer(s_logger, "depgraph");
      Pair<String, ValueSpecification> columnMappingPair =
          getGridStructure()
              .findCellSpecification(cell, getViewClient().getLatestCompiledViewDefinition());
      s_logger.debug("includeDepGraph took {}", timer.finished());
      // TODO should this ever happen? it is currently
      if (columnMappingPair != null) {
        PushWebViewDepGraphGrid grid =
            new PushWebViewDepGraphGrid(
                gridName,
                getViewClient(),
                getConverterCache(),
                cell,
                columnMappingPair.getFirst(),
                columnMappingPair.getSecond());
        _depGraphGrids.put(cell, grid);
      }
    }
  }
  /**
   * @param target The target whose result is required
   * @param resultModel The model containing the results
   * @param resultTimestamp The timestamp of the results
   * @return {@code {"rowId": rowId, "0": col0Val, "1": col1Val, ...}} cell values: {@code {"v":
   *     value, "h": [historyVal1, historyVal2, ...]}}
   */
  public Map<String, Object> getTargetResult(
      ComputationTargetSpecification target,
      ViewTargetResultModel resultModel,
      Long resultTimestamp) {
    Integer rowId = getGridStructure().getRowId(target);
    if (rowId == null) {
      // Result not in the grid
      return Collections.emptyMap();
    }

    Map<String, Object> valuesToSend = createTargetResult(rowId);
    // insert nulls into the results for cells which are unsatisfied in the dependency graph
    for (Integer unsatisfiedColId : getGridStructure().getUnsatisfiedCells(rowId)) {
      valuesToSend.put(Integer.toString(unsatisfiedColId), null);
    }

    // Whether or not the row is in the viewport, we may have to store history
    if (resultModel != null) {
      for (String calcConfigName : resultModel.getCalculationConfigurationNames()) {
        for (ComputedValue value : resultModel.getAllValues(calcConfigName)) {
          ValueSpecification specification = value.getSpecification();
          Collection<WebViewGridColumn> columns =
              getGridStructure().getColumns(calcConfigName, specification);
          if (columns == null) {
            // Expect a column for every value
            s_logger.warn(
                "Could not find column for calculation configuration {} with value specification {}",
                calcConfigName,
                specification);
            continue;
          }

          Object originalValue = value.getValue();
          for (WebViewGridColumn column : columns) {
            int colId = column.getId();
            WebGridCell cell = WebGridCell.of(rowId, colId);
            ResultConverter<Object> converter;
            if (originalValue == null) {
              converter = null;
            } else {
              converter =
                  getConverter(
                      column, value.getSpecification().getValueName(), originalValue.getClass());
            }
            Map<String, Object> cellData =
                getCellValue(cell, specification, originalValue, resultTimestamp, converter);
            if (cellData != null) {
              valuesToSend.put(Integer.toString(colId), cellData);
            }
          }
        }
      }
    }
    return valuesToSend;
  }
 @Override
 protected boolean isHistoryOutput(WebGridCell cell) {
   return _historyOutputs.contains(cell.getColumnId());
 }