/**
   * Add a scored ID. The ID is copied into the builder, not referenced. All side channels on the ID
   * must have already been added with one of the {@code addChannel} methods.
   *
   * @param id The ID.
   * @return The builder (for chaining).
   */
  public ScoredIdListBuilder add(ScoredId id) {
    Preconditions.checkState(ids != null, "builder has been finished");
    // check whether all symbols are valid
    Collection<SymbolValue<?>> chans = id.getChannels();
    if (!ignoreUnknown) {
      for (SymbolValue<?> chan : chans) {
        TypedSymbol<?> sym = chan.getSymbol();
        boolean good =
            sym.getType().equals(Double.class)
                ? channels.containsKey(sym.getRawSymbol())
                : typedChannels.containsKey(sym);
        if (!good) {
          throw new IllegalArgumentException("channel " + sym + " not known");
        }
      }
    }

    // now we're ready to add
    int idx = ids.size();
    add(id.getId(), id.getScore());
    assert ids.size() == idx + 1;
    assert scores.size() == idx + 1;
    for (SymbolValue<?> sv : chans) {
      TypedSymbol<?> sym = sv.getSymbol();
      if (sym.getType().equals(Double.class) && channels.containsKey(sym.getRawSymbol())) {
        ChannelStorage chan = channels.get(sym.getRawSymbol());
        assert chan.values.size() == idx + 1;
        if (sv instanceof DoubleSymbolValue) {
          chan.values.set(idx, ((DoubleSymbolValue) sv).getDoubleValue());
        } else {
          Object v = sv.getValue();
          chan.values.set(idx, (Double) v);
        }
      } else {
        TypedChannelStorage chan = typedChannels.get(sv.getSymbol());
        if (chan != null) {
          assert chan.values.size() == idx + 1;
          chan.values.set(idx, sv.getValue());
        }
      }
    }
    return this;
  }
 /**
  * Get the number of items currently in the builder.
  *
  * @return The number of items in the builder.
  */
 public int size() {
   assert ids.size() == scores.size();
   return ids.size();
 }