Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/BaseCharFilter.java
===================================================================
--- lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/BaseCharFilter.java	(revision 1690899)
+++ lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/BaseCharFilter.java	(working copy)
@@ -33,8 +33,9 @@
 
   private int offsets[];
   private int diffs[];
+  private int inputOffsets[];
   private int size = 0;
-  
+
   public BaseCharFilter(Reader in) {
     super(in);
   }
@@ -42,17 +43,24 @@
   /** Retrieve the corrected offset. */
   @Override
   protected int correct(int currentOff) {
+    if(offsets == null) return currentOff;
+
+    int correctIndex = getDiffsIndex(currentOff);
+    return currentOff + diffs[correctIndex];
+  }
+
+  protected int getDiffsIndex(int currentOff){
     if (offsets == null || currentOff < offsets[0]) {
-      return currentOff;
+      return 0;
     }
-    
+
     int hi = size - 1;
     if(currentOff >= offsets[hi])
-      return currentOff + diffs[hi];
+      return hi;
 
     int lo = 0;
     int mid = -1;
-    
+
     while (hi >= lo) {
       mid = (lo + hi) >>> 1;
       if (currentOff < offsets[mid])
@@ -60,18 +68,29 @@
       else if (currentOff > offsets[mid])
         lo = mid + 1;
       else
-        return currentOff + diffs[mid];
+        return mid;
     }
 
     if (currentOff < offsets[mid])
-      return mid == 0 ? currentOff : currentOff + diffs[mid-1];
+      return mid == 0 ? mid : mid-1;
     else
-      return currentOff + diffs[mid];
+      return mid;
   }
-  
+
+  @Override
+  protected int correctEndToken(int currentOff) {
+    if(offsets == null) return currentOff;
+
+    int correctIndex = getDiffsIndex(currentOff);
+    if(correctIndex == 0 || offsets[correctIndex] + diffs[correctIndex-1] > inputOffsets[correctIndex])
+      return currentOff + diffs[correctIndex];
+
+    return offsets[correctIndex] + diffs[correctIndex-1];
+  }
+
   protected int getLastCumulativeDiff() {
     return offsets == null ?
-      0 : diffs[size-1];
+        0 : diffs[size-1];
   }
 
   /**
@@ -88,23 +107,50 @@
    *                       to the output offset
    */
   protected void addOffCorrectMap(int off, int cumulativeDiff) {
+    addOffCorrectMap(off, cumulativeDiff, off - cumulativeDiff);
+  }
+
+  /**
+   * <p>
+   *   Adds an offset correction mapping at the given output stream offset.
+   * </p>
+   * <p>
+   *   Assumption: the offset given with each successive call to this method
+   *   will not be smaller than the offset given at the previous invocation.
+   * </p>
+   *
+   * @param off The output stream offset at which to apply the correction
+   * @param cumulativeDiff The input offset is given by adding this
+   *                       to the output offset
+   * @param inputOffset where the correction first applied
+   */
+  protected void addOffCorrectMap(int off, int cumulativeDiff, int inputOffset) {
     if (offsets == null) {
       offsets = new int[64];
       diffs = new int[64];
+      inputOffsets = new int[64];
+      offsets[0] = 0;
+      diffs[0] = 0;
+      inputOffsets[0] = 0;
+      size++;
     } else if (size == offsets.length) {
       offsets = ArrayUtil.grow(offsets);
       diffs = ArrayUtil.grow(diffs);
+      inputOffsets = ArrayUtil.grow(inputOffsets);
     }
-    
+
     assert (size == 0 || off >= offsets[size - 1])
         : "Offset #" + size + "(" + off + ") is less than the last recorded offset "
-          + offsets[size - 1] + "\n" + Arrays.toString(offsets) + "\n" + Arrays.toString(diffs);
-    
+        + offsets[size - 1] + "\n" + Arrays.toString(offsets) + "\n" + Arrays.toString(diffs);
+
     if (size == 0 || off != offsets[size - 1]) {
       offsets[size] = off;
-      diffs[size++] = cumulativeDiff;
+      diffs[size] = cumulativeDiff;
+      inputOffsets[size] = inputOffset;
+      size++;
     } else { // Overwrite the diff at the last recorded offset
       diffs[size - 1] = cumulativeDiff;
+      inputOffsets[size-1] = inputOffset;
     }
   }
 }
Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/MappingCharFilter.java
===================================================================
--- lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/MappingCharFilter.java	(revision 1690899)
+++ lucene/analysis/common/src/java/org/apache/lucene/analysis/charfilter/MappingCharFilter.java	(working copy)
@@ -150,7 +150,7 @@
           final int prevCumulativeDiff = getLastCumulativeDiff();
           if (diff > 0) {
             // Replacement is shorter than matched input:
-            addOffCorrectMap(inputOff - diff - prevCumulativeDiff, prevCumulativeDiff + diff);
+            addOffCorrectMap(inputOff - diff - prevCumulativeDiff, prevCumulativeDiff + diff, inputOff - lastMatchLen);
           } else {
             // Replacement is longer than matched input: remap
             // the "extra" chars all back to the same input
@@ -157,7 +157,7 @@
             // offset:
             final int outputStart = inputOff - prevCumulativeDiff;
             for(int extraIDX=0;extraIDX<-diff;extraIDX++) {
-              addOffCorrectMap(outputStart + extraIDX, prevCumulativeDiff - extraIDX - 1);
+              addOffCorrectMap(outputStart + extraIDX, prevCumulativeDiff - extraIDX - 1, inputOff - lastMatchLen);
             }
           }
         }
Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/pattern/PatternReplaceCharFilter.java
===================================================================
--- lucene/analysis/common/src/java/org/apache/lucene/analysis/pattern/PatternReplaceCharFilter.java	(revision 1690899)
+++ lucene/analysis/common/src/java/org/apache/lucene/analysis/pattern/PatternReplaceCharFilter.java	(working copy)
@@ -121,12 +121,12 @@
           cumulative += groupSize - replacementSize;
           int atIndex = lengthBeforeReplacement + replacementSize;
           // System.err.println(atIndex + "!" + cumulative);
-          addOffCorrectMap(atIndex, cumulative);
+          addOffCorrectMap(atIndex, cumulative, m.start());
         } else {
           // The replacement is larger. Every new index needs to point to the last
           // element of the original group (if any).
           for (int i = groupSize; i < replacementSize; i++) {
-            addOffCorrectMap(lengthBeforeReplacement + i, --cumulative);
+            addOffCorrectMap(lengthBeforeReplacement + i, --cumulative, m.start());
             // System.err.println((lengthBeforeReplacement + i) + " " + cumulative);
           }
         }
Index: lucene/analysis/common/src/test/org/apache/lucene/analysis/charfilter/TestMappingCharFilter.java
===================================================================
--- lucene/analysis/common/src/test/org/apache/lucene/analysis/charfilter/TestMappingCharFilter.java	(revision 1690899)
+++ lucene/analysis/common/src/test/org/apache/lucene/analysis/charfilter/TestMappingCharFilter.java	(working copy)
@@ -54,7 +54,7 @@
     builder.add( "k", "kkk" );
     builder.add( "ll", "llll" );
 
-    builder.add( "empty", "" );
+    builder.add("empty", "");
 
     // BMP (surrogate pair):
     builder.add(UnicodeUtil.newString(new int[] {0x1D122}, 0, 1), "fclef");
@@ -64,6 +64,21 @@
     normMap = builder.build();
   }
 
+
+  public void testWrapper() throws Exception {
+    NormalizeCharMap.Builder builder = new NormalizeCharMap.Builder();
+    builder.add("(", "");
+    builder.add(")", "");
+    NormalizeCharMap normMap = builder.build();
+    CharFilter cs = new MappingCharFilter( normMap, new StringReader( "F31)))))" ) );
+    TokenStream ts =whitespaceMockTokenizer(cs);
+    assertTokenStreamContents(ts, new String[]{"F31"}, new int[]{0}, new int[]{3}, 8);
+
+    cs = new MappingCharFilter( normMap, new StringReader( "(F31)" ) );
+    ts =whitespaceMockTokenizer(cs);
+    assertTokenStreamContents(ts, new String[]{"F31"}, new int[]{1}, new int[]{4}, 5);
+  }
+
   public void testReaderReset() throws Exception {
     CharFilter cs = new MappingCharFilter( normMap, new StringReader( "x" ) );
     char[] buf = new char[10];
Index: lucene/core/src/java/org/apache/lucene/analysis/CharFilter.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/analysis/CharFilter.java	(revision 1690899)
+++ lucene/core/src/java/org/apache/lucene/analysis/CharFilter.java	(working copy)
@@ -72,6 +72,13 @@
    * @return corrected offset
    */
   protected abstract int correct(int currentOff);
+
+  /**
+   * Default implementation for correct end token offset.
+   */
+  protected int correctEndToken(int currentOff){
+    return correct(currentOff);
+  }
   
   /**
    * Chains the corrected offset through the input
@@ -81,4 +88,9 @@
     final int corrected = correct(currentOff);
     return (input instanceof CharFilter) ? ((CharFilter) input).correctOffset(corrected) : corrected;
   }
+
+  public int correctEndTokenOffset(int currentOff) {
+    final int corrected = correctEndToken(currentOff);
+    return (input instanceof CharFilter) ? ((CharFilter) input).correctEndToken(corrected) : corrected;
+  }
 }
Index: lucene/core/src/java/org/apache/lucene/analysis/Tokenizer.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/analysis/Tokenizer.java	(revision 1690899)
+++ lucene/core/src/java/org/apache/lucene/analysis/Tokenizer.java	(working copy)
@@ -80,6 +80,10 @@
     return (input instanceof CharFilter) ? ((CharFilter) input).correctOffset(currentOff) : currentOff;
   }
 
+  protected final int correctEndTokenOffset(int currentOff){
+    return (input instanceof CharFilter) ? ((CharFilter) input).correctEndTokenOffset(currentOff) : currentOff;
+  }
+
   /** Expert: Set a new reader on the Tokenizer.  Typically, an
    *  analyzer (in its tokenStream method) will use
    *  this to re-use a previously created tokenizer. */
Index: lucene/test-framework/src/java/org/apache/lucene/analysis/MockCharFilter.java
===================================================================
--- lucene/test-framework/src/java/org/apache/lucene/analysis/MockCharFilter.java	(revision 1690899)
+++ lucene/test-framework/src/java/org/apache/lucene/analysis/MockCharFilter.java	(working copy)
@@ -95,6 +95,14 @@
     assert ret >= 0 : "currentOff=" + currentOff + ",diff=" + (ret-currentOff);
     return ret;
   }
+
+  @Override
+  public int correctEndToken(int currentOff) {
+    Map.Entry<Integer,Integer> lastEntry = corrections.lowerEntry(currentOff+1);
+    int ret = lastEntry == null ? currentOff : currentOff + lastEntry.getValue();
+    assert ret >= 0 : "currentOff=" + currentOff + ",diff=" + (ret-currentOff);
+    return ret;
+  }
   
   protected void addOffCorrectMap(int off, int cumulativeDiff) {
     corrections.put(off, cumulativeDiff);
Index: lucene/test-framework/src/java/org/apache/lucene/analysis/MockTokenizer.java
===================================================================
--- lucene/test-framework/src/java/org/apache/lucene/analysis/MockTokenizer.java	(revision 1690899)
+++ lucene/test-framework/src/java/org/apache/lucene/analysis/MockTokenizer.java	(working copy)
@@ -173,7 +173,7 @@
           bufferedCodePoint = -1;
         }
         int correctedStartOffset = correctOffset(startOffset);
-        int correctedEndOffset = correctOffset(endOffset);
+        int correctedEndOffset = correctEndTokenOffset(endOffset);
         if (correctedStartOffset < 0) {
           failAlways("invalid start offset: " + correctedStartOffset + ", before correction: " + startOffset);
         }
