content: package org.apache.lucene.analysis.miscellaneous;
  
  /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  import org.apache.lucene.analysis.Token;
  import org.apache.lucene.analysis.TokenStream;
  import org.apache.lucene.analysis.tokenattributes.FlagsAttribute;
  import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
  import org.apache.lucene.analysis.tokenattributes.PayloadAttribute;
  import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
  import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
  import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
  import org.apache.lucene.index.Payload;
  
  import java.io.IOException;
  
  
  /**
   * Joins two token streams and leaves the last token of the first stream available
   * to be used when updating the token values in the second stream based on that token.
   *
   * The default implementation adds last prefix token end offset to the suffix token start and end offsets.
   * <p/>
   * <b>NOTE:</b> This filter might not behave correctly if used with custom Attributes, i.e. Attributes other than
   * the ones located in org.apache.lucene.analysis.tokenattributes. 
   */
  public class PrefixAwareTokenFilter extends TokenStream {
  
    private TokenStream prefix;
    private TokenStream suffix;
    
    private CharTermAttribute termAtt;
    private PositionIncrementAttribute posIncrAtt;
    private PayloadAttribute payloadAtt;
    private OffsetAttribute offsetAtt;
    private TypeAttribute typeAtt;
    private FlagsAttribute flagsAtt;
  
    private CharTermAttribute p_termAtt;
    private PositionIncrementAttribute p_posIncrAtt;
    private PayloadAttribute p_payloadAtt;
    private OffsetAttribute p_offsetAtt;
    private TypeAttribute p_typeAtt;
    private FlagsAttribute p_flagsAtt;
  
    public PrefixAwareTokenFilter(TokenStream prefix, TokenStream suffix) {
      super(suffix);
      this.suffix = suffix;
      this.prefix = prefix;
      prefixExhausted = false;
      
      termAtt = addAttribute(CharTermAttribute.class);
      posIncrAtt = addAttribute(PositionIncrementAttribute.class);
      payloadAtt = addAttribute(PayloadAttribute.class);
      offsetAtt = addAttribute(OffsetAttribute.class);
      typeAtt = addAttribute(TypeAttribute.class);
      flagsAtt = addAttribute(FlagsAttribute.class);
  
      p_termAtt = prefix.addAttribute(CharTermAttribute.class);
      p_posIncrAtt = prefix.addAttribute(PositionIncrementAttribute.class);
      p_payloadAtt = prefix.addAttribute(PayloadAttribute.class);
      p_offsetAtt = prefix.addAttribute(OffsetAttribute.class);
      p_typeAtt = prefix.addAttribute(TypeAttribute.class);
      p_flagsAtt = prefix.addAttribute(FlagsAttribute.class);
    }
  
    private Token previousPrefixToken = new Token();
    private Token reusableToken = new Token();
  
    private boolean prefixExhausted;
  
    @Override
    public final boolean incrementToken() throws IOException {
      if (!prefixExhausted) {
        Token nextToken = getNextPrefixInputToken(reusableToken);
        if (nextToken == null) {
          prefixExhausted = true;
        } else {
          previousPrefixToken.reinit(nextToken);
          // Make it a deep copy
          Payload p = previousPrefixToken.getPayload();
          if (p != null) {
            previousPrefixToken.setPayload((Payload) p.clone());
          }
          setCurrentToken(nextToken);
          return true;
        }
      }
  
      Token nextToken = getNextSuffixInputToken(reusableToken);
      if (nextToken == null) {
        return false;
      }
  
      nextToken = updateSuffixToken(nextToken, previousPrefixToken);
      setCurrentToken(nextToken);
      return true;
    }
    
    private void setCurrentToken(Token token) {
      if (token == null) return;
      clearAttributes();
      termAtt.copyBuffer(token.buffer(), 0, token.length());
      posIncrAtt.setPositionIncrement(token.getPositionIncrement());
      flagsAtt.setFlags(token.getFlags());
      offsetAtt.setOffset(token.startOffset(), token.endOffset());
      typeAtt.setType(token.type());
      payloadAtt.setPayload(token.getPayload());
    }
    
    private Token getNextPrefixInputToken(Token token) throws IOException {
      if (!prefix.incrementToken()) return null;
      token.copyBuffer(p_termAtt.buffer(), 0, p_termAtt.length());
      token.setPositionIncrement(p_posIncrAtt.getPositionIncrement());
      token.setFlags(p_flagsAtt.getFlags());
      token.setOffset(p_offsetAtt.startOffset(), p_offsetAtt.endOffset());
      token.setType(p_typeAtt.type());
      token.setPayload(p_payloadAtt.getPayload());
      return token;
    }
  
    private Token getNextSuffixInputToken(Token token) throws IOException {
      if (!suffix.incrementToken()) return null;
      token.copyBuffer(termAtt.buffer(), 0, termAtt.length());
      token.setPositionIncrement(posIncrAtt.getPositionIncrement());
      token.setFlags(flagsAtt.getFlags());
      token.setOffset(offsetAtt.startOffset(), offsetAtt.endOffset());
      token.setType(typeAtt.type());
      token.setPayload(payloadAtt.getPayload());
      return token;
    }
  
    /**
     * The default implementation adds last prefix token end offset to the suffix token start and end offsets.
     *
     * @param suffixToken a token from the suffix stream
     * @param lastPrefixToken the last token from the prefix stream
     * @return consumer token
     */
    public Token updateSuffixToken(Token suffixToken, Token lastPrefixToken) {
      suffixToken.setStartOffset(lastPrefixToken.endOffset() + suffixToken.startOffset());
      suffixToken.setEndOffset(lastPrefixToken.endOffset() + suffixToken.endOffset());
      return suffixToken;
    }
  
    @Override
    public void end() throws IOException {
      prefix.end();
      suffix.end();
    }
  
    @Override
    public void close() throws IOException {
      prefix.close();
      suffix.close();
    }
  
    @Override
    public void reset() throws IOException {
      super.reset();
      if (prefix != null) {
        prefixExhausted = false;
        prefix.reset();
      }
      if (suffix != null) {
        suffix.reset();
      }
  
  
    }
  
    public TokenStream getPrefix() {
      return prefix;
    }
  
    public void setPrefix(TokenStream prefix) {
      this.prefix = prefix;
    }
  
    public TokenStream getSuffix() {
      return suffix;
    }
  
    public void setSuffix(TokenStream suffix) {
      this.suffix = suffix;
    }
  }
  