class ComputationTargetTypeMap {

  public void put(final ComputationTargetType key, final V value) {
    key.accept(new ComputationTargetTypeVisitor<Void, Void>() {

      @Override
      public Void visitMultipleComputationTargetTypes(final Set<ComputationTargetType> types, final Void data) {
        for (ComputationTargetType type : types) {
          type.accept(this, data);
        }
        return null;
      }

      @Override
      public Void visitNestedComputationTargetTypes(final List<ComputationTargetType> types, final Void data) {
        return types.get(types.size() - 1).accept(this, data);
      }

      @Override
      public Void visitNullComputationTargetType(final Void data) {
        final V nullValue = getNullValue();
        if (nullValue == null) {
          if (replaceNullValue(null, value)) {
            return null;
          } else {
            throw new ConcurrentModificationException();
          }
        } else {
          if (getFoldFunction() != null) {
            final V newValue = getFoldFunction().apply(nullValue, value);
            if (replaceNullValue(nullValue, newValue)) {
              return null;
            } else {
              throw new ConcurrentModificationException();
            }
          } else {
            throw new IllegalStateException("Already held " + nullValue + " for NULL");
          }
        }
      }

      @SuppressWarnings("unchecked")
      @Override
      public Void visitClassComputationTargetType(final Class<? extends UniqueIdentifiable> type, final Void data) {
        V newValue = (value != null) ? value : (V) NULL;
        final V previous = getUnderlying().putIfAbsent(type, newValue);
        if (previous == null) {
          return null;
        } else {
          if (previous == NULL) {
            if (getUnderlying().replace(type, previous, newValue)) {
              return null;
            } else {
              throw new ConcurrentModificationException();
            }
          }
          if (getFoldFunction() != null) {
            newValue = getFoldFunction().apply(previous, value);
            if (newValue == null) {
              newValue = (V) NULL;
            }
            if (getUnderlying().replace(type, previous, newValue)) {
              return null;
            } else {
              throw new ConcurrentModificationException();
            }
          } else {
            throw new IllegalStateException("Already held " + previous + " for " + key);
          }
        }
      }

    }, null);
  }

  public  ComputationTargetTypeMap();
  public  ComputationTargetTypeMap(BinaryOperator<V> fold);

}