/* DumpResult.java This class is a serializable dump result object, which conveys results from a dump operation along with methods that can be used to extract the results out of the dump. Created: 25 September 1997 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.common; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Vector; /*------------------------------------------------------------------------------ class DumpResult ------------------------------------------------------------------------------*/ /** * <p>This class is a serializable transport object, used to transmit * the results of a data dump query to the client. DumpResult objects * are created by the {@link * arlut.csd.ganymede.server.DumpResultBuilder DumpResultBuilder} * factory class.</p> * * <p>The way it works is that DumpResultBuilder creates the * DumpResult objects, which is transmitted through RMI to the client. * The client can then call the various accessor methods to access the * serialized query results.</p> * * <p>DumpResult encodes a list of field headers by name, a list of * field types encoded as {@link java.lang.Short Shorts} coded with * the values enumerated in the {@link * arlut.csd.ganymede.common.FieldType FieldType} interface, and a * list of object rows, each of which contains a Vector of encoded * field values.</p> * * <p>Field values are encoded as follows:</p> * * <ul> * <li>Date fields as {@link java.util.Date Date} objects</li> * <li>Float fields as {@link java.lang.Double Double} objects</li> * <li>Numeric fields as {@link java.lang.Integer Integer} objects</li> * </ul> * * <p>And Strings for everything else.</p> * * <p>The GUI client uses this object to generate its query result * tables.</p> * * <p>Later Note:</p> * * <p>Yes, I know how utterly horrifying this is. It's something I * did very early on during development, and it worked well for high * speed data dumping, so I just kept it. Mea culpa, mea maxima * culpa.</p> * * <p>Yet, it works.</p> */ public class DumpResult implements java.io.Serializable, List { static final boolean debug = false; static final long serialVersionUID = 8688161796723967714L; // --- // Created for us pre-serialization by // arlut.csd.ganymede.server.DumpResultBuilder public StringBuffer buffer = null; // for use post-serialized.. note that transient fields don't // actually get initialzed post serialization, so the initializers // here are actually redundant and non-operative on the client side // post serialization. transient private boolean unpacked = false; transient Vector<DumpResultCol> headerObjects = null; transient Vector<String> headers = null; transient Vector<Invid> invids = null; transient Vector<Map<String, Object>> rows = null; /* -- */ public DumpResult() { buffer = new StringBuffer(); } /** * <p>This method can be called on the client to obtain a {@link * java.util.Vector Vector} of field names, used to generate the * list of column headers in the GUI client.</p> * * <p>Note: The Vector returned is "live", and should not be * modified by the caller, at the risk of surprising behavior.</p> */ public synchronized Vector<String> getHeaders() { checkBuffer(); if (headers == null) { headers = new Vector<String>(headerObjects.size()); for (DumpResultCol drc: headerObjects) { headers.add(drc.getName()); } } return new Vector(headers); } /** * <p>This method can be called on the client to obtain an * independent Vector of {@link * arlut.csd.ganymede.common.DumpResultCol DumpResultCol} objects, * which define the field name, field id, and field type for each * column in this DumpResult.</p> * * <p>Note: The Vector returned is "live", and should not be * modified by the caller, at the risk of surprising behavior.</p> */ public synchronized Vector<DumpResultCol> getHeaderObjects() { checkBuffer(); return new Vector(headerObjects); } /** * <p>Returns the name of the field encoded in column col of the * DumpResult.</p> */ public synchronized String getFieldName(int col) { checkBuffer(); return headerObjects.get(col).getName(); } /** * <p>Returns the field code for the field encoded in column col of the * DumpResult.</p> */ public synchronized short getFieldId(int col) { checkBuffer(); return headerObjects.get(col).getFieldId(); } /** * <p>Returns the field type for the field encoded in column col of the * DumpResult.</p> * * <p>The field type returned is to be interpreted according to the * values enumerated in the {@link * arlut.csd.ganymede.common.FieldType FieldType} interface.</p> */ public synchronized short getFieldType(int col) { checkBuffer(); return headerObjects.get(col).getFieldType(); } /** * <p>This method can be called on the client to obtain a {@link * java.util.Vector Vector} of {@link * arlut.csd.ganymede.common.Invid Invids}, identifying the objects * that are being returned in the DumpResult.</p> * * <p>Note: The Vector returned is "live", and should not be * modified by the caller, at the risk of surprising behavior.</p> */ public synchronized Vector<Invid> getInvids() { checkBuffer(); return invids; } /** * <p>This method can be called on the client to obtain the object * identifier {@link arlut.csd.ganymede.common.Invid Invid} for a * given result row.</p> */ public synchronized Invid getInvid(int row) { checkBuffer(); return invids.get(row); } /** * <p>This method can be called on the client to obtain a {@link * java.util.Vector Vector} of Vectors, each of which contains the * data values returned for each object, in field order matching the * field names and types returned by {@link * arlut.csd.ganymede.common.DumpResult#getHeaders getHeaders()} and * {@link arlut.csd.ganymede.common.DumpResult#getHeaderObjects * getHeaderObjects()}.</p> * * <p>Note: The Vector returned is "live", and should not be * modified by the caller, at the risk of surprising behavior.</p> */ public synchronized Vector<Map<String,Object>> getRows() { checkBuffer(); return rows; } /** * <p>This method can be called on the client to obtain a {@link * java.util.Vector Vector} containing the data values returned for * the object at row <i>row</i>, in field order matching the field * names and types returned by {@link * arlut.csd.ganymede.common.DumpResult#getHeaders getHeaders()} and * {@link arlut.csd.ganymede.common.DumpResult#getHeaderObjects * getHeaderObjects()}.</p> */ public synchronized Vector<Object> getFieldRow(int rowNumber) { checkBuffer(); Map<String, Object> rowMap = rows.get(rowNumber); Vector<Object> row = new Vector(headerObjects.size()); for (DumpResultCol drc: headerObjects) { row.add(rowMap.get(drc.getName())); } return row; } /** * <p>This method can be called on the client to obtain an Object * encoding the result value for the <i>col</i>th field in the * <i>row</i>th object. These Objects may be a {@link * java.lang.Double Double} for Float fields, an {@link * java.lang.Integer Integer} for Numeric fields, a {@link * java.util.Date Date} for Date fields, or a String for other * fields.</p> */ public synchronized Object getResult(int row, int col) { checkBuffer(); return getFieldRow(row).get(col); } /** * <p>This method can be called on the client to determine the * number of objects encoded in this DumpResult.</p> */ public synchronized int resultSize() { checkBuffer(); return rows.size(); } /** * <p>This method takes care of deserializing the StringBuffer we * contain, to crack out the data we are interested in * conveying.</p> */ private synchronized void checkBuffer() { if (unpacked) { return; } char[] chars = buffer.toString().toCharArray();; arlut.csd.Util.SharedStringBuffer tempString = new arlut.csd.Util.SharedStringBuffer(); int index = 0; Map<String,Object> rowMap; short currentFieldType = -1; String currentHeader; /* -- */ headerObjects = new Vector<DumpResultCol>(); invids = new Vector<Invid>(); rows = new Vector<Map<String,Object>>(); // read in the header definition line if (debug) { System.err.println("*** unpacking buffer"); } while (chars[index] != '\n') { String fieldName; short fieldId; short fieldType; tempString.setLength(0); // truncate the buffer while (chars[index] != '|') { if (chars[index] == '\n') { throw new RuntimeException("parse error in header list"); } // if we have a backslashed character, take the backslashed char // as a literal if (chars[index] == '\\') { index++; } tempString.append(chars[index++]); } index++; // skip past | fieldName = tempString.toString(); tempString.setLength(0); // truncate the buffer again while (chars[index] != '|') { if (chars[index] == '\n') { throw new RuntimeException("parse error in header list"); } // if we have a backslashed character, take the backslashed char // as a literal if (chars[index] == '\\') { index++; } tempString.append(chars[index++]); } index++; // skip trailing | marker fieldId = Short.valueOf(tempString.toString()).shortValue(); tempString.setLength(0); // truncate the buffer again while (chars[index] != '|') { if (chars[index] == '\n') { throw new RuntimeException("parse error in header list"); } // if we have a backslashed character, take the backslashed char // as a literal if (chars[index] == '\\') { index++; } tempString.append(chars[index++]); } index++; // skip trailing | marker fieldType = Short.valueOf(tempString.toString()).shortValue(); headerObjects.add(new DumpResultCol(fieldName, fieldId, fieldType)); } index++; // skip past \n // now read in all the result lines while (index < chars.length) { // first read in the Invid tempString.setLength(0); // truncate the buffer if (debug) { System.err.println("*** Unpacking row " + rows.size()); } while (chars[index] != '|') { // if we have a backslashed character, take the backslashed char // as a literal if (chars[index] == '\n') { throw new RuntimeException("parse error in row"); } tempString.append(chars[index++]); } invids.addElement(Invid.createInvid(tempString.toString())); index++; // skip over | // now read in the fields for this invid rowMap = new HashMap<String,Object>(headerObjects.size()); while (chars[index] != '\n') { tempString.setLength(0); // truncate the buffer while (chars[index] != '|') { // if we have a backslashed character, take the backslashed char // as a literal if (chars[index] == '\n') { throw new RuntimeException("parse error in header list"); } if (chars[index] == '\\') { index++; } tempString.append(chars[index++]); } index++; // skip | DumpResultCol header = headerObjects.get(rowMap.size()); currentFieldType = header.getFieldType(); currentHeader = header.getName(); switch (currentFieldType) { case FieldType.DATE: if (debug) { System.err.println("parsing date: " + tempString.toString()); } if (tempString.toString().equals("null") || tempString.toString().equals("")) { rowMap.put(currentHeader, null); } else { try { rowMap.put(currentHeader, new Date(Long.parseLong(tempString.toString()))); } catch (NumberFormatException ex) { throw new RuntimeException("couldn't parse Long encoding (" + tempString.toString()+"): " + ex); } } break; case FieldType.NUMERIC: if (tempString.toString().equals("null") || tempString.toString().equals("")) { rowMap.put(currentHeader, null); } else { try { rowMap.put(currentHeader, Integer.valueOf(tempString.toString())); } catch (NumberFormatException ex) { throw new RuntimeException("couldn't parse numeric encoding for string *" + tempString.toString() + "* :" + ex); } } break; case FieldType.FLOAT: if (tempString.toString().equals("null") || tempString.toString().equals("")) { rowMap.put(currentHeader, null); } else { try { rowMap.put(currentHeader, new Double(tempString.toString())); } catch (NumberFormatException ex) { throw new RuntimeException("couldn't parse float encoding for string *" + tempString.toString() + "* :" + ex); } } break; default: rowMap.put(currentHeader, tempString.toString()); } } rows.add(rowMap); index++; // skip newline } unpacked = true; } /** * <p>This method breaks apart the data structures held by this * DumpResult.. it is intended to speed garbage collection when the * contents of this DumpResult buffer have been processed and are no * longer needed on the client.</p> */ public synchronized void dissociate() { if (headerObjects != null) { headerObjects.clear(); headerObjects = null; } if (headers != null) { headers.clear(); headers = null; } if (invids != null) { invids.clear(); invids = null; } if (rows != null) { rows.clear(); rows = null; } } /* ------------------------------------------------------------------------ * This is the start of the List interface implementation * */ /** * This is a no-op since a DumpResult is immutable. * * @see java.util.List#add(int, java.lang.Object) */ public void add(int index, Object element) { throw new UnsupportedOperationException(); } /** * This is a no-op since a DumpResult is immutable. * * @see java.util.Collection#add(java.lang.Object) */ public boolean add(Object o) { throw new UnsupportedOperationException(); } /** * This is a no-op since DumpResult is immutable. * * @see java.util.Collection#addAll(java.util.Collection) */ public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } /** * This is a no-op since DumpResult is immutable. * * @see java.util.List#addAll(int, java.util.Collection) */ public boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } /** * This is a no-op since DumpResult is immutable. * * @see java.util.Collection#clear() */ public void clear() { throw new UnsupportedOperationException(); } /** * @see java.util.Collection#contains(java.lang.Object) */ public boolean contains(Object o) { checkBuffer(); return rows.contains(o); } /** * @see java.util.Collection#containsAll(java.util.Collection) */ public boolean containsAll(Collection c) { checkBuffer(); return rows.containsAll(c); } /** * @see java.util.List#get(int) */ public Object get(int index) { checkBuffer(); return rows.get(index); } /** * @see java.util.List#indexOf(java.lang.Object) */ public int indexOf(Object o) { checkBuffer(); return rows.indexOf(o); } /** * @see java.util.Collection#isEmpty() */ public boolean isEmpty() { checkBuffer(); return rows.isEmpty(); } /** * @see java.util.Collection#iterator() */ public Iterator iterator() { checkBuffer(); return rows.iterator(); } /** * @see java.util.List#lastIndexOf(java.lang.Object) */ public int lastIndexOf(Object o) { checkBuffer(); return rows.lastIndexOf(o); } /** * @see java.util.List#listIterator() */ public ListIterator listIterator() { checkBuffer(); return rows.listIterator(); } /** * @see java.util.List#listIterator(int) */ public ListIterator listIterator(int index) { checkBuffer(); return rows.listIterator(index); } /** * This is a no-op since DumpResult is immutable. * * @see java.util.List#remove(int) */ public Object remove(int index) { throw new UnsupportedOperationException(); } /** * This is a no-op since DumpResult is immutable. * * @see java.util.Collection#remove(java.lang.Object) */ public boolean remove(Object o) { throw new UnsupportedOperationException(); } /** * This is a no-op since DumpResult is immutable. * * @see java.util.Collection#removeAll(java.util.Collection) */ public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } /** * This is a no-op since DumpResult is immutable. * * @see java.util.Collection#retainAll(java.util.Collection) */ public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } /** * This is a no-op since DumpResult is immutable. * * @see java.util.List#set(int, java.lang.Object) */ public Object set(int index, Object element) { throw new UnsupportedOperationException(); } /** * @see java.util.Collection#size() */ public int size() { checkBuffer(); return rows.size(); } /** * @see java.util.List#subList(int, int) */ public List subList(int fromIndex, int toIndex) { checkBuffer(); return rows.subList(fromIndex, toIndex); } /** * @see java.util.Collection#toArray() */ public Object[] toArray() { checkBuffer(); return rows.toArray(); } /** * @see java.util.Collection#toArray(java.lang.Object[]) */ public Object[] toArray(Object[] a) { checkBuffer(); return rows.toArray(a); } }