Completed
Push — master ( 4dd7c8...9d1314 )
by kacper
02:20
created

JsonBinaryDecoderService   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 360
Duplicated Lines 6.11 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 48
c 1
b 0
f 0
lcom 1
cbo 4
dl 22
loc 360
rs 8.4864

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A parseToString() 0 6 1
A readVariableInt() 0 18 3
B parseJson() 0 23 5
B parseObject() 12 43 6
C parseValueType() 0 67 8
A readLiteral() 0 16 4
B assignValues() 0 22 5
B parseArray() 10 26 4
A parseBoolean() 0 12 2
C parseScalar() 0 50 9

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like JsonBinaryDecoderService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JsonBinaryDecoderService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
4
namespace MySQLReplication\JsonBinaryDecoder;
5
6
use MySQLReplication\BinaryDataReader\BinaryDataReader;
7
use MySQLReplication\BinaryDataReader\Exception\BinaryDataReaderException;
8
9
/**
10
 * Class JsonBinaryDecoderService
11
 * @package MySQLReplication\JsonBinaryDecoder
12
 * @see https://github.com/mysql/mysql-server/blob/5.7/sql/json_binary.cc
13
 * @see https://github.com/shyiko/mysql-binlog-connector-java/blob/master/src/main/java/com/github/shyiko/mysql/binlog/event/deserialization/json/JsonBinary.java
14
 */
15
class JsonBinaryDecoderService
16
{
17
    const SMALL_OBJECT = 0;
18
    const LARGE_OBJECT = 1;
19
    const SMALL_ARRAY = 2;
20
    const LARGE_ARRAY = 3;
21
    const LITERAL = 4;
22
    const INT16 = 5;
23
    const UINT16 = 6;
24
    const INT32 = 7;
25
    const UINT32 = 8;
26
    const INT64 = 9;
27
    const UINT64 = 10;
28
    const DOUBLE = 11;
29
    const STRING = 12;
30
    const OPAQUE = 15;
31
32
    /**
33
     * @var BinaryDataReader
34
     */
35
    private $binaryDataReader;
36
    /**
37
     * @var JsonBinaryDecoderFormatter
38
     */
39
    private $jsonBinaryDecoderFormatter;
40
41
    /**
42
     * JsonBinaryDecoderService constructor.
43
     * @param BinaryDataReader $binaryDataReader
44
     * @param JsonBinaryDecoderFormatter $jsonBinaryDecoderFormatter
45
     */
46
    public function __construct(BinaryDataReader $binaryDataReader, JsonBinaryDecoderFormatter $jsonBinaryDecoderFormatter)
47
    {
48
        $this->binaryDataReader = $binaryDataReader;
49
        $this->jsonBinaryDecoderFormatter = $jsonBinaryDecoderFormatter;
50
    }
51
52
    /**
53
     * @return string
54
     * @throws JsonBinaryDecoderException
55
     * @throws BinaryDataReaderException
56
     */
57
    public function parseToString()
58
    {
59
        $this->parseJson($this->binaryDataReader->readUInt8());
60
61
        return $this->jsonBinaryDecoderFormatter->getJsonString();
62
    }
63
64
    /**
65
     * @return int
66
     * @throws BinaryDataReaderException
67
     */
68
    private function readVariableInt()
69
    {
70
        $length = $this->binaryDataReader->getBinaryDataLength();
71
        $len = 0;
72
        for ($i = 0; $i < $length; $i++)
73
        {
74
            $size = $this->binaryDataReader->readUInt8();
75
            // Get the next 7 bits of the length.
76
            $len |= ($size & 127) << (7 * $i);
77
            if (($size & 128) === 0)
78
            {
79
                // This was the last byte. Return successfully.
80
                return $len;
81
            }
82
        }
83
84
        return $len;
85
    }
86
87
    /**
88
     * @param int $type
89
     * @throws JsonBinaryDecoderException
90
     * @throws BinaryDataReaderException
91
     */
92
    private function parseJson($type)
93
    {
94
        if (self::SMALL_OBJECT === $type)
95
        {
96
            $this->parseObject(BinaryDataReader::UNSIGNED_SHORT_LENGTH);
97
        }
98
        else if (self::LARGE_OBJECT === $type)
99
        {
100
            $this->parseObject(BinaryDataReader::UNSIGNED_INT32_LENGTH);
101
        }
102
        else if (self::SMALL_ARRAY === $type)
103
        {
104
            $this->parseArray(BinaryDataReader::UNSIGNED_SHORT_LENGTH);
105
        }
106
        else if (self::LARGE_ARRAY === $type)
107
        {
108
            $this->parseObject(BinaryDataReader::UNSIGNED_INT32_LENGTH);
109
        }
110
        else
111
        {
112
            $this->parseScalar($type);
113
        }
114
    }
115
116
    private function parseObject($intSize)
117
    {
118
        $elementCount = $this->binaryDataReader->readUIntBySize($intSize);
119
        $size = $this->binaryDataReader->readUIntBySize($intSize);
120
121
        // Read each key-entry, consisting of the offset and length of each key ...
122
        $keyLengths = [];
123
        for ($i = 0; $i !== $elementCount; ++$i)
124
        {
125
            $this->binaryDataReader->readUIntBySize($intSize); // $keyOffset unused
126
            $keyLengths[$i] = $this->binaryDataReader->readUInt16();
127
        }
128
129
        $entries = [];
130
        for ($i = 0; $i !== $elementCount; ++$i)
131
        {
132
            $entries[$i] = $this->parseValueType($size, $intSize);
133
        }
134
135
        // Read each key ...
136
        $keys = [];
137
        for ($i = 0; $i !== $elementCount; ++$i)
138
        {
139
            $keys[$i] = $this->binaryDataReader->read($keyLengths[$i]);
140
        }
141
142
        $this->jsonBinaryDecoderFormatter->formatBeginObject();
143
144 View Code Duplication
        for ($i = 0; $i !== $elementCount; ++$i)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
        {
146
            if ($i !== 0)
147
            {
148
                $this->jsonBinaryDecoderFormatter->formatNextEntry();
149
            }
150
151
            $this->jsonBinaryDecoderFormatter->formatName($keys[$i]);
152
153
            /* @var JsonBinaryDecoderValue[] $entries */
154
            $this->assignValues($entries[$i]);
155
        }
156
157
        $this->jsonBinaryDecoderFormatter->formatEndObject();
158
    }
159
160
    /**
161
     * @param int $numBytes
162
     * @param int $intSize
163
     * @return JsonBinaryDecoderValue
164
     * @throws BinaryDataReaderException
165
     * @throws \LengthException
166
     */
167
    private function parseValueType($numBytes, $intSize)
168
    {
169
        $type = $this->binaryDataReader->readInt8();
170
171
        if (self::LITERAL === $type)
172
        {
173
            return new JsonBinaryDecoderValue(
174
                true,
175
                $this->readLiteral(),
176
                $type
177
            );
178
        }
179
        else if (self::INT16 === $type)
180
        {
181
            return new JsonBinaryDecoderValue(
182
                true,
183
                $this->binaryDataReader->readInt16(),
184
                $type
185
            );
186
        }
187
        else if (self::UINT16 === $type)
188
        {
189
            return new JsonBinaryDecoderValue(
190
                true,
191
                $this->binaryDataReader->readUInt16(),
192
                $type
193
            );
194
        }
195
        else if (BinaryDataReader::UNSIGNED_INT32_LENGTH === $intSize)
196
        {
197
            if (self::INT32 === $type)
198
            {
199
                return new JsonBinaryDecoderValue(
200
                    true,
201
                    $this->binaryDataReader->readInt32(),
202
                    $type
203
                );
204
            }
205
            else if (self::UINT32 === $type)
206
            {
207
                return new JsonBinaryDecoderValue(
208
                    true,
209
                    $this->binaryDataReader->readUInt32(),
210
                    $type
211
                );
212
            }
213
        }
214
        else
215
        {
216
            $offset = $this->binaryDataReader->readUIntBySize($intSize);
217
            if ($offset > $numBytes)
218
            {
219
                throw new \LengthException(
220
                    'The offset for the value in the JSON binary document is ' .
221
                    $offset .
222
                    ', which is larger than the binary form of the JSON document (' .
223
                    $numBytes . ' bytes)'
224
                );
225
            }
226
227
            return new JsonBinaryDecoderValue(
228
                false,
229
                null,
230
                $type
231
            );
232
        }
233
    }
234
235
    /**
236
     * @return bool|null
237
     * @throws BinaryDataReaderException
238
     */
239
    private function readLiteral()
240
    {
241
        $literal = ord($this->binaryDataReader->read(2));
242
        if (0 === $literal)
243
        {
244
            return null;
245
        }
246
        else if (1 === $literal)
247
        {
248
            return true;
249
        }
250
        else if (2 === $literal)
251
        {
252
            return false;
253
        }
254
    }
255
256
    /**
257
     * @param JsonBinaryDecoderValue $jsonBinaryDecoderValue
258
     * @throws JsonBinaryDecoderException
259
     * @throws BinaryDataReaderException
260
     */
261
    private function assignValues(JsonBinaryDecoderValue $jsonBinaryDecoderValue)
262
    {
263
        if (false === $jsonBinaryDecoderValue->isIsResolved())
264
        {
265
            $this->parseJson($jsonBinaryDecoderValue->getType());
266
        }
267
        else
268
        {
269
            if (null === $jsonBinaryDecoderValue->getValue())
270
            {
271
                $this->jsonBinaryDecoderFormatter->formatValueNull();
272
            }
273
            elseif (is_bool($jsonBinaryDecoderValue->getValue()))
274
            {
275
                $this->jsonBinaryDecoderFormatter->formatValueBool($jsonBinaryDecoderValue->getValue());
276
            }
277
            elseif (is_numeric($jsonBinaryDecoderValue->getValue()))
278
            {
279
                $this->jsonBinaryDecoderFormatter->formatValueNumeric($jsonBinaryDecoderValue->getValue());
280
            }
281
        }
282
    }
283
284
    private function parseArray($size)
285
    {
286
        $numElements = $this->binaryDataReader->readUInt16();
287
        $numBytes = $this->binaryDataReader->readUInt16();
288
289
        $entries = [];
290
        for ($i = 0; $i !== $numElements; ++$i)
291
        {
292
            $entries[$i] = $this->parseValueType($numBytes, $size);
293
        }
294
295
        $this->jsonBinaryDecoderFormatter->formatBeginArray();
296
297 View Code Duplication
        for ($i = 0; $i !== $numElements; ++$i)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
        {
299
            if ($i !== 0)
300
            {
301
                $this->jsonBinaryDecoderFormatter->formatNextEntry();
302
            }
303
304
            /* @var JsonBinaryDecoderValue[] $entries */
305
            $this->assignValues($entries[$i]);
306
        }
307
308
        $this->jsonBinaryDecoderFormatter->formatEndArray();
309
    }
310
311
    private function parseBoolean()
312
    {
313
        $r = $this->readLiteral();
314
        if (null === $r)
315
        {
316
            $this->jsonBinaryDecoderFormatter->formatValueNull();
317
        }
318
        else
319
        {
320
            $this->jsonBinaryDecoderFormatter->formatValueBool($r);
321
        }
322
    }
323
324
    private function parseScalar($type)
325
    {
326
        if (self::LITERAL === $type)
327
        {
328
            $this->parseBoolean();
329
        }
330
        else if (self::INT16 === $type)
331
        {
332
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt16());
333
        }
334
        else if (self::INT32 === $type)
335
        {
336
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt32());
337
        }
338
        else if (self::INT64 === $type)
339
        {
340
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt64());
341
        }
342
        else if (self::UINT16 === $type)
343
        {
344
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt16());
345
        }
346
        else if (self::UINT64 === $type)
347
        {
348
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt64());
349
        }
350
        else if (self::DOUBLE === $type)
351
        {
352
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readDouble());
353
        }
354
        else if (self::STRING === $type)
355
        {
356
            $this->jsonBinaryDecoderFormatter->formatValue(
357
                $this->binaryDataReader->read($this->readVariableInt())
358
            );
359
        }
360
        /**
361
         * else if (self::OPAQUE === $type)
362
         * {
363
         *
364
         * }
365
         */
366
        else
367
        {
368
            throw new JsonBinaryDecoderException(
369
                JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_MESSAGE . $type,
370
                JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_CODE
371
            );
372
        }
373
    }
374
}