앞에서 AttributeSource에 Attribute들 (term, offset, position, type 등) set 해주면
그 뒤에 오는 Filter나 Tokenizer가 해당 Attribute를 사용 할 수 있다고 했습니다.
각각의 Filter와 Tokenizer는 모두 TokenStream을 상속하고 있고 이 TokenStream은
AttributeSource를 상속받고 있습니다.

이 Filter와 Tokenizer가 서로 데코레이터 패턴으로 물려있기 때문에
앞에서 set한 결과를 뒤에 따라오는 Filter와 Tokenizer가 사용 할 수 있는 것 입니다.
(Java의 stream 관련 클래스들을 생각하시면 됩니다.)

소스를 보면
보통 맨 앞단에서 들어오는 String을 잘라내는 역할을 하는 Tokenizer는 아래와 같이
사용 됩니다.


보시면 생성자에서
TermAttribute등을 addAttribute 메서드를 사용해서 등록하고 있습니다
이 메서드는 attribute가 있으면 있는 attribute를 리턴하고, 없으면 새롭게 attribute를 AttributeSource에 set해주는
역할을 합니다.
그럼 그냥 setAttribute를 해주면 되지 왜 저런 역할 (있으면 return, 없으면 add)을 하는 메서드가 필요할까요?
루씬에서 사용되는 필터나 Tokenizer는 사실 맨 앞이나 맨 뒤나 아니며 중간이던 어느 위치에서도
사용 될 수 있습니다. 때문에 저런 메서드가 필요한 것입니다.
하나의 필터가 맨 앞에서 사용 될거라고 해서 setAttribute 메서드를 사용해서 구현을 했는데 다른 Analyzer에서
이 필터가 3번째 4번째에서 사용될 수도 있기 때문입니다.

그리고 boolean incrementToken 메서드를 보시면 결과 값을 attribute에 set해주고 있는 것을 보실 수 있습니다.

그러면 이 결과를 받아서 사용하는
다른 Filter를 한번 보겠습니다. 지금까지 Analyzer를 구현하면서 본 Filter들은 크게 두개의 패턴을 가지고 있었습니다.

하나는 Tokenizer 처럼 하나의 결과를 받아서 그 결과에 직접 작업을 하는 필터입니다.하나의 Term을 만들거나 받아서
하나의 Term을 리턴하는 경우죠. 자세한 방법은 나중에 Analyzer에 대해서 얘기 할 때 자세하게 작성하겠습니다.
이 경우 따로 결과를 저장해 놓을 필요 없이 그냥 바로 "결과 Attribute를 넣어주면 됩니다.

또 하나는 동의어 추출이나 복합명사 추출 처럼 하나의 결과를 받아서 그 결과로부터 다른 추가적인 term을 추출해내는
필터입니다.
"노트북" 이라는 term이 들어오면, 이 노트북이라는 단어와 동의어로 설정 된 notebook,노트PC등의 term을 추가로 리턴해주는
필터입니다. 보통, List나 Stack에 결과를 담아두고 값을 하나하나 리턴하게 됩니다.
모양새는 서로 비슷합니다.

incrementToken 메서드는 이전 버전에서의 next 메서드와 같은 역할을 합니다.

그런데, 이 메서드의 return type이 boolean입니다. 그럼 어떻게 후속 필터들이 이 결과를 받을 수 있을까요?

첫번째 경우는 앞어 있는 Tokenizer 소스를 보시면 되고
두번째 경우인 추가적인 term를 만들어내는 필터를 보겠습니다.
Stemming 필터로써 어미 사전을 사용해 앞 필터에서 만들어낸 Term에서 어미를 제거하여 리턴하는 것입니다.
그런데 왜 stack을 사용하냐면 원본 Term도 리턴을 해줘야 하기 때문입니다.
이것도 나중에 Analyzer에 대해서 작성 할 때 상세히 알아보겠습니다.

아무튼 아래 stemming 필터를 보겠습니다.

생성자에서 Attribute3개를 addAttribute 해주고 있습니다.
만약, 앞선 Filter나 Tokenizer가 있다면 이 3개의 Attribute에는 앞선 Filter와 Tokenizer의 결과가
들어 올 것입니다.

그리고, AttributeSource save라고 선언된 것이 있는데 현재 input (즉, 앞선 Filter와 Tokenizer)를 cloneAttributes 메서드를 사용해서 Attribute들을 복사하고 있습니다. (사실은 AttributeSource 한벌이 더 생기는 거겠죠..)
왜 이게 필요할까요..

boolean incrementToken() 메서드를 보면

이렇게 되어 있는데요..
우선 #4를 보시면 앞선 필터에서의 결과가 없으면 역시 false를 리턴해서 결과 없음을 표시합니다.
#5 는 term을 받아서 그것을 stem 메서드를 사용해 어미를 잘라내는 부분입니다.
#6 #5에서의 결과를 가지고 어미가 잘려나갔으면
#7 현재의 State를 capture하고 있는데요 , 이 부분이 중요합니다. AttributeSource는 state라는 것을 갖습니다.
그리고 이 State는 captureState 메서드를 통해 snapshot을 저장해 둘 수도 있고
restore메서드를 사용해 다시 되살릴 수도 있습니다. 왜 이 부분이 필요하냐면...
앞선 필터에서 받은 Term이 "사랑해서"라고 가정했을 때 이 Stem 필터에서는 "사랑" 이라고 어미를 잘라낸 후
AttributeSource에 해당 Attribute를 set 할 것 입니다. (#9 번)

그렇게 되면 앞선 필터로 받았던 "사랑해서"에 대한 Attribute가 모두 사라지게 됩니다.

나중에 이 "사랑해서"라는 Term을 가지고 작업을 하려면 다시 이 것을 retore 메서드를 사용해서 처음으로 돌려놓아야 하는데
이를 위해서 앞에서 AttributeSource를 clone해서 save라는 한벌을 더 가지고 있었고, 이 save AttributeSource의 State를
Attribute가 변하기 전에 capture해 놓는 것입니다.

#10을 보면
이전 버전에서는 결과 Token을 Stack에 넣어두고 Token을 리턴했지면
여기서는 해당 Attribute들이 저장되어 있는 AttributeSource의 State를 Stack에 넣어놓습니다.
이 AttributeSource로부터 stem된 결과 Attribute들을 받아낼 수 있기 때문입니다.

실제로 #1 에서 stack의 사이즈가 0 보다 크면
#2에서 state를 받아내고
#3에서 그 State를 restore한 후 true를 리턴해주고 있습니다.

어차피 다음 필터에서도
생성자에서 addAttribute 메서드를 사용해서 Attriute를 받아내고 있기 때문에
이렇게 restoreState메서드를 사용하면 이 Stem 필터에서 결과로 뽑아낸 Attribute들을
AttributeSource로부터 받아 쓸 수 있게 됩니다.
Posted by 용식
루씬이 버전업 되면서 바뀐 부분 중 하나가 이 부분인 것 같습니다.
이전에는 Tokenizer나 TokenFilter 클래스를 만들 때 TokenStream 클래스를 상속받아 만들고
이 TokenStream의 next 메서드를 사용해서 얻어지는 Token을 가지고
데코레이터 패턴으로 적용 된 Filter들이 Token을 처리하는 방식이었습니다.

예를 들어서,

Analyzer의 tokenStream 메서드가 위와 같이 정의되어 있다고 하고
분석해야 할 문장이 "검색엔진 개발자" 라고 했을 때
추출 되는 Token을 얻어내기 위해서 next 메서드를 실행했었습니다. 즉,


대략 위와 같은 방식이었죠.
next 메서드가 실행 되면 제일 상위의 Tokenizer에서 "검색엔진,(0,4)" 라는 Token을 만들어서 리턴하고
그것을 받아서 다음 Filter의 next 메서드에서 Token에서 원하는 작업을 하고
(뭐 검색엔진을 검색+엔진으로 분리하여 리턴한다던가..)
그 Token을 또 다음 Filter가 받아서 작업하는 방식이었습니다.

그런데 이번에 Analyzer를 lucene 3.0 버전으로 바꾸다보니
앞선 포스트에 잠깐 언급했던 것 처럼 이 가장 핵심이라고 할 수 있는
Token next() 메서드가 없어졌습니다. 그리고, boolean incrementToken() 이라는 메서드가 생겼습니다.
게다가 리턴타입이 boolean 입니다.

필터가 앞선 필터(즉, TokenStream)으로 부터 추출되는 Token을 받아야 자기 작업을 할텐데
리턴 타입이 boolean이니 이걸 어떻게 하라는건가 싶었습니다.

3.0 버전에서는 예전의 next() 메서드를 대신 전부 incrementToken() 메서드로 대체가 되었습니다.

그럼 3.0 버전에서는 어떻게 필터와 Tokenizer가 동작할까요..

이전버전에 있던 (3.0에도 있지만) Token 클래스는 문장을 나타내는 term과 위치정보를 나타내는 start/end offset
그리고 위치 이동에 대한 정보인 positionIncremental 또 Term의 타입을 지정 할 수 있는 Type을 그 속성으로 가지고 있었습니다.

이 속성들이 Attribute라는 클래스를 상속한 속성 클래스들로 전부 빠져나왔습니다.
즉,

이런식으로 ...
그래서, 이전에 Token에 넣어주던 정보들을 저 속성들에 넣어주게 됩니다.
그러면, 어떻게 각 필터와 Tokenizer가 저 값들을 공유하여 자신들의 작업 (동의어추출,stemming,복합명사 추출등)을
할 수 있는 것일까요..?

Lucene 3.0 (혹은 그 이전버전 부터인지..)에서는 TokenStream 클래스가 AttributeSource라는 클래스를 상속받고 있습니다.
AttributeSource는 내부적으로 Attribute들을 가지고 있고요.. 즉, 위에 나열된 저런 속성들을 가지고 있습니다.
결국 모든 필터들이 TokenStream을 상속받고 있기 때문에
저 AttributeSource를 사용해서 원하는 값들을 공유 할 수 있게 됩니다.

조금 자세하게 살펴보면
일반적으로 가장 앞단에서 Term을 잘라내는 Tokenizer를 살펴보면
생성자에서 아래와 같은 일을 하도록 정의 되어 있습니다.


addAttribute 메서드는 AttributeSource 클래스에 정의되어있는 메서드로
Attribute Class를 자기 속성에 add하고, 만약에 이미 있는 Attribute라면 그 Attribute를 return 하는 메서드입니다.
Tokenizer가 가장 앞단에서 실행 되므르 위 문장에 의해서 TermAttribute와 OffsetAttribute, TypeAttribute가 AttributeSource에
추가 될 것입니다.

그리고, 해당 Tokenizer가 incrementToken()메서드에서
읽어들인 문장에서 Term을 잘라내고나서는

해당 속성값을 Attribute에 넣어주고 true를 리턴하게 됩니다.

그렇게 되면 다음 필터에서는 저 AttributeSource의 Attribute들을 얻어 낼 수 있다면
앞에서 동작하는 Filter나 Tokenizer의 결과 값을 사용 할 수 있게 되는 것 입니다.

그럼 어떻게 얻어낼까요?

바로 addAttribute 혹은 getAttribute 메서드를 통해 얻어낼 수 있습니다.

 - 계속 -
Posted by 용식
예전에 만들었던 한글 명사 추출 Analyzer를 공개하려고 하다가
그 Analyzer의 core 버전이 2.4.0 base여서 이것을 최신 루씬 버전인
3.0으로 컨버팅 하려고 했는데... 바뀐게 한두가지가 아니라서.. 일단 바뀐 부분부터 공부를
해야겠다 싶었다. -_-;

제일 당황스러운 부분이...
TokenStream.next(Token token) 메서드가 사라진 것이었다. -_-;

이 부분이 TokenStream.incrementToken() 메서드를 대체가 되었는데 이건 조만간 다시 포스팅 하기로 하고..
일단, 기존에 사용되던 Token이란 클래스가 남아있기는 한데..
대부분 Attribute 클래스에 기초한 놈들로 변경이 된 것 같다.

예전에 하나의 String에서 Analyzer로 분석 된 Term 리스트를 얻으려는 소스는
아래와 같았다.


TokenStream의 next 메서드로 다음 Analyzer로 분석 된 Token들을 하나씩 얻어내고
거기서 Token의 term을 사용해서 단어를 얻거나 offset등의 데이터들을 모두 얻어낼 수 있었는데
이 next() 메서드가 사라진 것이다. -_-;

그 대신 Attribute 클래스를 상속한 TermAttribute 혹은 OffsetAttribute등의 클래스를 이용해서 위의 작업을 대신 할 수 있다.
다른 Attribute도 많이 존재한다.
결국, 예전에 Token 클래스가 가지고 있던 속성들이 각각의 클래스로 분리 되서 나온 것이라고 보면 정확 할 것 같다.


TokenStream으로부터 Attribute인 TermAttribute와 OffsetAttribute를 얻어내고 이것을 통해서 단어와 위치 정보를
보여주고 있다.
그리고, incrementToken 메서드를 사용해서 다음 Term으로의 접근을 하고 있다.

결국, 이전 버전의 Tokenizer나 TokenFilter에서 next메서드가 하는 일을
incrementToken 메서드가 대신 하고 있는 것이다. 다만, 리턴을 해주는 것이 아니라
내부적으로 Attribute에 set을 하고 있는 것.. 그리고 이것을 그냥 가져다 쓰는 방식.

이게 왜 이렇게 바뀌었나 살펴보면.. TokenStream 메서드 중에
현재 상태를 capture하는 등의 메서드가 추가 된 것이 보이는데 이것을 위해서
Attribute들이 전부 멤버로 들어갔나 싶다.. 아직 자세히 못 들여다봐서..

결국, 내가 만들었던 Analyzer의 Filter들과 모듈을 거의 전부
뜯어 고쳐야 한다는 얘기가 나온다.. 어휴.... -.-

그외 다른 것도 모르는 것 천지.. 하나하나 이것저것 해보는 수 밖에.. ㄷㄷ;;

이전에 사용되던 Token 클래스도
TokenStream.addAttributeImpl 메서드를 통해서 사용 할 수 있는 것 같기는 한데
이건 구현되어있는 Tokenizer에 따라서 다른 것 같다.

그 Tokenizer가ㅏ AttributeImpl에도 값을 set 해주면 값이 들어갈 것이고..
아니면 안 들어갈 것이고..

참고로 가장 많이 사용되는 (루씬 analyzer에서.)
charTokenizer는
incrementToken메서드가 아래와 같이 구현 되어 있다.


이전 버전과 비교해보면 하는 일은 거의 같다.
다만 메서드 이름이 바뀌고 Token에 추출 된 결과를 넣어 주는 것이 아니라
TermAttribute와 OffsetAttribute에 넣어주는 것...

그러면, 이 Tokenizer가 도대체 어떤 Attribute와 AttributeImpl을 가지고 있는지 알아야 할 텐데..
보니까 TokenStream에 두 클래스에 대한 Iterator를 얻을 수 있는 메서드가 있다.
그걸 사용하면 될 듯..
Posted by 용식