
io.lettuce.core.protocol.RedisStateMachine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lettuce-core Show documentation
Show all versions of lettuce-core Show documentation
Advanced and thread-safe Java Redis client for synchronous, asynchronous, and
reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs
and much more.
The newest version!
/*
* Copyright 2011-Present, Redis Ltd. and Contributors
* All rights reserved.
*
* Licensed under the MIT License.
*
* This file contains contributions from third-party contributors
* licensed 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
*
* https://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 io.lettuce.core.protocol;
import static io.lettuce.core.protocol.RedisStateMachine.State.Type.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.function.Consumer;
import io.lettuce.core.internal.LettuceStrings;
import io.lettuce.core.output.CommandOutput;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.ByteProcessor;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* State machine that decodes redis server responses encoded according to the Unified
* Request Protocol (RESP). Supports RESP2 and RESP3. Initialized with protocol discovery.
*
* @author Will Glozer
* @author Mark Paluch
* @author Helly Guo
* @author shikharid
*/
public class RedisStateMachine {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(RedisStateMachine.class);
private static final ByteBuffer QUEUED = StandardCharsets.US_ASCII.encode("QUEUED");
private static final int TERMINATOR_LENGTH = 2;
private static final int NOT_FOUND = -1;
private final static State.Type[] TYPE_BY_BYTE_MARKER = new State.Type[Byte.MAX_VALUE + 1];
static {
for (State.Type type : values()) {
if (type == BYTES || type == VERBATIM_STRING) {
continue;
}
if (TYPE_BY_BYTE_MARKER[type.marker] != null) {
throw new IllegalStateException("Cannot overwrite message marker assignment for '"
+ new String(new byte[] { type.marker }) + "' with " + type);
}
TYPE_BY_BYTE_MARKER[type.marker] = type;
}
}
static class State {
/**
* Callback interface to handle a {@link State}.
*/
@FunctionalInterface
interface StateHandler {
Result handle(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler);
}
enum Type implements StateHandler {
/**
* First byte: {@code +}.
*/
SINGLE('+', RedisStateMachine::handleSingle),
/**
* First byte: {@code -}.
*/
ERROR('-', RedisStateMachine::handleError),
/**
* First byte: {@code :}.
*/
INTEGER(':', RedisStateMachine::handleInteger),
/**
* First byte: {@code ,}.
*
* @since 6.0/RESP3
*/
FLOAT(',', RedisStateMachine::handleFloat),
/**
* First byte: {@code #}.
*
* @since 6.0/RESP3
*/
BOOLEAN('#', RedisStateMachine::handleBoolean),
/**
* First byte: {@code !}.
*
* @since 6.0/RESP3
*/
BULK_ERROR('!', RedisStateMachine::handleBulkError),
/**
* First byte: {@code =}.
*
* @since 6.0/RESP3
*/
VERBATIM('=', RedisStateMachine::handleBulkAndVerbatim), VERBATIM_STRING('=', RedisStateMachine::handleVerbatim),
/**
* First byte: {@code (}.
*
* @since 6.0/RESP3
*/
BIG_NUMBER('(', RedisStateMachine::handleBigNumber),
/**
* First byte: {@code %}.
*
* @see #HELLO_V3
* @since 6.0/RESP3
*/
MAP('%', RedisStateMachine::handleMap),
/**
* First byte: {@code ~}.
*
* @see #MULTI
* @since 6.0/RESP3
*/
SET('~', RedisStateMachine::handleSet),
/**
* First byte: {@code |}.
*
* @since 6.0/RESP3
*/
ATTRIBUTE('|', RedisStateMachine::handleAttribute),
/**
* First byte: {@code >}.
*
* @see #MULTI
* @since 6.0/RESP3
*/
PUSH('>', RedisStateMachine::handlePushAndMulti),
/**
* First byte: {@code @}.
*
* @see #MAP
* @since 6.0/RESP3
*/
HELLO_V3('@', RedisStateMachine::handleHelloV3),
/**
* First byte: {@code _}.
*
* @since 6.0/RESP3
*/
NULL('_', RedisStateMachine::handleNull),
/**
* First byte: {@code $}.
*/
BULK('$', RedisStateMachine::handleBulkAndVerbatim),
/**
* First byte: {@code *}.
*
* @see #SET
* @see #MAP
*/
MULTI('*', RedisStateMachine::handlePushAndMulti), BYTES('*', RedisStateMachine::handleBytes);
final byte marker;
private final StateHandler behavior;
Type(char marker, StateHandler behavior) {
this.marker = (byte) marker;
this.behavior = behavior;
}
@Override
public Result handle(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
return behavior.handle(rsm, state, buffer, output, errorHandler);
}
}
enum Result {
NORMAL_END, BREAK_LOOP, CONTINUE_LOOP
}
Type type = null;
int count = NOT_FOUND;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer();
sb.append(getClass().getSimpleName());
sb.append(" [type=").append(type);
sb.append(", count=").append(count);
sb.append(']');
return sb.toString();
}
void reset() {
this.type = null;
this.count = NOT_FOUND;
}
/**
* Pre-allocates a State array of given len
*
* @param len len of the states array to be created
* @return array of State's of len size
*/
private static State[] createStates(int len) {
final State[] stack = new State[len];
for (int i = 0; i < len; ++i) {
stack[i] = new State();
}
return stack;
}
}
private final State[] stack = State.createStates(32);
private final boolean debugEnabled = logger.isDebugEnabled();
private final Resp2LongProcessor longProcessor = new Resp2LongProcessor();
private ProtocolVersion protocolVersion = null;
private int stackElements;
/**
* Initialize a new instance.
*
* @deprecated since 6.3, {@link ByteBufAllocator no longer required}.
*/
@Deprecated
public RedisStateMachine(ByteBufAllocator alloc) {
this();
}
/**
* Initialize a new instance.
*/
public RedisStateMachine() {
}
public boolean isDiscoverProtocol() {
return this.protocolVersion == null;
}
public ProtocolVersion getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
}
/**
* Decode a command using the input buffer.
*
* @param buffer Buffer containing data from the server.
* @param output Current command output.
* @return true if a complete response was read.
*/
public boolean decode(ByteBuf buffer, CommandOutput, ?, ?> output) {
return decode(buffer, output, ex -> {
});
}
/**
* Attempt to decode a redis response and return a flag indicating whether a complete response was read.
*
* @param buffer Buffer containing data from the server.
* @param output Current command output.
* @param errorHandler the error handler
* @return true if a complete response was read.
*/
public boolean decode(ByteBuf buffer, CommandOutput, ?, ?> output, Consumer errorHandler) {
buffer.touch("RedisStateMachine.decode(…)");
if (isEmpty(stack)) {
addHead(stack);
}
if (output == null) {
return isEmpty(stack);
}
boolean resp3Indicator = doDecode(buffer, output, errorHandler);
if (debugEnabled) {
logger.debug("Decode done, empty stack: {}", isEmpty(stack));
}
if (isDiscoverProtocol()) {
if (resp3Indicator) {
setProtocolVersion(ProtocolVersion.RESP3);
} else {
setProtocolVersion(ProtocolVersion.RESP2);
}
}
return isEmpty(stack);
}
private boolean doDecode(ByteBuf buffer, CommandOutput, ?, ?> output, Consumer errorHandler) {
boolean resp3Indicator = false;
State.Result result;
while (!isEmpty(stack)) {
State state = peek(stack);
if (state.type == null) {
if (!buffer.isReadable()) {
break;
}
state.type = readReplyType(buffer);
if (state.type == HELLO_V3 || state.type == MAP) {
resp3Indicator = true;
}
buffer.markReaderIndex();
}
result = state.type.handle(this, state, buffer, output, errorHandler);
if (State.Result.BREAK_LOOP.equals(result)) {
break;
} else if (State.Result.CONTINUE_LOOP.equals(result)) {
continue;
}
buffer.markReaderIndex();
remove(stack);
output.complete(size(stack));
}
return resp3Indicator;
}
static State.Result handleSingle(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
ByteBuffer bytes;
if ((bytes = rsm.readLine(buffer)) == null) {
return State.Result.BREAK_LOOP;
}
if (!QUEUED.equals(bytes)) {
rsm.safeSetSingle(output, bytes, errorHandler);
}
return State.Result.NORMAL_END;
}
static State.Result handleBigNumber(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
ByteBuffer bytes;
if ((bytes = rsm.readLine(buffer)) == null) {
return State.Result.BREAK_LOOP;
}
rsm.safeSetBigNumber(output, bytes, errorHandler);
return State.Result.NORMAL_END;
}
static State.Result handleError(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
ByteBuffer bytes;
if ((bytes = rsm.readLine(buffer)) == null) {
return State.Result.BREAK_LOOP;
}
rsm.safeSetError(output, bytes, errorHandler);
return State.Result.NORMAL_END;
}
static State.Result handleNull(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
if (rsm.readLine(buffer) == null) {
return State.Result.BREAK_LOOP;
}
rsm.safeSet(output, null, errorHandler);
return State.Result.NORMAL_END;
}
static State.Result handleInteger(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
int end;
if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
long integer = rsm.readLong(buffer, buffer.readerIndex(), end);
rsm.safeSet(output, integer, errorHandler);
return State.Result.NORMAL_END;
}
static State.Result handleBoolean(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
if (rsm.findLineEnd(buffer) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
boolean value = rsm.readBoolean(buffer);
rsm.safeSet(output, value, errorHandler);
return State.Result.NORMAL_END;
}
static State.Result handleFloat(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
int end;
if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
double f = rsm.readFloat(buffer, buffer.readerIndex(), end);
rsm.safeSet(output, f, errorHandler);
return State.Result.NORMAL_END;
}
static State.Result handleBulkAndVerbatim(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
int length;
int end;
if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
length = (int) rsm.readLong(buffer, buffer.readerIndex(), end);
if (length == NOT_FOUND) {
rsm.safeSet(output, null, errorHandler);
} else {
state.type = state.type == VERBATIM ? VERBATIM_STRING : BYTES;
state.count = length + TERMINATOR_LENGTH;
buffer.markReaderIndex();
return State.Result.CONTINUE_LOOP;
}
return State.Result.NORMAL_END;
}
static State.Result handleBulkError(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
int length;
int end;
if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
length = (int) rsm.readLong(buffer, buffer.readerIndex(), end);
if (length == NOT_FOUND) {
rsm.safeSetError(output, null, errorHandler);
} else {
state.type = BYTES;
state.count = length + TERMINATOR_LENGTH;
buffer.markReaderIndex();
return State.Result.CONTINUE_LOOP;
}
return State.Result.NORMAL_END;
}
static State.Result handleHelloV3(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
int end;
if (state.count == NOT_FOUND) {
if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
readAndMarkReadIdx(rsm, state, buffer, end);
}
return returnDependStateCount(rsm, state);
}
static State.Result handlePushAndMulti(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
int end;
if (state.count == NOT_FOUND) {
if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
readAndMarkReadIdx(rsm, state, buffer, end);
rsm.safeMultiArray(output, state.count, errorHandler);
}
return returnDependStateCount(rsm, state);
}
static State.Result handleMap(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
int length;
int end;
if (state.count == NOT_FOUND) {
if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
length = readAndMarkReadIdx(rsm, state, buffer, end);
rsm.safeMultiMap(output, state.count, errorHandler);
state.count = length * 2;
}
return returnDependStateCount(rsm, state);
}
static State.Result handleSet(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
int end;
if (state.count == NOT_FOUND) {
if ((end = rsm.findLineEnd(buffer)) == NOT_FOUND) {
return State.Result.BREAK_LOOP;
}
readAndMarkReadIdx(rsm, state, buffer, end);
rsm.safeMultiSet(output, state.count, errorHandler);
}
return returnDependStateCount(rsm, state);
}
static int readAndMarkReadIdx(RedisStateMachine rsm, State state, ByteBuf buffer, int end) {
int length = (int) rsm.readLong(buffer, buffer.readerIndex(), end);
state.count = length;
buffer.markReaderIndex();
return length;
}
static State.Result returnDependStateCount(RedisStateMachine rsm, State state) {
if (state.count <= 0) {
return State.Result.NORMAL_END;
}
state.count--;
rsm.addHead(rsm.stack);
return State.Result.CONTINUE_LOOP;
}
static State.Result handleVerbatim(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
ByteBuffer bytes;
if ((bytes = rsm.readBytes(buffer, state.count)) == null) {
return State.Result.BREAK_LOOP;
}
// skip txt: and mkd:
bytes.position(bytes.position() + 4);
rsm.safeSet(output, bytes, errorHandler);
return State.Result.NORMAL_END;
}
static State.Result handleBytes(RedisStateMachine rsm, State state, ByteBuf buffer, CommandOutput, ?, ?> output,
Consumer errorHandler) {
ByteBuffer bytes;
if ((bytes = rsm.readBytes(buffer, state.count)) == null) {
return State.Result.BREAK_LOOP;
}
rsm.safeSet(output, bytes, errorHandler);
return State.Result.NORMAL_END;
}
private static State.Result handleAttribute(RedisStateMachine rsm, State state, ByteBuf buffer,
CommandOutput, ?, ?> output, Consumer errorHandler) {
throw new RedisProtocolException("Not implemented");
}
/**
* Reset the state machine.
*/
public void reset() {
for (State state : stack) {
state.reset();
}
stackElements = 0;
}
/**
* Close the state machine to free resources.
*/
public void close() {
}
private int findLineEnd(ByteBuf buffer) {
int index = buffer.forEachByte(ByteProcessor.FIND_LF);
return (index > 0 && buffer.getByte(index - 1) == '\r') ? index - 1 : NOT_FOUND;
}
private State.Type readReplyType(ByteBuf buffer) {
byte b = buffer.readByte();
State.Type type = TYPE_BY_BYTE_MARKER[b];
if (type == null) {
throw new RedisProtocolException("Invalid first byte: " + b + " (" + new String(new byte[] { b }) + ")"
+ " at buffer index " + buffer.readerIndex() + " decoding using " + getProtocolVersion());
}
return type;
}
private long readLong(ByteBuf buffer, int start, int end) {
return longProcessor.getValue(buffer, start, end);
}
private double readFloat(ByteBuf buffer, int start, int end) {
int valueLength = end - start;
String value = buffer.toString(start, valueLength, StandardCharsets.US_ASCII);
buffer.skipBytes(valueLength + TERMINATOR_LENGTH);
return LettuceStrings.toDouble(value);
}
private boolean readBoolean(ByteBuf buffer) {
byte b = buffer.readByte();
buffer.skipBytes(TERMINATOR_LENGTH);
switch (b) {
case 't':
return true;
case 'f':
return false;
}
throw new RedisProtocolException("Unexpected BOOLEAN value: " + b);
}
private ByteBuffer readLine(ByteBuf buffer) {
ByteBuffer bytes = null;
int end = findLineEnd(buffer);
if (end > NOT_FOUND) {
bytes = readBytes0(buffer, end - buffer.readerIndex());
buffer.skipBytes(TERMINATOR_LENGTH);
buffer.markReaderIndex();
}
return bytes;
}
private ByteBuffer readBytes(ByteBuf buffer, int count) {
if (buffer.readableBytes() >= count) {
ByteBuffer byteBuffer = readBytes0(buffer, count - TERMINATOR_LENGTH);
buffer.skipBytes(TERMINATOR_LENGTH);
buffer.markReaderIndex();
return byteBuffer;
}
return null;
}
private ByteBuffer readBytes0(ByteBuf buffer, int count) {
ByteBuffer byteBuffer = buffer.internalNioBuffer(buffer.readerIndex(), count);
// advance reader index
buffer.readerIndex(buffer.readerIndex() + count);
return byteBuffer;
}
/**
* Remove the head element from the stack.
*
* @param stack
*/
private void remove(State[] stack) {
stack[--stackElements].reset();
}
/**
* Add the element to the stack to be the new head element.
*
* @param stack
*/
private void addHead(State[] stack) {
++stackElements;
}
/**
* Returns the head element without removing it.
*
* @param stack
* @return
*/
private State peek(State[] stack) {
return stack[stackElements - 1];
}
/**
* @param stack
* @return number of stack elements.
*/
private int size(State[] stack) {
return stackElements;
}
/**
* @param stack
* @return true if the stack is empty.
*/
private boolean isEmpty(State[] stack) {
return stackElements == 0;
}
/**
* Safely sets {@link CommandOutput#set(boolean)}. Completes a errorHandler exceptionally in case an exception occurs.
*
* @param output
* @param value
* @param errorHandler
*/
protected void safeSet(CommandOutput, ?, ?> output, boolean value, Consumer errorHandler) {
try {
output.set(value);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#set(long)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param number
* @param errorHandler
*/
protected void safeSet(CommandOutput, ?, ?> output, long number, Consumer errorHandler) {
try {
output.set(number);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#set(double)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param number
* @param errorHandler
*/
protected void safeSet(CommandOutput, ?, ?> output, double number, Consumer errorHandler) {
try {
output.set(number);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#set(ByteBuffer)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param bytes
* @param errorHandler
*/
protected void safeSet(CommandOutput, ?, ?> output, ByteBuffer bytes, Consumer errorHandler) {
try {
output.set(bytes);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#set(ByteBuffer)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param bytes
* @param errorHandler
*/
protected void safeSetSingle(CommandOutput, ?, ?> output, ByteBuffer bytes, Consumer errorHandler) {
try {
output.set(bytes);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#set(ByteBuffer)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param bytes
* @param errorHandler
*/
protected void safeSetBigNumber(CommandOutput, ?, ?> output, ByteBuffer bytes, Consumer errorHandler) {
try {
output.setBigNumber(bytes);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#multiArray(int)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param count
* @param errorHandler
*/
protected void safeMultiArray(CommandOutput, ?, ?> output, int count, Consumer errorHandler) {
try {
output.multiArray(count);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#multiPush(int)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param count
* @param errorHandler
*/
protected void safeMultiPush(CommandOutput, ?, ?> output, int count, Consumer errorHandler) {
try {
output.multiPush(count);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#multiSet(int)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param count
* @param errorHandler
*/
protected void safeMultiSet(CommandOutput, ?, ?> output, int count, Consumer errorHandler) {
try {
output.multiSet(count);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#multiMap(int)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param count
* @param errorHandler
*/
protected void safeMultiMap(CommandOutput, ?, ?> output, int count, Consumer errorHandler) {
try {
output.multiMap(count);
} catch (Exception e) {
errorHandler.accept(e);
}
}
/**
* Safely sets {@link CommandOutput#setError(ByteBuffer)}. Notifies the {@code errorHandler} if an exception occurs.
*
* @param output
* @param bytes
* @param errorHandler
*/
protected void safeSetError(CommandOutput, ?, ?> output, ByteBuffer bytes, Consumer errorHandler) {
try {
output.setError(bytes);
} catch (Exception e) {
errorHandler.accept(e);
}
}
@SuppressWarnings("unused")
static class Resp2LongProcessor implements ByteProcessor {
long result;
boolean negative;
boolean first;
public long getValue(ByteBuf buffer, int start, int end) {
this.result = 0;
this.first = true;
int length = end - start;
buffer.forEachByte(start, length, this);
if (!this.negative) {
this.result = -this.result;
}
buffer.skipBytes(length + TERMINATOR_LENGTH);
return this.result;
}
@Override
public boolean process(byte value) {
if (first) {
first = false;
if (value == '-') {
negative = true;
} else {
negative = false;
int digit = value - '0';
result = result * 10 - digit;
}
return true;
}
int digit = value - '0';
result = result * 10 - digit;
return true;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy