Beispiel #1
0
  /**
   * Constructs the DataLink Parameters from an URL string containing a VOTable with DataLink
   * information The VOTABLE must contain only Datalink info! ????? or?
   *
   * @throws IOException
   */
  public DataLinkParams(String dataLinksrc) throws IOException {
    id_source = null;
    request = null;
    accessURL = null;
    contentType = null;
    service = new ArrayList<DataLinkService>();
    DataLinkService thisService = new DataLinkService();

    URL dataLinkURL = new URL(dataLinksrc);
    DataSource datsrc = new URLDataSource(dataLinkURL);

    StarTable starTable =
        new VOTableBuilder().makeStarTable(datsrc, true, StoragePolicy.getDefaultPolicy());

    int ncol = starTable.getColumnCount();

    for (int k = 0; k < ncol; k++) {
      ColumnInfo colInfo = starTable.getColumnInfo(k);

      //   String utype = colInfo.getUtype();
      String name = colInfo.getName();
      if (name != null) thisService.addParam(name, (String) starTable.getCell(0, k));

      //  if ( utype != null ) {
      //     utype = utype.toLowerCase();
      //     if ( utype.endsWith( "datalink.accessurl" ) ) {
      //        thisService.addParam("accessURL", (String) starTable.getCell(0, k));
      //     }
      //     if ( utype.endsWith( "datalink.contenttype" ) ) {
      //         thisService.addParam("contentType", (String) starTable.getCell(0, k));
      // }
    }
    service.add(thisService);
  }
Beispiel #2
0
    /**
     * Constructor.
     *
     * @param inTable saved table as read
     */
    public CodecTable(StarTable inTable) {

      /* Sort out table parameters. */
      codecParamMap_ = new HashMap<String, DescribedValue>();
      List<DescribedValue> dataParamList = new ArrayList<DescribedValue>();
      for (Iterator it = inTable.getParameters().iterator(); it.hasNext(); ) {
        DescribedValue param = (DescribedValue) it.next();
        String utype = param.getInfo().getUtype();
        if (isCodecUtype(utype)) {
          codecParamMap_.put(utype, param);
        } else {
          dataParamList.add(param);
        }
      }

      /* Sort out table columns. */
      codecIcolMap_ = new HashMap<String, Integer>();
      IntList dataIcolList = new IntList();
      for (int icol = 0; icol < inTable.getColumnCount(); icol++) {
        ColumnInfo info = inTable.getColumnInfo(icol);
        String utype = info.getUtype();
        if (isCodecUtype(utype)) {
          codecIcolMap_.put(utype, icol);
        } else {
          dataIcolList.add(icol);
        }
      }
      int[] dataColMap = dataIcolList.toIntArray();

      /* Construct a table containing only the data items. */
      dataTable_ = new MetaCopyStarTable(new ColumnPermutedStarTable(inTable, dataColMap, true));
      dataTable_.getParameters().clear();
      dataTable_.getParameters().addAll(dataParamList);
    }
Beispiel #3
0
    public ConeQueryRowSequence createQuerySequence(StarTable table) throws IOException {
      assert rowMap_ == null || rowMap_.length == table.getRowCount();
      final RowSequence rseq = table.getRowSequence();
      return new ConeQueryRowSequence() {
        long irow_ = -1;

        public boolean next() throws IOException {
          if (matchWorker_ != null && matchWorker_.cancelled_) {
            throw new IOException("Cancelled");
          }
          boolean retval = rseq.next();
          if (matchWorker_ != null && matchWorker_.cancelled_) {
            throw new IOException("Cancelled");
          }
          if (retval) {
            irow_++;
            if (matchWorker_ != null) {
              matchWorker_.setInputRow((int) irow_);
            }
          }
          return retval;
        }

        public Object getCell(int icol) throws IOException {
          return rseq.getCell(icol);
        }

        public Object[] getRow() throws IOException {
          return rseq.getRow();
        }

        public void close() throws IOException {
          rseq.close();
        }

        public double getRa() throws IOException {
          return Math.toDegrees(getDoubleValue(raData_));
        }

        public double getDec() throws IOException {
          return Math.toDegrees(getDoubleValue(decData_));
        }

        public double getRadius() throws IOException {
          return Math.toDegrees(getDoubleValue(srData_));
        }

        private double getDoubleValue(ColumnData cdata) throws IOException {
          if (cdata != null) {
            long jrow = rowMap_ == null ? irow_ : rowMap_[(int) irow_];
            Object value = cdata.readValue(jrow);
            return value instanceof Number ? ((Number) value).doubleValue() : Double.NaN;
          } else {
            return Double.NaN;
          }
        }
      };
    }
 /**
  * Sets the table value of this parameter directly.
  *
  * @param table
  */
 public void setValueFromTable(StarTable table) {
   table_ = table;
   String name = table.getName();
   if (name == null) {
     name = "unnamed table";
   }
   setStringValue(name);
   setGotValue(true);
 }
 /**
  * Loads a StarTable into TOPCAT. Must be called from the event dispatch thread.
  *
  * @param table table to load
  * @param sender sender ID
  * @param key identifier for the loaded table
  */
 private void loadTable(StarTable table, URI sender, String key) {
   String name = table.getName();
   if (name == null || name.trim().length() == 0) {
     name = sender.toString();
   }
   TopcatModel tcModel = controlWindow_.addTable(table, name, true);
   if (key != null && key.trim().length() > 0) {
     idMap_.put(key, new TableWithRows(tcModel, null));
   }
 }
Beispiel #6
0
    public void processResult(StarTable streamTable) throws IOException {

      /* Blocks until all results are in. */
      StarTable randomTable = policy_.copyTable(streamTable);
      long nrow = randomTable.getRowCount();

      /* Either note that there are no results. */
      if (nrow == 0) {
        schedule(
            new Runnable() {
              public void run() {
                JOptionPane.showMessageDialog(
                    parent_, "No matches were found", "Empty Match", JOptionPane.ERROR_MESSAGE);
              }
            });
      }

      /* Or invoke the method that does the work. */
      else {
        processRandomResult(randomTable);
      }
    }
  public void writeData(DataOutput strm) throws IOException {

    /* Work out the length of each row in bytes. */
    int rowBytes = 0;
    int ncol = table.getColumnCount();
    for (int icol = 0; icol < ncol; icol++) {
      ColumnWriter writer = colWriters[icol];
      if (writer != null) {
        rowBytes += writer.getLength();
      }
    }

    /* Write the data cells, delegating the item in each column to
     * the writer that knows how to handle it. */
    long nWritten = 0L;
    RowSequence rseq = table.getRowSequence();
    try {
      while (rseq.next()) {
        Object[] row = rseq.getRow();
        for (int icol = 0; icol < ncol; icol++) {
          ColumnWriter writer = colWriters[icol];
          if (writer != null) {
            writer.writeValue(strm, row[icol]);
          }
        }
        nWritten += rowBytes;
      }
    } finally {
      rseq.close();
    }

    /* Write padding. */
    int extra = (int) (nWritten % (long) 2880);
    if (extra > 0) {
      strm.write(new byte[2880 - extra]);
    }
  }
Beispiel #8
0
 /**
  * Returns a table which is the same as the input table, but with a zero-based index column as the
  * first column.
  *
  * @param inTable input table
  * @return output table
  */
 private static StarTable prependIndex(StarTable inTable) {
   final long nrow = inTable.getRowCount();
   ColumnStarTable indexTable =
       new ColumnStarTable(inTable) {
         public long getRowCount() {
           return nrow;
         }
       };
   indexTable.addColumn(
       new ColumnData(INDEX_INFO) {
         public Object readValue(long irow) {
           return new Integer((int) irow);
         }
       });
   return new JoinStarTable(new StarTable[] {indexTable, inTable});
 }
Beispiel #9
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;
   }
 }
Beispiel #10
0
 /**
  * Generates a RowSubset from a column like one generated by a call to {@link #createFlagsArray}.
  *
  * @param name subset name
  * @param table input table
  * @param icol index of column containing flag data
  * @param iflag index of flag within column
  * @return iflag'th subset derived from column icol in table
  */
 private RowSubset createRowSubset(String name, final StarTable table, final int icol, int iflag) {
   ColumnInfo info = table.getColumnInfo(icol);
   Class clazz = info.getContentClass();
   if (clazz == Short.class) {
     final short mask = (short) (1 << iflag);
     return new RowSubset(name) {
       public boolean isIncluded(long lrow) {
         try {
           return (((Number) table.getCell(lrow, icol)).shortValue() & mask) != 0;
         } catch (IOException e) {
           return false;
         }
       }
     };
   } else if (clazz == Integer.class) {
     final int mask = 1 << iflag;
     return new RowSubset(name) {
       public boolean isIncluded(long lrow) {
         try {
           return (((Number) table.getCell(lrow, icol)).intValue() & mask) != 0;
         } catch (IOException e) {
           return false;
         }
       }
     };
   } else if (clazz == Long.class) {
     final long mask = 1L << iflag;
     return new RowSubset(name) {
       public boolean isIncluded(long lrow) {
         try {
           return (((Number) table.getCell(lrow, icol)).longValue() & mask) != 0;
         } catch (IOException e) {
           return false;
         }
       }
     };
   } else {
     logger_.warning("Can't decode subsets column");
     return null;
   }
 }
Beispiel #11
0
  /**
   * @param ra - in degrees
   * @param dec - in degrees
   * @param angularWidthAlongRAAxis - width along RA axis
   * @param angularWidthAlongDecAxis - width along DEC axis
   * @param otherConstraints - map of UCDKey -> Value. Only images whose column UCDs match the
   *     values are selected
   * @return array of fits image urls
   * @throws Exception -
   */
  public String[] fetchFitsImages(
      double ra,
      double dec,
      double angularWidthAlongRAAxis,
      double angularWidthAlongDecAxis,
      Map<String, Object> otherConstraints)
      throws MalformedURLException, SAXException, IOException, NoValidResultsResourceFoundException,
          SiaQueryFailedException {
    // TODO: Need to take a call on the otherConstraints argument

    String modurl = url + "&FORMAT=image/fits";
    modurl += "&POS=" + ra + "," + dec;
    modurl += "&SIZE=" + angularWidthAlongRAAxis + "," + angularWidthAlongDecAxis;

    URL siaUrl = new URL(modurl);
    logger.info("Connecting to SIA URL: " + siaUrl);

    String interestedImageFormat = "image/fits";
    ArrayList<String> imageUrls = new ArrayList<String>();

    VOElement votable = new VOElementFactory().makeVOElement(siaUrl);
    NodeList resources = votable.getElementsByTagName("RESOURCE");

    // check for a "results" resource with of sia votable resu
    int resInd = 0;
    for (; resInd < resources.getLength(); resInd++) {
      VOElement resource = (VOElement) resources.item(resInd);
      if (SIAP_RESOURCE_TYPE_VALUE_RESULTS.equals(resource.getAttribute("type"))) {
        break;
      }
    }

    if (resInd == resources.getLength()) {
      throw new NoValidResultsResourceFoundException();
    } else {
      String queryStatus = null;
      String queryStatusDescription = null;

      // check query status
      VOElement resource = (VOElement) resources.item(resInd);
      NodeList infos = resource.getElementsByTagName("INFO");
      for (int infoInd = 0; infoInd < infos.getLength(); infoInd++) {
        VOElement info = (VOElement) infos.item(infoInd);
        String name = info.getAttribute("name");
        if (name.equals(QUERY_STATUS)) {
          queryStatus = info.getAttribute("value");
          queryStatusDescription = info.getNodeValue();
        }
      }

      // if query successful.equalsIgnoreCase()
      if (!queryStatus.equals(QUERY_STATUS_VALUE_OK)) {
        throw new SiaQueryFailedException(queryStatusDescription);
      }

      // handle the table
      VOElement[] tables = resource.getChildrenByName("TABLE");
      if (tables.length > 0) {
        // take the first table and look for URLs in it.
        TableElement tableElement = (TableElement) tables[0];

        // create star-table
        StarTable table = new VOStarTable(tableElement);

        // ideally its mandatory to have RA_UCD, DEC_UCD, NAXES_UCD,
        // NAXIS_UCD, FORMAT_UCD, SCALE_UCD in each row but we will not
        // check for those as of now.

        int formatColIndex = -1;
        int urlColIndex = -1;
        int otherConstraintsSize = (otherConstraints != null) ? otherConstraints.size() : 0;
        HashMap<String, Integer> otherConstraintsIndices = new HashMap<String, Integer>();

        for (int colInd = 0; colInd < table.getColumnCount(); colInd++) {
          ColumnInfo colInfo = table.getColumnInfo(colInd);
          if (colInfo.getUCD().equalsIgnoreCase(FORMAT_UCD)) {
            formatColIndex = colInd;
          } else if (colInfo.getUCD().equalsIgnoreCase(ACCESS_REF_UCD)) {
            urlColIndex = colInd;
          }
          if (otherConstraintsSize > 0) {
            Iterator<String> keyIte = otherConstraints.keySet().iterator();
            while (keyIte.hasNext()) {
              String key = keyIte.next();
              if (key.equalsIgnoreCase(colInfo.getUCD())) {
                otherConstraintsIndices.put(key, colInd);
              }
            }
          }
        }

        for (int rowInd = 0; rowInd < table.getRowCount(); rowInd++) {
          Object[] cells = table.getRow(rowInd);
          // looking for only FITS images or JPG images .. ..
          if (interestedImageFormat.equalsIgnoreCase((String) cells[formatColIndex])) {
            Iterator<String> keyIte = otherConstraintsIndices.keySet().iterator();
            boolean allConstraintsPassed = true;
            while (keyIte.hasNext()) {
              String key = keyIte.next();
              int keyInd = otherConstraintsIndices.get(key);
              if (!cells[keyInd].equals(otherConstraints.get(key))) {
                allConstraintsPassed = false;
                break;
              }
            }

            if (allConstraintsPassed) {
              imageUrls.add((String) cells[urlColIndex]);
            }
          }
        }
      }
      return imageUrls.toArray(new String[] {});
    }
  }
Beispiel #12
0
    public void run() {

      /* Initialise progress GUI. */
      final int nrow = (int) inTable_.getRowCount();
      schedule(
          new Runnable() {
            public void run() {
              progBar_.setMinimum(0);
              progBar_.setMaximum(nrow);
            }
          });
      setOutputRow(0);

      /* Acquire the table from the matcher in such a way that
       * when each row arrives the progress GUI is updated. */
      matcher_.setStreamOutput(true);
      try {
        ConeMatcher.ConeWorker coneWorker = matcher_.createConeWorker();
        coneThread_ = new Thread(coneWorker, "Cone worker");
        coneThread_.setDaemon(true);
        coneThread_.start();
        StarTable streamTable = coneWorker.getTable();
        StarTable progressTable =
            new WrapperStarTable(streamTable) {
              public RowSequence getRowSequence() throws IOException {
                return new WrapperRowSequence(super.getRowSequence()) {
                  long irow_ = -1;

                  public boolean next() throws IOException {
                    if (cancelled_) {
                      throw new IOException("Cancelled");
                    }
                    boolean retval = super.next();
                    if (cancelled_) {
                      throw new IOException("Cancelled");
                    }
                    if (retval) {
                      irow_++;
                      setOutputRow((int) irow_);
                    }
                    return retval;
                  }
                };
              }
            };

        /* And pass the table to the appropriate result handler.
         * The row data has not been acquired yet, it will be pulled
         * by the action of the processResult call. */
        resultHandler_.processResult(progressTable);
      }

      /* In case of error in result acquisition or processing,
       * inform the user. */
      catch (final Exception e) {
        schedule(
            new Runnable() {
              public void run() {
                ErrorDialog.showError(
                    DalMultiPanel.this, "Multi-" + service_.getName() + " Error", e);
              }
            });
      } catch (final OutOfMemoryError e) {
        schedule(
            new Runnable() {
              public void run() {
                TopcatUtils.memoryError(e);
              }
            });
      }

      /* In any case, deinstall this worker thread.  No further actions
       * on its behalf will now affect the GUI (important, since another
       * worker thread might take over). */
      finally {
        done_ = true;
        schedule(
            new Runnable() {
              public void run() {
                setActive(null);
              }
            });
      }
    }
Beispiel #13
0
  /**
   * Turns a TopcatModel into a StarTable, ready for serialization.
   *
   * @param tcModel model
   * @return table
   */
  public StarTable encode(TopcatModel tcModel) {

    /* Prepare table data and metadata for use and adjustment. */
    final StarTable dataModel = tcModel.getDataModel();
    List<DescribedValue> paramList = new ArrayList<DescribedValue>();
    long nrow = dataModel.getRowCount();
    ColumnStarTable extraTable = ColumnStarTable.makeTableWithRows(nrow);

    /* Mark as serialized TopcatModel. */
    paramList.add(new DescribedValue(IS_TCMODEL_INFO, Boolean.TRUE));
    paramList.add(new DescribedValue(VERSION_INFO, TopcatUtils.getVersion()));

    /* Record label. */
    paramList.add(new DescribedValue(LABEL_INFO, tcModel.getLabel()));

    /* Record column sequences. */
    ColumnList colList = tcModel.getColumnList();
    int nCol = colList.size();
    int[] icols = new int[nCol];
    boolean[] activs = new boolean[nCol];
    for (int jc = 0; jc < nCol; jc++) {
      icols[jc] = colList.getColumn(jc).getModelIndex();
      activs[jc] = colList.isActive(jc);
    }
    paramList.add(new DescribedValue(COLS_INDEX_INFO, icols));
    paramList.add(new DescribedValue(COLS_VISIBLE_INFO, activs));

    /* Record whether to broadcast rows. */
    paramList.add(new DescribedValue(SEND_ROWS_INFO, tcModel.getRowSendModel().isSelected()));

    /* Record sort order. */
    SortOrder sortOrder = tcModel.getSelectedSort();
    TableColumn sortCol = sortOrder == null ? null : sortOrder.getColumn();
    if (sortCol != null) {
      int icolSort = tcModel.getColumnList().indexOf(sortCol);
      if (icolSort >= 0) {
        boolean sense = tcModel.getSortSenseModel().isSelected();
        paramList.add(new DescribedValue(SORT_COLUMN_INFO, new Integer(icolSort)));
        paramList.add(new DescribedValue(SORT_SENSE_INFO, Boolean.valueOf(sense)));
      }
    }

    /* Store row subset flags in a new column. */
    List<RowSubset> subsetList = new ArrayList<RowSubset>(tcModel.getSubsets());
    boolean hadAll = subsetList.remove(RowSubset.ALL);
    assert hadAll;
    RowSubset[] subsets = subsetList.toArray(new RowSubset[0]);
    if (subsets.length > 0) {
      String[] subsetNames = new String[subsets.length];
      for (int is = 0; is < subsets.length; is++) {
        subsetNames[is] = subsets[is].getName();
      }
      paramList.add(new DescribedValue(SUBSET_NAMES_INFO, subsetNames));
      Object flagsArray = createFlagsArray(dataModel, subsets);
      if (flagsArray != null) {
        ColumnData flagsCol = ArrayColumn.makeColumn(SUBSET_FLAGS_INFO.getName(), flagsArray);
        ColumnInfo info = new ColumnInfo(SUBSET_FLAGS_INFO);
        info.setContentClass(flagsCol.getColumnInfo().getContentClass());
        flagsCol.setColumnInfo(info);
        extraTable.addColumn(flagsCol);
      }
    }

    /* Record current subset. */
    int iset = subsetList.indexOf(tcModel.getSelectedSubset());
    if (iset >= 0) {
      paramList.add(new DescribedValue(CURRENT_SUBSET_INFO, new Integer(iset)));
    }

    /* Copy parameters from the input table.
     * Be paranoid about possible name clashes. */
    for (Iterator it = dataModel.getParameters().iterator(); it.hasNext(); ) {
      Object item = it.next();
      if (item instanceof DescribedValue) {
        DescribedValue dval = (DescribedValue) item;
        String name = dval.getInfo().getName();
        String utype = dval.getInfo().getUtype();
        if (!isCodecUtype(utype)) {
          paramList.add(dval);
        }
      }
    }

    /* Prepare the output table object. */
    List<StarTable> joinList = new ArrayList<StarTable>();
    joinList.add(dataModel);
    if (extraTable.getColumnCount() > 0) {
      joinList.add(extraTable);
    }
    StarTable[] joins = joinList.toArray(new StarTable[0]);
    StarTable outTable =
        new MetaCopyStarTable(new JoinStarTable(joinList.toArray(new StarTable[0])));
    outTable.setName(dataModel.getName());

    /* Set the parameters. */
    outTable.getParameters().clear();
    outTable.getParameters().addAll(paramList);

    /* Return the result. */
    return outTable;
  }
  /**
   * 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;
      }
    }
  }
  public Header getHeader() throws HeaderCardException {

    /* Work out the dimensions in columns and bytes of the table. */
    int rowLength = 0;
    int nUseCol = 0;
    int ncol = table.getColumnCount();
    for (int icol = 0; icol < ncol; icol++) {
      ColumnWriter writer = colWriters[icol];
      if (writer != null) {
        nUseCol++;
        rowLength += writer.getLength();
      }
    }

    /* Prepare a FITS header block. */
    Header hdr = new Header();

    /* Add HDU layout metadata. */
    hdr.addValue("XTENSION", "BINTABLE", "binary table extension");
    hdr.addValue("BITPIX", 8, "8-bit bytes");
    hdr.addValue("NAXIS", 2, "2-dimensional table");
    hdr.addValue("NAXIS1", rowLength, "width of table in bytes");
    hdr.addValue("NAXIS2", rowCount, "number of rows in table");
    hdr.addValue("PCOUNT", 0, "size of special data area");
    hdr.addValue("GCOUNT", 1, "one data group");
    hdr.addValue("TFIELDS", nUseCol, "number of columns");

    /* Add EXTNAME record containing table name. */
    String tname = table.getName();
    if (tname != null && tname.trim().length() > 0) {
      FitsConstants.addTrimmedValue(hdr, "EXTNAME", tname, "table name");
    }

    /* Add HDU metadata describing columns. */
    int jcol = 0;
    for (int icol = 0; icol < ncol; icol++) {
      ColumnWriter colwriter = colWriters[icol];
      if (colwriter != null) {
        jcol++;
        String forcol = " for column " + jcol;
        ColumnInfo colinfo = colInfos[icol];

        /* Name. */
        String name = colinfo.getName();
        if (name != null && name.trim().length() > 0) {
          FitsConstants.addTrimmedValue(hdr, "TTYPE" + jcol, name, "label" + forcol);
        }

        /* Format. */
        String form = colwriter.getFormat();
        hdr.addValue("TFORM" + jcol, form, "format" + forcol);

        /* Units. */
        String unit = colinfo.getUnitString();
        if (unit != null && unit.trim().length() > 0) {
          FitsConstants.addTrimmedValue(hdr, "TUNIT" + jcol, unit, "units" + forcol);
        }

        /* Blank. */
        Number bad = colwriter.getBadNumber();
        if (bad != null) {
          hdr.addValue("TNULL" + jcol, bad.longValue(), "blank value" + forcol);
        }

        /* Shape. */
        int[] dims = colwriter.getDims();
        if (dims != null && dims.length > 1) {
          StringBuffer sbuf = new StringBuffer();
          for (int i = 0; i < dims.length; i++) {
            sbuf.append(i == 0 ? '(' : ',');
            sbuf.append(dims[i]);
          }
          sbuf.append(')');
          hdr.addValue("TDIM" + jcol, sbuf.toString(), "dimensions" + forcol);
        }

        /* Scaling. */
        double zero = colwriter.getZero();
        double scale = colwriter.getScale();
        if (zero != 0.0) {
          hdr.addValue("TZERO" + jcol, zero, "base" + forcol);
        }
        if (scale != 1.0) {
          hdr.addValue("TSCALE" + jcol, scale, "factor" + forcol);
        }

        /* Comment (non-standard). */
        String comm = colinfo.getDescription();
        if (comm != null && comm.trim().length() > 0) {
          try {
            hdr.addValue("TCOMM" + jcol, comm, null);
          } catch (HeaderCardException e) {
            // never mind.
          }
        }

        /* UCD (non-standard). */
        String ucd = colinfo.getUCD();
        if (ucd != null && ucd.trim().length() > 0 && ucd.length() < 68) {
          try {
            hdr.addValue("TUCD" + jcol, ucd, null);
          } catch (HeaderCardException e) {
            // never mind.
          }
        }

        /* Utype (non-standard). */
        String utype = colinfo.getUtype();
        if (utype != null && utype.trim().length() > 0 && utype.trim().length() < 68) {
          try {
            hdr.addValue("TUTYP" + jcol, utype, null);
          } catch (HeaderCardException e) {
            // never mind.
          }
        }
      }
    }
    return hdr;
  }