Completed
Push — master ( 15c40a...010522 )
by kacper
01:57
created

JsonBinaryDecoderService::parseScalar()   C

Complexity

Conditions 9
Paths 9

Size

Total Lines 50
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 50
rs 6
cc 9
eloc 22
nc 9
nop 1
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();
97
        }
98
        else if (self::LARGE_OBJECT === $type)
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
99
        {
100
            //TODO
101
        }
102
        else if (self::SMALL_ARRAY === $type)
103
        {
104
            $this->parseArray();
105
        }
106
        else if (self::LARGE_ARRAY === $type)
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
107
        {
108
            //TODO
109
        }
110
        else
111
        {
112
            $this->parseScalar($type);
113
        }
114
    }
115
116
    private function parseObject()
117
    {
118
        $elementCount = $this->binaryDataReader->readUInt16();
119
        $size = $this->binaryDataReader->readUInt16();
120
121
        // Read each key-entry, consisting of the offset and length of each key ...
122
        $keyLengths = [];
123 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...
124
        {
125
            $this->binaryDataReader->readUInt16(); // $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);
133
        }
134
135
        // Read each key ...
136
        $keys = [];
137 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...
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
     * @return JsonBinaryDecoderValue
163
     * @throws BinaryDataReaderException
164
     * @throws \LengthException
165
     */
166
    private function parseValueType($numBytes)
167
    {
168
        $type = $this->binaryDataReader->readInt8();
169
170
        if (self::LITERAL === $type)
171
        {
172
            return new JsonBinaryDecoderValue(
173
                true,
174
                $this->readLiteral(),
175
                $type
176
            );
177
        }
178
        else if (self::INT16 === $type)
179
        {
180
            return new JsonBinaryDecoderValue(
181
                true,
182
                $this->binaryDataReader->readInt16(),
183
                $type
184
            );
185
        }
186
        else if (self::UINT16 === $type)
187
        {
188
            return new JsonBinaryDecoderValue(
189
                true,
190
                $this->binaryDataReader->readUInt16(),
191
                $type
192
            );
193
        }
194
        else if (self::INT32 === $type)
195
        {
196
            return new JsonBinaryDecoderValue(
197
                true,
198
                $this->binaryDataReader->readInt32(),
199
                $type
200
            );
201
        }
202
        else if (self::UINT32 === $type)
203
        {
204
            return new JsonBinaryDecoderValue(
205
                true,
206
                $this->binaryDataReader->readUInt32(),
207
                $type
208
            );
209
        }
210
        else
211
        {
212
            $offset = $this->binaryDataReader->readUInt16();
213
            if ($offset > $numBytes)
214
            {
215
                throw new \LengthException(
216
                    'The offset for the value in the JSON binary document is ' .
217
                    $offset .
218
                    ', which is larger than the binary form of the JSON document (' .
219
                    $numBytes . ' bytes)'
220
                );
221
            }
222
223
            return new JsonBinaryDecoderValue(
224
                false,
225
                null,
226
                $type
227
            );
228
        }
229
    }
230
231
    /**
232
     * @return bool|null
233
     * @throws BinaryDataReaderException
234
     */
235
    private function readLiteral()
236
    {
237
        $literal = ord($this->binaryDataReader->read(2));
238
        if (0 === $literal)
239
        {
240
            return null;
241
        }
242
        else if (1 === $literal)
243
        {
244
            return true;
245
        }
246
        else if (2 === $literal)
247
        {
248
            return false;
249
        }
250
    }
251
252
    /**
253
     * @param JsonBinaryDecoderValue $jsonBinaryDecoderValue
254
     * @throws JsonBinaryDecoderException
255
     * @throws BinaryDataReaderException
256
     */
257
    private function assignValues(JsonBinaryDecoderValue $jsonBinaryDecoderValue)
258
    {
259
        if (false === $jsonBinaryDecoderValue->isIsResolved())
260
        {
261
            $this->parseJson($jsonBinaryDecoderValue->getType());
262
        }
263
        else
264
        {
265
            if (null === $jsonBinaryDecoderValue->getValue())
266
            {
267
                $this->jsonBinaryDecoderFormatter->formatValueNull();
268
            }
269
            elseif (is_bool($jsonBinaryDecoderValue->getValue()))
270
            {
271
                $this->jsonBinaryDecoderFormatter->formatValueBool($jsonBinaryDecoderValue->getValue());
272
            }
273
            elseif (is_numeric($jsonBinaryDecoderValue->getValue()))
274
            {
275
                $this->jsonBinaryDecoderFormatter->formatValueNumeric($jsonBinaryDecoderValue->getValue());
276
            }
277
        }
278
    }
279
280
    private function parseArray()
281
    {
282
        $numElements = $this->binaryDataReader->readUInt16();
283
        $numBytes = $this->binaryDataReader->readUInt16();
284
285
        $entries = [];
286
        for ($i = 0; $i !== $numElements; ++$i)
287
        {
288
            $entries[$i] = $this->parseValueType($numBytes);
289
        }
290
291
        $this->jsonBinaryDecoderFormatter->formatBeginArray();
292
293 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...
294
        {
295
            if ($i !== 0)
296
            {
297
                $this->jsonBinaryDecoderFormatter->formatNextEntry();
298
            }
299
300
            /* @var JsonBinaryDecoderValue[] $entries */
301
            $this->assignValues($entries[$i]);
302
        }
303
304
        $this->jsonBinaryDecoderFormatter->formatEndArray();
305
    }
306
307
    private function parseBoolean()
308
    {
309
        $r = $this->readLiteral();
310
        if (null === $r)
311
        {
312
            $this->jsonBinaryDecoderFormatter->formatValueNull();
313
        }
314
        else
315
        {
316
            $this->jsonBinaryDecoderFormatter->formatValueBool($r);
317
        }
318
    }
319
320
    private function parseScalar($type)
321
    {
322
        if (self::LITERAL === $type)
323
        {
324
            $this->parseBoolean();
325
        }
326
        else if (self::INT16 === $type)
327
        {
328
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt16());
329
        }
330
        else if (self::INT32 === $type)
331
        {
332
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt32());
333
        }
334
        else if (self::INT64 === $type)
335
        {
336
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readInt64());
337
        }
338
        else if (self::UINT16 === $type)
339
        {
340
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt16());
341
        }
342
        else if (self::UINT64 === $type)
343
        {
344
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readUInt64());
345
        }
346
        else if (self::DOUBLE === $type)
347
        {
348
            $this->jsonBinaryDecoderFormatter->formatValue($this->binaryDataReader->readDouble());
349
        }
350
        else if (self::STRING === $type)
351
        {
352
            $this->jsonBinaryDecoderFormatter->formatValue(
353
                $this->binaryDataReader->read($this->readVariableInt())
354
            );
355
        }
356
        /**
357
         * else if (self::OPAQUE === $type)
358
         * {
359
         *
360
         * }
361
         */
362
        else
363
        {
364
            throw new JsonBinaryDecoderException(
365
                JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_MESSAGE . $type,
366
                JsonBinaryDecoderException::UNKNOWN_JSON_TYPE_CODE
367
            );
368
        }
369
    }
370
}