/*
* Copyright (c) 2009-2015
* IT-Consulting Stephan Schloepke (http://www.schloepke.de/)
* klemm software consulting Mirko Klemm (http://www.klemm-scs.com/)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jbasics.csv;
import org.jbasics.arrays.unstable.ArrayIterator;
import org.jbasics.net.mediatype.MediaType;
import org.jbasics.pattern.container.Indexed;
import org.jbasics.pattern.container.Mapable;
import org.jbasics.pattern.container.TabularData;
import org.jbasics.pattern.factory.ParameterFactory;
import org.jbasics.types.sequences.Sequence;
import org.jbasics.types.tuples.Pair;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* CSVFile is a comma separated values file as defined by RFC4180.
*
* @author Stephan Schloepke
* @since 1.0
*/
public class CSVTable implements Iterable<CSVRecord>, Mapable<Sequence<String>, CSVRecord>, Indexed<CSVRecord>, CSVDataReference, TabularData<String> {
@SuppressWarnings("unchecked")
public static MediaType RFC4180_MEDIA_TYPE = new MediaType("text", "csv");
public static Pair<String, String> HEADER_PRESENT = new Pair<String, String>("header", "present");
public static Pair<String, String> HEADER_ABSENT = new Pair<String, String>("header", "absent");
private final Charset charset;
private final CSVRecord headers;
private final CSVRecord[] records;
private final char separator;
private final int maxColumnSize;
public CSVTable(final CSVRecord... records) {
this(null, ',', (CSVRecord) null, records);
}
public CSVTable(final Charset charset, final char separator, final CSVRecord headers, final CSVRecord[] records) {
this.charset = charset == null ? Charset.defaultCharset() : charset;
this.headers = headers;
this.records = records == null ? new CSVRecord[0] : records;
this.separator = separator;
int columnSize = 0;
for(CSVRecord record : this.records) {
columnSize = Math.max(columnSize, record.size());
}
this.maxColumnSize = columnSize;
}
public CSVTable(final Collection<CSVRecord> records) {
this(null, ',', (CSVRecord) null, records.toArray(new CSVRecord[records.size()]));
}
public CSVTable(final CSVRecord headers, final Collection<CSVRecord> records) {
this(null, ',', headers, records.toArray(new CSVRecord[records.size()]));
}
public CSVTable(final String[] headers, final CSVRecord... records) {
this(null, ',', headers, records.clone());
}
public CSVTable(final Charset charset, final char separator, final String[] headers, final CSVRecord[] records) {
this(charset, separator, headers == null || headers.length == 0 ? null : new CSVRecord(headers), records);
}
public int size() {
return this.records.length;
}
@Override
public int columnSize() {
return maxColumnSize;
}
@Override
public int rowSize() {
return size();
}
@Override
public String getValueAtRowAndColumn(int row, int column) {
if (this.maxColumnSize > column) {
CSVRecord record = getRecord(row);
if (record.size() > column) {
return record.getField(column);
} else {
return null;
}
} else {
throw new IndexOutOfBoundsException("Column higher than columnSize");
}
}
@Override
public CSVRecord getElementAtIndex(final int index) {
return getRecord(index);
}
public CSVRecord getRecord(final int index) {
return this.records[index];
}
public Charset getCharset() {
return this.charset;
}
public char getSeparator() {
return this.separator;
}
public CSVRecord getHeaders() {
return this.headers;
}
public String getMediaTypeString() {
return getMediaType().toString();
}
@SuppressWarnings("unchecked")
public MediaType getMediaType() {
return CSVTable.RFC4180_MEDIA_TYPE.deriveWithNewParameters(new Pair<String, String>("charset", this.charset.name()),
hasHeaders() ? CSVTable.HEADER_PRESENT : CSVTable.HEADER_ABSENT);
}
public boolean hasHeaders() {
return this.headers != null;
}
public Iterator<CSVRecord> iterator() {
return new ArrayIterator<CSVRecord>(this.records);
}
public String getField(final int recordIndex, final int fieldIndex) {
return this.records[recordIndex].getField(fieldIndex);
}
@Override
public String toString() {
try {
return append(new StringBuilder()).toString();
} catch (final IOException e) {
return "Exception in toString: " + e;
}
}
public Appendable append(final Appendable appendable) throws IOException {
return append(appendable, this.separator);
}
public Appendable append(final Appendable appendable, final char separator) throws IOException {
final CSVRecordWriter out = new CSVRecordWriter(appendable, separator);
if (hasHeaders()) {
out.write(this.headers);
}
out.write(this.records);
return appendable;
}
public Map<Sequence<String>, CSVRecord> map(final String... columnNames) {
return map(new CSVRecordSequenceTransposer(this, columnNames));
}
public Map<Sequence<String>, CSVRecord> map(final ParameterFactory<Sequence<String>, CSVRecord> keyFactory) {
final Map<Sequence<String>, CSVRecord> result = new HashMap<Sequence<String>, CSVRecord>();
for (final CSVRecord record : this.records) {
result.put(keyFactory.create(record), record);
}
return result;
}
public Map<Sequence<String>, CSVRecord> map(final int... fields) {
return map(new CSVRecordSequenceTransposer(fields));
}
@Override
public CSVDataConnection openConnection() {
return new CSVDataConnection() {
private Iterator<CSVRecord> iterator = CSVTable.this.iterator();
@Override
public Charset getCharset() {
return CSVTable.this.getCharset();
}
@Override
public boolean hasHeaders() {
return CSVTable.this.hasHeaders();
}
@Override
public CSVRecord getHeaders() {
return CSVTable.this.getHeaders();
}
@Override
public CSVRecord readNext() {
if (iterator == null) {
throw new IllegalStateException("Already closed");
}
if (this.iterator.hasNext()) {
return this.iterator.next();
} else {
return null;
}
}
@Override
public void close() {
this.iterator = null;
}
};
}
}