/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.autoplot.ascii; import java.io.File; import java.text.ParseException; import java.util.logging.Level; import java.util.logging.Logger; import org.das2.datum.Units; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.das2.datum.Datum; import org.das2.datum.EnumerationUnits; import org.das2.datum.TimeParser; import org.das2.datum.UnitsUtil; import org.das2.datum.format.DatumFormatter; import org.das2.datum.format.DefaultDatumFormatter; import org.das2.datum.format.TimeDatumFormatter; import org.das2.datum.format.TimeDatumFormatterFactory; import org.das2.util.monitor.ProgressMonitor; import org.json.JSONException; import org.json.JSONObject; import org.das2.qds.QDataSet; import org.das2.qds.SemanticOps; import org.autoplot.datasource.AbstractDataSourceFormat; import org.das2.datum.format.FormatStringFormatter; import org.das2.qds.BundleDataSet; import org.das2.qds.DDataSet; import org.das2.qds.DataSetOps; import org.das2.qds.DataSetUtil; import org.das2.qds.MutablePropertyDataSet; import org.das2.qds.ops.Ops; /** * Format the QDataSet into Ascii tables. * * @author jbf */ public class AsciiTableDataSourceFormat extends AbstractDataSourceFormat { private static final Logger logger= Logger.getLogger("apdss.ascii"); private DatumFormatter getTimeFormatter( ) { DatumFormatter timeFormatter; String tformat= getParam( "tformat", "ISO8601" ); String ft= tformat.toLowerCase(); String depend0Units= getParam( "depend0Units", "" ); Units dep0units= null; if ( depend0Units.length()>0 ) { try { dep0units= Units.lookupTimeUnits(depend0Units); } catch (ParseException ex) { Logger.getLogger(AsciiTableDataSourceFormat.class.getName()).log(Level.SEVERE, null, ex); } final Units tu= dep0units; if ( ft.equals("iso8601") ) ft=null; final String sformat= ft; timeFormatter= new DefaultDatumFormatter() { @Override public String format(Datum datum) { return format(datum, tu); } @Override public String format(Datum datum, Units units) { if ( datum.isFill() ) { return "fill"; } else { if ( sformat!=null && sformat.startsWith("%") ) { return String.format( sformat, datum.doubleValue(tu) ); } else { return String.valueOf( datum.doubleValue(tu) ); } } } }; } else if (ft.equals("iso8601")) { timeFormatter = TimeDatumFormatterFactory.getInstance().defaultFormatter(); } else if ( tformat.startsWith("%") || ft.startsWith("$") ) { if ( tformat.startsWith("$") ) { // provide convenient URI-friendly spec tformat= tformat.replaceAll("\\$", "%"); } tformat= tformat.replaceAll("\\+",getDelim()); try { timeFormatter = new TimeDatumFormatter(tformat); } catch (ParseException ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); try { timeFormatter = new TimeDatumFormatter("%Y-%m-%dT%H:%M:%S"); } catch (ParseException ex1) { throw new RuntimeException(ex1); } } } else { try { if (ft.equals("day")) { timeFormatter = new TimeDatumFormatter("%Y-%m-%d"); } else if (ft.equals("hour")) { timeFormatter = new TimeDatumFormatter("%Y-%m-%dT%H:%MZ"); } else if (ft.startsWith("min")) { timeFormatter = new TimeDatumFormatter("%Y-%m-%dT%H:%MZ"); } else if (ft.startsWith("sec")) { timeFormatter = new TimeDatumFormatter("%Y-%m-%dT%H:%M:%SZ"); } else if (ft.startsWith("millisec")) { final TimeParser tp= TimeParser.create("$Y-$m-$dT$H:$M:$S.$(subsec,places=3)"); timeFormatter= new DatumFormatter() { @Override public String format(Datum datum) { return tp.format(datum); } }; //timeFormatter = new TimeDatumFormatter("%Y-%m-%dT%H:%M:%S.%{milli}Z"); } else if (ft.startsWith("microsec")) { final TimeParser tp= TimeParser.create("$Y-$m-$dT$H:$M:$S.$(subsec,places=6)"); timeFormatter= new DatumFormatter() { @Override public String format(Datum datum) { return tp.format(datum); } }; //timeFormatter = new TimeDatumFormatter("%Y-%m-%dT%H:%M:%S.%{milli}%{micro}Z"); } else { logger.log(Level.FINE, "not implemented: {0}", ft); timeFormatter = new TimeDatumFormatter("%Y-%m-%dT%H:%M:%S"); } } catch (ParseException ex) { logger.log( Level.SEVERE, ex.getMessage(), ex); timeFormatter = TimeDatumFormatterFactory.getInstance().defaultFormatter(); } } return timeFormatter; } private DatumFormatter getDataFormatter( String df, Units u ) { try { if ( !df.contains("%") ) df= "%"+df; //TODO: would be nice if we could verify formatter. I had %f5.2 instead of %5.2f and it wasn't telling me. return new FormatStringFormatter( df, false ); } catch ( RuntimeException ex ) { logger.log( Level.SEVERE, ex.getMessage(), ex); return u.getDatumFormatterFactory().defaultFormatter(); } } /** * output the dataset property if it exists. The form will be * name: value. If header=none, simply return. * @param out the output stream that is writing the file. * @param data the dataset. * @param property the property name. */ private void maybeOutputProperty(PrintWriter out, QDataSet data, String property) { if ( getParam("header","").equals("none") ) return; Object v = data.property(property); if (v != null) { out.println("# " + property + ": " + v); } } /** * Insert the property at i the index, or -1 for non-indexed property. * @param jo1 object to collect properties * @param ds dataset which can be a bundle * @param prop the property name * @param i -1 or the index of the bundled dataset. * @return true if the property was found. * @throws JSONException */ private boolean jsonProp( JSONObject jo1, QDataSet ds, String prop, int i ) throws JSONException { Object o; Units u; if ( i>-1 ) { o= ds.property(prop,i); u= (Units)ds.property(QDataSet.UNITS,i); if ( u==null ) u= Units.dimensionless; } else { o= ds.property(prop); u= (Units)ds.property(QDataSet.UNITS); if ( u==null ) u= Units.dimensionless; } boolean isTime= UnitsUtil.isTimeLocation( u ); if ( isTime ) { // since we are formatting to UTC and not reporting the Units, we can't use these values. if ( prop.equals("VALID_MIN") || prop.equals("VALID_MAX") || prop.equals("TYPICAL_MIN") || prop.equals("TYPICAL_MAX") || prop.equals("FILL_VALUE") ) { return false; } } if ( prop.equals(QDataSet.START_INDEX) ) { prop= "START_COLUMN"; } if ( o!=null ) { if ( o instanceof QDataSet ) { jo1.put( prop, o.toString() ); } else if ( o instanceof Number ) { jo1.put( prop, (Number)o ); } else if ( o instanceof Units ) { if ( UnitsUtil.isTimeLocation((Units)o) ) { jo1.put( prop, "UTC" ); } else { jo1.put( prop, String.valueOf(o) ); } } else { jo1.put( prop, String.valueOf(o) ); } return true; } else { return false; } } /** * format the depend dataset within the rich header. * @param ds * @return * @throws JSONException */ private JSONObject formatDataSetInline( QDataSet ds ) throws JSONException { JSONObject jo1= new JSONObject(); jsonProp( jo1, ds, QDataSet.LABEL, -1 ); jsonProp( jo1, ds, QDataSet.UNITS, -1 ); jsonProp( jo1, ds, QDataSet.VALID_MIN, -1 ); jsonProp( jo1, ds, QDataSet.VALID_MAX, -1 ); jsonProp( jo1, ds, QDataSet.FILL_VALUE, -1 ); jsonProp( jo1, ds, QDataSet.TITLE, -1 ); jo1.put( "VALUES", DataSetUtil.asArrayOfDoubles(ds) ); jo1.put( "DIMENSION", new int[] { ds.length() } ); return jo1; } private final Map namesFor= new HashMap(); private String getNameFor( QDataSet ds ) { String s; synchronized (namesFor) { s= namesFor.get(ds); if ( s!=null ) return s; s= (String) Ops.guessName(ds,"data"+namesFor.size()); namesFor.put(ds,s); } return s; } /** * format the data, using column descriptions in bundleDesc. Note * that when data is Data[Dep0], that bundleDesc will have two columns. * @param out the output stream * @param data the data * @param bundleDesc the descriptor containing the metadata for each column * @throws JSONException */ private void formatBundleDescRichAscii(PrintWriter out, QDataSet data, QDataSet bundleDesc ) throws JSONException { assert data!=null; assert bundleDesc.length()==data.length(1); QDataSet dep0; dep0= (QDataSet) data.property(QDataSet.DEPEND_0); JSONObject jo= new JSONObject(); JSONObject jo1= new JSONObject(); // object for the dataset in data. int startColumn= dep0==null ? 0 : 1; // first column of the vector int dep0inc; // 0 or 1, the amount to inc because of dep0 column. String name; String dep1Name= null; QDataSet dep= (QDataSet) data.property(QDataSet.DEPEND_1); if ( dep!=null && UnitsUtil.isRatioMeasurement( SemanticOps.getUnits(dep) ) && data.property(QDataSet.BUNDLE_1)==null ) { dep1Name= (String) Ops.guessName(dep); if ( dep1Name==null ) dep1Name= "dep1"; jo.put( dep1Name, formatDataSetInline( dep ) ); } String dep2Name= null; dep= (QDataSet) data.property(QDataSet.DEPEND_2); if ( dep!=null && UnitsUtil.isRatioMeasurement( SemanticOps.getUnits(dep) ) && data.property(QDataSet.BUNDLE_1)==null ) { dep2Name= (String) Ops.guessName(dep); if ( dep2Name==null ) dep2Name= "dep2"; jo.put( dep2Name, formatDataSetInline( dep ) ); } if ( dep0!=null ) { JSONObject dep0jo= new JSONObject(); name= getNameFor( dep0 ); jsonProp( dep0jo, dep0, QDataSet.LABEL, -1 ); if ( UnitsUtil.isTimeLocation( SemanticOps.getUnits(dep0) ) ) { dep0jo.put("UNITS", getTimeUnitLabel() ); } else { jsonProp( dep0jo, dep0, QDataSet.UNITS, -1 ); } dep0jo.put( "START_COLUMN", 0 ); if ( data.rank()>1 ) { jo.put( name, dep0jo ); dep0inc=1; } else { dep0inc=0; } } else { dep0inc=0; } String[] elementNames= new String[bundleDesc.length()]; String[] elementLabels= new String[bundleDesc.length()]; if ( bundleDesc.length()==1 && bundleDesc.length(0)==1 ) { int n= DataSetUtil.product( DataSetUtil.qubeDims(data.slice(0) ) ); elementNames= new String[n]; for ( int i=0; i1 ) { if ( bundleDesc.length()==1 ) { // bundle of 1 rank 2 if ( data.rank()>2 ) { int[] dims= DataSetUtil.qubeDims(data.slice(0)); jo1.put( "DIMENSION", dims ); } else { jo1.put( "DIMENSION", new int[] { (int)bundleDesc.value(0,0)} ); } } else { // bundle of N rank 1 jo1.put( "DIMENSION", new int[] { bundleDesc.length()} ); } jo1.put( "ELEMENT_NAMES", elementNames ); if ( elementLabels!=null ) { jo1.put( "ELEMENT_LABELS", elementLabels ); } if ( dep1Name!=null ) { jo1.put("DEPEND_1", dep1Name ); jo1.put("RENDER_TYPE", "spectrogram" ); } if ( dep2Name!=null ) { jo1.put("DEPEND_2", dep2Name ); } jo.put( Ops.guessName(data,"data"), jo1 ); } String json= jo.toString( 3 ); String[] lines= json.split("\n"); StringBuilder sb= new StringBuilder(); for ( String line : lines ) { sb.append("# ").append(line).append("\n"); } out.print( sb.toString() ); } private String getTimeUnitLabel( ) { String depend0Units= getParam("depend0Units",""); if ( depend0Units.equals("") ) { return "UTC"; } else { try { Units u= Units.lookupTimeUnits(depend0Units); return u.toString(); } catch (ParseException ex) { throw new IllegalArgumentException(ex); } } } /** * return " " or ", " * @return the delimiter. */ private String getDelim( ) { String head= getParam( "header", "" ); // could be "rich" String delim = "rich".equals(head) ? " " : ", "; return delim; } /** * format the rank 2 bundle of data. * @param out * @param data * @param mon */ private void formatRank2Bundle(PrintWriter out, QDataSet data, ProgressMonitor mon) { QDataSet bundleDesc= (QDataSet) data.property(QDataSet.BUNDLE_1); QDataSet dep0 = (QDataSet) data.property(QDataSet.DEPEND_0); String head= getParam( "header", "" ); // could be "rich" boolean haveRich= false; if ( bundleDesc!=null && "rich".equals( head ) ) { try { formatBundleDescRichAscii( out, data, bundleDesc ); haveRich= true; } catch ( JSONException ex ) { logger.log( Level.SEVERE, ex.getMessage(), ex ); } } else { maybeOutputProperty(out, data, QDataSet.TITLE); } DatumFormatter tf= getTimeFormatter( ); String delim= getDelim(); String df= getParam( "format", "" ); DatumFormatter[] formats= new DatumFormatter[data.length(0)]; Units[] uu= new Units[data.length(0)]; if ( bundleDesc==null ) { throw new IllegalArgumentException("expected to find bundleDesc in dataset!"); } int jj=0; // index into rank2 array for ( int i=0; i1 ) { Units u2= (Units) bundleDesc.property(QDataSet.UNITS,1); if ( u2==null ) u2= Units.dimensionless; if ( UnitsUtil.isTimeLocation(u2)) { startStopTime= true; } } if ( Units.t2000.isConvertibleTo( u1 ) ) { if ( startStopTime ) { l1= "time"+i; } else { l1= "time"; } } else { l1= "field"+i; } } if ( uu[i]!=null && uu[i]!=Units.dimensionless ) { if ( uu[i] instanceof EnumerationUnits ) { } else if ( UnitsUtil.isTimeLocation(uu[i] ) ) { l1+= "("+getTimeUnitLabel()+")"; } else { l1+="("+uu[i]+")"; } } int nelements= 1; for ( int k=0; k0 ) { if ( df.equals("") ) { cf0= u0.createDatum( dep0.value(0)).getFormatter(); } else { cf0= getDataFormatter( df, uu[jj] ); } } } mon.setTaskSize(data.length()); mon.started(); for ( i = 0; i < data.length(); i++) { mon.setTaskProgress(i); if ( mon.isCancelled() ) break; if (dep0 != null) { assert u0!=null; out.print("" + cf0.format( u0.createDatum(dep0.value(i)) ) + delim ); } int j; for ( j = 0; j < data.length(i) - 1; j++) { out.print( formats[j].format( uu[j].createDatum(data.value(i,j)), uu[j] ) + delim ); } out.println( formats[j].format( uu[j].createDatum(data.value(i,j)), uu[j] ) ); } mon.finished(); } private void formatRank2(PrintWriter out, QDataSet data, ProgressMonitor mon) { QDataSet dep1 = (QDataSet) data.property(QDataSet.DEPEND_1); QDataSet dep0 = (QDataSet) data.property(QDataSet.DEPEND_0); String delim= getDelim(); boolean okay= DataSetUtil.checkQube(data); // some RBSP/ECT data has rank 2 DEPEND_1 when it is really a qube. if ( !okay ) { throw new IllegalArgumentException("Data is not a qube. Each record must have the same DEPEND_1."); } if ( dep1!=null && dep1.rank()==2 ) { throw new IllegalArgumentException("dep1 rank is 2, which is not supported."); } String head= getParam( "header", "" ); // could be "rich" if ( "rich".equals( head ) ) { try { BundleDataSet bds= BundleDataSet.createRank1Bundle(); //TODO: this is just so it does something. Fill this out. DDataSet ds= DDataSet.createRank1(1); ds.putProperty( QDataSet.TITLE, data.property(QDataSet.TITLE) ); ds.putProperty( QDataSet.LABEL, data.property(QDataSet.LABEL) ); ds.putProperty( QDataSet.NAME, data.property(QDataSet.NAME) ); ds.putProperty( QDataSet.UNITS, data.property(QDataSet.UNITS) ); ds.putProperty( QDataSet.VALID_MAX, data.property(QDataSet.VALID_MAX) ); ds.putProperty( QDataSet.VALID_MIN, data.property(QDataSet.VALID_MIN) ); ds.putProperty( QDataSet.FILL_VALUE, data.property(QDataSet.FILL_VALUE) ); ds.putValue( 0, data.length(0) ); bds.bundle( ds ); formatBundleDescRichAscii( out, data, bds ); } catch ( JSONException ex ) { logger.log( Level.SEVERE, ex.getMessage(), ex ); } } else { maybeOutputProperty(out, data, QDataSet.TITLE); } Units u = (Units) data.property(QDataSet.UNITS); if (u == null) u = Units.dimensionless; if ( u!=Units.dimensionless && !"rich".equals( head ) ) maybeOutputProperty( out, data, QDataSet.UNITS ); if (dep1 != null && !"none".equals(head) ) { if ( "rich".equals( head ) ) { out.print("#"); // Autoplot must be able to read what it formats. For rich headers we continue to comment this line. } if (dep0 != null) { String l = (String) dep0.property(QDataSet.LABEL); if ( l==null ) { if ( Units.t2000.isConvertibleTo( SemanticOps.getUnits(dep0) ) ) { l= "time("+getTimeUnitLabel()+")"; } else { l= "dep0"; } } out.print( l + delim ); } Units dep1units = (Units) dep1.property(QDataSet.UNITS); if (dep1units == null) dep1units = Units.dimensionless; if ( dep1.rank()>1 ) { } int i; for ( i = 0; i < dep1.length()-1; i++) { Datum d= dep1units.createDatum(dep1.value(i)); String s= d.toString().replaceAll("\\s+",""); out.print( s + delim ); } Datum d= dep1units.createDatum(dep1.value(i)); String s= d.toString().replaceAll("\\s+",""); out.println( s ); } Units u0 = null; if (dep0 != null) { u0 = (Units) dep0.property(QDataSet.UNITS); if (u0 == null) u0 = Units.dimensionless; } mon.setTaskSize(data.length()); mon.started(); DatumFormatter tf= getTimeFormatter(); DatumFormatter df; String format= getParam( "format", "" ); if ( format.equals("") ) { String dfs= (String)data.property(QDataSet.FORMAT); if ( dfs!=null && dfs.trim().length()>0 ) { df= getDataFormatter( dfs, u ); } else { df= u.getDatumFormatterFactory().defaultFormatter(); } } else { df= getDataFormatter(format, u ); } DatumFormatter cf0= dep0==null ? null : ( UnitsUtil.isTimeLocation(u0) ? tf : df ); DatumFormatter cf1= UnitsUtil.isTimeLocation(u) ? tf : df; for (int i = 0; i < data.length(); i++) { mon.setTaskProgress(i); if ( mon.isCancelled() ) break; if (dep0 != null) { assert dep0!=null; assert cf0!=null; assert u0!=null; out.print("" + cf0.format( u0.createDatum(dep0.value(i)),u0 ) + delim ); } int j; switch (data.rank()) { case 2: for ( j = 0; j < data.length(i) - 1; j++) { out.print( cf1.format( u.createDatum(data.value(i,j)), u ) + delim ); } out.println( cf1.format( u.createDatum(data.value(i,j)), u ) ); break; case 3: int m= data.length(i); int n= data.length(i,0); for ( j = 0; j < m; j++) { for ( int k=0; k planes= new ArrayList<>(); List planeUnits= new ArrayList<>(); // List planeFormats= new ArrayList(); String head= getParam( "header", "" ); // could be "rich" if ( "rich".equals( head ) ) { try { DDataSet ids= DDataSet.createRank1(1); ids.putProperty( QDataSet.TITLE, dep0.property(QDataSet.TITLE) ); ids.putProperty( QDataSet.LABEL, dep0.property(QDataSet.LABEL) ); ids.putProperty( QDataSet.NAME, dep0.property(QDataSet.NAME) ); ids.putProperty( QDataSet.UNITS, dep0.property(QDataSet.UNITS) ); ids.putProperty( QDataSet.VALID_MAX, dep0.property(QDataSet.VALID_MAX) ); ids.putProperty( QDataSet.VALID_MIN, dep0.property(QDataSet.VALID_MIN) ); ids.putProperty( QDataSet.FILL_VALUE, dep0.property(QDataSet.FILL_VALUE) ); DDataSet ds= DDataSet.createRank1(1); ds.putProperty( QDataSet.TITLE, data.property(QDataSet.TITLE) ); ds.putProperty( QDataSet.LABEL, data.property(QDataSet.LABEL) ); String name= (String)data.property(QDataSet.NAME); if ( name==null ) name= "data"; ds.putProperty( QDataSet.NAME, name ); ds.putProperty( QDataSet.UNITS, data.property(QDataSet.UNITS) ); ds.putProperty( QDataSet.VALID_MAX, data.property(QDataSet.VALID_MAX) ); ds.putProperty( QDataSet.VALID_MIN, data.property(QDataSet.VALID_MIN) ); ds.putProperty( QDataSet.FILL_VALUE, data.property(QDataSet.FILL_VALUE) ); QDataSet bds= Ops.join( ids, ds ); formatBundleDescRichAscii( out,data,bds ); } catch ( JSONException ex ) { logger.log( Level.SEVERE, ex.getMessage(), ex); } } else { maybeOutputProperty(out, data, QDataSet.TITLE); } StringBuilder buf= new StringBuilder(); String l; if (dep0 != null) { l = dataSetLabel( dep0, "dep0" ); buf.append( delim ).append( l); u0= (Units) dep0.property(QDataSet.UNITS); if ( u0==null ) u0= Units.dimensionless; } l = dataSetLabel( data, "data" ); buf.append( delim ).append( l); u= (Units) data.property(QDataSet.UNITS); if ( u==null ) u= Units.dimensionless; if ( !"rich".equals( head ) ) { maybeOutputProperty(out, data, QDataSet.TITLE); if ( u!=Units.dimensionless ) maybeOutputProperty( out, data, QDataSet.UNITS ); } for ( int i=0; i0 ) { // planeFormats= new ArrayList(planes.size()); // for ( int i=0; i0 ) { tf= Units.dimensionless.getDatumFormatterFactory().defaultFormatter(); try { dep0units= Units.lookupTimeUnits(depend0Units); } catch (ParseException ex) { throw new IllegalArgumentException("unable to parse depend0Units"); } } String format= getParam( "format", "" ); DatumFormatter df; if ( format.equals("") ) { String dfs= (String)data.property(QDataSet.FORMAT); if ( dfs!=null && dfs.trim().length()>0 ) { df= getDataFormatter( dfs, u ); } else { df= u.getDatumFormatterFactory().defaultFormatter(); } } else { df= getDataFormatter(format, u ); } DatumFormatter cf0= dep0==null ? null : ( UnitsUtil.isTimeLocation(u0) ? tf : df ); DatumFormatter cf1= UnitsUtil.isTimeLocation(u) ? tf : df; for (int i = 0; i < data.length(); i++ ) { mon.setTaskProgress(i); if ( mon.isCancelled() ) break; if (dep0 != null) { assert cf0!=null; assert u0!=null; out.print("" + cf0.format( u0.createDatum(dep0.value(i)),dep0units ) + delim ); } out.print( cf1.format(u.createDatum(data.value(i)), u) ); for ( int j=0; j0 && doDep.toUpperCase().charAt(0)=='F' ) { MutablePropertyDataSet mpds= DataSetOps.makePropertiesMutable(data); mpds.putProperty( QDataSet.DEPEND_0, null ); mpds.putProperty( QDataSet.DEPEND_1, null ); mpds.putProperty( QDataSet.BUNDLE_1, null ); data= mpds; } File f= new File( getResourceURI() ); // if ( !f.createNewFile() ) { // if ( f.exists() ) { // throw new IOException( "Unable to write to existing file: "+f ); // } else { // throw new IOException( "Unable to write file: "+f ); // } // } try ( PrintWriter out = new PrintWriter( f ) ) { //TODO: it would be nice to support a preview, this assumes file. String head= getParam( "header", "" ); // could be "rich" or "none" if ( !"rich".equals( head ) && !"none".equals(head)) { out.println("# Generated by Autoplot on " + new Date()); } if (data.rank() == 2) { if ( SemanticOps.isBundle(data) ) { formatRank2Bundle( out, data, mon ); // data should have property BUNDLE_1 because of isBundle==true } else { formatRank2(out, data, mon); } } else if (data.rank() == 1) { formatRank1(out, data, mon); } else if ( data.rank()==3 && "rich".equals( head ) ) { formatRank2(out, data, mon); } else { throw new IllegalArgumentException("only rank 1 and rank 2 data are supported"); } } // could be "rich" or "none" } @Override public boolean canFormat(QDataSet ds) { return ( ds.rank()>0 && ds.rank()<3 ); } @Override public String getDescription() { return "ASCII Table"; } }