fr.arakne.utils.maps.serializer.DefaultMapDataSerializer   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 204
Duplicated Lines 4.41 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 113
c 1
b 0
f 0
dl 9
loc 204
ccs 66
cts 66
cp 1
rs 10
wmc 18

26 Methods

Rating   Name   Duplication   Size   Complexity  
A deserialize(String) 0 17 3
A ByteArrayCell.layer2() 3 16 4
A serialize(CellData[]) 0 9 2
A ByteArrayCell.active() 0 3 1
A ByteArrayCell.lineOfSight() 0 3 1
B serializeCell(CellData) 0 28 7
A deserializeCell(String) 0 6 2
ByteArrayCell.$GroundCellData$.flip() 0 3 ?
A disableCache() 0 2 1
A ByteArrayCell.layer1() 3 16 4
ByteArrayCell.$GroundCellData$.number() 3 3 ?
ByteArrayCell.$CellLayerData$.number() 3 3 ?
A withKey(String) 0 2 1
ByteArrayCell.$InteractiveObjectData$.interactive() 0 3 ?
ByteArrayCell.$CellLayerData$.rotation() 0 3 ?
B ByteArrayCell.ground() 3 26 6
ByteArrayCell.$GroundCellData$.rotation() 0 3 ?
A ByteArrayCell.ByteArrayCell(byte[]) 0 2 1
A ByteArrayCell.movement() 0 3 1
ByteArrayCell.$GroundCellData$.level() 0 3 ?
ByteArrayCell.$InteractiveObjectData$.flip() 0 3 ?
ByteArrayCell.$InteractiveObjectData$.number() 3 3 ?
ByteArrayCell.$GroundCellData$.slope() 0 3 ?
ByteArrayCell.$CellLayerData$.flip() 0 3 ?
A enableCache() 0 2 1
A withKey(Key) 0 2 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
/*
2
 * This file is part of ArakneUtils.
3
 *
4
 * ArakneUtils is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * ArakneUtils is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with ArakneUtils.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2017-2020 Vincent Quatrevieux
18
 */
19
20
package fr.arakne.utils.maps.serializer;
21
22
import fr.arakne.utils.encoding.Base64;
23
import fr.arakne.utils.encoding.Key;
24
import fr.arakne.utils.maps.constant.CellMovement;
25
import org.checkerframework.checker.nullness.qual.Nullable;
26
import org.checkerframework.common.value.qual.ArrayLen;
27
import org.checkerframework.common.value.qual.IntRange;
28
import org.checkerframework.common.value.qual.MinLen;
29
30
import java.util.Map;
31
import java.util.concurrent.ConcurrentHashMap;
32
33
/**
34
 * Default implementation of the map data serializer, handling the plain (i.e. not crypted) map data format
35
 *
36
 * https://github.com/Emudofus/Dofus/blob/1.29/ank/battlefield/utils/Compressor.as#L54
37
 */
38 1
public final class DefaultMapDataSerializer implements MapDataSerializer {
39
    private static final int CELL_DATA_LENGTH = 10;
40
41
    private @Nullable Map<@ArrayLen(CELL_DATA_LENGTH) String, ByteArrayCell> cache;
42
43
    @Override
44
    @SuppressWarnings("argument") // Do not handle substring checks
45
    public CellData[] deserialize(String mapData) {
46 1
        if (mapData.length() % CELL_DATA_LENGTH != 0) {
47 1
            throw new IllegalArgumentException("Invalid map data");
48
        }
49
50 1
        final int size = mapData.length() / CELL_DATA_LENGTH;
51 1
        final CellData[] cells = new CellData[size];
52
53 1
        for (int i = 0; i < size; ++i) {
54 1
            final int startIndex = i * CELL_DATA_LENGTH;
55
56 1
            cells[i] = deserializeCell(mapData.substring(startIndex, startIndex + CELL_DATA_LENGTH));
57
        }
58
59 1
        return cells;
60
    }
61
62
    @Override
63
    public String serialize(CellData[] cells) {
64 1
        final StringBuilder sb = new StringBuilder(cells.length * CELL_DATA_LENGTH);
65
66 1
        for (CellData cell : cells) {
67 1
            sb.append(serializeCell(cell));
68
        }
69
70 1
        return sb.toString();
71
    }
72
73
    /**
74
     * Enable the cell data cache
75
     * Once enable, deserialize two same cell data will return the same cell instance
76
     */
77
    public void enableCache() {
78 1
        cache = new ConcurrentHashMap<>();
79 1
    }
80
81
    /**
82
     * Disable cell data cache
83
     */
84
    public void disableCache() {
85 1
        cache = null;
86 1
    }
87
88
    /**
89
     * Get a MapDataSerializer for encrypted map with the given key
90
     * Use the current serializer as inner serializer (and also use the current cache)
91
     *
92
     * @param key The encryption key
93
     *
94
     * @return The map serializer
95
     */
96
    public MapDataSerializer withKey(Key key) {
97 1
        return new EncryptedMapDataSerializer(key, this);
98
    }
99
100
    /**
101
     * Get a MapDataSerializer for encrypted map with the given key
102
     * This is equivalent to `serializer.withKey(Key.parse(key));`
103
     *
104
     * <code>
105
     *     CellData[] cells = serializer.withKey(gdm.key()).deserialize(mapData);
106
     * </code>
107
     *
108
     * @param key The encryption key as string
109
     *
110
     * @return The map serializer
111
     */
112
    public MapDataSerializer withKey(@MinLen(2) String key) {
113 1
        return withKey(Key.parse(key));
114
    }
115
116
    private CellData deserializeCell(@ArrayLen(CELL_DATA_LENGTH) String cellData) {
117 1
        if (cache == null) {
118 1
            return new ByteArrayCell(Base64.toBytes(cellData));
119
        }
120
121 1
        return cache.computeIfAbsent(cellData, data -> new ByteArrayCell(Base64.toBytes(data)));
122
    }
123
124
    @SuppressWarnings("assignment") // All assignations are safe
125
    private String serializeCell(CellData cell) {
126 1
        final @IntRange(from = 0, to = 63) byte[] data = new byte[CELL_DATA_LENGTH];
127
128 1
        data[0] = (byte) ((cell.active() ? (1) : (0)) << 5);
129 1
        data[0] = (byte) (data[0] | (cell.lineOfSight() ? (1) : (0)));
130 1
        data[0] = (byte) (data[0] | (cell.ground().number() & 1536) >> 6);
131 1
        data[0] = (byte) (data[0] | (cell.layer1().number() & 8192) >> 11);
132 1
        data[0] = (byte) (data[0] | (cell.layer2().number() & 8192) >> 12);
133 1
        data[1] = (byte) ((cell.ground().rotation() & 3) << 4);
134 1
        data[1] = (byte) (data[1] | cell.ground().level() & 15);
135 1
        data[2] = (byte) ((cell.movement().ordinal() & 7) << 3);
136 1
        data[2] = (byte) (data[2] | cell.ground().number() >> 6 & 7);
137 1
        data[3] = (byte) (cell.ground().number() & 63);
138 1
        data[4] = (byte) ((cell.ground().slope() & 15) << 2);
139 1
        data[4] = (byte) (data[4] | (cell.ground().flip() ? (1) : (0)) << 1);
140 1
        data[4] = (byte) (data[4] | cell.layer1().number() >> 12 & 1);
141 1
        data[5] = (byte) (cell.layer1().number() >> 6 & 63);
142 1
        data[6] = (byte) (cell.layer1().number() & 63);
143 1
        data[7] = (byte) ((cell.layer1().rotation() & 3) << 4);
144 1
        data[7] = (byte) (data[7] | (cell.layer1().flip() ? (1) : (0)) << 3);
145 1
        data[7] = (byte) (data[7] | (cell.layer2().flip() ? (1) : (0)) << 2);
146 1
        data[7] = (byte) (data[7] | (cell.layer2().interactive() ? (1) : (0)) << 1);
147 1
        data[7] = (byte) (data[7] | cell.layer2().number() >> 12 & 1);
148 1
        data[8] = (byte) (cell.layer2().number() >> 6 & 63);
149 1
        data[9] = (byte) (cell.layer2().number() & 63);
150
151 1
        return Base64.encode(data);
152
    }
153
154
    private static class ByteArrayCell implements CellData {
155
        private final byte @ArrayLen(CELL_DATA_LENGTH) [] data;
156
157 1
        public ByteArrayCell(byte @ArrayLen(CELL_DATA_LENGTH) [] data) {
158 1
            this.data = data;
159 1
        }
160
161
        @Override
162
        public boolean lineOfSight() {
163 1
            return (data[0] & 1) == 1;
164
        }
165
166
        @Override
167
        public CellMovement movement() {
168 1
            return CellMovement.byValue((data[2] & 56) >> 3);
169
        }
170
171
        @Override
172
        public boolean active() {
173 1
            return (data[0] & 32) >> 5 == 1;
174
        }
175
176
        @Override
177
        public GroundCellData ground() {
178 1
            return new GroundCellData() {
179
                @Override
180
                public int level() {
181 1
                    return data[1] & 15;
182
                }
183
184
                @Override
185
                public int slope() {
186 1
                    return (data[4] & 60) >> 2;
187
                }
188
189 View Code Duplication
                @Override
190
                public int number() {
191 1
                    return ((data[0] & 24) << 6) + ((data[2] & 7) << 6) + data[3];
192
                }
193
194
                @Override
195
                public int rotation() {
196 1
                    return (data[1] & 48) >> 4;
197
                }
198
199
                @Override
200
                public boolean flip() {
201 1
                    return (data[4] & 2) >> 1 == 1;
202
                }
203
            };
204
        }
205
206
        @Override
207
        public CellLayerData layer1() {
208 1
            return new CellLayerData() {
209 View Code Duplication
                @Override
210
                public int number() {
211 1
                    return ((data[0] & 4) << 11) + ((data[4] & 1) << 12) + (data[5] << 6) + data[6];
212
                }
213
214
                @Override
215
                public int rotation() {
216 1
                    return (data[7] & 48) >> 4;
217
                }
218
219
                @Override
220
                public boolean flip() {
221 1
                    return (data[7] & 8) >> 3 == 1;
222
                }
223
            };
224
        }
225
226
        @Override
227
        public InteractiveObjectData layer2() {
228 1
            return new InteractiveObjectData() {
229
                @Override
230
                public boolean interactive() {
231 1
                    return (data[7] & 2) >> 1 == 1;
232
                }
233
234 View Code Duplication
                @Override
235
                public int number() {
236 1
                    return ((data[0] & 2) << 12) + ((data[7] & 1) << 12) + (data[8] << 6) + data[9];
237
                }
238
239
                @Override
240
                public boolean flip() {
241 1
                    return (data[7] & 4) >> 2 == 1;
242
                }
243
            };
244
        }
245
    }
246
}
247