/*
* Copyright 2013-2015 Eric F. Savage, code@efsavage.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ajah.flatfile;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.java.Log;
import com.ajah.util.StringUtils;
/**
* @author <a href="http://efsavage.com">Eric F. Savage</a>, <a
* href="mailto:code@efsavage.com">code@efsavage.com</a>.
*/
@Log
public class FlatFileWriter implements Closeable {
private final OutputStreamWriter writer;
private final List<FlatFileColumn> columns = new ArrayList<>();
private final Map<String, FlatFileColumn> map = new HashMap<>();
private FlatFileRow row = null;
@Getter
private final FlatFileFormat format;
@Getter
private final File file;
@Getter
@Setter
private boolean flushEveryLine;
private int lineNumber;
@Getter
@Setter
private boolean columnsLocked;
@Getter
@Setter
private boolean trimContents = true;
public FlatFileWriter(final FlatFileFormat format, final File file) throws IOException {
this.format = format;
this.file = file;
this.writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
}
public void addColumn(final String name) {
addColumn(name, false);
}
public void addColumn(final String name, final boolean required) {
if (this.columnsLocked) {
throw new IllegalArgumentException("Columns are locked");
}
if (this.map.get(name) != null) {
throw new IllegalArgumentException("Duplicate column name " + name);
}
final FlatFileColumn column = new FlatFileColumn(name, null, required);
this.columns.add(column);
this.map.put(column.getName(), column);
}
public void addColumn(final String name, final String defaultValue) {
if (this.columnsLocked) {
throw new IllegalArgumentException("Columns are locked");
}
if (this.map.get(name) != null) {
throw new IllegalArgumentException("Duplicate column name " + name);
}
final FlatFileColumn column = new FlatFileColumn(name, defaultValue, false);
this.columns.add(column);
this.map.put(column.getName(), column);
}
@Override
public void close() throws IOException {
if (this.row == null) {
writeHeader();
} else {
writeRow();
}
this.writer.close();
}
public FlatFileRow newRow() throws IOException {
if (this.row == null) {
writeHeader();
} else {
writeRow();
}
this.row = new FlatFileRow(this.map, this, this.lineNumber++);
return this.row;
}
private void write(final String value, final boolean first) throws IOException {
switch (this.format) {
case CSV:
if (!first) {
this.writer.write(",");
}
if (value.contains(",") || value.contains("\"") || value.contains("\r") || value.contains("\n")) {
this.writer.write("\"" + value.replaceAll("\"", "\"\"") + "\"");
} else {
this.writer.write(value);
}
break;
case TAB:
if (value != null && value.contains("\t")) {
log.log(Level.WARNING, "Writing data containing tabs to a tab-delimited file");
}
if (!first) {
this.writer.write("\t");
}
this.writer.write(value);
break;
default:
throw new IllegalArgumentException("Unsupported format: " + this.format);
}
}
public void writeHeader() throws IOException {
boolean first = true;
for (final FlatFileColumn column : this.columns) {
write(column.getName(), first);
if (first) {
first = false;
}
}
this.writer.write("\r\n");
if (this.flushEveryLine) {
this.writer.flush();
}
}
private void writeRow() throws IOException {
boolean first = true;
for (final FlatFileColumn column : this.columns) {
final String value = this.row.get(column.getName());
if (column.isRequired() && StringUtils.isBlank(value)) {
throw new IllegalArgumentException("Column is required: " + column.getName());
}
write(value, first);
if (first) {
first = false;
}
}
this.writer.write("\r\n");
if (this.flushEveryLine) {
this.writer.flush();
}
this.row = null;
}
/**
* This is for injecting non-standard content such as an extra header or
* footer row.
*
* @param string
* The string to write as-is. Should include any line terminators
* if they are necessary.
* @throws IOException
* If the string could not be written.
*/
public void writeString(final String string) throws IOException {
this.writer.write(string);
}
}