/** * 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); } }); } }
/** * 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; } } }