/**
   * Takes a bit mask representing selected rows and causes it to become the Current Row Subset for
   * the given table. Usually this means creating a new Row Subset corresponding to that mask prior
   * to applying it. However, in the special case that the mask is identical to an existing subset,
   * that one will be used instead.
   *
   * @param tcModel topcat model
   * @param mask row selection mask
   * @param baseName name of the sending application
   */
  private void applyNewSubset(final TopcatModel tcModel, BitSet mask, String appName) {

    /* See if this is identical to an existing subset.  If so, don't
     * create a new one.  It's arguable whether this is the behaviour
     * that you want, but at least until we have some way to delete
     * subsets it's probably best to do it like this to cut down on
     * subset proliferation. */
    RowSubset matching = null;
    for (Iterator it = tcModel.getSubsets().iterator(); matching == null && it.hasNext(); ) {
      RowSubset rset = (RowSubset) it.next();
      int nrow = Tables.checkedLongToInt(tcModel.getDataModel().getRowCount());
      if (matches(mask, rset, nrow)) {
        matching = rset;
      }
    }

    /* If we've found an existing set with the same content,
     * apply that one. */
    if (matching != null) {
      final RowSubset rset = matching;
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              tcModel.applySubset(rset);
            }
          });
    }

    /* Otherwise make sure we have a unique name for the new subset. */
    else {
      int ipset = 0;
      for (Iterator it = tcModel.getSubsets().iterator(); it.hasNext(); ) {
        String setName = ((RowSubset) it.next()).getName();
        if (setName.matches(appName + "-[0-9]+")) {
          String digits = setName.substring(appName.length() + 1);
          ipset = Math.max(ipset, Integer.parseInt(digits));
        }
      }
      String setName = appName + '-' + (ipset + 1);

      /* Then construct, add and apply the new subset. */
      final RowSubset rset = new BitsRowSubset(setName, mask);
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              tcModel.addSubset(rset);
              tcModel.applySubset(rset);
            }
          });
    }
  }
Example #2
0
 /**
  * Generates an array of some kind of data item (probably an integer type or possibly integer
  * array) suitable for placing in a table column. This array has the same number of elements as
  * the table has rows, and encodes the content of the supplied subsets.
  *
  * @param table input table
  * @param subsets subsets applying to table
  * @return nrow-element array
  */
 private Object createFlagsArray(StarTable table, RowSubset[] subsets) {
   int nrow = Tables.checkedLongToInt(table.getRowCount());
   int nset = subsets.length;
   if (nset < 16) {
     short[] flags = new short[nrow];
     for (int irow = 0; irow < nrow; irow++) {
       int flag = 0;
       for (int iset = nset - 1; iset >= 0; iset--) {
         flag <<= 1;
         if (subsets[iset].isIncluded(irow)) {
           flag = flag | 1;
         }
       }
       flags[irow] = (short) flag;
     }
     return flags;
   } else if (nset < 32) {
     int[] flags = new int[nrow];
     for (int irow = 0; irow < nrow; irow++) {
       int flag = 0;
       for (int iset = nset - 1; iset >= 0; iset--) {
         flag <<= 1;
         if (subsets[iset].isIncluded(irow)) {
           flag = flag | 1;
         }
       }
       flags[irow] = flag;
     }
     return flags;
   } else if (nset < 64) {
     long[] flags = new long[nrow];
     for (int irow = 0; irow < nrow; irow++) {
       long flag = 0L;
       for (int iset = nset - 1; iset >= 0; iset--) {
         flag <<= 1;
         if (subsets[iset].isIncluded(irow)) {
           flag = flag | 1L;
         }
       }
       flags[irow] = flag;
     }
     return flags;
   } else {
     logger_.warning("More than 64 subsets??");
     return null;
   }
 }
  /**
   * 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;
    }
  }
  /**
   * Configures this serializer for use with a given table and column writer factory. Should be
   * called before this object is ready for use; in a constructor would be a good place. Calls
   * {@link #createColumnWriter}.
   *
   * @param table table to be written
   */
  final void init(StarTable table) throws IOException {
    if (this.table != null) {
      throw new IllegalStateException("Table already initialised");
    }
    this.table = table;

    /* Get table dimensions (though we may need to calculate the row
     * count directly later. */
    int ncol = table.getColumnCount();
    long nrow = table.getRowCount();

    /* Store column infos. */
    colInfos = Tables.getColumnInfos(table);

    /* Work out column shapes, and check if any are unknown (variable
     * last dimension). */
    boolean hasVarShapes = false;
    boolean checkForNullableInts = false;
    int[][] shapes = new int[ncol][];
    int[] maxChars = new int[ncol];
    int[] maxElements = new int[ncol];
    long[] totalElements = new long[ncol];
    boolean[] useCols = new boolean[ncol];
    boolean[] varShapes = new boolean[ncol];
    boolean[] varChars = new boolean[ncol];
    boolean[] varElementChars = new boolean[ncol];
    boolean[] mayHaveNullableInts = new boolean[ncol];
    Arrays.fill(useCols, true);
    boolean[] hasNulls = new boolean[ncol];
    for (int icol = 0; icol < ncol; icol++) {
      ColumnInfo colinfo = colInfos[icol];
      Class clazz = colinfo.getContentClass();
      if (clazz.isArray()) {
        shapes[icol] = (int[]) colinfo.getShape().clone();
        int[] shape = shapes[icol];
        if (shape[shape.length - 1] < 0) {
          varShapes[icol] = true;
          hasVarShapes = true;
        } else {
          int nel = shape.length > 0 ? 1 : 0;
          for (int id = 0; id < shape.length; id++) {
            nel *= shape[id];
          }
          assert nel >= 0;
          maxElements[icol] = nel;
        }
        if (clazz.getComponentType().equals(String.class)) {
          maxChars[icol] = colinfo.getElementSize();
          if (maxChars[icol] <= 0) {
            varElementChars[icol] = true;
            hasVarShapes = true;
          }
        }
      } else if (clazz.equals(String.class)) {
        maxChars[icol] = colinfo.getElementSize();
        if (maxChars[icol] <= 0) {
          varChars[icol] = true;
          hasVarShapes = true;
        }
      } else if (colinfo.isNullable()
          && (clazz == Byte.class
              || clazz == Short.class
              || clazz == Integer.class
              || clazz == Long.class)) {
        mayHaveNullableInts[icol] = true;

        /* Only set the flag which forces a first pass if we need
         * to work out whether nulls actually exist.  If the
         * aux datum giving a null value exists we will use it in
         * any case, so finding out whether there are in fact null
         * values by scanning the data is not necessary. */
        if (colinfo.getAuxDatumValue(Tables.NULL_VALUE_INFO, Number.class) != null) {
          hasNulls[icol] = true;
        } else {
          checkForNullableInts = true;
        }
      }
    }

    /* If necessary, make a first pass through the table data to
     * find out the maximum size of variable length fields and the length
     * of the table. */
    if (hasVarShapes || checkForNullableInts || nrow < 0) {
      StringBuffer sbuf = new StringBuffer("First pass needed: ");
      if (hasVarShapes) {
        sbuf.append("(variable array shapes) ");
      }
      if (checkForNullableInts) {
        sbuf.append("(nullable ints) ");
      }
      if (nrow < 0) {
        sbuf.append("(unknown row count) ");
      }
      logger.config(sbuf.toString());
      nrow = 0L;

      /* Get the maximum dimensions. */
      RowSequence rseq = table.getRowSequence();
      try {
        while (rseq.next()) {
          nrow++;
          for (int icol = 0; icol < ncol; icol++) {
            if (useCols[icol]
                && (varShapes[icol]
                    || varChars[icol]
                    || varElementChars[icol]
                    || (mayHaveNullableInts[icol] && !hasNulls[icol]))) {
              Object cell = rseq.getCell(icol);
              if (cell == null) {
                if (mayHaveNullableInts[icol]) {
                  hasNulls[icol] = true;
                }
              } else {
                if (varChars[icol]) {
                  int leng = ((String) cell).length();
                  maxChars[icol] = Math.max(maxChars[icol], leng);
                } else if (varElementChars[icol]) {
                  String[] svals = (String[]) cell;
                  for (int i = 0; i < svals.length; i++) {
                    maxChars[icol] = Math.max(maxChars[icol], svals[i].length());
                  }
                }
                if (varShapes[icol]) {
                  int nel = Array.getLength(cell);
                  maxElements[icol] = Math.max(maxElements[icol], nel);
                  totalElements[icol] += nel;
                }
              }
            }
          }
        }
      } finally {
        rseq.close();
      }

      /* In the case of variable string lengths and no non-null data
       * in any of the cells, maxChars could still be set negative.
       * Fix that here. */
      for (int icol = 0; icol < ncol; icol++) {
        if (maxChars[icol] < 0) {
          maxChars[icol] = 0;
        }
      }

      /* Furthermore, zero length strings are probably a bad idea
       * for FITS output.  Make sure that all output strings have
       * a length of at least 1. */
      for (int icol = 0; icol < ncol; icol++) {
        if (maxChars[icol] == 0) {
          maxChars[icol] = 1;
        }
      }

      /* Work out the actual shapes for columns which have variable ones,
       * based on the shapes that we encountered in the rows. */
      if (hasVarShapes) {
        for (int icol = 0; icol < ncol; icol++) {
          if (useCols[icol]) {
            if (varShapes[icol]) {
              int[] shape = shapes[icol];
              int ndim = shape.length;
              assert shape[ndim - 1] <= 0;
              int nel = 1;
              for (int i = 0; i < ndim - 1; i++) {
                nel *= shape[i];
              }
              shape[ndim - 1] = Math.max(1, (maxElements[icol] + nel - 1) / nel);
            }
          }
        }
      }
    }

    /* Store the row count, which we must have got by now. */
    assert nrow >= 0;
    rowCount = nrow;

    /* We now have all the information we need about the table.
     * Construct and store a custom writer for each column which
     * knows about the characteristics of the column and how to
     * write values to the stream.  For columns which can't be
     * written in FITS format store a null in the writers array
     * and log a message. */
    colWriters = new ColumnWriter[ncol];
    int rbytes = 0;
    for (int icol = 0; icol < ncol; icol++) {
      if (useCols[icol]) {
        ColumnInfo cinfo = colInfos[icol];
        ColumnWriter writer =
            createColumnWriter(
                cinfo,
                shapes[icol],
                varShapes[icol],
                maxChars[icol],
                maxElements[icol],
                totalElements[icol],
                mayHaveNullableInts[icol] && hasNulls[icol]);
        if (writer == null) {
          logger.warning(
              "Ignoring column " + cinfo.getName() + " - don't know how to write to FITS");
        }
        colWriters[icol] = writer;
      }
    }
  }