/**
  * Attempts to locate a table by its ID. This is currently a URL string; either one got from a
  * previous VOT_LOADURL message or one inherent in the table.
  *
  * @param tableId table identifier URL string
  * @return tableWithRows object corresponding to tableId, or null
  */
 private TableWithRows lookupTable(String tableId) {
   TableWithRows tr = (TableWithRows) idMap_.get(tableId);
   if (tr != null) {
     return tr;
   } else {
     ListModel tablesList = ControlWindow.getInstance().getTablesListModel();
     for (int i = 0; i < tablesList.getSize(); i++) {
       TopcatModel tcModel = (TopcatModel) tablesList.getElementAt(i);
       URL url = tcModel.getDataModel().getBaseTable().getURL();
       if (URLUtils.sameResource(url, URLUtils.makeURL(tableId))) {
         return new TableWithRows(tcModel, null);
       }
     }
   }
   return null;
 }
  /**
   * Sends a table to a specific list of PLASTIC listeners.
   *
   * @param tcModel the table model to broadcast
   * @param hub hub object
   * @param plasticId registration ID for this application
   * @param recipients listeners to receive it; null means do a broadcast
   */
  private void transmitTable(
      TopcatModel tcModel,
      final PlasticHubListener hub,
      final URI plasticId,
      final URI[] recipients)
      throws IOException {

    /* Write the data as a VOTable to a temporary file preparatory to
     * broadcast. */
    final File tmpfile = File.createTempFile("plastic", ".vot");
    final String tmpUrl = URLUtils.makeFileURL(tmpfile).toString();
    tmpfile.deleteOnExit();
    OutputStream ostrm = new BufferedOutputStream(new FileOutputStream(tmpfile));
    try {
      new VOTableWriter(DataFormat.TABLEDATA, true)
          .writeStarTable(tcModel.getApparentStarTable(), ostrm);
    } catch (IOException e) {
      tmpfile.delete();
      throw e;
    } finally {
      ostrm.close();
    }

    /* Store a record of the table that was broadcast with its
     * state. */
    int[] rowMap = tcModel.getViewModel().getRowMap();
    idMap_.put(tmpUrl, new TableWithRows(tcModel, rowMap));

    /* Do the broadcast, synchronously so that we don't delete the
     * temporary file too early, but in another thread so that we
     * don't block the GUI. */
    new Thread("PLASTIC table broadcast") {
      public void run() {
        List argList = Arrays.asList(new Object[] {tmpUrl, tmpUrl});
        Map responses =
            recipients == null
                ? hub.request(plasticId, MessageId.VOT_LOADURL, argList)
                : hub.requestToSubset(
                    plasticId, MessageId.VOT_LOADURL, argList, Arrays.asList(recipients));

        /* Delete the temp file. */
        tmpfile.delete();
      }
    }.start();
  }
  /**
   * Transmits a request for listening applications to highlight a given table row.
   *
   * @param tcModel topcat model of table to broadcast
   * @param lrow row index within tcModel
   * @param recipients array of plastic IDs for target applications; if null, broadcast will be to
   *     all
   * @return true iff message was broadcast successfully
   */
  public boolean highlightRow(TopcatModel tcModel, long lrow, URI[] recipients) throws IOException {

    /* Get the hub and ID. */
    register();
    PlasticHubListener hub = getHub();
    URI plasticId = getRegisteredId();
    int irow = Tables.checkedLongToInt(lrow);

    /* See if the table we're broadcasting the row for is any of the
     * tables we've previously broadcast.  If so, send the row using the
     * same ID. */
    boolean done = false;
    int sendRow = -1;
    String tableId = null;
    for (Iterator it = idMap_.entrySet().iterator(); !done && it.hasNext(); ) {
      Map.Entry entry = (Map.Entry) it.next();
      TableWithRows tr = (TableWithRows) entry.getValue();
      TopcatModel tcm = (TopcatModel) tr.tcModelRef_.get();
      if (tcm != null && tcm == tcModel) {
        int[] rowMap = tr.rowMap_;
        if (rowMap == null) {
          sendRow = irow;
        } else {
          for (int j = 0; j < rowMap.length; j++) {
            if (irow == rowMap[j]) {
              sendRow = j;
              break;
            }
          }
        }
        tableId = entry.getKey().toString();
        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) {
        sendRow = irow;
        tableId = url.toString();
        done = true;
      }
    }

    /* Send the message if we've got the arguments. */
    if (done && sendRow >= 0) {
      List args =
          Arrays.asList(
              new Object[] {
                tableId, new Integer(sendRow),
              });
      if (recipients == null) {
        hub.requestAsynch(plasticId, MessageId.VOT_HIGHLIGHTOBJECT, args);
      } else {
        hub.requestToSubsetAsynch(
            plasticId, MessageId.VOT_HIGHLIGHTOBJECT, args, Arrays.asList(recipients));
      }
      return true;
    }

    /* Otherwise return failure status. */
    else {
      return false;
    }
  }
  /**
   * 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));
        }
      }
    }
  }