[Java] Byte array naar Java object zonder setters

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • dream_in_code
  • Registratie: Februari 2010
  • Laatst online: 11:02
Hallo Tweakers,

In een hobbyproject heb ik een UDP stream die ik uitlees. Deze informatie komt binnen als een byte[]. Nu wil ik graag de inhoud van de byte array in een Java object steken. Nu kan dat natuurlijk door setters te gebruiken:
Java:
1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws ClassNotFoundException, IOException {
        byte[] a = new byte[] {-61, 105, 99, 65, 105, 82, -17, 64, -110, -47, 42, 67, -110, -47, 42, 67};
        ByteBuffer buffer = ByteBuffer.wrap(a).order(ByteOrder.LITTLE_ENDIAN);
        UDPPacket udpPacket = new UDPPacket();
        udpPacket.setM_time(buffer.getFloat());
        udpPacket.setM_lapTime(buffer.getFloat());
        udpPacket.setM_lapDistance(buffer.getFloat());
        udpPacket.setM_totalDistance(buffer.getFloat());
    }

Java:
1
2
3
4
5
6
7
8
public class UDPPacket {
    float m_time;
    float m_lapTime;
    float m_lapDistance;
    float m_totalDistance;
    // Tientallen variabelen meer

    // Getters and setters


Aangezien het om een flinke lijst met variabelen gaat die geset moeten worden, vroeg ik mij af: is er wellicht een manier om de code overzichtelijk te houden? Bijvoorbeeld iets wat lijkt op deserialisatie, alleen dan zonder te serializeren?
Java:
1
UDPPacket test = (UDPPacket) SerializationUtils.deserialize(a);


Ik maak gebruik van Spring Tool Suite en Java 9.

Alvast bedankt voor het meedenken!

Beste antwoord (via dream_in_code op 18-02-2018 22:33)


  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
Een goed voorbeeld van hoe het niet moet in ieder geval. Reflection is vrijwel altijd een slecht idee.

@dream_in_code gewoon met de hand die dingen op de juiste volgorde uitpakken. Het is nauwelijks code, helemaal als je het via de constructor doet:

Java:
1
2
3
4
5
        UDPPacket udpPacket = new UDPPacket(
            buffer.getFloat(),
            buffer.getFloat(),
            buffer.getFloat(),
            buffer.getFloat());

https://niels.nu

Alle reacties


Acties:
  • 0 Henk 'm!

  • kunnen
  • Registratie: Februari 2004
  • Niet online
Ik zou sowieso zorgen dat je UDPPacket-class een constructor of builder heeft met de ByteBuffer/array als parameter, zodat je de constructie binnen die class houdt.

Zend je het object zelf aan de andere kant? Of zit je vast aan dit exacte byteformaat?

Verder zou ik de class trouwens hernoemen: de eigenschappen zijn niet eigen aan UDPPackets, maar meer aan een SportStatisticsPacket ofzo.

[ Voor 12% gewijzigd door kunnen op 14-02-2018 21:08 ]


Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

Wat is de bron van de stream, heb je die in de hand? Je kunt het object aan de sturende kant gewoon serializen met Objectoutput stream, en vervolgens deserializen met ObjectInputStream.

En anders wat @kunnen zegt.

Acties:
  • 0 Henk 'm!

  • dream_in_code
  • Registratie: Februari 2010
  • Laatst online: 11:02
Ik zou sowieso zorgen dat je UDPPacket-class een constructor of builder heeft met de ByteBuffer/array als parameter, zodat je de constructie binnen die class houdt.
Ja, maar dan zit ik daar met een soortgelijk probleem.
Zend je het object zelf aan de andere kant? Of zit je vast aan dit exacte byteformaat?
Ik zend niet zelf, ik zit vast aan dit byteformaat.
Verder zou ik de class trouwens hernoemen: de eigenschappen zijn niet eigen aan UDPPackets, maar meer aan een SportStatisticsPacket ofzo.
Dat was ik ook zeker van plan, alleen voor het voorbeeld zo genoemd ;)

Acties:
  • 0 Henk 'm!

  • kunnen
  • Registratie: Februari 2004
  • Niet online
Het casten van een byte array naar een struct-like class (zoals in C) is in Java is niet mogelijk. Je zult dus ergens handmatig voor elke property een regel moeten hebben die hem set op basis van de array/buffer.

Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

kunnen schreef op woensdag 14 februari 2018 @ 22:27:
Het casten van een byte array naar een struct-like class (zoals in C) is in Java is niet mogelijk. Je zult dus ergens handmatig voor elke property een regel moeten hebben die hem set op basis van de array/buffer.
In C is dat sowieso niet aan te raden, omdat members van een struct niet gegarandeerd opeenvolgend in memory staan door mogelijke padding afhankelijk van alignment eisen.

Acties:
  • 0 Henk 'm!

Verwijderd

Het onderstaande werkt:
- Als je voor elke field type die je hebt een if implementeert
- Als je vooraf weet/berekent hoeveel bytes je moet alloceren
- Als je fields public zijn

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class UDPPacket {

    public float m_time;
    public float m_lapTime;
    public float m_lapDistance;
    public float m_totalDistance;

    public ByteBuffer serialize() throws IllegalArgumentException, IllegalAccessException, IOException{
        // 4 floats * 4 byte per stuk
        ByteBuffer b = ByteBuffer.allocate(4 * 4).order(ByteOrder.LITTLE_ENDIAN);
        
        Field[] fields = this.getClass().getFields();
        for(Field field : fields){
            if(field.getGenericType().getTypeName().equals("byte")) {
                b.put(field.getByte(this));
            } else if(field.getGenericType().getTypeName().equals("int")) {
                b.putInt(field.getInt(this));
            } else if(field.getGenericType().getTypeName().equals("float")) {
                b.putFloat(field.getFloat(this));
            }
            // andere types
        }
        
        return b;
    }

    public void deserialize(ByteBuffer buffer) throws IllegalArgumentException, IllegalAccessException, IOException{
        Field[] fields = this.getClass().getFields();
        for(Field field : fields){
            if(field.getGenericType().getTypeName().equals("byte")) {
                field.setByte(this, buffer.get());
            } else if(field.getGenericType().getTypeName().equals("int")) {
                field.setInt(this, buffer.getInt());
            } else if(field.getGenericType().getTypeName().equals("float")) {
                field.setFloat(this, buffer.getFloat());
            }
            // andere types
        }
    }
    
    public static void main(String[] args) throws IOException, IllegalArgumentException, IllegalAccessException {
        byte[] a = new byte[] {-61, 105, 99, 65, 105, 82, -17, 64, -110, -47, 42, 67, -110, -47, 42, 67};
        ByteBuffer buffer = ByteBuffer.wrap(a).order(ByteOrder.LITTLE_ENDIAN);
        
        UDPPacket udpPacket = new UDPPacket();
        udpPacket.deserialize(buffer);
        ByteBuffer buffer2 = udpPacket.serialize();
        System.out.println("identical buffers: " + buffer.equals(buffer2));
    }
}

[ Voor 73% gewijzigd door Verwijderd op 14-02-2018 23:16 ]


Acties:
  • Beste antwoord
  • +1 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
Een goed voorbeeld van hoe het niet moet in ieder geval. Reflection is vrijwel altijd een slecht idee.

@dream_in_code gewoon met de hand die dingen op de juiste volgorde uitpakken. Het is nauwelijks code, helemaal als je het via de constructor doet:

Java:
1
2
3
4
5
        UDPPacket udpPacket = new UDPPacket(
            buffer.getFloat(),
            buffer.getFloat(),
            buffer.getFloat(),
            buffer.getFloat());

https://niels.nu


Acties:
  • +1 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

Hydra schreef op donderdag 15 februari 2018 @ 08:30:
Een goed voorbeeld van hoe het niet moet in ieder geval. Reflection is vrijwel altijd een slecht idee.

@dream_in_code gewoon met de hand die dingen op de juiste volgorde uitpakken. Het is nauwelijks code, helemaal als je het via de constructor doet:
In zijn eigen code-comment staat dat het om 'tientallen meer' gaat. In dat geval is een constructor niet erg handig.
dream_in_code schreef op woensdag 14 februari 2018 @ 21:02:
Aangezien het om een flinke lijst met variabelen gaat die geset moeten worden, vroeg ik mij af: is er wellicht een manier om de code overzichtelijk te houden? Bijvoorbeeld iets wat lijkt op deserialisatie, alleen dan zonder te serializeren?
Java:
1
UDPPacket test = (UDPPacket) SerializationUtils.deserialize(a);
Je kan 'speciaal gedrag voor serialize' regelen met de writeObject en readObject methodes, maar ik weet eigenlijk niet of je daar met arbirtraire bytes kan werken. Ik vermoed dat er minimaal een object-header in de serialization zal staan. Met Externalizable kan je nog wat meer vrijheid krijgen...

Maar dat lijkt me geen praktische oplossing voor je.

Als je graag van setters af wil en geen lange constructor wilt, zou ik die ByteBuffer in je object stoppen (of pas in je object de byte-array wrappen). In je get-methodes kan je dan gewoon de float op de juiste plek uitlezen. Dat kan met de getFloat(index) methode. Als het uitsluitend float's zijn en je geen zin hebt om te klooien met het omrekenen naar bytes kan je ook nog weer van je ByteBuffer een FloatBuffer maken via de asFloatBuffer() methode.

[ Voor 5% gewijzigd door ACM op 15-02-2018 23:01 ]


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
Normaliter zorg je dat er aan beide kanten iets zit wat een soort standaard serialisatie formaat gebruikt. Kryo, Avro, protobuf, etc. Als dat niet het geval is en je de andere kant ook niet aan kunt passen blijft er niks anders over dan het gewoon met de hand te doen. En zelfs met 20 velden ofzo is dat erg weinig werk; minder dan er een topic over openen.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Schnupperpuppe
  • Registratie: Maart 2008
  • Laatst online: 09:17
EddoH schreef op woensdag 14 februari 2018 @ 22:46:
[...]


In C is dat sowieso niet aan te raden, omdat members van een struct niet gegarandeerd opeenvolgend in memory staan door mogelijke padding afhankelijk van alignment eisen.
Voor de gewenste alignment heb je doorgaans wel compiler-opties.

Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

Schnupperpuppe schreef op vrijdag 16 februari 2018 @ 09:08:
[...]


Voor de gewenste alignment heb je doorgaans wel compiler-opties.
Overal is een mouw aan te passen (hoewel je soms gewoon met bittere hardware constraints zit), maar unaligned access of bepaalde alignment forceren omdat je een stuk data als struct wilt kunnen casten is gewoon plain wrong IMO.

Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
EddoH schreef op vrijdag 16 februari 2018 @ 10:25:
Overal is een mouw aan te passen (hoewel je soms gewoon met bittere hardware constraints zit), maar unaligned access of bepaalde alignment forceren omdat je een stuk data als struct wilt kunnen casten is gewoon plain wrong IMO.
Mee eens. En bovendien voor de TS uberhaupt niet relevant.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Schnupperpuppe
  • Registratie: Maart 2008
  • Laatst online: 09:17
EddoH schreef op vrijdag 16 februari 2018 @ 10:25:
[...]


Overal is een mouw aan te passen (hoewel je soms gewoon met bittere hardware constraints zit), maar unaligned access of bepaalde alignment forceren omdat je een stuk data als struct wilt kunnen casten is gewoon plain wrong IMO.
Mee eens dat het niet de fraaiste oplossing is, maar Java gebruiken om UDP packets uit elkaar te peuteren is nou ook niet de meest voor de hand liggende aanpak...

Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

Schnupperpuppe schreef op vrijdag 16 februari 2018 @ 15:24:
[...]


Mee eens dat het niet de fraaiste oplossing is, maar Java gebruiken om UDP packets uit elkaar te peuteren is nou ook niet de meest voor de hand liggende aanpak...
Wat is er nou weer mis met Java gebruiken voor UDP packets? :?

Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
Schnupperpuppe schreef op vrijdag 16 februari 2018 @ 15:24:
Mee eens dat het niet de fraaiste oplossing is, maar Java gebruiken om UDP packets uit elkaar te peuteren is nou ook niet de meest voor de hand liggende aanpak...
Kan je dit ook even uitleggen? Want dit is pertinente onzin.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Schnupperpuppe
  • Registratie: Maart 2008
  • Laatst online: 09:17
Hydra schreef op zaterdag 17 februari 2018 @ 11:28:
[...]


Kan je dit ook even uitleggen? Want dit is pertinente onzin.
Kijk naar de lappen code in deze thread ;-)
Het peuteren met binary data (bitn**ken), is in C/C++ of Perl beduidend eenvoudiger. That's all.

Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
Schnupperpuppe schreef op zaterdag 17 februari 2018 @ 11:39:
Kijk naar de lappen code in deze thread ;-)
Hoe is rare meuk die sommige mensen produceren relevant? De OP laat zien dat het helemaal niet veel werk is. Gewoon vanuit een bytebuffer de zaken uitlezen in welke order je ook wil.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Hydra schreef op vrijdag 16 februari 2018 @ 09:02:
Normaliter zorg je dat er aan beide kanten iets zit wat een soort standaard serialisatie formaat gebruikt. Kryo, Avro, protobuf, etc. Als dat niet het geval is en je de andere kant ook niet aan kunt passen blijft er niks anders over dan het gewoon met de hand te doen. En zelfs met 20 velden ofzo is dat erg weinig werk; minder dan er een topic over openen.
Wat je gebruikt is json. Binaire onzin is vrijwel nergens meer voor nodig. Als je minder bytes wil, wat meestal hooguit bij lange termijn opslag interessant is, comprimeer je de boel.. :p
Schnupperpuppe schreef op zaterdag 17 februari 2018 @ 11:39:
Het peuteren met binary data (bitn**ken), is in C/C++ of Perl beduidend eenvoudiger. That's all.
Voor verreweg de meeste programmeurs niet meer, die gebruiken dat soort talen namelijk niet meer..

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
pedorus schreef op zaterdag 17 februari 2018 @ 14:01:
Wat je gebruikt is json. Binaire onzin is vrijwel nergens meer voor nodig. Als je minder bytes wil, wat meestal hooguit bij lange termijn opslag interessant is, comprimeer je de boel.. :p
Anders lees je ff de rest van het topic? Dat doet niet ter zake; hij kan het formaat niet veranderen:
dream_in_code schreef op woensdag 14 februari 2018 @ 21:13:
Ik zend niet zelf, ik zit vast aan dit byteformaat.
Hydra schreef op vrijdag 16 februari 2018 @ 09:02:
Normaliter zorg je dat er aan beide kanten iets zit wat een soort standaard serialisatie formaat gebruikt. Kryo, Avro, protobuf, etc. Als dat niet het geval is en je de andere kant ook niet aan kunt passen blijft er niks anders over dan het gewoon met de hand te doen. En zelfs met 20 velden ofzo is dat erg weinig werk; minder dan er een topic over openen.
Daarbij komt ook nog eens dat vooral in de IoT wereld overhead nogal een rol speelt. Devices hebben vaak erg weinig geheugen, erg weinig processing power en een beperkte hoeveelheid energie om mee te werken. Daar zijn efficiente binaire formaten eerder regel dan uitzondering en worden deze formaten pas op een centrale hub omgezet naar een generiek formaat.

[ Voor 23% gewijzigd door Hydra op 17-02-2018 15:01 ]

https://niels.nu


Acties:
  • 0 Henk 'm!

  • bille
  • Registratie: Mei 2000
  • Laatst online: 16-08 14:37

bille

Don't call me Buff

Zal de volgorde van variabelen in de buffer altijd gelijk blijven? Stel dat in een volgende versie van de verzendende software er een variabele bij komt, hoe ga je om met die verandering? Maw; in hoeverre wil je jouw code iedere keer moeten aanpassen als de verzender iets toevoegt / verwijdert?

Ik zou een ParamTypes map maken die configureerbaar is dmv een settings file. In de map leg je de link tussen de parameter en de positie in de buffer. Vervolgens iets als (pseudocode)
code:
1
buffer.map((float val, int index)=>ParamBuilder.withValue(val).forParam(ParamTypes[index]).build())


Het resultaat is een array van Param (key/value) objecten. Je zou er evt in plaats van een array ook direct een hashmap van kunnen maken met de ParamType als index.

[ Voor 13% gewijzigd door bille op 17-02-2018 15:26 ]

Ultra Pilammo 6666Mhz AMD, 4251Mbit/s RAM, Gefors V6666 MegaTurbo, 43" TFS, Ultra 80Gig Firewire netwerkkaart en 5D geluid met 66 speakers in 5 dimensies


Acties:
  • +1 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
Waarom in hemelsnaam? Da's per attribuut nog steeds een stuk meer code dan de TS nu al heeft.

KISS.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

pedorus schreef op zaterdag 17 februari 2018 @ 14:01:
[...]

Wat je gebruikt is json. Binaire onzin is vrijwel nergens meer voor nodig. Als je minder bytes wil, wat meestal hooguit bij lange termijn opslag interessant is, comprimeer je de boel.. :p
Ga jij lekker json sturen over een LoRa netwerk bijvoorbeeld :+

Acties:
  • 0 Henk 'm!

  • bille
  • Registratie: Mei 2000
  • Laatst online: 16-08 14:37

bille

Don't call me Buff

Hydra schreef op zaterdag 17 februari 2018 @ 15:36:
Waarom in hemelsnaam? Da's per attribuut nog steeds een stuk meer code dan de TS nu al heeft.

KISS.
Hoezo per attribuut? Mijn regel pseudo code loopt door de hele buffer en voor iedere param in de buffer wordt de value in een apart Param object gepropt. Al zijn het 200 variabelen.. deze ene regel is genoeg. Het idee van een ParamTypes map is dat de positie en de betekenis van de data in de buffer configureerbaar is. Wellicht een beetje vooruitlopen op de mogelijkheid dat de verzendende partij vroeger of later variabelen toe gaat voegen.

Ultra Pilammo 6666Mhz AMD, 4251Mbit/s RAM, Gefors V6666 MegaTurbo, 43" TFS, Ultra 80Gig Firewire netwerkkaart en 5D geluid met 66 speakers in 5 dimensies


Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

bille schreef op zaterdag 17 februari 2018 @ 19:41:
[...]


Hoezo per attribuut? Mijn regel pseudo code loopt door de hele buffer en voor iedere param in de buffer wordt de value in een apart Param object gepropt. Al zijn het 200 variabelen.. deze ene regel is genoeg. Het idee van een ParamTypes map is dat de positie en de betekenis van de data in de buffer configureerbaar is. Wellicht een beetje vooruitlopen op de mogelijkheid dat de verzendende partij vroeger of later variabelen toe gaat voegen.
Totdat de verzender er een integer tussen stopt en je hele zwikkie het raam uit kan.

Acties:
  • 0 Henk 'm!

  • bille
  • Registratie: Mei 2000
  • Laatst online: 16-08 14:37

bille

Don't call me Buff

EddoH schreef op zaterdag 17 februari 2018 @ 20:52:
[...]
Totdat de verzender er een integer tussen stopt en je hele zwikkie het raam uit kan.
Waar heb je het over? Robuustheid van de pseudo code? Er kan makkelijk typechecking in gestopt worden en exception handling. In de config kan je toch per parameter het te verwachte type opgeven en je builder gebruik laten maken van dat type?

Ultra Pilammo 6666Mhz AMD, 4251Mbit/s RAM, Gefors V6666 MegaTurbo, 43" TFS, Ultra 80Gig Firewire netwerkkaart en 5D geluid met 66 speakers in 5 dimensies


Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

bille schreef op zondag 18 februari 2018 @ 01:16:
[...]


Waar heb je het over? Robuustheid van de pseudo code? Er kan makkelijk typechecking in gestopt worden en exception handling. In de config kan je toch per parameter het te verwachte type opgeven en je builder gebruik laten maken van dat type?
Je voorbeeld ging uit van alleen floats. Je kunt er vanalles bij maken maar volgens mij is dat de definitie van overengineering. Het gaat om een paar velden uit een byte array die in een constructor aan enkele members geassignd moeten worden, dan ga je toch niet meer configuratiefiles en een soort variabele type storage aan de gang?

Het zal aan mij liggen.

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Je kan het ook omdraaien, je stopt het array in een class en gebruikt Getters om de juiste waarden eruit te toveren.

Mijn Java is waardeloos maar dit is in C#, zal vast ook wel in Java werken:

code:
1
2
3
4
5
6
7
8
9
10
11
12
    public class UDPPacket
    {
        private byte[] _packet;
        public float LapTime
        { get
            { return _packet[0]; }
        }
        public UDPPacket(byte[] packet)
        {
            _packet = packet;
        }
    }


Edit: De conversie van byte naar de juiste waarde moet je er maar even bij verzinnen ;)

[ Voor 68% gewijzigd door Sandor_Clegane op 18-02-2018 10:50 ]

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Lye
  • Registratie: Januari 2010
  • Nu online

Lye

Verwijderd schreef op woensdag 14 februari 2018 @ 22:49:
Het onderstaande werkt:
- Als je voor elke field type die je hebt een if implementeert
- Als je vooraf weet/berekent hoeveel bytes je moet alloceren
- Als je fields public zijn

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class UDPPacket {

    public float m_time;
    public float m_lapTime;
    public float m_lapDistance;
    public float m_totalDistance;

    public ByteBuffer serialize() throws IllegalArgumentException, IllegalAccessException, IOException{
        // 4 floats * 4 byte per stuk
        ByteBuffer b = ByteBuffer.allocate(4 * 4).order(ByteOrder.LITTLE_ENDIAN);
        
        Field[] fields = this.getClass().getFields();
        for(Field field : fields){
            if(field.getGenericType().getTypeName().equals("byte")) {
                b.put(field.getByte(this));
            } else if(field.getGenericType().getTypeName().equals("int")) {
                b.putInt(field.getInt(this));
            } else if(field.getGenericType().getTypeName().equals("float")) {
                b.putFloat(field.getFloat(this));
            }
            // andere types
        }
        
        return b;
    }

    public void deserialize(ByteBuffer buffer) throws IllegalArgumentException, IllegalAccessException, IOException{
        Field[] fields = this.getClass().getFields();
        for(Field field : fields){
            if(field.getGenericType().getTypeName().equals("byte")) {
                field.setByte(this, buffer.get());
            } else if(field.getGenericType().getTypeName().equals("int")) {
                field.setInt(this, buffer.getInt());
            } else if(field.getGenericType().getTypeName().equals("float")) {
                field.setFloat(this, buffer.getFloat());
            }
            // andere types
        }
    }
    
    public static void main(String[] args) throws IOException, IllegalArgumentException, IllegalAccessException {
        byte[] a = new byte[] {-61, 105, 99, 65, 105, 82, -17, 64, -110, -47, 42, 67, -110, -47, 42, 67};
        ByteBuffer buffer = ByteBuffer.wrap(a).order(ByteOrder.LITTLE_ENDIAN);
        
        UDPPacket udpPacket = new UDPPacket();
        udpPacket.deserialize(buffer);
        ByteBuffer buffer2 = udpPacket.serialize();
        System.out.println("identical buffers: " + buffer.equals(buffer2));
    }
}
Nee. Dit werkt niet. getFields() geeft de fields niet gegarandeerd in een bepaalde volgorde terug, dat de huidige implementatie dat wel doet is toevallig. Je raakt hierbij dus de volgorde van de fields kwijt en daarmee zit je dus fields te vullen met "onzin". Dus naast dat reflectie bijna nooit de goede keus is, werkt het in dit geval ook niet.
bille schreef op zaterdag 17 februari 2018 @ 15:21:
Zal de volgorde van variabelen in de buffer altijd gelijk blijven? Stel dat in een volgende versie van de verzendende software er een variabele bij komt, hoe ga je om met die verandering? Maw; in hoeverre wil je jouw code iedere keer moeten aanpassen als de verzender iets toevoegt / verwijdert?

Ik zou een ParamTypes map maken die configureerbaar is dmv een settings file. In de map leg je de link tussen de parameter en de positie in de buffer. Vervolgens iets als (pseudocode)
code:
1
buffer.map((float val, int index)=>ParamBuilder.withValue(val).forParam(ParamTypes[index]).build())


Het resultaat is een array van Param (key/value) objecten. Je zou er evt in plaats van een array ook direct een hashmap van kunnen maken met de ParamType als index.
Deze oplossing werkt alleen voor float waarden, hoe gaat deze datastructuur om met bijvoorbeeld integers en byte values? Met deze oplossing lijkt het alsof je data verliest op het moment dat je gaat proberen om een byte uit te lezen in plaats van een float waarde. Nou kun je daar natuurlijk omheen bouwen, maar dat komt zoals @EddoH al beschreef aardig in de buurt van overengineering.
Sandor_Clegane schreef op zondag 18 februari 2018 @ 10:43:
Je kan het ook omdraaien, je stopt het array in een class en gebruikt Getters om de juiste waarden eruit te toveren.

Mijn Java is waardeloos maar dit is in C#, zal vast ook wel in Java werken:

code:
1
2
3
4
5
6
7
8
9
10
11
12
    public class UDPPacket
    {
        private byte[] _packet;
        public float LapTime
        { get
            { return _packet[0]; }
        }
        public UDPPacket(byte[] packet)
        {
            _packet = packet;
        }
    }


Edit: De conversie van byte naar de juiste waarde moet je er maar even bij verzinnen ;)
Ook dit werkt niet fatsoenlijk, het veld Laptime geeft een byte terug, geen float. Dat zal niet eens compileren (ook niet in C# denk ik). Het probleem met deze oplossing is weer dat je niet fatsoenlijk onderscheid kunt maken tussen floats, bytes en integers, tenzij je elke keer dat je een waarde opvraagt opnieuw de benodigde bytes uit de byte array trekt en hier het goede datatype van maakt. Behalve dat dit systeem dus niet robuust genoeg is, is een betere versie al eerder gebruikt (Zelfs door TS zelf) in de vorm van een ByteBuffer.




Naar mijn mening is de mooiste oplossing gewoon om een builder class met een berg setters te maken, op deze manier blijft het simpel genoeg om het overzichtelijk en onderhoudbaar te houden.

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Lye schreef op zondag 18 februari 2018 @ 12:32:
Ook dit werkt niet fatsoenlijk, het veld Laptime geeft een byte terug, geen float. Dat zal niet eens compileren (ook niet in C# denk ik
Vandaar ook de opmerking: Edit: De conversie van byte naar de juiste waarde moet je er maar even bij verzinnen ;)

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

Verwijderd

Je kan eventueel gebruik maken van een parser generator zoals ANTLR: https://github.com/antlr/...c/parsing-binary-files.md

Een ANTLR grammar in jouw geval zou dan ongeveer zo eruit zien:

code:
1
2
3
4
5
6
7
grammar UDP;

packet: m_time=float m_lapTime=float m_lapDistance=float m_totalDistance=float;

float: BYTE BYTE BYTE BYTE;

BYTE : '\u0000'..'\u00FF';

[ Voor 11% gewijzigd door Verwijderd op 18-02-2018 13:05 ]


Acties:
  • 0 Henk 'm!

  • bille
  • Registratie: Mei 2000
  • Laatst online: 16-08 14:37

bille

Don't call me Buff

Lye schreef op zondag 18 februari 2018 @ 12:32:
[...]

Deze oplossing werkt alleen voor float waarden, hoe gaat deze datastructuur om met bijvoorbeeld integers en byte values? Met deze oplossing lijkt het alsof je data verliest op het moment dat je gaat proberen om een byte uit te lezen in plaats van een float waarde. Nou kun je daar natuurlijk omheen bouwen, maar dat komt zoals @EddoH al beschreef aardig in de buurt van overengineering.
[...]
8)7 De TS vraagt om een eenvoudigere manier om zooi uit die byte array te halen... hoeveel precies geeft de TS niet aan, vandaar mijn aanname dat het wel een lijst kan zijn die potentieel veranderbaar is. De oplossing hoeft natuurlijk helemaal niet perse daadwerkelijk een config file te gebruiken. Kan net zo goed aan hardcoded map zijn.

Het punt is dat je gewoon objecten kunt gebruiken voor iedere waarde die je uit de array haalt en in plaats dat je de context van de waarde af moet lezen uit de naam van de variabele waarin je de data stopt kan je dat ook gewoon OO aanpakken. De context van de iedere waarde uit de array sla je op bij de waarde in de vorm van een type object.

Eerder genoemde oplossingen resulteren in een Class met voor iedere waarde in de byte array een eigen variabele. Mijn oplossing resulteert in een array van objecten met een type en een waarde. Heel handig voor map reduce achtige toepassing.

Laat vooral de TS maar oordelen of hij de potentie in ziet van de suggestie O-)

[ Voor 8% gewijzigd door bille op 18-02-2018 20:52 ]

Ultra Pilammo 6666Mhz AMD, 4251Mbit/s RAM, Gefors V6666 MegaTurbo, 43" TFS, Ultra 80Gig Firewire netwerkkaart en 5D geluid met 66 speakers in 5 dimensies


Acties:
  • 0 Henk 'm!

  • dream_in_code
  • Registratie: Februari 2010
  • Laatst online: 11:02
Hydra schreef op donderdag 15 februari 2018 @ 08:30:
Een goed voorbeeld van hoe het niet moet in ieder geval. Reflection is vrijwel altijd een slecht idee.

@dream_in_code gewoon met de hand die dingen op de juiste volgorde uitpakken. Het is nauwelijks code, helemaal als je het via de constructor doet:

Java:
1
2
3
4
5
        UDPPacket udpPacket = new UDPPacket(
            buffer.getFloat(),
            buffer.getFloat(),
            buffer.getFloat(),
            buffer.getFloat());
Lijkt inderdaad een goede oplossing. Misschien is het dan wel beter om de byte array mee te geven zodat er niet teveel argumenten hoeven worden meegegeven, zoals ACM al zei:
ACM schreef op donderdag 15 februari 2018 @ 22:59:
Als je graag van setters af wil en geen lange constructor wilt, zou ik die ByteBuffer in je object stoppen (of pas in je object de byte-array wrappen). In je get-methodes kan je dan gewoon de float op de juiste plek uitlezen. Dat kan met de getFloat(index) methode. Als het uitsluitend float's zijn en je geen zin hebt om te klooien met het omrekenen naar bytes kan je ook nog weer van je ByteBuffer een FloatBuffer maken via de asFloatBuffer() methode.
Voorstel van Wizardus:
Verwijderd schreef op zondag 18 februari 2018 @ 12:55:
Je kan eventueel gebruik maken van een parser generator zoals ANTLR: https://github.com/antlr/...c/parsing-binary-files.md

Een ANTLR grammar in jouw geval zou dan ongeveer zo eruit zien:

code:
1
2
3
4
5
6
7
grammar UDP;

packet: m_time=float m_lapTime=float m_lapDistance=float m_totalDistance=float;

float: BYTE BYTE BYTE BYTE;

BYTE : '\u0000'..'\u00FF';
Bedankt voor de suggestie, maar ik denk dat dit wel richting over-engineering gaat voor dit probleem.


Ik heb het opgelost nu met een constructor die de byte array meekrijgt:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class F12017UDPPacket {
    float m_time;
    float m_lapTime;
    float m_lapDistance;
    float m_totalDistance;
    //...
    WheelValuesFloat m_susp_pos; // Note: All wheel arrays have the order:
    WheelValuesFloat m_susp_vel; // RL, RR, FL, FR
    WheelValuesFloat m_wheel_speed;
    float m_throttle;
    float m_steer;
    //...
    
public F12017UDPPacket(byte[] bytes) {
        try {
            ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);

            m_time = buffer.getFloat();
            m_lapTime = buffer.getFloat();
            m_lapDistance = buffer.getFloat();
            m_totalDistance = buffer.getFloat();
            //...
            m_susp_pos = new WheelValuesFloat(buffer.getFloat(), buffer.getFloat(), buffer.getFloat(), buffer.getFloat());
            m_susp_vel = new WheelValuesFloat(buffer.getFloat(), buffer.getFloat(), buffer.getFloat(), buffer.getFloat());
            m_wheel_speed = new WheelValuesFloat(buffer.getFloat(), buffer.getFloat(), buffer.getFloat(),
                    buffer.getFloat());
            m_throttle = buffer.getFloat();
            m_steer = buffer.getFloat();

            //...
            if (buffer.hasRemaining()) {
            //throw error
            }
        }
        //catch


Bedankt allemaal voor het meedenken!

Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

dream_in_code schreef op zondag 18 februari 2018 @ 22:32:

Ik heb het opgelost nu met een constructor die de byte array meekrijgt:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class F12017UDPPacket {
    float m_time;
    float m_lapTime;
    float m_lapDistance;
    float m_totalDistance;
        // meer code
    
public F12017UDPPacket(byte[] bytes) {
        try {
            
            //...
            

            //...
            if (buffer.hasRemaining()) {
            //throw error
            }
        }
        //catch


Bedankt allemaal voor het meedenken!
Geen dank.

Heb je daadwerkelijk je properties package private (min of meer public) of was dat simpelweg om het code-voorbeeld simpel te houden? Als je ze public hebt, maak ze dan op zijn minst final :P

Voor wat betreft de try/catch. Het lijkt me sterk dat je constructor 'het probleem' kan oplossen dat er dan blijkbaar is, je kan beter die exception laten afvangen door een laag erboven. De try/catch is dan hooguit nog nuttig om de BufferUnderflowException van ByteBuffer.getFloat om te zetten in een eigen exception.

Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 06-10 13:59
dream_in_code schreef op zondag 18 februari 2018 @ 22:32:
Lijkt inderdaad een goede oplossing. Misschien is het dan wel beter om de byte array mee te geven zodat er niet teveel argumenten hoeven worden meegegeven, zoals ACM al zei:
Kan, maar meestal wordt dat soort dingen in een andere laag gedaan. Normaliter worden data transfer object / domain classes zo dom mogelijk gehouden. Van het 'oude' OO programmeren waar 'logica' bij de class waar de data in zit is men een beetje weggegaan tegenwoordig.

https://niels.nu

Pagina: 1