Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

列全体を囲み文字で囲むアノテーションとポリシーを追加 #39

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 58 additions & 4 deletions src/main/java/com/orangesignal/csv/CsvWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,31 @@ private void ensureOpen() throws IOException {
* @throws IOException 入出力エラーが発生した場合
*/
public void writeValues(final List<String> values) throws IOException {
writeValuesCore(values, null);
}

/**
* 指定された CSV トークンの値リストを書き込みます。
*
* @param values 書き込む CSV トークンの値リスト
* @param quotes 列全体を囲み文字で囲むかどうかのリスト
* @throws CsvValueException 可変項目数が禁止されている場合に項目数が一致しない場合
* @throws IOException 入出力エラーが発生した場合
* @since 2.2
*/
public void writeValues(final List<String> values, final List<Boolean> quotes) throws IOException {
writeValuesCore(values, quotes);
}

/**
* 指定された CSV トークンの値リストを書き込みます。
*
* @param values 書き込む CSV トークンの値リスト
* @param quotes 列全体を囲み文字で囲むかどうかのリスト
* @throws CsvValueException 可変項目数が禁止されている場合に項目数が一致しない場合
* @throws IOException 入出力エラーが発生した場合
*/
private void writeValuesCore(final List<String> values, List<Boolean> quotes) throws IOException {
synchronized (this) {
ensureOpen();

Expand All @@ -150,16 +175,24 @@ public void writeValues(final List<String> values) throws IOException {
final StringBuilder buf = new StringBuilder();
if (values != null) {
final int max = values.size();

if (quotes == null) {
quotes = new ArrayList<Boolean>();
for (int i = 0; i < max; i++) {
quotes.add(false);
}
}

for (int i = 0; i < max; i++) {
if (i > 0) {
buf.append(cfg.getSeparator());
}

String value = values.get(i);
boolean enclose = false; // 項目を囲み文字で囲むかどうか
if (value == null) {
// 項目値が null の場合に NULL 文字列が有効であれば NULL 文字列へ置換えます。
if (cfg.getNullString() == null) {
if (value == null || "".equals(value)) {
// 項目値が null もしくは空白の場合に NULL 文字列が有効であれば NULL 文字列へ置換えます。
if (cfg.getNullString() == null) {
continue;
}
value = cfg.getNullString();
Expand All @@ -170,6 +203,18 @@ public void writeValues(final List<String> values) throws IOException {
enclose = true;
break;

case COLUMN:
// CsvColumn の columnQuote が true の場合に列全体を囲み文字で囲みます。
if (quotes.get(i)) {
enclose = true;
} else {
// 項目値に区切り文字、囲み文字、改行文字のいずれかを含む場合は囲み文字で囲むべきと判断します。
enclose = value.indexOf(cfg.getSeparator()) != -1
|| value.indexOf(cfg.getQuote()) != -1
|| value.indexOf('\r') != -1 || value.indexOf('\n') != -1;
}
break;

case MINIMAL:
default:
// 項目値に区切り文字、囲み文字、改行文字のいずれかを含む場合は囲み文字で囲むべきと判断します。
Expand Down Expand Up @@ -262,6 +307,15 @@ private String escapeQuote(final String value) {
);
}

/**
* 区切り文字形式情報を返します。
* @return 区切り文字形式情報
* @since 2.2
*/
public CsvConfig getCfg() {
return cfg;
}

@Override
public void flush() throws IOException {
synchronized (this) {
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/com/orangesignal/csv/QuotePolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ public enum QuotePolicy {
/**
* 項目内に区切り文字、囲み文字または改行文字が含まれる場合にだけ項目を囲み文字で囲むようにします。
*/
MINIMAL;
MINIMAL,

/**
* 列全体の項目を囲み文字で囲むようにします。<p>
* {@link com.orangesignal.csv.annotation.CsvColumn#columnQuote} を true にして使用して下さい。
* @since 2.2
*/
COLUMN;

// NON_NUMERIC

Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/orangesignal/csv/annotation/CsvColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,13 @@
*/
String defaultValue() default "";

/**
* 列全体に囲み文字を出力するかどうかを返します。<p>
* {@link com.orangesignal.csv.QuotePolicy#COLUMN} と組み合わせて使います。
*
* @return 列全体に囲み文字を出力するかどうか
* @since 2.2
*/
boolean columnQuote() default false;

}
31 changes: 26 additions & 5 deletions src/main/java/com/orangesignal/csv/io/CsvEntityWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -271,17 +271,18 @@ public boolean write(final T entity) throws IOException {
return true;
}

final List<String> values = toValues(entity);
if (template.isAccept(columnNames, values)) {
final Tuple2<List<String>, List<Boolean>> values = toValues(entity);
if (template.isAccept(columnNames, values.value1)) {
return false;
}
writer.writeValues(values);
writer.writeValues(values.value1, values.value2);
return true;
}
}

private List<String> toValues(final T entity) throws IOException {
private Tuple2<List<String>, List<Boolean>> toValues(final T entity) throws IOException {
final String[] values = new String[columnCount];
final Boolean[] quotes = new Boolean[columnCount];
for (final Field field : entity.getClass().getDeclaredFields()) {
final CsvColumns columns = field.getAnnotation(CsvColumns.class);
if (columns != null) {
Expand Down Expand Up @@ -313,6 +314,12 @@ private List<String> toValues(final T entity) throws IOException {
if (values[pos] == null && column.required()) {
throw new CsvColumnException(String.format("%s must not be null", columnNames.get(pos)), entity);
}
if (values[pos] != null && values[pos] != "" && column.columnQuote()) {
// 列全体を囲み文字で囲むためにマークします。
quotes[pos] = true;
} else {
quotes[pos] = false;
}
}
}
final CsvColumn column = field.getAnnotation(CsvColumn.class);
Expand All @@ -332,9 +339,15 @@ private List<String> toValues(final T entity) throws IOException {
if (values[pos] == null && column.required()) {
throw new CsvColumnException(String.format("%s must not be null", columnNames.get(pos)), entity);
}
if (values[pos] != null && values[pos] != "" && column.columnQuote()) {
// 列全体を囲み文字で囲むためにマークします。
quotes[pos] = true;
} else {
quotes[pos] = false;
}
}
}
return Arrays.asList(values);
return new Tuple2<List<String>, List<Boolean>>(Arrays.asList(values), Arrays.asList(quotes));
}

// ------------------------------------------------------------------------
Expand All @@ -360,4 +373,12 @@ public boolean isDisableWriteHeader() {
return disableWriteHeader;
}

private class Tuple2<T1, T2> {
private T1 value1 = null;
private T2 value2 = null;
public Tuple2(T1 asList, T2 asList2) {
this.value1 = asList;
this.value2 = asList2;
}
}
}
23 changes: 23 additions & 0 deletions src/test/java/com/orangesignal/csv/CsvWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,29 @@ public void testWriteUtf8bomToStringWriter() throws IOException {
}
*/

@Test
public void testWriteNoNQuote() throws IOException {
final CsvConfig cfg = new CsvConfig(',', '"', '\\');
cfg.setEscapeDisabled(false);
cfg.setQuoteDisabled(false);
cfg.setQuotePolicy(QuotePolicy.COLUMN);
cfg.setNullString("NULL");
cfg.setLineSeparator("\r\n");

final StringWriter sw = new StringWriter();
final CsvWriter writer = new CsvWriter(sw, cfg);
try {
// Act
writer.writeValues(Arrays.asList(new String[]{ "aaa", "b,\"b", "ccc" }));
writer.writeValues(Arrays.asList(new String[]{ "zzz", "", null }));
writer.flush();
// Assert
assertThat(sw.getBuffer().toString(), is("aaa,\"b,\\\"b\",ccc\r\nzzz,NULL,NULL\r\n"));
} finally {
writer.close();
}
}

@Test
public void testWriteValuesCsvValueException() throws IOException {
final CsvConfig cfg = new CsvConfig();
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/com/orangesignal/csv/QuotePolicyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void testValues() {
final QuotePolicy[] values = QuotePolicy.values();
for (final QuotePolicy value : values) {
switch (value) {
case ALL: case MINIMAL:
case ALL: case MINIMAL: case COLUMN:
break;
default:
fail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@
import com.orangesignal.csv.CsvConfig;
import com.orangesignal.csv.CsvReader;
import com.orangesignal.csv.CsvWriter;
import com.orangesignal.csv.QuotePolicy;
import com.orangesignal.csv.entity.Price;
import com.orangesignal.csv.entity.Price2;
import com.orangesignal.csv.filters.SimpleBeanFilter;
import com.orangesignal.csv.filters.SimpleCsvNamedValueFilter;
import com.orangesignal.csv.model.SampleBean;
import com.orangesignal.csv.model.SampleQuote;

/**
* {@link CsvEntityListHandler} クラスの単体テストです。
Expand Down Expand Up @@ -217,4 +219,32 @@ public void testSaveFilter() throws Exception {
assertThat(sw.getBuffer().toString(), is("シンボル,名称,価格,出来高,日付,時刻\r\nGCV09,COMEX 金 2009年10月限,1\\,078,11,2008/10/06,12:00:00\r\n"));
}

@Test
public void testSaveQuote() throws Exception {
CsvConfig cfg = new CsvConfig(',');
cfg.setEscapeDisabled(false);
cfg.setNullString("NULL");
cfg.setIgnoreTrailingWhitespaces(true);
cfg.setIgnoreLeadingWhitespaces(true);
cfg.setIgnoreEmptyLines(true);
cfg.setLineSeparator(Constants.CRLF);
cfg.setQuoteDisabled(false);
cfg.setQuotePolicy(QuotePolicy.COLUMN);

final List<SampleQuote> list = new ArrayList<SampleQuote>();
list.add(new SampleQuote(1, "aaa"));
list.add(new SampleQuote(2, ""));
list.add(new SampleQuote(3, null));
list.add(new SampleQuote(4, "d\"d\"d"));

final StringWriter sw = new StringWriter();
final CsvWriter writer = new CsvWriter(sw, cfg);
try {
new CsvEntityListHandler<SampleQuote>(SampleQuote.class).save(list, writer);
} finally {
writer.close();
}
assertThat(sw.getBuffer().toString(), is("No.,ラベル\r\n1,\"aaa\"\r\n2,NULL\r\n3,NULL\r\n4,\"d\\\"d\\\"d\"\r\n"));
}

}
53 changes: 52 additions & 1 deletion src/test/java/com/orangesignal/csv/io/CsvEntityWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@
import com.orangesignal.csv.Constants;
import com.orangesignal.csv.CsvConfig;
import com.orangesignal.csv.CsvWriter;
import com.orangesignal.csv.QuotePolicy;
import com.orangesignal.csv.bean.CsvEntityTemplate;
import com.orangesignal.csv.entity.DefaultValuePrice;
import com.orangesignal.csv.entity.Price;
import com.orangesignal.csv.entity.Travel;
import com.orangesignal.csv.entity.WritableEntity;
import com.orangesignal.csv.entity.WritableNoHeaderEntity;
import com.orangesignal.csv.filters.SimpleCsvNamedValueFilter;
import com.orangesignal.csv.model.SampleQuote;

/**
* {@link CsvEntityWriter} クラスの単体テストです。
Expand Down Expand Up @@ -590,6 +591,56 @@ public void testFilter() throws Exception {
assertThat(sw.getBuffer().toString(), is("シンボル,名称,価格,出来高,日付,時刻\r\nGCV09,COMEX 金 2009年10月限,1\\,078,11,2008/10/06,12:00:00\r\n"));
}

/**
*
public void testWrite() throws Exception {
final StringWriter sw = new StringWriter();
final CsvEntityWriter<Price> writer = CsvEntityWriter.newInstance(
new CsvWriter(sw, cfg),
Price.class
);
try {
final DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));

writer.write(new Price("AAAA", "aaa", 10000, 10, df.parse("2008/10/28 10:24:00")));
writer.write(new Price("BBBB", "bbb", null, 0, null));
writer.write(new Price("CCCC", "ccc", 20000, 100, df.parse("2008/10/26 14:20:10")));
} finally {
writer.close();
}
assertThat(sw.getBuffer().toString(), is("シンボル,名称,価格,出来高,日付,時刻\r\nAAAA,aaa,10\\,000,10,2008/10/28,10:24:00\r\nBBBB,bbb,NULL,0,NULL,NULL\r\nCCCC,ccc,20\\,000,100,2008/10/26,14:20:10\r\n"));
} *
*/

@Test
public void testWriteQuote() throws Exception {
CsvConfig cfg = new CsvConfig(',');
cfg.setEscapeDisabled(false);
cfg.setNullString("NULL");
cfg.setIgnoreTrailingWhitespaces(true);
cfg.setIgnoreLeadingWhitespaces(true);
cfg.setIgnoreEmptyLines(true);
cfg.setLineSeparator(Constants.CRLF);
cfg.setQuoteDisabled(false);
cfg.setQuotePolicy(QuotePolicy.COLUMN);

final StringWriter sw = new StringWriter();
final CsvEntityWriter<SampleQuote> writer = CsvEntityWriter.newInstance(
new CsvWriter(sw, cfg),
SampleQuote.class
);
try {
writer.write(new SampleQuote(1, "aaa"));
writer.write(new SampleQuote(2, ""));
writer.write(new SampleQuote(3, null));
writer.write(new SampleQuote(4, "d\"d\"d"));
} finally {
writer.close();
}
assertThat(sw.getBuffer().toString(), is("No.,ラベル\r\n1,\"aaa\"\r\n2,NULL\r\n3,NULL\r\n4,\"d\\\"d\\\"d\"\r\n"));
}

// ------------------------------------------------------------------------
// getter / setter

Expand Down
38 changes: 38 additions & 0 deletions src/test/java/com/orangesignal/csv/model/SampleQuote.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2013 the original author or authors.
*
* 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.orangesignal.csv.model;

import com.orangesignal.csv.annotation.CsvColumn;
import com.orangesignal.csv.annotation.CsvEntity;

/**
* @author Koichi Kobayashi
*/
@CsvEntity
public final class SampleQuote {

@CsvColumn(position = 0, name = "No.")
public Integer no;

@CsvColumn(position = 1, name = "ラベル", columnQuote = true)
public String label;

public SampleQuote(Integer no, String label) {
this.no = no;
this.label = label;
}
}