You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mi...@apache.org on 2012/02/14 12:37:46 UTC

svn commit: r1243882 - in /lucene/dev/branches/lucene3767: lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/ modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/ modules/analysis/kuromoji/src/java/org/apache/lucene/ana...

Author: mikemccand
Date: Tue Feb 14 11:37:45 2012
New Revision: 1243882

URL: http://svn.apache.org/viewvc?rev=1243882&view=rev
Log:
LUCENE-3767: fix bug in 2nd best path; add Mode.SEARCH_WITH_COMPOUNDS; fix tests to test it; remove nocommits; add comments

Added:
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-with-compounds-tests.txt
Modified:
    lucene/dev/branches/lucene3767/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttribute.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/KuromojiTokenizer2.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Segmenter.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Token.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/viterbi/Viterbi.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/SegmenterTest.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiAnalyzer.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiTokenizer.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestQuality.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestSearchMode.java
    lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-tests.txt

Modified: lucene/dev/branches/lucene3767/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttribute.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttribute.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttribute.java (original)
+++ lucene/dev/branches/lucene3767/lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/PositionIncrementAttribute.java Tue Feb 14 11:37:45 2012
@@ -57,6 +57,7 @@ public interface PositionIncrementAttrib
    */
   public int getPositionIncrement();
 
+  // nocommit break into new attr...
   // nocommit better names...?  getLength?  getSpan?
   /** @param positionLength how many positions this token
    *  spans.  NOTE: this is optional, and most analyzers

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/KuromojiTokenizer2.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/KuromojiTokenizer2.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/KuromojiTokenizer2.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/KuromojiTokenizer2.java Tue Feb 14 11:37:45 2012
@@ -20,14 +20,10 @@ package org.apache.lucene.analysis.kurom
 import java.io.IOException;
 import java.io.Reader;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
-import org.apache.lucene.analysis.CharStream;
 import org.apache.lucene.analysis.Tokenizer;
 import org.apache.lucene.analysis.kuromoji.Segmenter.Mode;
 import org.apache.lucene.analysis.kuromoji.dict.CharacterDefinition;
@@ -43,7 +39,6 @@ import org.apache.lucene.analysis.tokena
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.util.ArrayUtil;
-import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.IntsRef;
 import org.apache.lucene.util.RamUsageEstimator;
 import org.apache.lucene.util.fst.FST;
@@ -53,20 +48,12 @@ import org.apache.lucene.util.fst.FST;
 
 // nocommit add toDot and look at 1st pass intersection
 
-// nocommit need better testing of compound output... need
-// assertAnalyzesTo to accept posLength too
-
-// nocommit add comment explaining this isn't quite a real viterbi
+// nocommit needs assertAnalyzersTo to accept
+// posLength... then fix compound tests to verify it
 
 // nocomit explain how the 2nd best tokenization is
 // "contextual"...
 
-// nocommit -- need a test that doesn't pre-split by
-// sentence... ie, we don't BOS/EOS on each sentence
-// break any more... so this can change the results
-// depending on whether ipadic was "trained" with sentence
-// breaks?
-
 // nocommit -- should we use the sentence breakiterator
 // too..?  we can simply use it to slip an EOS/BOS token
 // in...
@@ -87,9 +74,6 @@ public final class KuromojiTokenizer2 ex
 
   private static final int SEARCH_MODE_OTHER_PENALTY = 1700;
 
-  // nocommit
-  public static boolean DO_OUTPUT_COMPOUND = false;
-
   private final EnumMap<Type, Dictionary> dictionaryMap = new EnumMap<Type, Dictionary>(Type.class);
 
   private final TokenInfoFST fst;
@@ -106,29 +90,36 @@ public final class KuromojiTokenizer2 ex
   private final FST.BytesReader userFSTReader;
   private final TokenInfoFST userFST;
 
-  private Reader reader;
+  private final WrappedCharArray buffer = new WrappedCharArray();
 
-  // Next absolute position to process:
-  private int pos;
+  private final WrappedPositionArray positions = new WrappedPositionArray();
 
-  private WrappedCharArray buffer = new WrappedCharArray();
+  private final boolean discardPunctuation;
+  private final boolean searchMode;
+  private final boolean extendedMode;
+  private final boolean outputCompounds;
 
   // index of the last character of unknown word:
-  // nocommit put back:
-  // int unknownWordEndIndex = -1;
-
-  private WrappedPositionArray positions = new WrappedPositionArray();
+  private int unknownWordEndIndex = -1;
 
+  // True once we've hit the EOF from the input reader:
   private boolean end;
-  private final boolean discardPunctuation;
-  private final boolean searchMode;
-  private final boolean extendedMode;
+
+  // Used to determine if all extensions from a given node
+  // originated from the same backtrace point:
   private int sameLeastIndex;
 
+  // Last absolute position we backtraced from:
   private int lastBackTracePos;
+
+  // Position of last token we returned; we use this to
+  // figure out whether to set posIncr to 0 or 1:
   private int lastTokenPos;
 
-  // Already parsed but not yet passed to caller:
+  // Next absolute position to process:
+  private int pos;
+
+  // Already parsed, but not yet passed to caller, tokens:
   private final List<Token> pending = new ArrayList<Token>();
 
   private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
@@ -160,14 +151,22 @@ public final class KuromojiTokenizer2 ex
       case SEARCH:
         searchMode = true;
         extendedMode = false;
+        outputCompounds = false;
+        break;
+      case SEARCH_WITH_COMPOUNDS:
+        searchMode = true;
+        extendedMode = false;
+        outputCompounds = true;
         break;
       case EXTENDED:
         searchMode = true;
         extendedMode = true;
+        outputCompounds = false;
         break;
       default:
         searchMode = false;
         extendedMode = false;
+        outputCompounds = false;
         break;
     }
     buffer.reset(input);
@@ -187,8 +186,7 @@ public final class KuromojiTokenizer2 ex
 
   private void resetState() {
     positions.reset();
-    // nocommit put back
-    //unknownWordEndIndex = -1;
+    unknownWordEndIndex = -1;
     pos = 0;
     end = false;
     lastBackTracePos = 0;
@@ -200,11 +198,24 @@ public final class KuromojiTokenizer2 ex
   }
 
   @Override
-  public final void end() {
+  public void end() {
     // set final offset
     offsetAtt.setOffset(correctOffset(pos), correctOffset(pos));
   }
 
+  // Returns the added cost that a 2nd best segmentation is
+  // allowed to have.  Ie, if we see path with cost X,
+  // ending in a compound word, and this method returns
+  // threshold > 0, then we will also find the 2nd best
+  // segmentation and if its path score is within this
+  // threshold of X, we'll include it in the output:
+  private int computeSecondBestThreshold(int pos, int length) throws IOException {
+    // TODO: maybe we do something else here, instead of just
+    // using the penalty...?  EG we can be more aggressive on
+    // when to also test for 2nd best path
+    return computePenalty(pos, length);
+  }
+
   private int computePenalty(int pos, int length) throws IOException {
     if (length > SEARCH_MODE_KANJI_LENGTH) {
       boolean allKanji = true;
@@ -216,7 +227,6 @@ public final class KuromojiTokenizer2 ex
           break;
         }				
       }
-      final int penalty;
       if (allKanji) {	// Process only Kanji keywords
         return (length - SEARCH_MODE_KANJI_LENGTH) * SEARCH_MODE_KANJI_PENALTY;
       } else if (length > SEARCH_MODE_OTHER_LENGTH) {
@@ -279,9 +289,14 @@ public final class KuromojiTokenizer2 ex
     }
 
     public void add(int cost, int nodeID, int backPos, int backIndex, int backID, Type backType) {
-      // nocommit in theory, we should check if nodeID is
-      // already present here, and update it if
-      // so... instead of just always adding:
+      // NOTE: this isn't quite a true Viterbit search,
+      // becase we should check if nodeID is
+      // already present here, and only update if the new
+      // cost is less than the current cost, instead of
+      // simply appending.  However, that will likely hurt
+      // performance (usually we add a nodeID only once),
+      // and it means we actually create the full graph
+      // intersection instead of a "normal" Viterbi lattice:
       if (count == costs.length) {
         grow();
       }
@@ -310,37 +325,36 @@ public final class KuromojiTokenizer2 ex
       // forwardCount naturally resets after it runs:
       assert forwardCount == 0: "pos=" + pos + " forwardCount=" + forwardCount;
     }
-
-    // nocommit maybe?
-    // public void update(int cost, int nodeID, int backPos, int backIndex, int backID)
   }
 
-  // nocommit absorb into add...?
-  private void add(Dictionary dict, Position posData, int endPos, int wordID, Type type) throws IOException {
+  private void add(Dictionary dict, Position fromPosData, int endPos, int wordID, Type type, boolean addPenalty) throws IOException {
     final int wordCost = dict.getWordCost(wordID);
     final int leftID = dict.getLeftId(wordID);
     int leastCost = Integer.MAX_VALUE;
     int leastIDX = -1;
-    assert posData.count > 0;
-    for(int idx=0;idx<posData.count;idx++) {
-      // Cost is path cost so far, plus word cost, plus
-      // bigram cost:
-      final int cost = posData.costs[idx] + wordCost + costs.get(posData.nodeID[idx], leftID);
+    assert fromPosData.count > 0;
+    for(int idx=0;idx<fromPosData.count;idx++) {
+      // Cost is path cost so far, plus word cost (added at
+      // end of loop), plus bigram cost:
+      final int cost = fromPosData.costs[idx] + costs.get(fromPosData.nodeID[idx], leftID);
+      if (VERBOSE) {
+        System.out.println("      fromIDX=" + idx + ": cost=" + cost + " (prevCost=" + fromPosData.costs[idx] + " wordCost=" + wordCost + " bgCost=" + costs.get(fromPosData.nodeID[idx], leftID) + " leftID=" + leftID);
+      }
       if (cost < leastCost) {
         leastCost = cost;
         leastIDX = idx;
+        //System.out.println("        **");
       }
     }
 
+    leastCost += wordCost;
+
     if (VERBOSE) {
-      System.out.println("      + cost=" + leastCost + " wordID=" + wordID + " leftID=" + leftID + " tok=" + new String(buffer.get(posData.pos, endPos-posData.pos)) + " leastIDX=" + leastIDX + " toPos.idx=" + positions.get(endPos).count);
+      System.out.println("      + cost=" + leastCost + " wordID=" + wordID + " leftID=" + leftID + " leastIDX=" + leastIDX + " toPos.idx=" + positions.get(endPos).count);
     }
 
-    // nocommit ideally we don't have to do this ... just
-    // putting it here to confirm same results as current
-    // segmenter:
-    if (!DO_OUTPUT_COMPOUND && searchMode && type != Type.USER) {
-      final int penalty = computePenalty(posData.pos, endPos - posData.pos);
+    if ((addPenalty || (!outputCompounds && searchMode)) && type != Type.USER) {
+      final int penalty = computePenalty(fromPosData.pos, endPos - fromPosData.pos);
       if (VERBOSE) {
         if (penalty > 0) {
           System.out.println("        + penalty=" + penalty + " cost=" + (leastCost+penalty));
@@ -349,14 +363,15 @@ public final class KuromojiTokenizer2 ex
       leastCost += penalty;
     }
 
-    positions.get(endPos).add(leastCost, dict.getRightId(wordID), posData.pos, leastIDX, wordID, type);
-    /*
+    //positions.get(endPos).add(leastCost, dict.getRightId(wordID), fromPosData.pos, leastIDX, wordID, type);
+    assert leftID == dict.getRightId(wordID);
+    positions.get(endPos).add(leastCost, leftID, fromPosData.pos, leastIDX, wordID, type);
+
     if (sameLeastIndex == -1) {
       sameLeastIndex = leastIDX;
     } else if (sameLeastIndex != leastIDX) {
-      sameLeastIndex = Integer.MAX_VALUE;
+      sameLeastIndex = -2;
     }
-    */
   }
 
   @Override
@@ -651,7 +666,8 @@ public final class KuromojiTokenizer2 ex
 
       // nocommit if I change 40 below it changes number of
       // tokens in TestQuality!
-      if ((pos - lastBackTracePos >= 100) && posData.count == 1 && isFrontier) {
+      //if ((pos - lastBackTracePos >= 100) && posData.count == 1 && isFrontier) {
+      if (pos > lastBackTracePos && posData.count == 1 && isFrontier) {
         // We are at a "frontier", and only one node is
         // alive, so whatever the eventual best path is must
         // come through this node.  So we can safely commit
@@ -664,7 +680,6 @@ public final class KuromojiTokenizer2 ex
         if (pending.size() != 0) {
           return;
         } else {
-          // nocommit: make punctuation-only testcase
           // This means the backtrace only produced
           // punctuation tokens, so we must keep parsing.
         }
@@ -689,11 +704,7 @@ public final class KuromojiTokenizer2 ex
         System.out.println("    " + posData.count + " arcs in");
       }
 
-      // nocommit must also 1) detect a
-      // less-obvious-yet-still-committable backtrace op,
-      // when, even though N > 1 states are/were alive, they
-      // all back through a single state, and also 2) maybe
-      // need to "force" a "when all else fails" backtrace
+      // nocommit may need "force" a "when all else fails" backtrace?
 
       boolean anyMatches = false;
 
@@ -711,22 +722,21 @@ public final class KuromojiTokenizer2 ex
           }
           output += arc.output.intValue();
           if (arc.isFinal()) {
-            add(userDictionary, posData, posAhead+1, output + arc.nextFinalOutput.intValue(), Type.USER);
+            add(userDictionary, posData, posAhead+1, output + arc.nextFinalOutput.intValue(), Type.USER, false);
             anyMatches = true;
           }
         }
       }
 
-      // nocommit we can be more aggressive about user
+      // TODO: we can be more aggressive about user
       // matches?  if we are "under" a user match then don't
-      // extend these paths?
+      // extend KNOWN/UNKNOWN paths?
 
       if (!anyMatches) {
         // Next, try known dictionary matches
         fst.getFirstArc(arc);
         int output = 0;
 
-        boolean allKanji = true;
         for(int posAhead=posData.pos;;posAhead++) {
           final int ch = buffer.get(posAhead);
           if (ch == -1) {
@@ -740,10 +750,11 @@ public final class KuromojiTokenizer2 ex
 
           output += arc.output.intValue();
 
-          // NOTE: for known words that are too-long
+          // Optimization: for known words that are too-long
           // (compound), we should pre-compute the 2nd
           // best segmentation and store it in the
-          // dictionary.  This is just a time/space opto...
+          // dictionary instead of recomputing it each time a
+          // match is found.
 
           if (arc.isFinal()) {
             dictionary.lookupWordIds(output + arc.nextFinalOutput.intValue(), wordIdRef);
@@ -751,7 +762,7 @@ public final class KuromojiTokenizer2 ex
               System.out.println("    KNOWN word " + new String(buffer.get(pos, posAhead - pos + 1)) + " toPos=" + (posAhead + 1) + " " + wordIdRef.length + " wordIDs");
             }
             for (int ofs = 0; ofs < wordIdRef.length; ofs++) {
-              add(dictionary, posData, posAhead+1, wordIdRef.ints[wordIdRef.offset + ofs], Type.KNOWN);
+              add(dictionary, posData, posAhead+1, wordIdRef.ints[wordIdRef.offset + ofs], Type.KNOWN, false);
               anyMatches = true;
             }
           }
@@ -760,12 +771,10 @@ public final class KuromojiTokenizer2 ex
 
       // In the case of normal mode, it doesn't process unknown word greedily.
 
-      // nocommit: fix
-      /*
       if (!searchMode && unknownWordEndIndex > posData.pos) {
         continue;
       }
-      */
+
       final char firstCharacter = (char) buffer.get(pos);
       // nocommit -- can't we NOT pursue unk if a known
       // token "covers" us...?
@@ -799,20 +808,18 @@ public final class KuromojiTokenizer2 ex
           System.out.println("    UNKNOWN word len=" + unknownWordLength + " " + wordIdRef.length + " wordIDs");
         }
         for (int ofs = 0; ofs < wordIdRef.length; ofs++) {
-          add(unkDictionary, posData, posData.pos + unknownWordLength, wordIdRef.ints[wordIdRef.offset + ofs], Type.UNKNOWN);
+          add(unkDictionary, posData, posData.pos + unknownWordLength, wordIdRef.ints[wordIdRef.offset + ofs], Type.UNKNOWN, false);
         }
 
-        // nocommit fixme
-        //unknownWordEndIndex = posData.pos + unknownWordLength;
+        unknownWordEndIndex = posData.pos + unknownWordLength;
       }
 
       // nocommit explainme:
-      if (false && (pos - lastBackTracePos >= 100) && sameLeastIndex != -1 && sameLeastIndex != Integer.MAX_VALUE &&
-          pos != lastBackTracePos && isFrontier) {
+      if ((pos - lastBackTracePos >= 100) && sameLeastIndex >= 0 && isFrontier) {
 
         final int sav = sameLeastIndex;
 
-        //System.out.println("**SAME: " + sameLeastIndex);
+        //System.out.println("**SAME: " + sameLeastIndex + " vs " + posData.count);
         backtrace(posData, sameLeastIndex);
 
         // Re-base cost so we don't risk int overflow:
@@ -822,7 +829,6 @@ public final class KuromojiTokenizer2 ex
         if (pending.size() != 0) {
           return;
         } else {
-          // nocommit: make punctuation-only testcase
           // This means the backtrace only produced
           // punctuation tokens, so we must keep parsing.
         }
@@ -871,7 +877,6 @@ public final class KuromojiTokenizer2 ex
 
     int minBackPos = Integer.MAX_VALUE;
 
-    final int compoundLength = endPos - startPos;
     // First pass: walk backwards, building up the forward
     // arcs and pruning inadmissible arcs:
     for(int pos=endPos; pos >= startPos; pos--) {
@@ -881,14 +886,7 @@ public final class KuromojiTokenizer2 ex
       }
       for(int arcIDX=0;arcIDX<posData.count;arcIDX++) {
         final int backPos = posData.backPos[arcIDX];
-        // nocommit: O(N^2) cost here!!  adversary could
-        // exploit this i think...?  how to protect?  hmm
-        // maybe i need to track lastKanjiCount at each
-        // position..
-        // nocommit hacky:
-        // if (backPos >= startPos && computePenalty(backPos, pos-backPos) == 0) {
-        if (backPos >= startPos && (pos-backPos) <= 2*compoundLength/3) {
-          //if (backPos != -1 && (pos-backPos) <= 2*compoundLength/3) {
+        if (backPos >= startPos) {
           // Keep this arc:
           //System.out.println("      keep backPos=" + backPos);
           minBackPos = Math.min(backPos, minBackPos);
@@ -907,8 +905,14 @@ public final class KuromojiTokenizer2 ex
       }
     }
 
+    // nocommit shouldn't we just "run w/ penalties" in this
+    // pass...?
+
     // nocommit what if minBackPos not set...?
 
+    // nocommit it must be startPos...?
+    assert minBackPos == startPos;
+
     // Second pass: walk forward, re-scoring:
     //for(int pos=startPos; pos < endPos; pos++) {
     for(int pos=minBackPos; pos < endPos; pos++) {
@@ -924,23 +928,29 @@ public final class KuromojiTokenizer2 ex
         posData.forwardCount = 0;
         continue;
       }
+
       if (pos == startPos) {
         // On the initial position, only consider the best
         // path so we "force congruence":  the
         // sub-segmentation is "in context" of what the best
         // path (compound token) had matched:
+
+        // nocommit could be -1?:  don't i need to check
+        // startPos==0 too...?
         final Dictionary dict = getDict(posData.backType[bestStartIDX]);
+
         final int rightID = startPos == 0 ? 0 : dict.getRightId(posData.backID[bestStartIDX]);
-        final int pathCost = posData.backID[bestStartIDX];
+        final int pathCost = posData.costs[bestStartIDX];
         for(int forwardArcIDX=0;forwardArcIDX<posData.forwardCount;forwardArcIDX++) {
           final Type forwardType = posData.forwardType[forwardArcIDX];
           final Dictionary dict2 = getDict(forwardType);
           final int wordID = posData.forwardID[forwardArcIDX];
-          final int newCost = pathCost + dict2.getWordCost(wordID) + 
-            costs.get(rightID, dict2.getLeftId(wordID));
           final int toPos = posData.forwardPos[forwardArcIDX];
+          final int newCost = pathCost + dict2.getWordCost(wordID) + 
+            costs.get(rightID, dict2.getLeftId(wordID)) +
+            computePenalty(pos, toPos-pos);
           if (VERBOSE) {
-            System.out.println("      + " + forwardType + " word " + new String(buffer.get(pos, toPos-pos)) + " toPos=" + toPos);
+            System.out.println("      + " + forwardType + " word " + new String(buffer.get(pos, toPos-pos)) + " toPos=" + toPos + " cost=" + newCost + " penalty=" + computePenalty(pos, toPos-pos) + " toPos.idx=" + positions.get(toPos).count);
           }
           positions.get(toPos).add(newCost,
                                    dict2.getRightId(wordID),
@@ -962,7 +972,8 @@ public final class KuromojiTokenizer2 ex
               posData,
               toPos,
               posData.forwardID[forwardArcIDX],
-              forwardType);
+              forwardType,
+              true);
         }
       }
       posData.forwardCount = 0;
@@ -1003,8 +1014,9 @@ public final class KuromojiTokenizer2 ex
       int backPos = posData.backPos[bestIDX];
       int length = pos - backPos;
       Type backType = posData.backType[bestIDX];
+      int backID = posData.backID[bestIDX];
 
-      if (DO_OUTPUT_COMPOUND && searchMode && altToken == null && backType != Type.USER) {
+      if (outputCompounds && searchMode && altToken == null && backType != Type.USER) {
         
         // In searchMode, if best path had picked a too-long
         // token, we use the "penalty" to compute the allowed
@@ -1013,10 +1025,7 @@ public final class KuromojiTokenizer2 ex
         // threshold, we pursue it instead (but also output
         // the long token).
 
-        // nocommit -- maybe re-tune this "penalty", now
-        // that it means something very different ("output
-        // smaller segmentation"):
-        final int penalty = computePenalty(backPos, pos-backPos);
+        final int penalty = computeSecondBestThreshold(backPos, pos-backPos);
         
         if (penalty > 0) {
           if (VERBOSE) {
@@ -1025,38 +1034,45 @@ public final class KuromojiTokenizer2 ex
 
           // Use the penalty to set maxCost on the 2nd best
           // segmentation:
-          final int maxCost = posData.costs[bestIDX] + penalty;
-
-          final int backID = posData.backID[bestIDX];
+          int maxCost = posData.costs[bestIDX] + penalty;
+          if (lastLeftWordID != -1) {
+            maxCost += costs.get(getDict(backType).getRightId(backID), lastLeftWordID);
+          }
 
           // Now, prune all too-long tokens from the graph:
           pruneAndRescore(backPos, pos,
                           posData.backIndex[bestIDX]);
 
-          if (VERBOSE) {
-            System.out.println("  afterPrune: " + posData.count + " arcs arriving");
-          }
-
           // Finally, find 2nd best back-trace and resume
           // backtrace there:
           int leastCost = Integer.MAX_VALUE;
           int leastIDX = -1;
           for(int idx=0;idx<posData.count;idx++) {
+            // nocommit needs wordCost added here?
             int cost = posData.costs[idx];
+            //System.out.println("    idx=" + idx + " prevCost=" + cost);
+            
             if (lastLeftWordID != -1) {
               cost += costs.get(getDict(posData.backType[idx]).getRightId(posData.backID[idx]),
                                 lastLeftWordID);
+              //System.out.println("      += bgCost=" + costs.get(getDict(posData.backType[idx]).getRightId(posData.backID[idx]),
+              //lastLeftWordID) + " -> " + cost);
             }
+            //System.out.println("penalty " + posData.backPos[idx] + " to " + pos);
+            //cost += computePenalty(posData.backPos[idx], pos - posData.backPos[idx]);
             if (cost < leastCost) {
+              //System.out.println("      ** ");
               leastCost = cost;
               leastIDX = idx;
             }
           }
           //System.out.println("  leastIDX=" + leastIDX);
 
-          // nocommit -- must also check whether 2nd best score falls w/in threshold?
+          if (VERBOSE) {
+            System.out.println("  afterPrune: " + posData.count + " arcs arriving; leastCost=" + leastCost + " vs threshold=" + maxCost + " lastLeftWordID=" + lastLeftWordID);
+          }
 
-          if (leastIDX != -1 && posData.backPos[leastIDX] <= maxCost) {
+          if (leastIDX != -1 && leastCost <= maxCost && posData.backPos[leastIDX] != backPos) {
             // We should have pruned the altToken from the graph:
             assert posData.backPos[leastIDX] != backPos;
 
@@ -1076,6 +1092,7 @@ public final class KuromojiTokenizer2 ex
             backPos = posData.backPos[bestIDX];
             length = pos - backPos;
             backType = posData.backType[bestIDX];
+            backID = posData.backID[bestIDX];
             backCount = 0;
             
           } else {
@@ -1104,10 +1121,14 @@ public final class KuromojiTokenizer2 ex
         if (VERBOSE) {
           System.out.println("    add altToken=" + altToken);
         }
-        assert backCount >= 1;
-        backCount++;
-        altToken.setPositionLength(backCount);
-        pending.add(altToken);
+        if (backCount > 0) {
+          backCount++;
+          altToken.setPositionLength(backCount);
+          pending.add(altToken);
+        } else {
+          // This means alt token was all punct tokens:
+          assert discardPunctuation;
+        }
         altToken = null;
       }
 
@@ -1117,7 +1138,7 @@ public final class KuromojiTokenizer2 ex
 
         // Expand the phraseID we recorded into the actual
         // segmentation:
-        final int[] wordIDAndLength = userDictionary.lookupSegmentation(posData.backID[bestIDX]);
+        final int[] wordIDAndLength = userDictionary.lookupSegmentation(backID);
         int wordID = wordIDAndLength[0];
         int current = backPos;
         for(int j=1; j < wordIDAndLength.length; j++) {
@@ -1142,7 +1163,7 @@ public final class KuromojiTokenizer2 ex
         backCount += wordIDAndLength.length-1;
       } else {
 
-        if (extendedMode && posData.backType[bestIDX] == Type.UNKNOWN) {
+        if (extendedMode && backType == Type.UNKNOWN) {
           // nocommit what if the altToken is unknonwn?  
           // In EXTENDED mode we convert unknown word into
           // unigrams:
@@ -1153,21 +1174,24 @@ public final class KuromojiTokenizer2 ex
               i--;
               charLen = 2;
             }
-            //System.out.println("    extended tok offset=" + (offset + i));
-            pending.add(new Token(CharacterDefinition.NGRAM,
-                                  fragment,
-                                  offset + i,
-                                  charLen,
-                                  Type.UNKNOWN,
-                                  backPos + i,
-                                  unkDictionary));
-            unigramTokenCount++;
+            //System.out.println("    extended tok offset="
+            //+ (offset + i));
+            if (!discardPunctuation || !isPunctuation(fragment[offset+i])) {
+              pending.add(new Token(CharacterDefinition.NGRAM,
+                                    fragment,
+                                    offset + i,
+                                    charLen,
+                                    Type.UNKNOWN,
+                                    backPos + i,
+                                    unkDictionary));
+              unigramTokenCount++;
+            }
           }
           backCount += unigramTokenCount;
           
         } else if (!discardPunctuation || length == 0 || !isPunctuation(fragment[offset])) {
           //System.out.println("backPos=" + backPos);
-          pending.add(new Token(posData.backID[bestIDX],
+          pending.add(new Token(backID,
                                 fragment,
                                 offset,
                                 length,
@@ -1185,7 +1209,7 @@ public final class KuromojiTokenizer2 ex
         }
       }
 
-      lastLeftWordID = dict.getLeftId(posData.backID[bestIDX]);
+      lastLeftWordID = dict.getLeftId(backID);
       pos = backPos;
       bestIDX = posData.backIndex[bestIDX];
     }
@@ -1209,7 +1233,7 @@ public final class KuromojiTokenizer2 ex
     return dictionaryMap.get(type);
   }
 
-  private static final boolean isPunctuation(char ch) {
+  private static boolean isPunctuation(char ch) {
     // TODO: somehow this is slowish.. takes ~5% off
     // chars/msec from Perf.java; maybe we
     // can spend RAM...

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Segmenter.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Segmenter.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Segmenter.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Segmenter.java Tue Feb 14 11:37:45 2012
@@ -38,7 +38,7 @@ import org.apache.lucene.analysis.kuromo
  */
 public class Segmenter {
   public static enum Mode {
-    NORMAL, SEARCH, EXTENDED
+    NORMAL, SEARCH, SEARCH_WITH_COMPOUNDS, EXTENDED
   }
   
   public static final Mode DEFAULT_MODE = Mode.SEARCH;
@@ -128,8 +128,7 @@ public class Segmenter {
         position = Math.min(indexOfMaru, indexOfTen);				
       }
 
-      // nocommit
-      if(false && position >= 0) {
+      if(position >= 0) {
         splitPositions.add(position);
         currentPosition = position + 1;
       } else {

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Token.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Token.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Token.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/Token.java Tue Feb 14 11:37:45 2012
@@ -47,7 +47,7 @@ public class Token {
 
   @Override
   public String toString() {
-    return "Token(\"" + new String(surfaceForm, offset, length) + "\" pos=" + position + " type=" + type + " wordId=" + wordId + ")";
+    return "Token(\"" + new String(surfaceForm, offset, length) + "\" pos=" + position + " type=" + type + " wordId=" + wordId + " leftID=" + dictionary.getLeftId(wordId) + ")";
   }
   
   /**

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/viterbi/Viterbi.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/viterbi/Viterbi.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/viterbi/Viterbi.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/java/org/apache/lucene/analysis/kuromoji/viterbi/Viterbi.java Tue Feb 14 11:37:45 2012
@@ -126,7 +126,7 @@ public class Viterbi {
           break;
         }
 
-        //System.out.println("  leaving node.wordID=" + node.getWordId() + " leftID=" + node.getLeftId());
+        //System.out.println("  leaving node.wordID=" + node.getWordId() + " leftID=" + node.getLeftId() + " toPos=" + (node.getOffset()+node.getLength()));
         
         int backwardConnectionId = node.getLeftId();
         int wordCost = node.getWordCost();
@@ -160,13 +160,14 @@ public class Viterbi {
               if (allKanji) {	// Process only Kanji keywords
                 pathCost += (length - SEARCH_MODE_KANJI_LENGTH) * SEARCH_MODE_KANJI_PENALTY;
                 //System.out.println("    + kanji penalty=" + (length - SEARCH_MODE_KANJI_LENGTH) * SEARCH_MODE_KANJI_PENALTY + " cost=" + pathCost);
+                //System.out.println("        after penalty pathCost=" + pathCost);
               } else if (length > SEARCH_MODE_OTHER_LENGTH) {
                 pathCost += (length - SEARCH_MODE_OTHER_LENGTH) * SEARCH_MODE_OTHER_PENALTY;								
                 //System.out.println("    + non-kanji penalty=" + (length - SEARCH_MODE_OTHER_LENGTH) * SEARCH_MODE_OTHER_PENALTY + " cost=" + pathCost);
+                //System.out.println("        after penalty pathCost=" + pathCost);
               }
             }
           }
-          //System.out.println("      after penalty pathCost=" + pathCost);
           
           if (pathCost < leastPathCost){	// If total cost is lower than before, set current previous node as best left node (previous means left).
             //System.out.println("        **");
@@ -318,7 +319,7 @@ public class Viterbi {
   private void processUserDictionary(char text[], int offset, int len, ViterbiNode[][] startIndexArr, ViterbiNode[][] endIndexArr, int[] startSizeArr, int[] endSizeArr) throws IOException {
     int[][] result = userDictionary.lookup(text, offset, len);
     for(int[] segmentation : result) {
-      System.out.println("SEG=" + Arrays.toString(segmentation));
+      //System.out.println("SEG=" + Arrays.toString(segmentation));
       int wordId = segmentation[0];
       int index = segmentation[1];
       int length = segmentation[2];

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/SegmenterTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/SegmenterTest.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/SegmenterTest.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/SegmenterTest.java Tue Feb 14 11:37:45 2012
@@ -26,8 +26,6 @@ import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-// nocommit cut these over to KT2
-
 public class SegmenterTest extends LuceneTestCase {
   
   private static Segmenter segmenter;

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiAnalyzer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiAnalyzer.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiAnalyzer.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiAnalyzer.java Tue Feb 14 11:37:45 2012
@@ -23,7 +23,7 @@ import java.io.StringReader;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.BaseTokenStreamTestCase;
 import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
+import org.apache.lucene.analysis.kuromoji.Segmenter.Mode;
 
 public class TestKuromojiAnalyzer extends BaseTokenStreamTestCase {
   /** This test fails with NPE when the 
@@ -51,101 +51,92 @@ public class TestKuromojiAnalyzer extend
    */
   public void testDecomposition() throws IOException {
 
-    // nocommit
+    // Senior software engineer:
+    assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "シニアソフトウェアエンジニア",
+                     new String[] { "シニア",
+                                    "ソフトウェア",
+                                    "エンジニア" },
+                     new int[] { 1, 1, 1}
+                     );
+    // Kansai International Airport:
+    assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "関西国際空港",
+                     new String[] { "関西",
+                                    "国際",
+                                    "空港" },
+                     new int[] {1, 1, 1}
+                     );
+
+    // Konika Minolta Holdings; not quite the right
+    // segmentation (see LUCENE-3726):
+    assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "コニカミノルタホールディングス",
+                     new String[] { "コニカ",
+                                    "ミノルタ", 
+                                    "ホールディングス"},
+                     new int[] {1, 1, 1}
+                     );
+
+    // Narita Airport
+    assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "成田空港",
+                     new String[] { "成田",
+                                    "空港" },
+                     new int[] {1, 1});
+
+    // Kyoto University Baseball Club
+    assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "京都大学硬式野球部",
+                     new String[] { "京都",
+                                    "大学",
+                                    "硬式",
+                                    "野球",
+                                    "部" },
+                     new int[] {1, 1, 1, 1, 1});
+  }
+
+  public void testDecompositionWithCompounds() throws IOException {
+
+    final Analyzer a = new KuromojiAnalyzer(TEST_VERSION_CURRENT, null, Mode.SEARCH_WITH_COMPOUNDS,
+                                            KuromojiAnalyzer.getDefaultStopSet(),
+                                            KuromojiAnalyzer.getDefaultStopTags());
+
     /*
-    TokenStream ts = new KuromojiAnalyzer(TEST_VERSION_CURRENT)
-        .tokenStream("foo",
-                     //new StringReader("楽器・機材の質は完全にプロ仕様!ですが値段は何処よりも安い!"));
-                     //new StringReader("ですが値段は何処よりも安い!"));
-                     new StringReader("木工芸では、木を、硬木、軟木と、唐木に分類します。"));
+    TokenStream ts = a.tokenStream("foo", new StringReader("京都大学硬式野球部"));
     while(ts.incrementToken());
     */
 
     // Senior software engineer:
-    if (KuromojiTokenizer2.DO_OUTPUT_COMPOUND) {
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "シニアソフトウェアエンジニア",
-                       new String[] { "シニア",
-                                      "シニアソフトウェアエンジニア",
-                                      "ソフトウェア",
-                                      "エンジニア" },
-                       new int[] { 1, 0, 1, 1}
-                       );
-
-      // Kansai International Airport:
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "関西国際空港",
-                       new String[] { "関西",
-                                      "関西国際空港", // zero pos inc
-                                      "国際",
-                                      "空港" },
-                       new int[] {1, 0, 1, 1}
-                       );
-
-      // Konika Minolta Holdings; not quite the right
-      // segmentation (see LUCENE-3726):
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "コニカミノルタホールディングス",
-                       new String[] { "コニカ",
-                                      "コニカミノルタホールディングス", // zero pos inc
-                                      "ミノルタ", 
-                                      "ホールディングス"},
-                       new int[] {1, 0, 1, 1}
-                       );
-
-      // Narita Airport
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "成田空港",
-                       new String[] { "成田",
-                                      "成田空港",
-                                      "空港" },
-                       new int[] {1, 0, 1});
-
-      // Kyoto University Baseball Club
-      // nocommit --segments differently but perhaps OK
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "京都大学硬式野球部",
-                       new String[] { "京都",
-                                      "京都大学硬式野球部",
-                                      "大学",
-                                      "硬式",
-                                      "野球",
-                                      "部" },
-                       new int[] {1, 0, 1, 1, 1, 1});
-    } else {
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "シニアソフトウェアエンジニア",
-                       new String[] { "シニア",
-                                      "ソフトウェア",
-                                      "エンジニア" },
-                       new int[] { 1, 1, 1}
-                       );
-      // Kansai International Airport:
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "関西国際空港",
-                       new String[] { "関西",
-                                      "国際",
-                                      "空港" },
-                       new int[] {1, 1, 1}
-                       );
-
-      // Konika Minolta Holdings; not quite the right
-      // segmentation (see LUCENE-3726):
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "コニカミノルタホールディングス",
-                       new String[] { "コニカ",
-                                      "ミノルタ", 
-                                      "ホールディングス"},
-                       new int[] {1, 1, 1}
-                       );
-
-      // Narita Airport
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "成田空港",
-                       new String[] { "成田",
-                                      "空港" },
-                       new int[] {1, 1});
-
-      // Kyoto University Baseball Club
-      assertAnalyzesTo(new KuromojiAnalyzer(TEST_VERSION_CURRENT), "京都大学硬式野球部",
-                       new String[] { "京都",
-                                      "大学",
-                                      "硬式",
-                                      "野球",
-                                      "部" },
-                       new int[] {1, 1, 1, 1, 1});
-    }
+    assertAnalyzesTo(a, "シニアソフトウェアエンジニア",
+                     new String[] { "シニア",
+                                    "シニアソフトウェアエンジニア",
+                                    "ソフトウェア",
+                                    "エンジニア" },
+                     new int[] { 1, 0, 1, 1}
+                     );
+
+    // Kansai International Airport:
+    assertAnalyzesTo(a, "関西国際空港",
+                     new String[] { "関西",
+                                    "関西国際空港", // zero pos inc
+                                    "国際",
+                                    "空港" },
+                     new int[] {1, 0, 1, 1}
+                     );
+
+    // Konika Minolta Holdings; not quite the right
+    // segmentation (see LUCENE-3726):
+    assertAnalyzesTo(a, "コニカミノルタホールディングス",
+                     new String[] { "コニカ",
+                                    "コニカミノルタホールディングス", // zero pos inc
+                                    "ミノルタ", 
+                                    "ホールディングス"},
+                     new int[] {1, 0, 1, 1}
+                     );
+
+    // Narita Airport
+    assertAnalyzesTo(a, "成田空港",
+                     new String[] { "成田",
+                                    "成田空港",
+                                    "空港" },
+                     new int[] {1, 0, 1});
+
   }
 
   
@@ -153,6 +144,16 @@ public class TestKuromojiAnalyzer extend
    * blast random strings against the analyzer
    */
   public void testRandom() throws IOException {
-    checkRandomData(random, new KuromojiAnalyzer(TEST_VERSION_CURRENT), atLeast(10000));
+    final Analyzer a = new KuromojiAnalyzer(TEST_VERSION_CURRENT, null, Mode.SEARCH,
+                                            KuromojiAnalyzer.getDefaultStopSet(),
+                                            KuromojiAnalyzer.getDefaultStopTags());
+    checkRandomData(random, a, atLeast(10000));
+  }
+
+  public void testRandomWithCompounds() throws IOException {
+    final Analyzer a = new KuromojiAnalyzer(TEST_VERSION_CURRENT, null, Mode.SEARCH_WITH_COMPOUNDS,
+                                            KuromojiAnalyzer.getDefaultStopSet(),
+                                            KuromojiAnalyzer.getDefaultStopTags());
+    checkRandomData(random, a, atLeast(10000));
   }
 }

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiTokenizer.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiTokenizer.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiTokenizer.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestKuromojiTokenizer.java Tue Feb 14 11:37:45 2012
@@ -17,10 +17,10 @@ package org.apache.lucene.analysis.kurom
  * limitations under the License.
  */
 
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.LineNumberReader;
 import java.io.Reader;
 import java.io.StringReader;
 
@@ -30,6 +30,7 @@ import org.apache.lucene.analysis.TokenS
 import org.apache.lucene.analysis.Tokenizer;
 import org.apache.lucene.analysis.kuromoji.Segmenter.Mode;
 import org.apache.lucene.analysis.kuromoji.dict.UserDictionary;
+import org.apache.lucene.analysis.kuromoji.tokenattributes.*;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.util.IOUtils;
 import org.apache.lucene.util.UnicodeUtil;
@@ -57,16 +58,37 @@ public class TestKuromojiTokenizer exten
   private Analyzer analyzer = new Analyzer() {
     @Override
     protected TokenStreamComponents createComponents(String fieldName, Reader reader) {
-      //Tokenizer tokenizer = new KuromojiTokenizer(new Segmenter(readDict()), reader);
-      //Tokenizer tokenizer = new KuromojiTokenizer(reader);
+      Tokenizer tokenizer = new KuromojiTokenizer2(reader, readDict(), false, Mode.SEARCH);
+      return new TokenStreamComponents(tokenizer, tokenizer);
+    }
+  };
+
+  private Analyzer analyzerNoPunct = new Analyzer() {
+    @Override
+    protected TokenStreamComponents createComponents(String fieldName, Reader reader) {
       Tokenizer tokenizer = new KuromojiTokenizer2(reader, readDict(), true, Mode.SEARCH);
-      //Tokenizer tokenizer = new KuromojiTokenizer2(reader, null, true, true);
+      return new TokenStreamComponents(tokenizer, tokenizer);
+    }
+  };
+
+  private Analyzer analyzerWithCompounds = new Analyzer() {
+    @Override
+    protected TokenStreamComponents createComponents(String fieldName, Reader reader) {
+      Tokenizer tokenizer = new KuromojiTokenizer2(reader, readDict(), false, Mode.SEARCH_WITH_COMPOUNDS);
+      return new TokenStreamComponents(tokenizer, tokenizer);
+    }
+  };
+  
+  private Analyzer extendedModeAnalyzerNoPunct = new Analyzer() {
+    @Override
+    protected TokenStreamComponents createComponents(String fieldName, Reader reader) {
+      Tokenizer tokenizer = new KuromojiTokenizer2(reader, readDict(), true, Mode.EXTENDED);
       return new TokenStreamComponents(tokenizer, tokenizer);
     }
   };
   
   public void testDecomposition1() throws Exception {
-    assertAnalyzesTo(analyzer, "本来は、貧困層の女性や子供に医療保護を提供するために創設された制度である、" +
+    assertAnalyzesTo(analyzerNoPunct, "本来は、貧困層の女性や子供に医療保護を提供するために創設された制度である、" +
                          "アメリカ低所得者医療援助制度が、今日では、その予算の約3分の1を老人に費やしている。",
      new String[] { "本来", "は",  "貧困", "層", "の", "女性", "や", "子供", "に", "医療", "保護", "を",      
                     "提供", "する", "ため", "に", "創設", "さ", "れ", "た", "制度", "で", "ある",  "アメリカ", 
@@ -82,7 +104,7 @@ public class TestKuromojiTokenizer exten
   }
   
   public void testDecomposition2() throws Exception {
-    assertAnalyzesTo(analyzer, "麻薬の密売は根こそぎ絶やさなければならない",
+    assertAnalyzesTo(analyzerNoPunct, "麻薬の密売は根こそぎ絶やさなければならない",
       new String[] { "麻薬", "の", "密売", "は", "根こそぎ", "絶やさ", "なけれ", "ば", "なら", "ない" },
       new int[] { 0, 2, 3, 5, 6,  10, 13, 16, 17, 19 },
       new int[] { 2, 3, 5, 6, 10, 13, 16, 17, 19, 21 }
@@ -90,7 +112,7 @@ public class TestKuromojiTokenizer exten
   }
   
   public void testDecomposition3() throws Exception {
-    assertAnalyzesTo(analyzer, "魔女狩大将マシュー・ホプキンス。",
+    assertAnalyzesTo(analyzerNoPunct, "魔女狩大将マシュー・ホプキンス。",
       new String[] { "魔女", "狩", "大将", "マシュー",  "ホプキンス" },
       new int[] { 0, 2, 3, 5, 10 },
       new int[] { 2, 3, 5, 9, 15 }
@@ -132,7 +154,7 @@ public class TestKuromojiTokenizer exten
 
   /** Tests that sentence offset is incorporated into the resulting offsets */
   public void testTwoSentences() throws Exception {
-    assertAnalyzesTo(analyzer, "魔女狩大将マシュー・ホプキンス。 魔女狩大将マシュー・ホプキンス。",
+    assertAnalyzesTo(analyzerNoPunct, "魔女狩大将マシュー・ホプキンス。 魔女狩大将マシュー・ホプキンス。",
       new String[] { "魔女", "狩", "大将", "マシュー", "ホプキンス",  "魔女", "狩", "大将", "マシュー",  "ホプキンス"  },
       new int[] { 0, 2, 3, 5, 10, 17, 19, 20, 22, 27 },
       new int[] { 2, 3, 5, 9, 15, 19, 20, 22, 26, 32 }
@@ -176,18 +198,32 @@ public class TestKuromojiTokenizer exten
       }
     }
   }
+
+  public void testOnlyPunctuation() throws IOException {
+    TokenStream ts = analyzerNoPunct.tokenStream("foo", new StringReader("。、。。"));
+    ts.reset();
+    assertFalse(ts.incrementToken());
+    ts.end();
+  }
+
+  public void testOnlyPunctuationExtended() throws IOException {
+    TokenStream ts = extendedModeAnalyzerNoPunct.tokenStream("foo", new StringReader("......"));
+    ts.reset();
+    assertFalse(ts.incrementToken());
+    ts.end();
+  }
   
   // note: test is kinda silly since kuromoji emits punctuation tokens.
   // but, when/if we filter these out it will be useful.
   public void testEnd() throws Exception {
-    assertTokenStreamContents(analyzer.tokenStream("foo", new StringReader("これは本ではない")),
+    assertTokenStreamContents(analyzerNoPunct.tokenStream("foo", new StringReader("これは本ではない")),
         new String[] { "これ", "は", "本", "で", "は", "ない" },
         new int[] { 0, 2, 3, 4, 5, 6 },
         new int[] { 2, 3, 4, 5, 6, 8 },
         new Integer(8)
     );
     
-    assertTokenStreamContents(analyzer.tokenStream("foo", new StringReader("これは本ではない    ")),
+    assertTokenStreamContents(analyzerNoPunct.tokenStream("foo", new StringReader("これは本ではない    ")),
         new String[] { "これ", "は", "本", "で", "は", "ない"  },
         new int[] { 0, 2, 3, 4, 5, 6, 8 },
         new int[] { 2, 3, 4, 5, 6, 8, 9 },
@@ -215,6 +251,273 @@ public class TestKuromojiTokenizer exten
                               new Integer(3)
     );
   }
+  
+  public void testSegmentation() throws Exception {
+    // Skip tests for Michelle Kwan -- UniDic segments Kwan as ク ワン
+    //		String input = "ミシェル・クワンが優勝しました。スペースステーションに行きます。うたがわしい。";
+    //		String[] surfaceForms = {
+    //				"ミシェル", "・", "クワン", "が", "優勝", "し", "まし", "た", "。",
+    //				"スペース", "ステーション", "に", "行き", "ます", "。",
+    //				"うたがわしい", "。"
+    //		};
+    String input = "スペースステーションに行きます。うたがわしい。";
+    String[] surfaceForms = {
+        "スペース", "ステーション", "に", "行き", "ます", "。",
+        "うたがわしい", "。"
+    };
+    assertAnalyzesTo(analyzer,
+                     input,
+                     surfaceForms);
+    assertAnalyzesTo(analyzerWithCompounds,
+                     input,
+                     surfaceForms);
+  }
+
+  private void assertReadings(String input, String... readings) throws IOException {
+    TokenStream ts = analyzer.tokenStream("ignored", new StringReader(input));
+    ReadingAttribute readingAtt = ts.addAttribute(ReadingAttribute.class);
+    ts.reset();
+    for(String reading : readings) {
+      assertTrue(ts.incrementToken());
+      assertEquals(reading, readingAtt.getReading());
+    }
+    assertFalse(ts.incrementToken());
+    ts.end();
+  }
+
+  private void assertPronunciations(String input, String... pronunciations) throws IOException {
+    TokenStream ts = analyzer.tokenStream("ignored", new StringReader(input));
+    ReadingAttribute readingAtt = ts.addAttribute(ReadingAttribute.class);
+    ts.reset();
+    for(String pronunciation : pronunciations) {
+      assertTrue(ts.incrementToken());
+      assertEquals(pronunciation, readingAtt.getPronunciation());
+    }
+    assertFalse(ts.incrementToken());
+    ts.end();
+  }
+  
+  private void assertBaseForms(String input, String... baseForms) throws IOException {
+    TokenStream ts = analyzer.tokenStream("ignored", new StringReader(input));
+    BaseFormAttribute baseFormAtt = ts.addAttribute(BaseFormAttribute.class);
+    ts.reset();
+    for(String baseForm : baseForms) {
+      assertTrue(ts.incrementToken());
+      assertEquals(baseForm, baseFormAtt.getBaseForm());
+    }
+    assertFalse(ts.incrementToken());
+    ts.end();
+  }
+
+  private void assertInflectionTypes(String input, String... inflectionTypes) throws IOException {
+    TokenStream ts = analyzer.tokenStream("ignored", new StringReader(input));
+    InflectionAttribute inflectionAtt = ts.addAttribute(InflectionAttribute.class);
+    ts.reset();
+    for(String inflectionType : inflectionTypes) {
+      assertTrue(ts.incrementToken());
+      assertEquals(inflectionType, inflectionAtt.getInflectionType());
+    }
+    assertFalse(ts.incrementToken());
+    ts.end();
+  }
+
+  private void assertInflectionForms(String input, String... inflectionForms) throws IOException {
+    TokenStream ts = analyzer.tokenStream("ignored", new StringReader(input));
+    InflectionAttribute inflectionAtt = ts.addAttribute(InflectionAttribute.class);
+    ts.reset();
+    for(String inflectionForm : inflectionForms) {
+      assertTrue(ts.incrementToken());
+      assertEquals(inflectionForm, inflectionAtt.getInflectionForm());
+    }
+    assertFalse(ts.incrementToken());
+    ts.end();
+  }
+  
+  private void assertPartsOfSpeech(String input, String... partsOfSpeech) throws IOException {
+    TokenStream ts = analyzer.tokenStream("ignored", new StringReader(input));
+    PartOfSpeechAttribute partOfSpeechAtt = ts.addAttribute(PartOfSpeechAttribute.class);
+    ts.reset();
+    for(String partOfSpeech : partsOfSpeech) {
+      assertTrue(ts.incrementToken());
+      assertEquals(partOfSpeech, partOfSpeechAtt.getPartOfSpeech());
+    }
+    assertFalse(ts.incrementToken());
+    ts.end();
+  }
+  
+  public void testReadings() throws Exception {
+    assertReadings("寿司が食べたいです。",
+                   "スシ",
+                   "ガ",
+                   "タベ",
+                   "タイ",
+                   "デス",
+                   "。");
+  }
+  
+  public void testReadings2() throws Exception {
+    assertReadings("多くの学生が試験に落ちた。",
+                   "オオク",
+                   "ノ",
+                   "ガクセイ",
+                   "ガ",
+                   "シケン",
+                   "ニ",
+                   "オチ",
+                   "タ",
+                   "。");
+  }
+  
+  public void testPronunciations() throws Exception {
+    assertPronunciations("寿司が食べたいです。",
+                         "スシ",
+                         "ガ",
+                         "タベ",
+                         "タイ",
+                         "デス",
+                         "。");
+  }
+  
+  public void testPronunciations2() throws Exception {
+    // pronunciation differs from reading here
+    assertPronunciations("多くの学生が試験に落ちた。",
+                         "オーク",
+                         "ノ",
+                         "ガクセイ",
+                         "ガ",
+                         "シケン",
+                         "ニ",
+                         "オチ",
+                         "タ",
+                         "。");
+  }
+  
+  public void testBasicForms() throws Exception {
+    assertBaseForms("それはまだ実験段階にあります。",
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    "ある",
+                    null,
+                    null);
+  }
+  
+  public void testInflectionTypes() throws Exception {
+    assertInflectionTypes("それはまだ実験段階にあります。",
+                          null,
+                          null,
+                          null,
+                          null,
+                          null,
+                          null,
+                          "五段・ラ行",
+                          "特殊・マス",
+                          null);
+  }
+  
+  public void testInflectionForms() throws Exception {
+    assertInflectionForms("それはまだ実験段階にあります。",
+                          null,
+                          null,
+                          null,
+                          null,
+                          null,
+                          null,
+                          "連用形",
+                          "基本形",
+                          null);
+  }
+  
+  public void testPartOfSpeech() throws Exception {
+    assertPartsOfSpeech("それはまだ実験段階にあります。",
+                        "名詞-代名詞-一般",
+                        "助詞-係助詞",
+                        "副詞-助詞類接続",
+                        "名詞-サ変接続",
+                        "名詞-一般",
+                        "助詞-格助詞-一般",
+                        "動詞-自立",
+                        "助動詞",
+                        "記号-句点");
+  }
+
+  // TODO: the next 2 tests are no longer using the first/last word ids, maybe lookup the words and fix?
+  // do we have a possibility to actually lookup the first and last word from dictionary?
+  public void testYabottai() throws Exception {
+    assertAnalyzesTo(analyzer, "やぼったい",
+                     new String[] {"やぼったい"});
+    assertAnalyzesTo(analyzerWithCompounds, "やぼったい",
+                     new String[] {"やぼったい"});
+  }
+
+  public void testTsukitosha() throws Exception {
+    assertAnalyzesTo(analyzer, "突き通しゃ",
+                     new String[] {"突き通しゃ"});
+    assertAnalyzesTo(analyzerWithCompounds, "突き通しゃ",
+                     new String[] {"突き通しゃ"});
+  }
+
+  public void testBocchan() throws Exception {
+    doTestBocchan(1, true);
+    doTestBocchan(1, false);
+  }
+
+  @Nightly
+  public void testBocchanBig() throws Exception {
+    doTestBocchan(100, true);
+    doTestBocchan(100, false);
+  }
+  
+  private void doTestBocchan(int numIterations, boolean includeCompounds) throws Exception {
+    LineNumberReader reader = new LineNumberReader(new InputStreamReader(
+        this.getClass().getResourceAsStream("bocchan.utf-8")));
+    
+    String line = reader.readLine();
+    reader.close();
+    
+    if (VERBOSE) {
+      System.out.println("Test for Bocchan without pre-splitting sentences");
+    }
+
+    /*
+    if (numIterations > 1) {
+      // warmup
+      for (int i = 0; i < numIterations; i++) {
+        final TokenStream ts = analyzer.tokenStream("ignored", new StringReader(line));
+        ts.reset();
+        while(ts.incrementToken());
+      }
+    }
+    */
+
+    long totalStart = System.currentTimeMillis();
+    for (int i = 0; i < numIterations; i++) {
+      final Analyzer a = includeCompounds ? analyzerWithCompounds : analyzer;
+      final TokenStream ts = a.tokenStream("ignored", new StringReader(line));
+      ts.reset();
+      while(ts.incrementToken());
+    }
+    String[] sentences = line.split("、|。");
+    if (VERBOSE) {
+      System.out.println("Total time : " + (System.currentTimeMillis() - totalStart));
+      System.out.println("Test for Bocchan with pre-splitting sentences (" + sentences.length + " sentences)");
+    }
+    totalStart = System.currentTimeMillis();
+    for (int i = 0; i < numIterations; i++) {
+      for (String sentence: sentences) {
+        final TokenStream ts = analyzer.tokenStream("ignored", new StringReader(sentence));
+        ts.reset();
+        while(ts.incrementToken());
+      }
+    }
+    if (VERBOSE) {
+      System.out.println("Total time : " + (System.currentTimeMillis() - totalStart));
+    }
+  }
+
 
   // nocommit need a still better test, where an input is
   // tokenized to multiple tokens that would be different

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestQuality.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestQuality.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestQuality.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestQuality.java Tue Feb 14 11:37:45 2012
@@ -59,7 +59,6 @@ public class TestQuality extends LuceneT
      sentence agreement?: 0.998161495317142
      word agreement?: 0.999587584716181
      */
-    //final Tokenizer tokenizer = new KuromojiTokenizer(null, null, true, Mode.SEARCH);
 
     StringBuilder sb = new StringBuilder();
 
@@ -73,15 +72,17 @@ public class TestQuality extends LuceneT
       // end of each sentence...
       maxLen = Math.max(line1.length(), maxLen);
       sb.append(line1);
+      /*
       if (ONE_TIME && count++ == 100) {
         // nocommit;
         break;
       }
+      */
     }
-    System.out.println("maxLen=" + maxLen);
+    //System.out.println("maxLen=" + maxLen);
 
-    //final Tokenizer tokenizer = new KuromojiTokenizer2(new StringReader(""), null, true, Mode.SEARCH);
-    final Tokenizer tokenizer = new KuromojiTokenizer(new StringReader(""));
+    final Tokenizer tokenizer = new KuromojiTokenizer2(new StringReader(""), null, true, Mode.SEARCH_WITH_COMPOUNDS);
+    //final Tokenizer tokenizer = new KuromojiTokenizer(new StringReader(""));
     final String all = sb.toString();
     final int ITERS = 20;
     CharTermAttribute termAtt = tokenizer.addAttribute(CharTermAttribute.class); 
@@ -123,7 +124,7 @@ public class TestQuality extends LuceneT
     
     //final Tokenizer tokenizer = new KuromojiTokenizer(new StringReader(""));
     //final Tokenizer tokenizer = new KuromojiTokenizer(new Segmenter(Mode.NORMAL), new StringReader(""));
-    final Tokenizer tokenizer = new KuromojiTokenizer2(new StringReader(""), null, true, Mode.SEARCH);
+    final Tokenizer tokenizer = new KuromojiTokenizer2(new StringReader(""), null, true, Mode.SEARCH_WITH_COMPOUNDS);
     
     String line1 = null;
     String line2 = null;
@@ -256,6 +257,7 @@ public class TestQuality extends LuceneT
       }
     }
     assert minPath != null;
+    /*
     if (minEdits > 0) {
       if (!VERBOSE) {
         System.out.println("\nTEST " + lineCount + ": input " + unseg + "; " + minEdits + " edits");
@@ -263,6 +265,7 @@ public class TestQuality extends LuceneT
       System.out.println("    expected: " + expectedTokens);
       System.out.println("      actual: " + minPath);
     }
+    */
     stats.numChars += seg.length();
     stats.numEdits += minEdits;
     stats.numWords += expectedTokens.size();

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestSearchMode.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestSearchMode.java?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestSearchMode.java (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/TestSearchMode.java Tue Feb 14 11:37:45 2012
@@ -33,15 +33,22 @@ import org.apache.lucene.util.IOUtils;
 
 public class TestSearchMode extends BaseTokenStreamTestCase {
   private final static String SEGMENTATION_FILENAME = "search-segmentation-tests.txt";
-  private final Segmenter segmenter = new Segmenter(Mode.SEARCH);
+  private final static String SEGMENTATION_WITH_COMPOUNDS_FILENAME = "search-segmentation-with-compounds-tests.txt";
   private final Analyzer analyzer = new Analyzer() {
     @Override
     protected TokenStreamComponents createComponents(String fieldName, Reader reader) {
-      //Tokenizer tokenizer = new KuromojiTokenizer(segmenter, reader);
       Tokenizer tokenizer = new KuromojiTokenizer2(reader, null, true, Mode.SEARCH);
       return new TokenStreamComponents(tokenizer, tokenizer);
     }
   };
+
+  private final Analyzer analyzerWithCompounds = new Analyzer() {
+    @Override
+    protected TokenStreamComponents createComponents(String fieldName, Reader reader) {
+      Tokenizer tokenizer = new KuromojiTokenizer2(reader, null, true, Mode.SEARCH_WITH_COMPOUNDS);
+      return new TokenStreamComponents(tokenizer, tokenizer);
+    }
+  };
   
   /** Test search mode segmentation */
   public void testSearchSegmentation() throws IOException {
@@ -65,26 +72,46 @@ public class TestSearchMode extends Base
         String[] fields = line.split("\t", 2);
         String sourceText = fields[0];
         String[] expectedTokens = fields[1].split("\\s+");
-        int[] expectedPosIncrs;
-        if (KuromojiTokenizer2.DO_OUTPUT_COMPOUND && expectedTokens.length > 1) {
-          String[] newTokens = new String[expectedTokens.length+1];
-          newTokens[0] = expectedTokens[0];
-          newTokens[1] = fields[0];
-          System.arraycopy(expectedTokens, 1, newTokens, 2, expectedTokens.length-1);
-          expectedTokens = newTokens;
-          expectedPosIncrs = new int[expectedTokens.length];
-          Arrays.fill(expectedPosIncrs, 1);
-          expectedPosIncrs[1] = 0;
-          System.out.println("fixup: " + Arrays.toString(expectedTokens));
-        } else {
-          expectedPosIncrs = new int[expectedTokens.length];
-          Arrays.fill(expectedPosIncrs, 1);
-        }
-        try {
+        int[] expectedPosIncrs = new int[expectedTokens.length];
+        Arrays.fill(expectedPosIncrs, 1);
         assertAnalyzesTo(analyzer, sourceText, expectedTokens, expectedPosIncrs);
-        } catch (Throwable t) {
-        System.out.println("  diffs: " + t);
+      }
+    } finally {
+      is.close();
+    }
+  }
+
+  /** Test search_with_compounds mode segmentation */
+  public void testSearchSegmentationWithCompounds() throws IOException {
+    InputStream is = TestSearchMode.class.getResourceAsStream(SEGMENTATION_WITH_COMPOUNDS_FILENAME);
+    if (is == null) {
+      throw new FileNotFoundException("Cannot find " + SEGMENTATION_FILENAME + " in test classpath");
+    }
+    try {
+      LineNumberReader reader = new LineNumberReader(new InputStreamReader(is, IOUtils.CHARSET_UTF_8));
+      String line = null;
+      while ((line = reader.readLine()) != null) {
+        // Remove comments
+        line = line.replaceAll("#.*$", "");
+        // Skip empty lines or comment lines
+        if (line.trim().isEmpty()) {
+          continue;
+        }
+        if (VERBOSE) {
+          System.out.println("Line no. " + reader.getLineNumber() + ": " + line);
+        }
+        String[] fields = line.split("\t", 2);
+        String sourceText = fields[0];
+        String[] expectedTokens = fields[1].split("\\s+");
+        int[] expectedPosIncrs = new int[expectedTokens.length];
+        for(int tokIDX=0;tokIDX<expectedTokens.length;tokIDX++) {
+          if (expectedTokens[tokIDX].endsWith("/0")) {
+            expectedTokens[tokIDX] = expectedTokens[tokIDX].replace("/0", "");
+          } else {
+            expectedPosIncrs[tokIDX] = 1;
+          }
         }
+        assertAnalyzesTo(analyzerWithCompounds, sourceText, expectedTokens, expectedPosIncrs);
       }
     } finally {
       is.close();

Modified: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-tests.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-tests.txt?rev=1243882&r1=1243881&r2=1243882&view=diff
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-tests.txt (original)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-tests.txt Tue Feb 14 11:37:45 2012
@@ -100,7 +100,7 @@
 スティーブジョブズ	スティーブ ジョブズ
 # Harry Potter	NOTE: Becomes one token (short word)
 ハリーポッター	ハリーポッター
-# Bill Gates	NOTE: Becomes one token (short work)
+# Bill Gates	NOTE: Becomes one token (short word)
 ビルゲイツ	ビルゲイツ
 # Sean Connery	NOTE: Becomes one token (okay)
 ショーンコネリー	ショーンコネリー

Added: lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-with-compounds-tests.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-with-compounds-tests.txt?rev=1243882&view=auto
==============================================================================
--- lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-with-compounds-tests.txt (added)
+++ lucene/dev/branches/lucene3767/modules/analysis/kuromoji/src/test/org/apache/lucene/analysis/kuromoji/search-segmentation-with-compounds-tests.txt Tue Feb 14 11:37:45 2012
@@ -0,0 +1,142 @@
+###
+### Tests for Kuromoji's search mode heuristic
+###
+### In search-mode, Kuromoji uses a heuristic to do extra splitting of words
+### to get a decompounding effect useful for search.  This file includes tests
+### for this heuristic and demonstrates its usefulness, but also weaknesses.
+###
+### This file's format is as follows:
+###	  <text><tab><token1> <token2> ... <token>
+###
+### This file should use UTF-8 encoding and there is one test per line.  The
+### text to be segmented and its expected surface form token sequence is 
+### separated by a tab ('\t').  Tokens are  separated by a half-width space.
+### Whitespace lines and lines starting with a '#' are ignored.  Comments
+### are not allowed on entry line.
+###
+### NOTE: These tests depends on IPADIC
+###
+### Revision history:
+###  - 2012-01-29: Initial version
+###
+
+##
+## Organizations
+##
+
+# Kansai Internationl Airport
+関西国際空港	関西 関西国際空港/0 国際 空港
+# Narita Airport
+成田空港	成田 成田空港/0 空港
+# Haneda Airport
+羽田空港	羽田 羽田空港/0 空港
+# Nara Institute of Science and Technology
+奈良先端科学技術大学院大学	奈良 奈良先端科学技術大学院大学/0 先端 科学 技術 大学院 大学
+# Tokyo University
+東京大学	東京 東京大学/0 大学
+# Kyoto University
+京都大学	京都 京都大学/0 大学
+
+# NOTE: differs from non-compound mode:
+# Kyoto University Baseball Club
+京都大学硬式野球部	京都大 学 硬式 野球 部
+
+##
+## Katakana titles
+##
+
+# Senior Software Engineer
+シニアソフトウェアエンジニア	シニア シニアソフトウェアエンジニア/0 ソフトウェア エンジニア
+# Software Engineer
+ソフトウェアエンジニア	ソフトウェア エンジニア
+# Senior Project Manager
+シニアプロジェクトマネジャー	シニア シニアプロジェクトマネジャー/0 プロジェクト マネジャー
+# Project Manager
+プロジェクトマネジャー	プロジェクト マネジャー
+# Senior Sales Engineer
+シニアセールスエンジニア	シニア シニアセールスエンジニア/0 セールス エンジニア
+# System Architect
+システムアーキテクト	システム システムアーキテクト/0 アーキテクト
+# Senior System Architect
+シニアシステムアーキテクト	シニア シニアシステムアーキテクト/0 システム アーキテクト
+# System Administrator
+システムアドミニストレータ	システム アドミニストレータ
+システムアドミニストレーター	システム システムアドミニストレーター/0 アドミニストレーター
+# Senior System Administrator
+シニアシステムアドミニストレーター	シニア シニアシステムアドミニストレーター/0 システム アドミニストレーター
+
+##
+## Company names (several are fictitious)
+##
+
+# SoftBank Mobile
+ソフトバンクモバイル	ソフトバンク モバイル
+# Alpine Materials
+アルパインマテリアルズ	アルパイン アルパインマテリアルズ/0 マテリアルズ
+# Sapporo Holdings
+サッポロホールディングス	サッポロ ホールディングス
+# Yamada Corporation
+ヤマダコーポレーション	ヤマダ ヤマダコーポレーション/0 コーポレーション
+# Canon Semiconductor equipement	NOTE: Semiconductor becomes semi + conductor
+キヤノンセミコンダクターエクィップメント	キヤノン キヤノンセミコンダクターエクィップメント/0 セミ コンダクター エクィップメント
+# Orental Chain
+オリエンタルチエン	オリエンタル オリエンタルチエン/0 チエン
+# Ally Projects Japan	NOTE: Becomes one token as プロジェクツ is not in IPADIC
+アーリープロジェクツジャパン	アーリープロジェクツジャパン
+# Peter Pan Corporation
+ピーターパンコーポレーション	ピーター ピーターパンコーポレーション/0 パン コーポレーション
+# AIM Create
+エイムクリエイツ	エイムクリエイツ
+# Mars Engineering
+マースエンジニアリング	マース マースエンジニアリング/0 エンジニアリング
+# Fuji Protein Technology
+フジプロテインテクノロジー	フジ フジプロテインテクノロジー/0 プロテイン テクノロジー
+
+##
+## Person names
+##
+
+# Michael Jackson
+マイケルジャクソン	マイケル ジャクソン
+# Steve Jobs
+スティーブジョブズ	スティーブ ジョブズ
+# Harry Potter	NOTE: Becomes one token (short word)
+ハリーポッター	ハリーポッター
+# Bill Gates	NOTE: Becomes one token (short word)
+ビルゲイツ	ビルゲイツ
+# Sean Connery	NOTE: Becomes one token (okay)
+ショーンコネリー	ショーンコネリー
+
+##
+## Other nouns
+##
+
+# Holdings
+ホールディングス	ホールディングス
+# Engineering
+エンジニアリング	エンジニアリング
+# Software Engineering
+ソフトウェアエンジニアリング	ソフトウェア エンジニアリング
+# Shopping center
+ショッピングセンター	ショッピング センター
+# Game center (arcade)	NOTE: One token because of short word
+ゲームセンター	ゲームセンター
+# Christmas shopping
+クリスマスショッピング	クリスマス ショッピング
+# Download file
+ダウンロードファイル	ダウンロード ファイル
+# Technology
+テクノロジー	テクノロジー
+# Lillehammer Olympics
+リレハンメルオリンピック	リレハンメル オリンピック
+
+##
+## Problematic terms
+##
+
+# JT Engineering	NOTE: Becomes J Tien ginia ring (substrings are in IPADIC)
+ジェイティエンジニアリング	ジェイ ジェイティエンジニアリング/0 ティエン ジニア リング
+# Anchovy pasta	NOTE: Become Anch yvipasta
+アンチョビパスタ	アンチ アンチョビパスタ/0 ョビパスタ
+# Surprise gift	NOTE: Becomes one token (surprise not in IPADIC)
+サプライズギフト	サプライズギフト