// from Operator
  @Override
  public List<String> process(List<String> tuple) {
    _numTuplesProcessed++;
    if (_distinct != null) {
      tuple = _distinct.process(tuple);
      if (tuple == null) {
        return null;
      }
    }
    String tupleHash;
    if (_groupByType == GB_PROJECTION) {
      tupleHash =
          MyUtilities.createHashString(
              tuple, _groupByColumns, _groupByProjection.getExpressions(), _map);
    } else {
      tupleHash = MyUtilities.createHashString(tuple, _groupByColumns, _map);
    }
    T value = _storage.update(tuple, tupleHash);
    String strValue = _wrapper.toString(value);

    // propagate further the affected tupleHash-tupleValue pair
    List<String> affectedTuple = new ArrayList<String>();
    affectedTuple.add(tupleHash);
    affectedTuple.add(strValue);

    return affectedTuple;
  }
 @Override
 public AggregateSumOperator setGroupByProjection(ProjectOperator groupByProjection) {
   if (!alreadySetOther(GB_PROJECTION)) {
     _groupByType = GB_PROJECTION;
     _groupByProjection = groupByProjection;
     _storage.setSingleEntry(false);
     return this;
   } else {
     throw new RuntimeException("Aggragation already has groupBy set!");
   }
 }
 // from AgregateOperator
 @Override
 public AggregateSumOperator<T> setGroupByColumns(List<Integer> groupByColumns) {
   if (!alreadySetOther(GB_COLUMNS)) {
     _groupByType = GB_COLUMNS;
     _groupByColumns = groupByColumns;
     _storage.setSingleEntry(false);
     return this;
   } else {
     throw new RuntimeException("Aggragation already has groupBy set!");
   }
 }
 // for this method it is essential that HASH_DELIMITER, which is used in tupleToString method,
 //  is the same as DIP_GLOBAL_ADD_DELIMITER
 @Override
 public List<String> getContent() {
   String str = _storage.getContent();
   return str == null ? null : Arrays.asList(str.split("\\r?\\n"));
 }
 @Override
 public String printContent() {
   return _storage.getContent();
 }
 @Override
 public void clearStorage() {
   _storage.reset();
 }