com.spotify.folsom.client.ascii.AsciiMemcacheDecoder.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.folsom.client.ascii.AsciiMemcacheDecoder.java

Source

/*
 * Copyright (c) 2014-2015 Spotify AB
 *
 * 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
 *
 * 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 com.spotify.folsom.client.ascii;

import java.io.IOException;
import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

public class AsciiMemcacheDecoder extends ByteToMessageDecoder {

    private final StringBuilder line = new StringBuilder();
    private boolean consumed = false;
    private boolean valueMode = false;

    private ValueAsciiResponse valueResponse = new ValueAsciiResponse();

    private String key = null;
    private byte[] value = null;
    private long cas = 0;
    private int valueOffset;

    @Override
    protected void decode(final ChannelHandlerContext ctx, final ByteBuf buf, final List<Object> out)
            throws Exception {
        while (true) {
            //      String tmp = new String(buf.copy().array());
            int readableBytes = buf.readableBytes();
            if (readableBytes == 0) {
                return;
            }

            if (key != null) {
                final int toCopy = Math.min(value.length - valueOffset, readableBytes);
                if (toCopy > 0) {
                    buf.readBytes(value, valueOffset, toCopy);
                    readableBytes -= toCopy;
                    valueOffset += toCopy;
                    if (valueOffset < value.length) {
                        return;
                    }
                }
                final StringBuilder line = readLine(buf, readableBytes);
                if (line == null) {
                    return;
                }
                if (line.length() > 0) {
                    throw new IOException(String.format("Unexpected end of data block: %s", line));
                }
                valueResponse.addGetResult(key, value, cas);
                key = null;
                value = null;
                cas = 0;
            } else {
                final StringBuilder line = readLine(buf, readableBytes);
                if (line == null) {
                    return;
                }

                final int firstEnd = endIndex(line, 0);
                if (firstEnd < 1) {
                    throw new IOException("Unexpected line: " + line);
                }

                final char firstChar = line.charAt(0);

                if (Character.isDigit(firstChar)) {
                    try {
                        long numeric = Long.valueOf(line.toString());
                        out.add(new NumericAsciiResponse(numeric));
                    } catch (NumberFormatException e) {
                        throw new IOException("Unexpected line: " + line, e);
                    }
                } else if (firstEnd == 2) {
                    if (firstChar == 'O') {
                        expect(line, "OK");
                        out.add(AsciiResponse.OK);
                        return;
                    }
                } else if (firstEnd == 3) {
                    expect(line, "END");
                    out.add(valueResponse);
                    valueResponse = new ValueAsciiResponse();
                    valueMode = false;
                    return;
                } else if (firstEnd == 5) {
                    expect(line, "VALUE");
                    valueMode = true;
                    // VALUE <key> <flags> <bytes> [<cas unique>]\r\n
                    final int keyStart = firstEnd + 1;
                    final int keyEnd = endIndex(line, keyStart);
                    final String key = line.substring(keyStart, keyEnd);
                    if (key.isEmpty()) {
                        throw new IOException("Unexpected line: " + line);
                    }

                    final int flagsStart = keyEnd + 1;
                    final int flagsEnd = endIndex(line, flagsStart);
                    if (flagsEnd <= flagsStart) {
                        throw new IOException("Unexpected line: " + line);
                    }

                    final int sizeStart = flagsEnd + 1;
                    final int sizeEnd = endIndex(line, sizeStart);
                    if (sizeEnd <= sizeStart) {
                        throw new IOException("Unexpected line: " + line);
                    }
                    final int size = (int) parseLong(line, sizeStart, sizeEnd);

                    final int casStart = sizeEnd + 1;
                    final int casEnd = endIndex(line, casStart);
                    long cas = 0;
                    if (casStart < casEnd) {
                        cas = parseLong(line, casStart, casEnd);
                    }
                    this.key = key;
                    this.value = new byte[size];
                    this.valueOffset = 0;
                    this.cas = cas;
                } else if (valueMode) {
                    // when in valueMode, the only valid responses are "END" and "VALUE"
                    throw new IOException("Unexpected line: " + line);
                } else if (firstEnd == 2) {
                    if (firstChar == 'O') {
                        expect(line, "OK");
                        out.add(AsciiResponse.OK);
                        return;
                    }
                } else if (firstEnd == 6) {
                    if (firstChar == 'S') {
                        expect(line, "STORED");
                        out.add(AsciiResponse.STORED);
                        return;
                    } else {
                        expect(line, "EXISTS");
                        out.add(AsciiResponse.EXISTS);
                        return;
                    }
                } else if (firstEnd == 7) {
                    if (firstChar == 'T') {
                        expect(line, "TOUCHED");
                        out.add(AsciiResponse.TOUCHED);
                        return;
                    } else {
                        expect(line, "DELETED");
                        out.add(AsciiResponse.DELETED);
                        return;
                    }
                } else if (firstEnd == 9) {
                    expect(line, "NOT_FOUND");
                    out.add(AsciiResponse.NOT_FOUND);
                    return;
                } else if (firstEnd == 10) {
                    expect(line, "NOT_STORED");
                    out.add(AsciiResponse.NOT_STORED);
                    return;
                } else {
                    throw new IOException("Unexpected line: " + line);
                }
            }
        }
    }

    private void expect(final StringBuilder line, final String compareTo) throws IOException {
        final int length = compareTo.length();
        for (int i = 0; i < length; i++) {
            if (line.charAt(i) != compareTo.charAt(i)) {
                throw new IOException("Unexpected line: " + line);
            }
        }
    }

    private long parseLong(final StringBuilder line, final int from, final int to) throws IOException {
        long res = 0;
        for (int i = from; i < to; i++) {
            final int digit = line.charAt(i) - '0';
            if (digit < 0 || digit > 9) {
                throw new IOException("Unexpected line: " + line);
            }
            res *= 10;
            res += digit;
        }
        return res;
    }

    private int endIndex(final StringBuilder line, final int from) {
        final int length = line.length();
        for (int i = from; i < length; i++) {
            if (line.charAt(i) == ' ') {
                return i;
            }
        }
        return length;
    }

    private StringBuilder readLine(final ByteBuf buf, final int available) throws IOException {
        if (consumed) {
            line.setLength(0);
            consumed = false;
        }
        for (int i = 0; i < available - 1; i++) {
            final char b = (char) buf.readUnsignedByte();
            if (b == '\r') {
                if (buf.readUnsignedByte() == '\n') {
                    consumed = true;
                    return line;
                }
                throw new IOException("Expected newline, got something else");
            }
            line.append(b);
        }
        return null;
    }

}