TSVを総称型指定のクラスで読込み

昔作成して公開している CSVの読み書き、
GitHub - yipuran/yipuran-csv: Java CSV read and write
の中の
csvobject · yipuran/yipuran-csv Wiki · GitHub
から、CsvObject を継承して、TSV を総称型指定のクラスで読込む。
変更点は、

  • org.yipuran.csv4j.CSVReader からデリミタ指定をカンマ区切りからタブ文字に変えるだけ
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Stream;
import org.yipuran.csv.BOMfunction;
import org.yipuran.csv.CsvObject;
import org.yipuran.csv4j.CSVReader;
import org.yipuran.csv4j.ProcessingException;
/**
 * TSV を総称型指定のクラスで読込み
 */
public class TsvObject<T> extends CsvObject<T>{
    private static final char DELIMITER = '\t';
    private boolean blankIsNull = false;
    private Class<T> cls;
    private List<Class<?>> typelist;
    private List<Method> methodlist;
    private DateTimeFormatter dateFormatter =  DateTimeFormatter.ISO_LOCAL_DATE;//  DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private DateTimeFormatter localdatetimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    private DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_LOCAL_TIME;
    private Function<String, Boolean> booleanReader = s -> Boolean.parseBoolean(s.toLowerCase());

    /**
     * コンストラクタ.
     * @param t 可変長引数、指定しても使用はされない
     */
    @SuppressWarnings("unchecked")
    public TsvObject(T...t) {
        cls = (Class<T>)t.getClass().getComponentType();
    }
    /**
     * ブランク→null指定.
     * @param true=ブランク、",," は、null として読み込む。
     */
    public void setBlanknull(boolean blankIsNull) {
        this.blankIsNull = blankIsNull;
    }

    /**
     * 総称型指定TSV読込み実行.
     * @param inReader InputStreamReader
     * @param biconsumer コンテンツ行BiConsumer、TSV行読込みカウント(1始まり)と総称型Tのオブジェクト
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public void read(InputStreamReader inReader, BiConsumer<Integer, T> biconsumer) throws IOException, ProcessingException{
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER, getComment(),
                                         blankIsNull);
        try{
            typelist = new ArrayList<>();
            methodlist = new ArrayList<>();
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (isHasHeader() && lineCount==0){
                        String rep = fields.get(0);
                        if (BOMfunction.match(rep)) {
                            fields.remove(0);
                            fields.add(0, BOMfunction.chop(rep));
                        }
                        for(String f:fields){
                            try{
                                Class<?> c =  cls.getDeclaredField(f).getType();
                                Method m = cls.getDeclaredMethod("set" + f.substring(0, 1).toUpperCase() + f.substring(1), c);
                                typelist.add(c);
                                methodlist.add(m);
                            }catch(NoSuchFieldException | NoSuchMethodException e){
                                typelist.add(null);
                                methodlist.add(null);
                            }
                        }
                    }else{
                        T t = cls.getConstructor().newInstance();
                        int i=0;
                        for(String f:fields){
                            Method m = methodlist.get(i);
                            if (m != null){
                                setValue(m, t, i, f);
                            }
                            i++;
                        }
                        biconsumer.accept(lineCount, t);
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
    }
    /**
     * 総称型指定TSV読込み結果Stream生成.
     * @param inReader InputStreamReader
     * @return 総称型 T のStream
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public Stream<T> read(InputStreamReader inReader) throws IOException, ProcessingException{
        Stream.Builder<T> builder = Stream.builder();
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            typelist = new ArrayList<>();
            methodlist = new ArrayList<>();
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (isHasHeader() && lineCount==0){
                        String rep = fields.get(0);
                        if (BOMfunction.match(rep)) {
                            fields.remove(0);
                            fields.add(0, BOMfunction.chop(rep));
                        }
                        for(String f:fields){
                            try{
                                Class<?> c =  cls.getDeclaredField(f).getType();
                                Method m = cls.getDeclaredMethod("set" + f.substring(0, 1).toUpperCase() + f.substring(1), c);
                                typelist.add(c);
                                methodlist.add(m);
                            }catch(NoSuchFieldException | NoSuchMethodException e){
                                typelist.add(null);
                                methodlist.add(null);
                            }
                        }
                    }else{
                        T t = cls.getConstructor().newInstance();
                        int i=0;
                        for(String f:fields){
                            Method m = methodlist.get(i);
                            if (m != null){
                                setValue(m, t, i, f);
                            }
                            i++;
                        }
                        builder.add(t);
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
        return builder.build();
    }
    /**
     * 総称型指定TSV読込み実行(コンバーター指定).
     * @param inReader InputStreamReader
     * @param converter TSV1行分の文字列リストから、総称型Tを生成取得するコンバーター
     * @param biconsumer コンテンツ行BiConsumer、TSV行読込みカウント(1始まり)と総称型Tのオブジェクト
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public void read(InputStreamReader inReader, Function<List<String>, T> converter,  BiConsumer<Integer, T> biconsumer)
    throws IOException, ProcessingException{
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            methodlist = new ArrayList<>();
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (!isHasHeader() || lineCount > 0){
                        biconsumer.accept(lineCount, converter.apply(fields));
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
    }
    /**
     * 総称型指定TSV読込み結果Stream生成(コンバーター指定).
     * @param inReader InputStreamReader
     * @param converter TSV1行分の文字列リストから、総称型Tを生成取得するコンバーター
     * @return 総称型 T のStream
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public Stream<T> read(InputStreamReader inReader, Function<List<String>, T> converter)
    throws IOException, ProcessingException{
        Stream.Builder<T> builder = Stream.builder();
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (!isHasHeader() || lineCount > 0){
                        builder.add(converter.apply(fields));
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
        return builder.build();
    }

    private void setValue(Method m, Object obj, int n, String str) throws NoSuchMethodException, SecurityException
    , IllegalAccessException, IllegalArgumentException {
        try{
            if (typelist.get(n).isPrimitive()) {
                if (blankIsNull && str==null) return;
                Class<?> c = typelist.get(n);
                if (c.equals(int.class)) {
                    m.invoke(obj, Integer.parseInt(str));
                }else if(c.equals(long.class)) {
                    m.invoke(obj, Long.parseLong(str));
                }else if(c.equals(double.class)) {
                    m.invoke(obj, Double.parseDouble(str));
                }else if(c.equals(short.class)) {
                    m.invoke(obj, Short.parseShort(str));
                }else if(c.equals(float.class)) {
                    m.invoke(obj, Float.parseFloat(str));
                }else if(c.equals(boolean.class)) {
                    m.invoke(obj, booleanReader.apply(str));
                }
            }else{
                if (typelist.get(n).equals(String.class)) {
                    m.invoke(obj, str);
                }else if(typelist.get(n).equals(Boolean.class)) {
                    m.invoke(obj, booleanReader.apply(str));
                }else if(typelist.get(n).equals(LocalDate.class)) {
                    m.invoke(obj, LocalDate.parse(str, dateFormatter));
                }else if(typelist.get(n).equals(LocalDateTime.class)) {
                    m.invoke(obj, LocalDateTime.parse(str, localdatetimeFormatter));
                }else if(typelist.get(n).equals(LocalTime.class)) {
                    m.invoke(obj, LocalTime.parse(str, timeFormatter));
                }else{
                    Method getter = typelist.get(n).getDeclaredMethod("valueOf", String.class);
                    m.invoke(obj, getter.invoke(null, str));
                }
            }
        }catch(InvocationTargetException e){
        }
    }
}

(使用例)

TSVの1行を任意クラスFoo として読み込む。ただしヘッダ1行目がフィールド名である。

TsvObject<Foo> co = new TsvObject<>();
co.setBlanknull(true);
co.read(new InputStreamReader(in, StandardCharsets.UTF_8), (i, f)->{
    // i=読み出し行番号 1 始まり、
    // f=Fooインスタンス(1行 → Foo 変換結果)
    System.out.println("["+i+"]# "+f.toString());
});
in.close();
TsvObject<Foo> co = new TsvObject<>();
co.setBlanknull(true);
co.read(new InputStreamReader(in, StandardCharsets.UTF_8)).forEach(f->{
    System.out.println(f.toString());
});
in.close();