code: package morfologik.stemming;
  
  import java.nio.ByteBuffer;
  
  /**
   * Encodes <code>dst</code> relative to <code>src</code> by trimming whatever
   * non-equal suffix and prefix <code>src</code> and <code>dst</code> have. The
   * output code is (bytes):
   * 
   * <pre>
   * {P}{K}{suffix}
   * </pre>
   * 
   * where (<code>P</code> - 'A') bytes should be trimmed from the start of
   * <code>src</code>, (<code>K</code> - 'A') bytes should be trimmed from the
   * end of <code>src</code> and then the <code>suffix</code> should be appended
   * to the resulting byte sequence.
   * 
   * <p>
   * Examples:
   * </p>
   * 
   * <pre>
   * src: abc
   * dst: abcd
   * encoded: AAd
   * 
   * src: abc
   * dst: xyz
   * encoded: ADxyz
   * </pre>
   */
  public class TrimPrefixAndSuffixEncoder implements ISequenceEncoder {
    /**
     * Maximum encodable single-byte code.
     */
    private static final int REMOVE_EVERYTHING = 255;
  
    public ByteBuffer encode(ByteBuffer reuse, ByteBuffer source, ByteBuffer target) {
      // Search for the maximum matching subsequence that can be encoded. 
      int maxSubsequenceLength = 0;
      int maxSubsequenceIndex = 0;
      for (int i = 0; i < source.remaining(); i++) {
        // prefix at i => shared subsequence (infix)
        int sharedPrefix = BufferUtils.sharedPrefixLength(source, i, target, 0);
        // Only update maxSubsequenceLength if we will be able to encode it.
        if (sharedPrefix > maxSubsequenceLength && i < REMOVE_EVERYTHING
            && (source.remaining() - (i + sharedPrefix)) < REMOVE_EVERYTHING) {
          maxSubsequenceLength = sharedPrefix;
          maxSubsequenceIndex = i;
        }
      }
  
      // Determine how much to remove (and where) from src to get a prefix of dst.
      int truncatePrefixBytes = maxSubsequenceIndex;
      int truncateSuffixBytes = (source.remaining() - (maxSubsequenceIndex + maxSubsequenceLength));
      if (truncatePrefixBytes >= REMOVE_EVERYTHING || truncateSuffixBytes >= REMOVE_EVERYTHING) {
        maxSubsequenceIndex = maxSubsequenceLength = 0;
        truncatePrefixBytes = truncateSuffixBytes = REMOVE_EVERYTHING;
      }
  
      final int len1 = target.remaining() - maxSubsequenceLength;
      reuse = BufferUtils.clearAndEnsureCapacity(reuse, 2 + len1);
  
      assert target.hasArray() && 
             target.position() == 0 && 
             target.arrayOffset() == 0;
  
      reuse.put((byte) ((truncatePrefixBytes + 'A') & 0xFF));
      reuse.put((byte) ((truncateSuffixBytes + 'A') & 0xFF));
      reuse.put(target.array(), maxSubsequenceLength, len1);
      reuse.flip();
  
      return reuse;
    }
    
    @Override
    public int prefixBytes() {
      return 2;
    }
  
    public ByteBuffer decode(ByteBuffer reuse, ByteBuffer source, ByteBuffer encoded) {
      assert encoded.remaining() >= 2;
  
      final int p = encoded.position();
      int truncatePrefixBytes = (encoded.get(p)     - 'A') & 0xFF;
      int truncateSuffixBytes = (encoded.get(p + 1) - 'A') & 0xFF;
  
      if (truncatePrefixBytes == REMOVE_EVERYTHING || 
          truncateSuffixBytes == REMOVE_EVERYTHING) {
        truncatePrefixBytes = source.remaining();
        truncateSuffixBytes = 0;
      }
  
      assert source.hasArray() && 
             source.position() == 0 && 
             source.arrayOffset() == 0;
  
      assert encoded.hasArray() && 
             encoded.position() == 0 && 
             encoded.arrayOffset() == 0;
  
      final int len1 = source.remaining() - (truncateSuffixBytes + truncatePrefixBytes);
      final int len2 = encoded.remaining() - 2;
      reuse = BufferUtils.clearAndEnsureCapacity(reuse, len1 + len2);
  
      reuse.put(source.array(), truncatePrefixBytes, len1);
      reuse.put(encoded.array(), 2, len2);
      reuse.flip();
  
      return reuse;
    }
  
    @Override
    public String toString() {
      return getClass().getSimpleName();
    }
  }