InternalBuffer
1.
relocateAndLoad() 메서드에서 발생하는 IndexOutOfBoundsException 예외 발생
원인 - read(char[] buf, int off, int len) 에서 len 사이즈 고정으로 인하여 발생한 문제.
해결 - read(buf, off, len - off); 변경
2.
extendAndLoad() 무한 루프 문제 발생
원인 - relocateAndLoad() 실행시 begin가 잘못 갱신
기존 원인 코드 -
private void relocateAndLoad() {
final int copyLength = limit - pos;
System.arraycopy(buf, pos, buf, 0, copyLength);
int cnt = read(buf, copyLength);
state = cnt == -1 ? BufferState.LAST_LINE : BufferState.PROGRESSING;
pos = 0;
begin = limit; // limit갱신시 끝지점 즉 8192로 갱신되어 버림
limit = state == BufferState.LAST_LINE ? limit : copyLength + cnt;
}
Java
복사
해결 -
private void relocateAndLoad() {
final int copyLength = limit - pos;
System.arraycopy(buf, pos, buf, 0, copyLength);
int cnt = read(buf, copyLength);
state = cnt == -1 ? BufferState.LAST_LINE : BufferState.PROGRESSING;
pos = 0;
begin = copyLength; // copyLength를 begin으로 갱신
limit = state == BufferState.LAST_LINE ? limit : copyLength + cnt;
}
Java
복사
3.
isBeginAtLimit() 메서드 변경 → notBeginAtLimit()
이유 - 사용시 !isBeginAtLimit(); 형태로 사용함이 직관적이지 못하다고 생각함. notXX가 좀 더 직관적이라고 판단.
RecordParser
1.
normalParse() 제작
@Override
public List<Record> parse(){
while (buffer.fill()){
// Optional<Function<Object, Object>> parse =
// InternalBuffer2.ifLastRowOrElse(buffer, this :: lastRowParse, this :: normalParse);
// if (parse.isEmpty()){
// break;
// }
// parse.get().apply(buffer);
/**
TODO 위의 코드 쪽으로 추가 예정
*/
normalParse(buffer); // TODO Test 사용 용도
}
return List.of();
}
private List<Record> normalParse(InternalBuffer2 buffer){
boolean inQuote = false;
int matchIndex = 0;
while (buffer.notBeginAtLimit()){
char c = buffer.getCharAtBegin();
if (c == csvConfig.getQuote()) {
inQuote = !inQuote;
}
if (!inQuote){
matchIndex = incrementIfMatch(csvConfig.getLineDelimiter(), c, matchIndex);
if (matchIndex == csvConfig.getLineDelimiterLength()){
parse(matchIndex);
break;
}
}
buffer.getIncrementBegin();
}
return null; //TODO 반환 값은 fieldParser 제작 후
}
private void parse(int matchIndex){ // TODO parse라는 로직 따로 분리
int length = (matchIndex == 1 ? buffer.getBegin() + matchIndex : buffer.getBegin() + matchIndex - 1) - buffer.getPos();
System.out.println(new StringBuilder().append(buffer.getBuf(), buffer.getPos(), length));
buffer.setPos(buffer.getIncrementBegin());
}
Java
복사
최종 결과 코드
package parser3;
import lombok.AccessLevel;
import lombok.Getter;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.util.Optional;
import java.util.function.Function;
@Getter
public class InternalBuffer2 {
@Getter(AccessLevel.NONE)
private final Reader reader;
private static final int DEFAULT_BUFFER_SIZE = 8192;
private static final int READ_SIZE = DEFAULT_BUFFER_SIZE;
private BufferState state = BufferState.INITIAL;
private char[] buf = new char[DEFAULT_BUFFER_SIZE];
private int pos, begin, limit;
private enum BufferState {
INITIAL,
PROGRESSING,
EXTEND,
RELOCATE,
LAST_LINE;
}
public InternalBuffer2(Reader reader) {
this.reader = reader;
}
/**
* TODO isLastLine 추가
* RecordParser내에서 마지막 줄 여부 판단을 위해서 필요함
* <p>
* 상태 전이 [] 반복, () 선택, {} 종료
* INITIAL -> [PROGRESSING <-> (EXTEND | RELOCATE)] -> LAST_LINE{종료}
*/
boolean fill() {
while (true) {
switch (state) {
case INITIAL:
int cnt = read(buf, pos);
state = BufferState.PROGRESSING;
if (cnt == -1){
return false;
}
limit = cnt;
return true;
case PROGRESSING:
/**
* if -> begin >= limit 일 경우 즉, 커서가 끝까지 갔는데 아무 변화 없을때
* state = (pos == 0) ? EXTEND : RELOCATE;
* continue;
* retrun true;
*/
if (begin >= limit) {
state = pos == 0 ? BufferState.EXTEND : BufferState.RELOCATE;
continue;
}
return true;
case EXTEND:
/**
* 확장 및 read
* extend() 확장
* cnt = read(begin) -> READ_SIZE 만큼 읽기
* if -> cnt == -1 LAST_LINE
* else -> PROGRESSING and limit += cnt
* continue;
*/
extendAndLoad();
return true;
case RELOCATE:
/**
* 재배치 및 read
* relocate()
* begin = limit - pos;
* pos = 0;
* cnt = read(begin) // READ_SIZE 만큼 데이터 로드
* if -> cnt == -1 LAST_LINE
* else -> PROGRESSING
*/
relocateAndLoad();
return true;
case LAST_LINE: //TODO 라스트 삭제 예정
return false;
}
}
}
static <T, R> Optional<Function<T, R>> ifLastRowOrElse(InternalBuffer2 buffer,
Function<T, R> action,
Function<T, R> elseAction) {
if (buffer.getPos() == buffer.getLimit()){
return Optional.empty();
}
boolean isLastLine = buffer.getState() == BufferState.LAST_LINE && buffer.getPos() < buffer.getLimit();
return Optional.of(isLastLine ? action : elseAction);
}
char getCharAtBegin(){
return buf[begin];
}
int getIncrementBegin(){
return begin++;
}
void setPos(int newPos){
this.pos = newPos;
}
boolean notBeginAtLimit(){
return begin < limit;
}
private int read(char[] buf, int off) {
int read = 0;
try {
read = reader.read(buf, off, READ_SIZE - off);
return read;
} catch (IOException e) {
System.out.println(read);
throw new UncheckedIOException(e);
}
}
private void extendAndLoad() {
// TODO max size 예외처리
char[] newBuf = new char[buf.length * 2];
System.arraycopy(buf, 0, newBuf, 0, buf.length);
buf = newBuf;
int cnt = read(buf, begin);
state = cnt == -1 ? BufferState.LAST_LINE : BufferState.PROGRESSING;
limit = state == BufferState.LAST_LINE ? limit : limit + cnt;
}
private void relocateAndLoad() {
final int copyLength = limit - pos;
System.arraycopy(buf, pos, buf, 0, copyLength);
int cnt = read(buf, copyLength);
state = cnt == -1 ? BufferState.LAST_LINE : BufferState.PROGRESSING;
pos = 0;
begin = copyLength;
limit = state == BufferState.LAST_LINE ? limit : copyLength + cnt;
}
}
Java
복사
package parser3;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
public class RecordParser implements CSVParser<List<Record>> {
private final InternalBuffer2 buffer;
private final CSVConfig csvConfig;
// TODO 차후 추가 private final CSVParser<List<String>> fieldParser;
private int skipLine;
public RecordParser(Reader reader, int bufferCapacity ,CSVConfig csvConfig) {
if (bufferCapacity <= 0) throw new IllegalArgumentException("Buffer capacity cannot be less than 1.");
this.buffer = new InternalBuffer2(reader);
this.csvConfig = csvConfig;
// TODO 차후 추가 this.fieldParser = new FieldParser(buffer, csvConfig);
this.skipLine = csvConfig.getSkipLine();
}
@Override
public List<Record> parse(){
while (buffer.fill()){
// Optional<Function<Object, Object>> parse =
// InternalBuffer2.ifLastRowOrElse(buffer, this :: lastRowParse, this :: normalParse);
// if (parse.isEmpty()){
// break;
// }
// parse.get().apply(buffer);
normalParse(buffer);
}
return List.of();
}
private List<Record> normalParse(InternalBuffer2 buffer){
boolean inQuote = false;
int matchIndex = 0;
while (buffer.notBeginAtLimit()){
char c = buffer.getCharAtBegin();
if (c == csvConfig.getQuote()) {
inQuote = !inQuote;
}
if (!inQuote){
matchIndex = incrementIfMatch(csvConfig.getLineDelimiter(), c, matchIndex);
if (matchIndex == csvConfig.getLineDelimiterLength()){
parse(matchIndex);
break;
}
}
buffer.getIncrementBegin();
}
return null; //TODO 반환 값은 fieldParser 제작 후
}
private void parse(int matchIndex){ // TODO parse라는 로직 따로 분리
int length = (matchIndex == 1 ? buffer.getBegin() + matchIndex : buffer.getBegin() + matchIndex - 1) - buffer.getPos();
System.out.println(new StringBuilder().append(buffer.getBuf(), buffer.getPos(), length));
buffer.setPos(buffer.getIncrementBegin());
}
/**
* lastRow의 경우는 pos 부터 limit까지 넘겨버리면 됨
* 어차피 row 단위로 하기때문에 row가 존재함은 무조건 보장됨
*
* normalParse -> return List<Record>
* boolean inQuote = false;
* int matchIndex = 0;
*
* -> while 조건
* char c = getCharAtBegin();
* if -> csvConfig.getQoute() == c
* inQuote = !inQuote
*
* if -> !inQuote && mathcIndex == 1
* fieldParser -> 넘김
* matchIndex = 0; 초기화
* buffer.newPos(buffer.getIncrementBegin());
* break;
* buffer.getIncrementBegin();
*
*/
@Override
public boolean canParse() {
if (skipLine > 0){
skipLine--;
return false;
}
return true;
}
}
Java
복사