org.apache.lucene.analysis.util.CharTokenizer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.lucene.analysis.util.CharTokenizer.java

Source

/*
 * 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.
 */
package org.apache.lucene.analysis.util;

import java.io.IOException;
import java.util.Objects;
import java.util.function.IntPredicate;

import org.apache.lucene.analysis.CharacterUtils;
import org.apache.lucene.analysis.CharacterUtils.CharacterBuffer;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.core.LetterTokenizer;
import org.apache.lucene.analysis.core.WhitespaceTokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.util.AttributeFactory;

import static org.apache.lucene.analysis.standard.StandardTokenizer.MAX_TOKEN_LENGTH_LIMIT;

/**
 * An abstract base class for simple, character-oriented tokenizers.
 * <p>
 * The base class also provides factories to create instances of
 * {@code CharTokenizer} using Java 8 lambdas or method references.
 * It is possible to create an instance which behaves exactly like
 * {@link LetterTokenizer}:
 * <pre class="prettyprint lang-java">
 * Tokenizer tok = CharTokenizer.fromTokenCharPredicate(Character::isLetter);
 * </pre>
 */
public abstract class CharTokenizer extends Tokenizer {

    /**
     * Creates a new {@link CharTokenizer} instance
     */
    public CharTokenizer() {
        this.maxTokenLen = DEFAULT_MAX_WORD_LEN;
    }

    /**
     * Creates a new {@link CharTokenizer} instance
     * 
     * @param factory
     *          the attribute factory to use for this {@link Tokenizer}
     */
    public CharTokenizer(AttributeFactory factory) {
        super(factory);
        this.maxTokenLen = DEFAULT_MAX_WORD_LEN;
    }

    /**
     * Creates a new {@link CharTokenizer} instance
     *
     * @param factory the attribute factory to use for this {@link Tokenizer}
     * @param maxTokenLen maximum token length the tokenizer will emit. 
     *        Must be greater than 0 and less than MAX_TOKEN_LENGTH_LIMIT (1024*1024)
     * @throws IllegalArgumentException if maxTokenLen is invalid.
     */
    public CharTokenizer(AttributeFactory factory, int maxTokenLen) {
        super(factory);
        if (maxTokenLen > MAX_TOKEN_LENGTH_LIMIT || maxTokenLen <= 0) {
            throw new IllegalArgumentException("maxTokenLen must be greater than 0 and less than "
                    + MAX_TOKEN_LENGTH_LIMIT + " passed: " + maxTokenLen);
        }
        this.maxTokenLen = maxTokenLen;
    }

    /**
     * Creates a new instance of CharTokenizer using a custom predicate, supplied as method reference or lambda expression.
     * The predicate should return {@code true} for all valid token characters.
     * <p>
     * This factory is intended to be used with lambdas or method references. E.g., an elegant way
     * to create an instance which behaves exactly as {@link LetterTokenizer} is:
     * <pre class="prettyprint lang-java">
     * Tokenizer tok = CharTokenizer.fromTokenCharPredicate(Character::isLetter);
     * </pre>
     */
    public static CharTokenizer fromTokenCharPredicate(final IntPredicate tokenCharPredicate) {
        return fromTokenCharPredicate(DEFAULT_TOKEN_ATTRIBUTE_FACTORY, tokenCharPredicate);
    }

    /**
     * Creates a new instance of CharTokenizer with the supplied attribute factory using a custom predicate, supplied as method reference or lambda expression.
     * The predicate should return {@code true} for all valid token characters.
     * <p>
     * This factory is intended to be used with lambdas or method references. E.g., an elegant way
     * to create an instance which behaves exactly as {@link LetterTokenizer} is:
     * <pre class="prettyprint lang-java">
     * Tokenizer tok = CharTokenizer.fromTokenCharPredicate(factory, Character::isLetter);
     * </pre>
     */
    public static CharTokenizer fromTokenCharPredicate(AttributeFactory factory,
            final IntPredicate tokenCharPredicate) {
        Objects.requireNonNull(tokenCharPredicate, "predicate must not be null.");
        return new CharTokenizer(factory) {
            @Override
            protected boolean isTokenChar(int c) {
                return tokenCharPredicate.test(c);
            }
        };
    }

    /**
     * Creates a new instance of CharTokenizer using a custom predicate, supplied as method reference or lambda expression.
     * The predicate should return {@code true} for all valid token separator characters.
     * This method is provided for convenience to easily use predicates that are negated
     * (they match the separator characters, not the token characters).
     * <p>
     * This factory is intended to be used with lambdas or method references. E.g., an elegant way
     * to create an instance which behaves exactly as {@link WhitespaceTokenizer} is:
     * <pre class="prettyprint lang-java">
     * Tokenizer tok = CharTokenizer.fromSeparatorCharPredicate(Character::isWhitespace);
     * </pre>
     */
    public static CharTokenizer fromSeparatorCharPredicate(final IntPredicate separatorCharPredicate) {
        return fromSeparatorCharPredicate(DEFAULT_TOKEN_ATTRIBUTE_FACTORY, separatorCharPredicate);
    }

    /**
     * Creates a new instance of CharTokenizer with the supplied attribute factory using a custom predicate, supplied as method reference or lambda expression.
     * The predicate should return {@code true} for all valid token separator characters.
     * <p>
     * This factory is intended to be used with lambdas or method references. E.g., an elegant way
     * to create an instance which behaves exactly as {@link WhitespaceTokenizer} is:
     * <pre class="prettyprint lang-java">
     * Tokenizer tok = CharTokenizer.fromSeparatorCharPredicate(factory, Character::isWhitespace);
     * </pre>
     */
    public static CharTokenizer fromSeparatorCharPredicate(AttributeFactory factory,
            final IntPredicate separatorCharPredicate) {
        return fromTokenCharPredicate(factory, separatorCharPredicate.negate());
    }

    private int offset = 0, bufferIndex = 0, dataLen = 0, finalOffset = 0;
    public static final int DEFAULT_MAX_WORD_LEN = 255;
    private static final int IO_BUFFER_SIZE = 4096;
    private final int maxTokenLen;

    private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
    private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);

    private final CharacterBuffer ioBuffer = CharacterUtils.newCharacterBuffer(IO_BUFFER_SIZE);

    /**
     * Returns true iff a codepoint should be included in a token. This tokenizer
     * generates as tokens adjacent sequences of codepoints which satisfy this
     * predicate. Codepoints for which this is false are used to define token
     * boundaries and are not included in tokens.
     */
    protected abstract boolean isTokenChar(int c);

    @Override
    public final boolean incrementToken() throws IOException {
        clearAttributes();
        int length = 0;
        int start = -1; // this variable is always initialized
        int end = -1;
        char[] buffer = termAtt.buffer();
        while (true) {
            if (bufferIndex >= dataLen) {
                offset += dataLen;
                CharacterUtils.fill(ioBuffer, input); // read supplementary char aware with CharacterUtils
                if (ioBuffer.getLength() == 0) {
                    dataLen = 0; // so next offset += dataLen won't decrement offset
                    if (length > 0) {
                        break;
                    } else {
                        finalOffset = correctOffset(offset);
                        return false;
                    }
                }
                dataLen = ioBuffer.getLength();
                bufferIndex = 0;
            }
            // use CharacterUtils here to support < 3.1 UTF-16 code unit behavior if the char based methods are gone
            final int c = Character.codePointAt(ioBuffer.getBuffer(), bufferIndex, ioBuffer.getLength());
            final int charCount = Character.charCount(c);
            bufferIndex += charCount;

            if (isTokenChar(c)) { // if it's a token char
                if (length == 0) { // start of token
                    assert start == -1;
                    start = offset + bufferIndex - charCount;
                    end = start;
                } else if (length >= buffer.length - 1) { // check if a supplementary could run out of bounds
                    buffer = termAtt.resizeBuffer(2 + length); // make sure a supplementary fits in the buffer
                }
                end += charCount;
                length += Character.toChars(c, buffer, length); // buffer it, normalized
                if (length >= maxTokenLen) { // buffer overflow! make sure to check for >= surrogate pair could break == test
                    break;
                }
            } else if (length > 0) { // at non-Letter w/ chars
                break; // return 'em
            }
        }

        termAtt.setLength(length);
        assert start != -1;
        offsetAtt.setOffset(correctOffset(start), finalOffset = correctOffset(end));
        return true;

    }

    @Override
    public final void end() throws IOException {
        super.end();
        // set final offset
        offsetAtt.setOffset(finalOffset, finalOffset);
    }

    @Override
    public void reset() throws IOException {
        super.reset();
        bufferIndex = 0;
        offset = 0;
        dataLen = 0;
        finalOffset = 0;
        ioBuffer.reset(); // make sure to reset the IO buffer!!
    }
}