Separation of parser and protocol handler in Java

I work with a simple binary protocol. Each packet consists of 10 bytes. The first byte determines the type of packet. There are many types of packages (~ 50).

I want to write a general parser for this protocol, which is independent of packet processing. Therefore, the analyzer must determine the type of packet and place the data in an instance of the corresponding packet class that contains the protocol data. For example, given the following classes: When the parser detects packet type 1 → new Type1 () and reads raw bytes and sets the temperature and humidity. Similarly for package type 2 and all other package types.

class Packet { byte[] raw; } class Type1 extends Packet { int temperature; int humidity; } class Type2 extends Packet { DateTime sunrise; DateTime sunset; } 

Since there are so many types of packages, but very little is used for each application, it should be possible to register certain types before starting parsing. All other packet types are ignored.

I plan to have PacketParser for each type of package. Maybe I need a handler class for each type. For instance:.

 abstract class Type1Parser { abstract void handle(Type1 packet); } class Type1Parser extends PacketParser { //how to use/set handler? how to pass packet to handler? static public Type1Handler type1Handler = null; @override void parse(Packet input) { if(type1Handler == null) return; Type1 packet = new Type1(input); packet.temperature = byteToInt(input.raw, 0, 3); packet.humidity = byteToInt(input.raw, 4, 7); type1Handler.handle(packet); } } 

How to connect parser and handler? Above the naive approach: The program needs to implement Type1Handler and set the static variable Type1Parser.type1Handler.

Then the main parser might look like this:

 class MainParser { Type1Parser type1 = new Type1Parser(); Type2Parser type2 = new Type2Parser(); ... void parse(byte[] packet) { switch(packet[0]) { case 1: type1.parse(packet); break; case 2: type2.parse(packet); break; ... } } } 

However, this, apparently, 1) a lot of very similar lines of code 2) a lot of overhead, since the entire batch parser is created and is called for each batch call (), even if the handler is not registered.

Any ideas on improving this code?

Note. Parsing should be transparent to the program. The analysis code must remain inside the parsing library. Therefore, ideally, the program only “knows” the TypeXHandler and TypeX classes.

+6
source share
3 answers

Well, almost like a torquestomp answer, here is my code:

 interface Packet { } interface PacketParser<T extends Packet> { Class<T> getPacketClass(); int getPacketId(); int getPacketLength(); Packet parse(byte[] raw, int offset); } interface PacketListener<T extends Packet> { Class<T> getPacketClass(); void onPacket(T packet); } interface PacketParsersRegistry { <T extends Packet> void registerPacketParser(PacketParser<T> packetParser); <T extends Packet> void registerPacketListener(PacketListener<T> packetListener); } class PacketHandlers<T extends Packet> { final PacketParser<T> parser; PacketListener<T> listener; PacketHandlers(PacketParser<T> parser) { this.parser = parser; } void setListener(PacketListener<T> listener) { this.listener = listener; } } class MainParser implements PacketParsersRegistry { private final HashMap<Class<?>, PacketHandlers<?>> handlers = new HashMap<>(); private final HashMap<Integer, PacketParser> parsers = new HashMap<>(); @Override public <T extends Packet> void registerPacketParser(PacketParser<T> packetParser) { parsers.put(packetParser.getPacketId(), packetParser); Class<T> packetClass = packetParser.getPacketClass(); handlers.put(packetClass, new PacketHandlers<>(packetParser)); } @Override public <T extends Packet> void registerPacketListener(PacketListener<T> packetListener) { //noinspection unchecked PacketHandlers<T> handlers = (PacketHandlers<T>) this.handlers.get(packetListener.getPacketClass()); if (handlers != null) { handlers.setListener(packetListener); } } void parse(byte[] stream, int offset) { while (offset < stream.length) { int type = stream[offset]; PacketParser parser = parsers.get(type); // parser mb != null here PacketListener listener = (PacketListener) handlers.get(parser.getPacketClass()); if (listener != null) { Packet packet = parser.parse(stream, offset); //noinspection unchecked listener.onPacket(packet); } offset += parser.getPacketLength(); } } } 

And here is how you can use it:

 class HumidityPacket implements Packet {} public class Main { public static void main(String[] args) { MainParser parser = new MainParser(); //... parser.registerPacketListener(new PacketListener<HumidityPacket>() { @Override public Class<HumidityPacket> getPacketClass() { return HumidityPacket.class; } @Override public void onPacket(HumidityPacket packet) { // todo } }); } } 
0
source

There is no perfect answer to this design question, and I do not want to pretend to be mine, but hopefully my instinctive approach to this problem teaches you something that you did not know yet! The main missing component from your code that I see is Generics:

 public interface Parser<T extends Packet> { T parse(Packet packet); } public interface Handler<T extends Packet> { void handle(T packet); } 

That way, you can use lazy static initialization to manage the types of packages you know about. I will not fully state the code here, but give you an idea:

 public class TypeRegistry { private static Map<Integer, TypeHandlerBundle<?>> typeHandlerBundles; static <T> register(int typeNum, Class<T> clazz, Parser<T> parser, Handler<T> handler) { // Make bundle, add to map } ... void parse(Packet packet) { if (typeHandlerBundles.containsKey((int) packet[0])) { TypeHandlerBundle<?> bundle = typeHandlerBundles.get((int) packet[0]); bundle.parseAndHandle(packet); } } } public class TypeHandlerBundle<T extends Packet> { ... private final Parser<T> parser; private final Handler<T> handler; ... void parseAndHandle(Packet packet) { T parsedPacket = parser.parse(packet); handler.handle(parsedPacket); } } ... public class Type1Processor { static { TypeRegistry.register(1, Type1.class, TYPE1_PARSER, TYPE1_HANDLER); } // Definition of constants, implementation, etc. // ... } 

===

Things I missed: qualifiers, lower level implementation, error checking, synchronization, main method, etc. Depending on your setup, static initialization may not be the right way to call TypeRegistry.register , so you could instead consider a property file that lists the classes (ugh, but has its merits) or a hard-coded sequence of calls in your main method.

Since Parser and Handler are functional interfaces, do not forget that you can implement them with lambdas! You can save tons of lines of code this way.

+2
source

You were right when you said you needed one abstract class to parse a data array.

  package parser; public abstract class TypeParser { public abstract void parse(byte[] arr); } 

Then for each type of package (you said that you can have 50, but if the first byte indicates the type of package, then 256 types of grace are possible), you can create a class as needed for a specific type, for example .. Type1Parser for type 1 Type122Parser for type 122.

 package parser.type; import parser.TypeParser; public class Type1Parser extends TypeParser{ public void parse(byte[] array){ // do with the bytes of array what you want } } package parser.type; import parser.TypeParser; public class Type122Parser extends TypeParser { public void parse(byte[] arr) {} } 

Then you can have one class representing the main parser for everyone. If you want each income package to have one object for future use, you can keep it in a vector.

 package parser; import java.util.Vector; public class MainParser { private Vector<TypeParser> vecTypeParse=new Vector<TypeParser>(); public void parsePacket(byte[] array){ if(array==null || array.length<1) return; // or throw some exception int typePacket=array[0]&0xff; String s="parser.type.Type"+String.valueOf(typePacket)+"Parser"; TypeParser type=null; try { type=(TypeParser)Class.forName(s).newInstance(); //here you create class that you need } catch(InstantiationException e) {e.printStackTrace(); } catch(IllegalAccessException e) {e.printStackTrace(); } catch(ClassNotFoundException e) {e.printStackTrace();} // you can do something with the exceptons if(type==null) return; // or throw some exception type.parse(array); // here parse data for class you just created. this.vecTypeParse.addElement(type); } } 
+1
source

Source: https://habr.com/ru/post/972050/


All Articles