예전에 작성해 놓은 Analyzer와 Filter 포스트는 1.4.X 버젼대의 소스입니다.
예전에는 Analyzer의 형식이
return new GSKoreanSynonymFilter(
new GSKoreanStopWordFilter(
new GSKoreanSeperatorWordFilter(
new GSKoreanSeperatorNameWordFilter(
new GSKoreanTokenizer(content){} //GSKoreanTokenizer
) // GSKoreanSeperatorNameWordFilter
) //GSKoreanSeperatorWordFilter
),
engine);
}
이런 식이었는데 새버젼 소스를 받아서 열어보니 많이 바뀌었습니다.
아직 기존의 Analyzer를 새 버젼에 맞춰서 변경시키지는 못 했는데, 일단 어느 부분이 바뀌었는지 간략하게나마
알아봤으면 좋겠습니다.
간단한 모양을 하고 있는 StopAnalyzer를 열어보았습니다.
return new StopFilter(new LowerCaseTokenizer(reader), stopWords);
}
기존에 사용하던 방식의 tokenStream 메서드도 있습니다. 버젼이 올라갔다고 이걸 그냥 없애버리면 아무래도 버젼을 업그레이드 하기 어렵겠죠.. ^^
그리고 새로운 메서드와 내부클래스가 눈에 들어옵니다.
Tokenizer source;
TokenStream result;
};
public TokenStream reusableTokenStream(String fieldName, Reader reader) throws IOException {
SavedStreams streams = (SavedStreams) getPreviousTokenStream();
if (streams == null) {
streams = new SavedStreams();
streams.source = new LowerCaseTokenizer(reader);
streams.result = new StopFilter(streams.source, stopWords);
setPreviousTokenStream(streams);
} else
streams.source.reset(reader);
return streams.result;
}
reusableTokenStream 이 메서드인데요, 그냥 한번 훑어보니 뭔가 tokenStream을 저장해놓고 그것을 재 사용하는 방식을 쓰는 것 같습니다.
일단은 최상위 추상 클래스인 Analyzer에 정의 되어 있는 getPreviousTokenStream(); 메서드를 호출하여
뭔가 예전의 TokenStream을 가져옵니다. 위 메서드는 아래와 같이 되어 있습니다.
private ThreadLocal tokenStreams = new ThreadLocal();
protected Object getPreviousTokenStream() {
return tokenStreams.get();
}
protected void setPreviousTokenStream(Object obj) {
tokenStreams.set(obj);
}
ThreadLocal 이라는 녀석이 저의 짧은 지식으로는 수행되는 쓰레드별로 별도의 저장소를 만들어
쓰레드당 고유의 저장소에서 객첼르 저장하고 사용하는 방식을 제공하는 그런거로 알고 있는데 일단 이것은 좀 더 공부가 필요 할 듯 하다. ㅠㅠ
아무튼, 싱글턴 이라는 것을 기억해두고 계속 진행해 보면, 그렇게해서 저장되어 있는 (공유되고 있는) SavedStream이 없을 경우에는 새로운 SavedStream을 생성 시킵니다.
그리고 tokenizer를 SavedStreams.source에 set하고
SavedStreams.result 에 TokenStream (FilterClass들) 들을 set 한 후 1.4.X 버젼과 비슷한 방식으로 사용을 하게 됩니다. 그리고 마지막에는
setPreviousTokenStream(streams);
메서드를 통해 생성했던 SavedStreams 객체를 ThreadLocal에 넣어놓습니다.
그리고, 만약 streams != null 이면
streams.source.reset(reader); 를 통해서 contents를 reset하고
TokenStream을 리턴합니다.
쓰레드가 살아있는 동안 TokenStream을 새로 생성 할 필요없이..
다시 가져와 사용 할 수 있다.. 라는 정도로 이해하면 무리가 없을지 잘 모르겠습니다. ^^
파싱하려는 내용이 변경되어도 streams.source.reset(reader); 을 통해서 contents를 reset해주고 있기
때문에 문제는 없을 것 같구요.
그 다음에 살펴 볼 부분이 next(final Token token) 메서드입니다.
2.4.0으로 넘어오면서 final 이 붙어버렸습니다.
일단 쉬운 비교를 위해서 예전과 소스를 비교해보겠습니다.
예전:
// return the first non-stop word found
int skippedPositions = 0;
while((result = input.next(result)) != null) {
if (!stopWords.contains(result.termBuffer(), 0, result.termLength)) {
if (enablePositionIncrements) {
result.setPositionIncrement(result.getPositionIncrement() + skippedPositions);
}
return result;
}
skippedPositions += result.getPositionIncrement();
}
// reached EOS -- return null
return null;
}
assert reusableToken != null;
// return the first non-stop word found
int skippedPositions = 0;
for (Token nextToken = input.next(reusableToken); nextToken != null; nextToken = input.next(reusableToken)) {
if (!stopWords.contains(nextToken.termBuffer(), 0, nextToken.termLength())) {
if (enablePositionIncrements) {
nextToken.setPositionIncrement(nextToken.getPositionIncrement() + skippedPositions);
}
return nextToken;
}
skippedPositions += nextToken.getPositionIncrement();
}
// reached EOS -- return null
return null;
}
bold 처리 된 부분이 바뀐 부분인데요
파라메터가 final로 변경되면서 그 값을 변경하지 못 하기 때문에
내부적으로 nextToken이라는 로컬 객체를 하나 더 생성하여 사용하고 있습니다. reusableToken은 그냥 다음 필터클래스나 토크나이저 클래스에 넘어갈 뿐입니다.
한가지 궁금한 것은.. reusableToken이라는 것이 어디에 사용되는가 하는 건데요...;;;
사실 각각의 필터에서 조작하고 작업하는 대상이 되는 Token은 nextToken일 것인데..
reusableToken은 무엇 때문에 final로 선언되어 파라메터로 넘어가고 있는건지 아직은 잘 모르겠습니다..
나중에는 Tokenizer에서 추출된 Term과 포지션 정보를 set해서 넘겨주는데 사용하고 로직을 보면 새로운 Token 객체의 생성없이 계속 처음에 생성된 그 Token을 재사용하면서 쓰고 있긴한데..
그외 다른 목적에 대해서
나중에 알게 되면 다시 정리하거나.. 혹시 알고 계신분이 있으시면
댓글로라도 답변을 좀 부탁드리고 싶네요. ^^