Java tutorial
/** * Copyright 2012 Nikita Koksharov * * 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 whitespell.net.websockets.socketio.parser; import whitespell.net.websockets.socketio.AckCallback; import whitespell.net.websockets.socketio.ack.AckManager; import whitespell.net.websockets.socketio.namespace.Namespace; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import java.io.IOException; import java.util.UUID; public class Decoder { private final UTF8CharsScanner charsScanner = new UTF8CharsScanner(); private final JsonSupport jsonSupport; private final AckManager ackManager; public Decoder(JsonSupport jsonSupport, AckManager ackManager) { this.jsonSupport = jsonSupport; this.ackManager = ackManager; } // fastest way to parse chars to int private long parseLong(ByteBuf chars) { return parseLong(chars, chars.readerIndex() + chars.readableBytes()); } private long parseLong(ByteBuf chars, int length) { long result = 0; for (int i = chars.readerIndex(); i < length; i++) { int digit = ((int) chars.getByte(i) & 0xF); for (int j = 0; j < length - 1 - i; j++) { digit *= 10; } result += digit; } return result; } private Packet decodePacket(ByteBuf buffer, UUID uuid) throws IOException { if (buffer.readableBytes() < 3) { throw new DecoderException("Can't parse " + buffer.toString(CharsetUtil.UTF_8)); } PacketType type = getType(buffer); int readerIndex = buffer.readerIndex() + 1; // 'null' to avoid unnecessary StringBuilder creation boolean hasData = false; StringBuilder messageId = null; for (readerIndex += 1; readerIndex < buffer.readableBytes(); readerIndex++) { if (messageId == null) { messageId = new StringBuilder(4); } byte msg = buffer.getByte(readerIndex); if (msg == Packet.SEPARATOR) { break; } if (msg != (byte) '+') { messageId.append((char) msg); } else { hasData = true; } } Long id = null; if (messageId != null && messageId.length() > 0) { id = Long.valueOf(messageId.toString()); } // 'null' to avoid unnecessary StringBuilder creation StringBuilder endpointBuffer = null; for (readerIndex += 1; readerIndex < buffer.readableBytes(); readerIndex++) { if (endpointBuffer == null) { endpointBuffer = new StringBuilder(); } byte msg = buffer.getByte(readerIndex); if (msg == Packet.SEPARATOR) { break; } endpointBuffer.append((char) msg); } String endpoint = Namespace.DEFAULT_NAME; if (endpointBuffer != null && endpointBuffer.length() > 0) { endpoint = endpointBuffer.toString(); } if (buffer.readableBytes() == readerIndex) { buffer.readerIndex(buffer.readableBytes()); } else { readerIndex += 1; buffer.readerIndex(readerIndex); } Packet packet = new Packet(type); packet.setEndpoint(endpoint); if (id != null) { packet.setId(id); if (hasData) { packet.setAck(Packet.ACK_DATA); } else { packet.setAck(true); } } switch (type) { case ERROR: { if (!buffer.isReadable()) { break; } String[] pieces = buffer.toString(CharsetUtil.UTF_8).split("\\+"); if (pieces.length > 0 && pieces[0].trim().length() > 0) { ErrorReason reason = ErrorReason.valueOf(Integer.valueOf(pieces[0])); packet.setReason(reason); if (pieces.length > 1) { ErrorAdvice advice = ErrorAdvice.valueOf(Integer.valueOf(pieces[1])); packet.setAdvice(advice); } } break; } case MESSAGE: { if (buffer.isReadable()) { packet.setData(buffer.toString(CharsetUtil.UTF_8)); } else { packet.setData(""); } break; } case EVENT: { ByteBufInputStream in = new ByteBufInputStream(buffer); Event event = jsonSupport.readValue(in, Event.class); packet.setName(event.getName()); if (event.getArgs() != null) { packet.setArgs(event.getArgs()); } break; } case JSON: { ByteBufInputStream in = new ByteBufInputStream(buffer); JsonObject obj = jsonSupport.readValue(in, JsonObject.class); if (obj != null) { packet.setData(obj.getObject()); } else { in.reset(); Object object = jsonSupport.readValue(in, Object.class); packet.setData(object); } break; } case CONNECT: { if (buffer.isReadable()) { packet.setQs(buffer.toString(CharsetUtil.UTF_8)); } break; } case ACK: { if (!buffer.isReadable()) { break; } boolean validFormat = true; int plusIndex = -1; for (int i = buffer.readerIndex(); i < buffer.readerIndex() + buffer.readableBytes(); i++) { byte dataChar = buffer.getByte(i); if (!Character.isDigit(dataChar)) { if (dataChar == '+') { plusIndex = i; break; } else { validFormat = false; break; } } } if (!validFormat) { break; } if (plusIndex == -1) { packet.setAckId(parseLong(buffer)); break; } else { packet.setAckId(parseLong(buffer, plusIndex)); buffer.readerIndex(plusIndex + 1); ByteBufInputStream in = new ByteBufInputStream(buffer); AckCallback<?> callback = ackManager.getCallback(uuid, packet.getAckId()); AckArgs args = jsonSupport.readAckArgs(in, callback.getResultClass()); packet.setArgs(args.getArgs()); } break; } case DISCONNECT: case HEARTBEAT: case NOOP: break; } buffer.readerIndex(buffer.readerIndex() + buffer.readableBytes()); return packet; } private PacketType getType(ByteBuf buffer) { int typeId = buffer.getByte(buffer.readerIndex()) & 0xF; if (typeId >= PacketType.VALUES.length || buffer.getByte(buffer.readerIndex() + 1) != Packet.SEPARATOR) { throw new DecoderException("Can't parse " + buffer.toString(CharsetUtil.UTF_8)); } return PacketType.valueOf(typeId); } public Packet decodePacket(String string, UUID uuid) throws IOException { ByteBuf buf = Unpooled.copiedBuffer(string, CharsetUtil.UTF_8); Packet packet = decodePacket(buf, uuid); buf.release(); return packet; } public Packet decodePackets(ByteBuf buffer, UUID uuid) throws IOException { if (isCurrentDelimiter(buffer, buffer.readerIndex())) { buffer.readerIndex(buffer.readerIndex() + Packet.DELIMITER_BYTES.length); Integer len = extractLength(buffer); int startIndex = buffer.readerIndex(); ByteBuf frame = buffer.slice(startIndex, len); Packet packet = decodePacket(frame, uuid); buffer.readerIndex(startIndex + len); return packet; } else { Packet packet = decodePacket(buffer, uuid); return packet; } } private Integer extractLength(ByteBuf buffer) { int len = (int) parseLengthHeader(buffer); // scan utf8 symbols if needed if (buffer.capacity() > buffer.readerIndex() + len && !isCurrentDelimiter(buffer, buffer.readerIndex() + len)) { int index = charsScanner.findTailIndex(buffer, buffer.readerIndex(), buffer.capacity(), len); len = index - buffer.readerIndex(); } return len; } private long parseLengthHeader(ByteBuf buffer) { int delimiterIndex = delimiterIndexOf(buffer, buffer.readerIndex(), buffer.readableBytes()); if (delimiterIndex == -1) { throw new DecoderException("Can't find tail delimiter"); } long len = parseLong(buffer, delimiterIndex); buffer.readerIndex(delimiterIndex + Packet.DELIMITER_BYTES.length); return len; } private int delimiterIndexOf(ByteBuf buffer, int startIndex, int length) { ByteBuf buf = buffer.slice(startIndex, length); for (int i = 0; i < buf.readableBytes(); i++) { if (isCurrentDelimiter(buf, i)) { return startIndex + i; } } return -1; } private boolean isCurrentDelimiter(ByteBuf buffer, int index) { for (int i = 0; i < Packet.DELIMITER_BYTES.length; i++) { if (buffer.getByte(index + i) != Packet.DELIMITER_BYTES[i]) { return false; } } return true; } }