package com.nq.ws.parser; import com.nq.ws.hasbinary.HasBinary; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; final public class IOParser implements Parser { private static final Logger logger = Logger.getLogger(IOParser.class.getName()); private IOParser() {} final public static class Encoder implements Parser.Encoder { public Encoder() {} @Override public void encode(Packet obj, Callback callback) { if ((obj.type == EVENT || obj.type == ACK) && HasBinary.hasBinary(obj.data)) { obj.type = obj.type == EVENT ? BINARY_EVENT : BINARY_ACK; } if (logger.isLoggable(Level.FINE)) { logger.fine(String.format("encoding packet %s", obj)); } if (BINARY_EVENT == obj.type || BINARY_ACK == obj.type) { encodeAsBinary(obj, callback); } else { String encoding = encodeAsString(obj); callback.call(new String[] {encoding}); } } private String encodeAsString(Packet obj) { StringBuilder str = new StringBuilder("" + obj.type); if (BINARY_EVENT == obj.type || BINARY_ACK == obj.type) { str.append(obj.attachments); str.append("-"); } if (obj.nsp != null && obj.nsp.length() != 0 && !"/".equals(obj.nsp)) { str.append(obj.nsp); str.append(","); } if (obj.id >= 0) { str.append(obj.id); } if (obj.data != null) { str.append(obj.data); } if (logger.isLoggable(Level.FINE)) { logger.fine(String.format("encoded %s as %s", obj, str)); } return str.toString(); } private void encodeAsBinary(Packet obj, Callback callback) { Binary.DeconstructedPacket deconstruction = Binary.deconstructPacket(obj); String pack = encodeAsString(deconstruction.packet); List buffers = new ArrayList(Arrays.asList(deconstruction.buffers)); buffers.add(0, pack); callback.call(buffers.toArray()); } } final public static class Decoder implements Parser.Decoder { /*package*/ BinaryReconstructor reconstructor; private Decoder.Callback onDecodedCallback; public Decoder() { this.reconstructor = null; } @Override public void add(String obj) { Packet packet = decodeString(obj); if (BINARY_EVENT == packet.type || BINARY_ACK == packet.type) { this.reconstructor = new BinaryReconstructor(packet); if (this.reconstructor.reconPack.attachments == 0) { if (this.onDecodedCallback != null) { this.onDecodedCallback.call(packet); } } } else { if (this.onDecodedCallback != null) { this.onDecodedCallback.call(packet); } } } @Override public void add(byte[] obj) { if (this.reconstructor == null) { throw new RuntimeException("got binary data when not reconstructing a packet"); } else { Packet packet = this.reconstructor.takeBinaryData(obj); if (packet != null) { this.reconstructor = null; if (this.onDecodedCallback != null) { this.onDecodedCallback.call(packet); } } } } private static Packet decodeString(String str) { int i = 0; int length = str.length(); Packet p = new Packet<>(Character.getNumericValue(str.charAt(0))); if (p.type < 0 || p.type > types.length - 1) { throw new DecodingException("unknown packet type " + p.type); } if (BINARY_EVENT == p.type || BINARY_ACK == p.type) { if (!str.contains("-") || length <= i + 1) { throw new DecodingException("illegal attachments"); } StringBuilder attachments = new StringBuilder(); while (str.charAt(++i) != '-') { attachments.append(str.charAt(i)); } p.attachments = Integer.parseInt(attachments.toString()); } if (length > i + 1 && '/' == str.charAt(i + 1)) { StringBuilder nsp = new StringBuilder(); while (true) { ++i; char c = str.charAt(i); if (',' == c) break; nsp.append(c); if (i + 1 == length) break; } p.nsp = nsp.toString(); } else { p.nsp = "/"; } if (length > i + 1){ Character next = str.charAt(i + 1); if (Character.getNumericValue(next) > -1) { StringBuilder id = new StringBuilder(); while (true) { ++i; char c = str.charAt(i); if (Character.getNumericValue(c) < 0) { --i; break; } id.append(c); if (i + 1 == length) break; } try { p.id = Integer.parseInt(id.toString()); } catch (NumberFormatException e){ throw new DecodingException("invalid payload"); } } } if (length > i + 1){ try { str.charAt(++i); p.data = new JSONTokener(str.substring(i)).nextValue(); } catch (JSONException e) { logger.log(Level.WARNING, "An error occured while retrieving data from JSONTokener", e); throw new DecodingException("invalid payload"); } if (!isPayloadValid(p.type, p.data)) { throw new DecodingException("invalid payload"); } } if (logger.isLoggable(Level.FINE)) { logger.fine(String.format("decoded %s as %s", str, p)); } return p; } private static boolean isPayloadValid(int type, Object payload) { switch (type) { case Parser.CONNECT: case Parser.CONNECT_ERROR: return payload instanceof JSONObject; case Parser.DISCONNECT: return payload == null; case Parser.EVENT: case Parser.BINARY_EVENT: return payload instanceof JSONArray && ((JSONArray) payload).length() > 0 && !((JSONArray) payload).isNull(0); case Parser.ACK: case Parser.BINARY_ACK: return payload instanceof JSONArray; default: return false; } } @Override public void destroy() { if (this.reconstructor != null) { this.reconstructor.finishReconstruction(); } this.onDecodedCallback = null; } @Override public void onDecoded (Callback callback) { this.onDecodedCallback = callback; } } /*package*/ static class BinaryReconstructor { public Packet reconPack; /*package*/ List buffers; BinaryReconstructor(Packet packet) { this.reconPack = packet; this.buffers = new ArrayList<>(); } public Packet takeBinaryData(byte[] binData) { this.buffers.add(binData); if (this.buffers.size() == this.reconPack.attachments) { Packet packet = Binary.reconstructPacket(this.reconPack, this.buffers.toArray(new byte[this.buffers.size()][])); this.finishReconstruction(); return packet; } return null; } public void finishReconstruction () { this.reconPack = null; this.buffers = new ArrayList<>(); } } }