/**
  * Determines whether a BitSet contains the same information as a RowSubset.
  *
  * @param mask bit set
  * @param rset row subset
  * @param nrow number of rows over which they are required to match
  * @return true iff they represent the same data
  */
 private static boolean matches(BitSet mask, RowSubset rset, int nrow) {
   for (int i = 0; i < nrow; i++) {
     if (mask.get(i) != rset.isIncluded((long) i)) {
       return false;
     }
   }
   return true;
 }
  /**
   * Sends a row subset to a specific list of PLASTIC listeners. It uses the <code>
   * ivo://votech.org/votable/showObjects</code> message.
   *
   * @param tcModel topcat model
   * @param rset row subset within tcModel
   * @param hub hub object
   * @param plasticId registration ID for this application
   * @param recipients listeners to receive it; null means do a broadcast
   */
  private void transmitSubset(
      TopcatModel tcModel,
      RowSubset rset,
      PlasticHubListener hub,
      URI plasticId,
      final URI[] recipients)
      throws IOException {

    /* See if the table we're broadcasting the set for is any of the
     * tables we've previously broadcast.  If so, send the rows using
     * the same ID. */
    boolean done = false;
    for (Iterator it = idMap_.entrySet().iterator(); it.hasNext(); ) {
      Map.Entry entry = (Map.Entry) it.next();
      String tableId = entry.getKey().toString();
      TableWithRows tr = (TableWithRows) entry.getValue();
      TopcatModel tcm = (TopcatModel) tr.tcModelRef_.get();
      if (tcm != null && tcm == tcModel) {
        List rowList = new ArrayList();

        /* Assemble a list of rows, possibly modulated by the
         * row order when the table was sent originally. */
        int[] rowMap = tr.rowMap_;
        if (rowMap == null) {
          int nrow = (int) Math.min((long) Integer.MAX_VALUE, tcModel.getDataModel().getRowCount());
          for (int i = 0; i < nrow; i++) {
            if (rset.isIncluded(i)) {
              rowList.add(new Integer(i));
            }
          }
        } else {
          int nrow = rowMap.length;
          for (int i = 0; i < nrow; i++) {
            if (rset.isIncluded(rowMap[i])) {
              rowList.add(new Integer(i));
            }
          }
        }

        /* Send the request. */
        List argList = Arrays.asList(new Object[] {tableId, rowList});
        if (recipients == null) {
          hub.requestAsynch(plasticId, MessageId.VOT_SHOWOBJECTS, argList);
        } else {
          hub.requestToSubsetAsynch(
              plasticId, MessageId.VOT_SHOWOBJECTS, argList, Arrays.asList(recipients));
        }
        done = true;
      }
    }

    /* If that didn't result in any sends, try using the basic URL of
     * the table. */
    if (!done) {
      URL url = URLUtils.fixURL(tcModel.getDataModel().getBaseTable().getURL());
      if (url != null) {
        List rowList = new ArrayList();
        int nrow = (int) Math.min((long) Integer.MAX_VALUE, tcModel.getDataModel().getRowCount());
        for (int i = 0; i < nrow; i++) {
          if (rset.isIncluded(i)) {
            rowList.add(new Integer(i));
          }
        }
        List argList = Arrays.asList(new Object[] {url.toString(), rowList});
        if (recipients == null) {
          hub.requestAsynch(plasticId, MessageId.VOT_SHOWOBJECTS, argList);
        } else {
          hub.requestToSubsetAsynch(
              plasticId, MessageId.VOT_SHOWOBJECTS, argList, Arrays.asList(recipients));
        }
      }
    }
  }