Skip to content

Instantly share code, notes, and snippets.

@briandignan
Created August 28, 2013 14:33
Show Gist options
  • Select an option

  • Save briandignan/6366687 to your computer and use it in GitHub Desktop.

Select an option

Save briandignan/6366687 to your computer and use it in GitHub Desktop.
The set of classes that I use to represent SOAP query results and CSV files in a common format so they can easily be converted from one to the other. It's especially useful when doing unit tests where I need to mock out a Cisco CallManager SOAP query resultset.
package mil.nga.common.db;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import net.jcip.annotations.Immutable;
/**
* The primary element that's contained within a <code>Records</code> object. <p>
*
* It's a Map<String, String> wrapper, with the potential for additional methods in the future.
*
* @author Brian Dignan
*
*/
@Immutable
public class Record implements Map<String, String> {
private final Map<String, String> data;
public Record( Map<String, String> data ) {
// Make a defensive copy of the map parameter.
this.data = new LinkedHashMap<String, String>();
this.data.putAll( data );
}
// Unsupported map methods
@Override public void clear() { throw new UnsupportedOperationException( "This method isn't supported" ); }
@Override public String put( String key, String value ) { throw new UnsupportedOperationException( "This method isn't supported" ); }
@Override public void putAll( Map<? extends String, ? extends String> arg0 ) { throw new UnsupportedOperationException( "This method isn't supported" ); }
@Override public String remove( Object obj ) { throw new UnsupportedOperationException( "This method isn't supported" ); }
// Pass-through map methods
@Override public boolean containsKey( Object key ) { return data.containsKey( key ); }
@Override public boolean containsValue( Object value ) { return data.containsValue( value ); }
@Override public Set<Entry<String, String>> entrySet() { return data.entrySet(); }
@Override public String get( Object key ) { return data.get( key ); }
@Override public boolean isEmpty() { return data.isEmpty(); }
@Override public Set<String> keySet() { return data.keySet(); }
@Override public int size() { return data.size(); }
@Override public Collection<String> values() { return data.values(); }
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append( "RECORD: " );
for ( String key : data.keySet() ) {
result.append( key + "->" + data.get( key ) + " " );
}
return result.toString();
}
}
package mil.nga.common.db;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import net.jcip.annotations.Immutable;
import au.com.bytecode.opencsv.CSVWriter;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
/**
* A list of records. <p>
* <br>
* Also contains additional methods to allow for the import and export of records<br>
*
* @author Brian Dignan
*
*/
@Immutable
public class Records implements Iterable<Record> {
private final static Logger logger = Logger.getLogger( Records.class );
private final List<Record> records;
private Records( List<Record> records ) {
logger.debug( "Creating new records object with " + records.size() + " rows" );
this.records = new ImmutableList.Builder<Record>().addAll( records ).build();
}
/**
*
* @param records A list of Record objects
* @return A new Records object
*/
public static Records fromRecordList( List<Record> records ) {
if ( records.size() == 0 ) {
return new Records( records );
} else {
// Confirm that all records have a consistent number of mappings and that the column headers are consistent.
Record recordOne = records.get( 0 );
for ( int index = 1; index < records.size(); index++ ) {
Record currentRecord = records.get( index );
if ( currentRecord.size() != recordOne.size() ) {
String message = "Record " + index + " has " + currentRecord.size() + " mappings, which is inconsistent with the " + recordOne.size() + " mappings in record 0" ;
logger.error( message );
throw new IllegalArgumentException( message );
} else {
// The current record has the correct number of mappings
for ( String key : recordOne.keySet() ) {
if ( !currentRecord.containsKey( key ) ) {
String message = "Record 0 contains the key \"" + key + "\", but record " + index + " does not.";
logger.error( message );
throw new IllegalArgumentException( message );
}
}
}
}
// All records have the same key->value mappings.
return new Records( records );
}
}
/**
*
* @param csv A String in CSV format
* @return A new Records object
*/
public static Records fromCsvString( String csv ) {
ImmutableList.Builder<Record> result = new ImmutableList.Builder<Record>();
CSVParser parser = null;
List<CSVRecord> csvRecordList = null;
try {
parser = new CSVParser( new StringReader( csv ), CSVFormat.EXCEL );
csvRecordList = parser.getRecords();
} catch ( IOException e ) {
// There should never be an IOException when reading from a string. Convert it to an
// unchecked exception so the API isn't polluted with an unnecessary checked exception.
throw new RuntimeException( e );
}
if ( csvRecordList.size() == 0 ) {
String message = "No records found in the CSV";
logger.error( message );
throw new IllegalArgumentException( message );
} else if ( csvRecordList.size() == 1 ) {
// There's only a header row. Return an empty list.
return new Records( result.build() );
} else {
// There is a header row and at least one data row.
CSVRecord csvHeaderRecord = csvRecordList.get( 0 );
// Confirm that no header fields are empty
for ( String field : csvHeaderRecord ) {
if ( field == null || "".equals( field ) ) {
String message = "The header row contains an empty element.";
logger.error( message );
throw new IllegalArgumentException( message );
}
}
// Add all of the 'data' records to the resulting Record list.
for ( int index = 1; index < csvRecordList.size(); index++ ) {
CSVRecord csvDataRecord = csvRecordList.get( index );
if ( csvDataRecord.size() != csvHeaderRecord.size() ) {
String message = "Row " + ( index + 1 ) + " row size of " + csvDataRecord.size() + " is inconsistent with the header row size of " + csvHeaderRecord.size();
logger.error( message );
throw new IllegalArgumentException( message );
} else {
// This data record has the same number of columns as the header record
Map<String, String> dataMap = new TreeMap<String, String>();
for ( int indexTwo = 0; indexTwo < csvDataRecord.size(); indexTwo++ ) {
logger.trace( "Row: " + index + " // Adding: " + csvHeaderRecord.get( indexTwo ) + " / " + csvDataRecord.get( indexTwo ) );
dataMap.put( csvHeaderRecord.get( indexTwo ), csvDataRecord.get( indexTwo ) );
}
Record record = new Record( dataMap );
result.add( record );
}
}
}
return new Records( result.build() );
}
/**
*
* @param csvFile A CSV File
* @return A new Records object
* @throws IOException If there is an issue reading from the File
*/
public static Records fromCsvFile( File csvFile ) throws IOException {
return Records.fromCsvString( FileUtils.readFileToString( csvFile ) );
}
@Override
public Iterator<Record> iterator() {
return records.iterator();
}
/**
*
* @return A String in CSV format.
*/
public String toCsvString() {
StringWriter result = new StringWriter();
if ( records.size() == 0 ) {
// No records to return, so return an empty string.
result.append( "" );
} else {
CSVWriter writer = new CSVWriter( result, CSVWriter.DEFAULT_SEPARATOR, CSVWriter.DEFAULT_QUOTE_CHARACTER );
Record firstRecord = records.get( 0 );
int columnQuantity = firstRecord.size();
String headerFields[] = new String[columnQuantity];
// Add the header row to the result
int index = 0;
for ( String key : firstRecord.keySet() ) {
headerFields[index] = key;
index++;
}
writer.writeNext( headerFields );
// Add each data row to the result
for ( index = 0; index < records.size(); index++ ) {
String rowValues[] = new String[columnQuantity];
Record record = records.get( index );
// Add each value to the array for this record.
for ( int valIndex = 0; valIndex < record.size(); valIndex++ ) {
rowValues[valIndex] = record.get( headerFields[valIndex] );
}
writer.writeNext( rowValues );
}
try {
writer.close();
} catch ( IOException e ) {
// Since it's writing to a String (and not something like a file
// This should never happen unless it runs out of memory. Convert
// this to unchecked exception.
throw new RuntimeException( e );
}
}
return result.toString();
}
/**
* Converts the Records object into a mapping of field -> List<Record>, where field is a particular
* field name that exists in every record.
*
* @param field The field that should used as they key
* @return The mapping of fieldValue -> to the list of records that contain that field
*/
public Map<String, List<Record>> toMapOfKeyToRecords( String field ) {
Preconditions.checkNotNull( field );
Map<String, List<Record>> result = new LinkedHashMap<String, List<Record>>();
if ( records.size() == 0 )
return result;
// Confirm that the field argument is valid
if ( !records.get( 0 ).containsKey( field ) )
throw new IllegalArgumentException( field + " is not a valid field in the record list" );
for ( Record record : records ) {
String key = record.get( field );
if ( key == null ) throw new NullPointerException( "record contains a null entry for the " + field + " field. record data: " + record );
if ( !result.containsKey( key ) ) {
// This is the first record with this key
result.put( key, new ArrayList<Record>() );
}
result.get( key ).add( record );
}
return result;
}
/**
*
* @return The immutable list of immutable internal Record objects
*/
public List<Record> getRecordList() {
return records;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append( "RECORDS (Quantity " + records.size() + ")\r\n" );
for ( Record record : records ) {
result.append( record + "\r\n" );
}
return result.toString();
}
public static class Builder {
private List<Record> recordList = new ArrayList<Record>();
public Builder add( Record record ) { recordList.add( record ); return this; }
public Records build() { return Records.fromRecordList( recordList ); }
}
}
package mil.nga.common.db;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.junit.Test;
public class RecordsTests {
@Test
public void constructionFromBasicCsvString() throws Exception {
String csv =
"col1,col2,col3\n" +
"r1c1,r1c2,r1c3\n" +
"r2c1,r2c2,r2c3\n";
Records records = Records.fromCsvString( csv );
List<Record> recordList = records.getRecordList();
assertEquals( 2, recordList.size() );
Record record;
record = recordList.get( 0 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "r1c1", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "r1c2", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "r1c3", record.get( "col3" ) );
record = recordList.get( 1 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "r2c1", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "r2c2", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "r2c3", record.get( "col3" ) );
String backToCsv = records.toCsvString();
assertEquals( csv, backToCsv.replaceAll( "\"", "" ) );
}
@Test
public void constructionFromCsvWithSomeEmptyElements() throws Exception {
String csv =
"col1,col2,col3\n" +
"r1c1,,r1c3\n" +
",r2c2,\n" +
",,\n" +
"r4c1,r4c2,r4c3\n";
Records records = Records.fromCsvString( csv );
List<Record> recordList = records.getRecordList();
assertEquals( 4, recordList.size() );
Record record;
record = recordList.get( 0 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "r1c1", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "r1c3", record.get( "col3" ) );
record = recordList.get( 1 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "r2c2", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "", record.get( "col3" ) );
record = recordList.get( 2 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "", record.get( "col3" ) );
record = recordList.get( 3 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "r4c1", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "r4c2", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "r4c3", record.get( "col3" ) );
String backToCsv = records.toCsvString();
assertEquals( csv, backToCsv.replaceAll( "\"", "" ) );
}
@Test( expected = IllegalArgumentException.class )
public void constructionFromMalformedCsvOne() throws Exception {
String csv =
"col1,col2,col3,col4\n" +
"r1c1,r1c2,r1c3\n";
Records.fromCsvString( csv );
}
@Test( expected = IllegalArgumentException.class )
public void constructionFromMalformedCsvTwo() throws Exception {
String csv =
"col1,col2,col3\n" +
"r1c1,r1c2,r1c3,r1c4\n";
Records.fromCsvString( csv );
}
@Test( expected = IllegalArgumentException.class )
public void constructionFromMalformedCsvThree() throws Exception {
String csv =
"col1,col2,,col4\n" + // All header elements must have at least one character
"r1c1,r1c2,r1c3,r1c4\n";
Records.fromCsvString( csv );
}
@Test( expected = IllegalArgumentException.class )
public void constructionFromMalformedCsvFour() throws Exception {
String csv =
"col1,col2,col3,col4\n" +
"r1c1,r1c2,r1c3,r1c4\n" +
"r2c1,r2c2,r2c3\n";
Records.fromCsvString( csv );
}
@Test( expected = IllegalArgumentException.class )
public void constructionFromMalformedCsvFive() throws Exception {
String csv = "";
Records.fromCsvString( csv );
}
@Test
public void constructionFromCsvWithOnlyHeaderRow() throws Exception {
String csv =
"col1,col2,col3,col4\n";
Records records = Records.fromCsvString( csv );
assertEquals( 0, records.getRecordList().size() );
//String backToCsv = records.toCsvString();
// Doesn't work because the header row is only captures if there is at least one data row
// assertEquals( csv, backToCsv.replaceAll( "\"", "" ) );
}
@Test
public void constructFromCsvWithQuotes() throws Exception {
String csv =
"col1,col2,col3\n" +
"\"\"\"insidequotes\"\"\",stuff,foo\n";
Records records = Records.fromCsvString( csv );
Record record;
List<Record> recordList = records.getRecordList();
assertEquals( 1, recordList.size() );
record = recordList.get( 0 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "\"insidequotes\"", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "stuff", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "foo", record.get( "col3" ) );
String backToCsv = records.toCsvString();
assertEquals( csv.replaceAll( "\"", "" ) , backToCsv.replaceAll( "\"", "" ) );
}
@Test
public void constructFromCsvWithQuotesAndCommas() throws Exception {
String csv =
"col1,col2,col3\n" +
"\",betweencommas,\",\"\"\",betweencommasandquotes\"\",\",\",betweenmorecommas,\"\n";
Records records = Records.fromCsvString( csv );
Record record;
List<Record> recordList = records.getRecordList();
assertEquals( 1, recordList.size() );
record = recordList.get( 0 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( ",betweencommas,", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "\",betweencommasandquotes\",", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( ",betweenmorecommas,", record.get( "col3" ) );
String backToCsv = records.toCsvString();
assertEquals( csv.replaceAll( "\"", "" ) , backToCsv.replaceAll( "\"", "" ) );
}
@Test
public void constructFromValidRecordList() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Map<String, String> recordOneData = new TreeMap<String, String>();
recordOneData.put( "col1", "r1c1" );
recordOneData.put( "col2", "r1c2" );
recordOneData.put( "col3", "r1c3" );
recordList.add( new Record( recordOneData ) );
Map<String, String> recordTwoData = new TreeMap<String, String>();
recordTwoData.put( "col1", "r2c1" );
recordTwoData.put( "col2", "r2c2" );
recordTwoData.put( "col3", "r2c3" );
recordList.add( new Record( recordTwoData ) );
Records records = Records.fromRecordList( recordList );
recordList = records.getRecordList();
assertEquals( 2, recordList.size() );
Record record;
record = recordList.get( 0 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "r1c1", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "r1c2", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "r1c3", record.get( "col3" ) );
record = recordList.get( 1 );
assertEquals( 3, record.size() );
assertTrue( record.containsKey( "col1" ) );
assertEquals( "r2c1", record.get( "col1" ) );
assertTrue( record.containsKey( "col2" ) );
assertEquals( "r2c2", record.get( "col2" ) );
assertTrue( record.containsKey( "col3" ) );
assertEquals( "r2c3", record.get( "col3" ) );
}
@Test( expected = IllegalArgumentException.class )
public void constructFromRecordListRowWithTooFewColumns() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Map<String, String> recordOneData = new TreeMap<String, String>();
recordOneData.put( "col1", "r1c1" );
recordOneData.put( "col2", "r1c2" );
recordOneData.put( "col3", "r1c3" );
recordList.add( new Record( recordOneData ) );
Map<String, String> recordTwoData = new TreeMap<String, String>();
recordTwoData.put( "col1", "r2c1" );
recordTwoData.put( "col2", "r2c2" );
recordList.add( new Record( recordTwoData ) );
Records.fromRecordList( recordList );
}
@Test( expected = IllegalArgumentException.class )
public void constructFromRecordListRowWithTooManyColumns() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Map<String, String> recordOneData = new TreeMap<String, String>();
recordOneData.put( "col1", "r1c1" );
recordOneData.put( "col2", "r1c2" );
recordOneData.put( "col3", "r1c3" );
recordList.add( new Record( recordOneData ) );
Map<String, String> recordTwoData = new TreeMap<String, String>();
recordTwoData.put( "col1", "r2c1" );
recordTwoData.put( "col2", "r2c2" );
recordTwoData.put( "col3", "r2c3" );
recordTwoData.put( "col4", "r2c4" );
recordList.add( new Record( recordTwoData ) );
Records.fromRecordList( recordList );
}
@Test( expected = IllegalArgumentException.class )
public void constructFromRecordListWithInconsistentColumnHeaders() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Map<String, String> recordOneData = new TreeMap<String, String>();
recordOneData.put( "col1", "r1c1" );
recordOneData.put( "col2", "r1c2" );
recordOneData.put( "col3", "r1c3" );
recordList.add( new Record( recordOneData ) );
Map<String, String> recordTwoData = new TreeMap<String, String>();
recordTwoData.put( "column1", "r2c1" );
recordTwoData.put( "col2", "r2c2" );
recordTwoData.put( "col3", "r2c3" );
recordList.add( new Record( recordTwoData ) );
Records.fromRecordList( recordList );
}
@Test
public void convertToFieldMapping() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Map<String, String> recordOneData = new TreeMap<String, String>();
recordOneData.put( "col1", "r1c1" );
recordOneData.put( "col2", "r1c2" );
recordOneData.put( "col3", "r1c3" );
recordList.add( new Record( recordOneData ) );
Map<String, String> recordTwoData = new TreeMap<String, String>();
recordTwoData.put( "col1", "r2c1" );
recordTwoData.put( "col2", "r2c2" );
recordTwoData.put( "col3", "r2c3" );
recordList.add( new Record( recordTwoData ) );
Records records = Records.fromRecordList( recordList );
Map<String, List<Record>> fieldToRecords = records.toMapOfKeyToRecords( "col1" );
assertEquals( 2, fieldToRecords.size() );
assertEquals( 1, fieldToRecords.get( "r1c1" ).size() );
assertEquals( 1, fieldToRecords.get( "r2c1" ).size() );
assertNull( fieldToRecords.get( "invalid" ) );
assertEquals( "r1c2", fieldToRecords.get( "r1c1" ).get( 0 ).get( "col2" ) );
assertEquals( "r2c2", fieldToRecords.get( "r2c1" ).get( 0 ).get( "col2" ) );
}
@Test( expected = IllegalArgumentException.class )
public void convertToFieldMappingWithInvalidField() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Map<String, String> recordOneData = new TreeMap<String, String>();
recordOneData.put( "col1", "r1c1" );
recordOneData.put( "col2", "r1c2" );
recordOneData.put( "col3", "r1c3" );
recordList.add( new Record( recordOneData ) );
Map<String, String> recordTwoData = new TreeMap<String, String>();
recordTwoData.put( "col1", "r2c1" );
recordTwoData.put( "col2", "r2c2" );
recordTwoData.put( "col3", "r2c3" );
recordList.add( new Record( recordTwoData ) );
Records records = Records.fromRecordList( recordList );
records.toMapOfKeyToRecords( "r1c1" );
}
@Test
public void convertToFieldMappingWithDuplicateKeys() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Map<String, String> recordOneData = new TreeMap<String, String>();
recordOneData.put( "col1", "key1" );
recordOneData.put( "col2", "r1c2" );
recordOneData.put( "col3", "r1c3" );
recordList.add( new Record( recordOneData ) );
Map<String, String> recordTwoData = new TreeMap<String, String>();
recordTwoData.put( "col1", "key1" );
recordTwoData.put( "col2", "r2c2" );
recordTwoData.put( "col3", "r2c3" );
recordList.add( new Record( recordTwoData ) );
Records records = Records.fromRecordList( recordList );
Map<String, List<Record>> fieldToRecords = records.toMapOfKeyToRecords( "col1" );
assertEquals( 1, fieldToRecords.size() );
assertEquals( 2, fieldToRecords.get( "key1" ).size() );
assertEquals( "r1c2", fieldToRecords.get( "key1" ).get( 0 ).get( "col2" ) );
assertEquals( "r2c2", fieldToRecords.get( "key1" ).get( 1 ).get( "col2" ) );
}
@Test( expected = NullPointerException.class )
public void convertToFieldMappingWithNulArgument() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Map<String, String> recordOneData = new TreeMap<String, String>();
recordOneData.put( "col1", "key1" );
recordOneData.put( "col2", "r1c2" );
recordOneData.put( "col3", "r1c3" );
recordList.add( new Record( recordOneData ) );
Map<String, String> recordTwoData = new TreeMap<String, String>();
recordTwoData.put( "col1", "key1" );
recordTwoData.put( "col2", "r2c2" );
recordTwoData.put( "col3", "r2c3" );
recordList.add( new Record( recordTwoData ) );
Records records = Records.fromRecordList( recordList );
records.toMapOfKeyToRecords( null );
}
@Test
public void convertToFieldMappingWithNoRecords() throws Exception {
List<Record> recordList = new ArrayList<Record>();
Records records = Records.fromRecordList( recordList );
Map<String, List<Record>> result = records.toMapOfKeyToRecords( "col1" );
assertEquals( 0, result.size() );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment