2014-07-10 8 views
6

Sto lavorando con un semplice protocollo binario. Ogni pacchetto è composto da 10 byte. Il primo byte specifica il tipo di pacchetto. Ci sono molti (~ 50) tipi di pacchetti usati.Separazione del parser e del gestore del protocollo in Java

Voglio scrivere un parser generale per questo protocollo che è indipendente dalla gestione dei pacchetti. Quindi il parser dovrebbe rilevare il tipo di pacchetto e inserire i dati in un'istanza della classe di pacchetto appropriata, che contiene i dati del protocollo. Ad esempio, considerando le classi sottostanti: Quando il parser rileva il tipo di pacchetto 1 -> nuovo Tipo1() e legge i byte non elaborati e imposta la temperatura e l'umidità. Allo stesso modo per il tipo di pacchetto 2 e tutti gli altri tipi di pacchetti.

class Packet { 
    byte[] raw; 
} 

class Type1 extends Packet { 
    int temperature; 
    int humidity; 
} 

class Type2 extends Packet { 
    DateTime sunrise; 
    DateTime sunset; 
} 

Poiché ci sono tanti tipi di pacchetti, ma ogni applicazione utilizza solo pochissime, dovrebbe essere possibile registrare per determinati tipi prima di analizzare inizio. Tutti gli altri tipi di pacchetti sono ignorati.

Sto pianificando di avere un PacketParser per ogni tipo di pacchetto. Probabilmente, ho bisogno anche di un corso per ogni tipo. Es .:

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); 
    } 
} 

Come collegare parser e gestore? Sopra un approccio ingenuo: Il programma deve implementare Type1Handler e impostare la variabile statica Type1Parser.type1Handler.

Poi il parser principale può apparire come segue:

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; 
     ... 
    } 
    } 
} 

Tuttavia, questo sembra essere 1) un sacco di linee molto simili di codice 2) un sacco di spese generali, dal momento che tutti parser pacchetto sono istanziati e per ogni pacchetto viene chiamato parse(), anche se non è registrato alcun gestore.

Qualche idea su come migliorare questo codice?

Nota: l'analisi deve essere trasparente per il programma. Il codice di analisi dovrebbe rimanere all'interno della "libreria di analisi". Quindi idealmente, il programma solo "conosce" le classi TypeXHandler e TypeX.

+0

"per ogni pacchetto parse() viene chiamato, anche se non è registrato alcun gestore." - Sembra che sia necessario chiamare un parser, almeno, per ignorare i byte dei pacchetti nel flusso di input. È possibile leggere il tipo di pacchetto e saltare il resto dell'analisi semplicemente saltando la lunghezza del pacchetto (ho assunto che ogni tipo di pacchetto abbia una lunghezza fissa). –

+0

Gestisce la porzione di codice a cui si desidera passare le informazioni sui pacchetti? Cosa si suppone di fare? – NESPowerGlove

+0

Per sbarazzarsi di * alcune * ripetizioni in 'parse' non si potrebbe fare' parser PacketParser; 'usare l'interruttore per determinare e impostare il parser, e poi fuori dal commutatore fare' parser.parse (pacchetto); '? So che non è profondo ma riduce il verbatim. – ChiefTwoPencils

risposta

0

Beh, quasi come risposta torquestomp, ecco che arriva il mio codice:

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 m.b. != 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(); 
     } 
    } 
} 

Ed ecco come si può utilizzare:

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 
      } 
     }); 
    } 
} 
2

Non c'è una risposta perfetta a questa domanda di progettazione, e non voglio fingere che il mio sia, ma se tutto va bene il mio approccio istintuale a questo problema ti insegna cose che non sapevi già! Il componente principale mancante dal codice che vedo è Generics:

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

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

In questo modo, è possibile utilizzare l'inizializzazione statica pigro per gestire i tipi di pacchetto si è a conoscenza. Non voglio rimpolpare il codice del tutto qui, ma per dare un'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. 
    // ... 
} 

===

Le cose che mi omesso: Qualificazioni, implementazione di livello inferiore, Controllo errori, la sincronizzazione, il metodo principale , ecc. A seconda del tuo set-up, l'inizializzazione statica potrebbe non essere il modo giusto per chiamare TypeRegistry.register, quindi potresti prendere in considerazione un file di proprietà che elenca le classi (ugh, ma ha i suoi meriti), o una sequenza hard-coded di chiama il tuo metodo principale.

Poiché Parser e Handler sono interfacce funzionali qui, non dimenticare che è possibile implementarle con lambda! Puoi salvare tonnellate di linee di codice in questo modo.

1

avevi ragione quando ha detto che hanno bisogno di una classe astratta per analizzando la matrice di dati.

package parser; 

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

    } 

Allora per ogni tipo di pacchetto (si dice che si può avere 50 ma se il primo byte indica il tipo di pacchetto poi 256 tipi deferenti sono possibili), è possibile creare classe come necessario per certi tipi es. . Type1Parser per il tipo 1 Type122Parser per il tipo 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) {} 
    } 

allora si può avere una classe che rappresenta il parser principale per tutti. Se è necessario per ogni pacchetto di reddito avere un oggetto per un uso successivo, è possibile tenerlo in vettoriale.

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);  
     } 

}