Completed
Push — 3.5 ( 1a9180...1bec8a )
by Daniel
24s
created

Moxiecode_JSONReader   D

Complexity

Total Complexity 82

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 330
rs 4.8717
c 0
b 0
f 0
wmc 82
lcom 1
cbo 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A Moxiecode_JSONReader() 0 8 1
A getToken() 0 3 1
A getLocation() 0 3 1
C getTokenName() 0 35 11
A getValue() 0 3 1
C readToken() 0 69 16
A _readBool() 0 16 4
A _readNull() 0 12 3
C _readString() 0 80 18
B _int2utf8() 0 20 6
D _readNumber() 0 30 9
B readAway() 0 8 5
A read() 0 13 3
A skip() 0 3 1
A peek() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like Moxiecode_JSONReader 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 Moxiecode_JSONReader, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * $Id: JSON.php 40 2007-06-18 11:43:15Z spocke $
4
 *
5
 * @package MCManager.utils
6
 * @author Moxiecode
7
 * @copyright Copyright � 2007, Moxiecode Systems AB, All rights reserved.
8
 */
9
10
define('JSON_BOOL', 1);
11
define('JSON_INT', 2);
12
define('JSON_STR', 3);
13
define('JSON_FLOAT', 4);
14
define('JSON_NULL', 5);
15
define('JSON_START_OBJ', 6);
16
define('JSON_END_OBJ', 7);
17
define('JSON_START_ARRAY', 8);
18
define('JSON_END_ARRAY', 9);
19
define('JSON_KEY', 10);
20
define('JSON_SKIP', 11);
21
22
define('JSON_IN_ARRAY', 30);
23
define('JSON_IN_OBJECT', 40);
24
define('JSON_IN_BETWEEN', 50);
25
26
class Moxiecode_JSONReader {
27
	var $_data, $_len, $_pos;
28
	var $_value, $_token;
29
	var $_location, $_lastLocations;
30
	var $_needProp;
31
32
	function Moxiecode_JSONReader($data) {
33
		$this->_data = $data;
34
		$this->_len = strlen($data);
35
		$this->_pos = -1;
36
		$this->_location = JSON_IN_BETWEEN;
37
		$this->_lastLocations = array();
38
		$this->_needProp = false;
39
	}
40
41
	function getToken() {
42
		return $this->_token;
43
	}
44
45
	function getLocation() {
46
		return $this->_location;
47
	}
48
49
	function getTokenName() {
50
		switch ($this->_token) {
51
			case JSON_BOOL:
52
				return 'JSON_BOOL';
53
54
			case JSON_INT:
55
				return 'JSON_INT';
56
57
			case JSON_STR:
58
				return 'JSON_STR';
59
60
			case JSON_FLOAT:
61
				return 'JSON_FLOAT';
62
63
			case JSON_NULL:
64
				return 'JSON_NULL';
65
66
			case JSON_START_OBJ:
67
				return 'JSON_START_OBJ';
68
69
			case JSON_END_OBJ:
70
				return 'JSON_END_OBJ';
71
72
			case JSON_START_ARRAY:
73
				return 'JSON_START_ARRAY';
74
75
			case JSON_END_ARRAY:
76
				return 'JSON_END_ARRAY';
77
78
			case JSON_KEY:
79
				return 'JSON_KEY';
80
		}
81
82
		return 'UNKNOWN';
83
	}
84
85
	function getValue() {
86
		return $this->_value;
87
	}
88
89
	function readToken() {
90
		$chr = $this->read();
91
92
		if ($chr != null) {
93
			switch ($chr) {
94
				case '[':
95
					$this->_lastLocation[] = $this->_location;
96
					$this->_location = JSON_IN_ARRAY;
97
					$this->_token = JSON_START_ARRAY;
98
					$this->_value = null;
99
					$this->readAway();
100
					return true;
101
102
				case ']':
103
					$this->_location = array_pop($this->_lastLocation);
104
					$this->_token = JSON_END_ARRAY;
105
					$this->_value = null;
106
					$this->readAway();
107
108
					if ($this->_location == JSON_IN_OBJECT)
109
						$this->_needProp = true;
110
111
					return true;
112
113
				case '{':
114
					$this->_lastLocation[] = $this->_location;
115
					$this->_location = JSON_IN_OBJECT;
116
					$this->_needProp = true;
117
					$this->_token = JSON_START_OBJ;
118
					$this->_value = null;
119
					$this->readAway();
120
					return true;
121
122
				case '}':
123
					$this->_location = array_pop($this->_lastLocation);
124
					$this->_token = JSON_END_OBJ;
125
					$this->_value = null;
126
					$this->readAway();
127
128
					if ($this->_location == JSON_IN_OBJECT)
129
						$this->_needProp = true;
130
131
					return true;
132
133
				// String
134
				case '"':
135
				case '\'':
136
					return $this->_readString($chr);
137
138
				// Null
139
				case 'n':
140
					return $this->_readNull();
141
142
				// Bool
143
				case 't':
144
				case 'f':
145
					return $this->_readBool($chr);
146
147
				default:
148
					// Is number
149
					if (is_numeric($chr) || $chr == '-' || $chr == '.')
150
						return $this->_readNumber($chr);
151
152
					return true;
153
			}
154
		}
155
156
		return false;
157
	}
158
159
	function _readBool($chr) {
160
		$this->_token = JSON_BOOL;
161
		$this->_value = $chr == 't';
162
163
		if ($chr == 't')
164
			$this->skip(3); // rue
165
		else
166
			$this->skip(4); // alse
167
168
		$this->readAway();
169
170
		if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
171
			$this->_needProp = true;
172
173
		return true;
174
	}
175
176
	function _readNull() {
177
		$this->_token = JSON_NULL;
178
		$this->_value = null;
179
180
		$this->skip(3); // ull
181
		$this->readAway();
182
183
		if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
184
			$this->_needProp = true;
185
186
		return true;
187
	}
188
189
	function _readString($quote) {
190
		$output = "";
191
		$this->_token = JSON_STR;
192
		$endString = false;
193
194
		while (($chr = $this->peek()) != -1) {
195
			switch ($chr) {
196
				case '\\':
197
					// Read away slash
198
					$this->read();
199
200
					// Read escape code
201
					$chr = $this->read();
202
					switch ($chr) {
203
							case 't':
204
								$output .= "\t";
205
								break;
206
207
							case 'b':
208
								$output .= "\b";
209
								break;
210
211
							case 'f':
212
								$output .= "\f";
213
								break;
214
215
							case 'r':
216
								$output .= "\r";
217
								break;
218
219
							case 'n':
220
								$output .= "\n";
221
								break;
222
223
							case 'u':
224
								$output .= $this->_int2utf8(hexdec($this->read(4)));
225
								break;
226
227
							default:
228
								$output .= $chr;
229
								break;
230
					}
231
232
					break;
233
234
					case '\'':
235
					case '"':
236
						if ($chr == $quote)
237
							$endString = true;
238
239
						$chr = $this->read();
240
						if ($chr != -1 && $chr != $quote)
241
							$output .= $chr;
242
243
						break;
244
245
					default:
246
						$output .= $this->read();
247
			}
248
249
			// String terminated
250
			if ($endString)
251
				break;
252
		}
253
254
		$this->readAway();
255
		$this->_value = $output;
256
257
		// Needed a property
258
		if ($this->_needProp) {
259
			$this->_token = JSON_KEY;
260
			$this->_needProp = false;
261
			return true;
262
		}
263
264
		if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
265
			$this->_needProp = true;
266
267
		return true;
268
	}
269
270
	function _int2utf8($int) {
271
		$int = intval($int);
272
273
		switch ($int) {
274
			case 0:
275
				return chr(0);
276
277
			case ($int & 0x7F):
278
				return chr($int);
279
280
			case ($int & 0x7FF):
281
				return chr(0xC0 | (($int >> 6) & 0x1F)) . chr(0x80 | ($int & 0x3F));
282
283
			case ($int & 0xFFFF):
284
				return chr(0xE0 | (($int >> 12) & 0x0F)) . chr(0x80 | (($int >> 6) & 0x3F)) . chr (0x80 | ($int & 0x3F));
285
286
			case ($int & 0x1FFFFF):
287
				return chr(0xF0 | ($int >> 18)) . chr(0x80 | (($int >> 12) & 0x3F)) . chr(0x80 | (($int >> 6) & 0x3F)) . chr(0x80 | ($int & 0x3F));
288
		}
289
	}
290
291
	function _readNumber($start) {
292
		$value = "";
293
		$isFloat = false;
294
295
		$this->_token = JSON_INT;
296
		$value .= $start;
297
298
		while (($chr = $this->peek()) != -1) {
299
			if (is_numeric($chr) || $chr == '-' || $chr == '.') {
300
				if ($chr == '.')
301
					$isFloat = true;
302
303
				$value .= $this->read();
304
			} else
305
				break;
306
		}
307
308
		$this->readAway();
309
310
		if ($isFloat) {
311
			$this->_token = JSON_FLOAT;
312
			$this->_value = floatval($value);
313
		} else
314
			$this->_value = intval($value);
315
316
		if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
317
			$this->_needProp = true;
318
319
		return true;
320
	}
321
322
	function readAway() {
323
		while (($chr = $this->peek()) != null) {
324
			if ($chr != ':' && $chr != ',' && $chr != ' ')
325
				return;
326
327
			$this->read();
328
		}
329
	}
330
331
	function read($len = 1) {
332
		if ($this->_pos < $this->_len) {
333
			if ($len > 1) {
334
				$str = substr($this->_data, $this->_pos + 1, $len);
335
				$this->_pos += $len;
336
337
				return $str;
338
			} else
339
				return $this->_data[++$this->_pos];
340
		}
341
342
		return null;
343
	}
344
345
	function skip($len) {
346
		$this->_pos += $len;
347
	}
348
349
	function peek() {
350
		if ($this->_pos < $this->_len)
351
			return $this->_data[$this->_pos + 1];
352
353
		return null;
354
	}
355
}
356
357
/**
358
 * This class handles JSON stuff.
359
 *
360
 * @package MCManager.utils
361
 */
362
class Moxiecode_JSON {
363
	function Moxiecode_JSON() {
364
	}
365
366
	function decode($input) {
367
		$reader = new Moxiecode_JSONReader($input);
368
369
		return $this->readValue($reader);
370
	}
371
372
	function readValue(&$reader) {
373
		$this->data = array();
374
		$this->parents = array();
375
		$this->cur =& $this->data;
376
		$key = null;
377
		$loc = JSON_IN_ARRAY;
378
379
		while ($reader->readToken()) {
380
			switch ($reader->getToken()) {
381
				case JSON_STR:
382
				case JSON_INT:
383
				case JSON_BOOL:
384
				case JSON_FLOAT:
385
				case JSON_NULL:
386
					switch ($reader->getLocation()) {
387
						case JSON_IN_OBJECT:
388
							$this->cur[$key] = $reader->getValue();
389
							break;
390
391
						case JSON_IN_ARRAY:
392
							$this->cur[] = $reader->getValue();
393
							break;
394
395
						default:
396
							return $reader->getValue();
397
					}
398
					break;
399
400
				case JSON_KEY:
401
					$key = $reader->getValue();
402
					break;
403
404
				case JSON_START_OBJ:
405
				case JSON_START_ARRAY:
406
					if ($loc == JSON_IN_OBJECT)
407
						$this->addArray($key);
408
					else
409
						$this->addArray(null);
410
411
					$cur =& $obj;
412
413
					$loc = $reader->getLocation();
414
					break;
415
416
				case JSON_END_OBJ:
417
				case JSON_END_ARRAY:
418
					$loc = $reader->getLocation();
419
420
					if (count($this->parents) > 0) {
421
						$this->cur =& $this->parents[count($this->parents) - 1];
422
						array_pop($this->parents);
423
					}
424
					break;
425
			}
426
		}
427
428
		return $this->data[0];
429
	}
430
431
	// This method was needed since PHP is crapy and doesn't have pointers/references
432
	function addArray($key) {
433
		$this->parents[] =& $this->cur;
434
		$ar = array();
435
436
		if ($key)
437
			$this->cur[$key] =& $ar;
438
		else
439
			$this->cur[] =& $ar;
440
441
		$this->cur =& $ar;
442
	}
443
444
	function getDelim($index, &$reader) {
445
		switch ($reader->getLocation()) {
446
			case JSON_IN_ARRAY:
447
			case JSON_IN_OBJECT:
448
				if ($index > 0)
449
					return ",";
450
				break;
451
		}
452
453
		return "";
454
	}
455
456
	function encode($input) {
457
		switch (gettype($input)) {
458
			case 'boolean':
459
				return $input ? 'true' : 'false';
460
461
			case 'integer':
462
				return (int) $input;
463
464
			case 'float':
465
			case 'double':
466
				return (float) $input;
467
468
			case 'NULL':
469
				return 'null';
470
471
			case 'string':
472
				return $this->encodeString($input);
473
474
			case 'array':
475
				return $this->_encodeArray($input);
476
477
			case 'object':
478
				return $this->_encodeArray(get_object_vars($input));
479
		}
480
481
		return '';
482
	}
483
484
	function encodeString($input) {
485
		// Needs to be escaped
486
		if (preg_match('/[^a-zA-Z0-9]/', $input)) {
487
			$output = '';
488
489
			for ($i=0; $i<strlen($input); $i++) {
490
				switch ($input[$i]) {
491
					case "\b":
492
						$output .= "\\b";
493
						break;
494
495
					case "\t":
496
						$output .= "\\t";
497
						break;
498
499
					case "\f":
500
						$output .= "\\f";
501
						break;
502
503
					case "\r":
504
						$output .= "\\r";
505
						break;
506
507
					case "\n":
508
						$output .= "\\n";
509
						break;
510
511
					case '\\':
512
						$output .= "\\\\";
513
						break;
514
515
					case '\'':
516
						$output .= "\\'";
517
						break;
518
519
					case '"':
520
						$output .= '\"';
521
						break;
522
523
					default:
524
						$byte = ord($input[$i]);
525
526
						if (($byte & 0xE0) == 0xC0) {
527
							$char = pack('C*', $byte, ord($input[$i + 1]));
528
							$i += 1;
529
							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
530
						} if (($byte & 0xF0) == 0xE0) {
531
							$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2]));
532
							$i += 2;
533
							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
534
						} if (($byte & 0xF8) == 0xF0) {
535
							$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3])));
536
							$i += 3;
537
							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
538
						} if (($byte & 0xFC) == 0xF8) {
539
							$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3]), ord($input[$i + 4])));
540
							$i += 4;
541
							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
542
						} if (($byte & 0xFE) == 0xFC) {
543
							$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3]), ord($input[$i + 4]), ord($input[$i + 5])));
544
							$i += 5;
545
							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
546
						} else if ($byte < 128)
547
							$output .= $input[$i];
548
				}
549
			}
550
551
			return '"' . $output . '"';
552
		}
553
554
		return '"' . $input . '"';
555
	}
556
557
	function _utf82utf16($utf8) {
558
		if (function_exists('mb_convert_encoding'))
559
			return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
560
561
		switch (strlen($utf8)) {
562
			case 1:
563
				return $utf8;
564
565
			case 2:
566
				return chr(0x07 & (ord($utf8[0]) >> 2)) . chr((0xC0 & (ord($utf8[0]) << 6)) | (0x3F & ord($utf8[1])));
567
568
			case 3:
569
				return chr((0xF0 & (ord($utf8[0]) << 4)) | (0x0F & (ord($utf8[1]) >> 2))) . chr((0xC0 & (ord($utf8[1]) << 6)) | (0x7F & ord($utf8[2])));
570
		}
571
572
		return '';
573
	}
574
575
	function _encodeArray($input) {
576
		$output = '';
577
		$isIndexed = true;
578
579
		$keys = array_keys($input);
580
		for ($i=0; $i<count($keys); $i++) {
581
			if (!is_int($keys[$i])) {
582
				$output .= $this->encodeString($keys[$i]) . ':' . $this->encode($input[$keys[$i]]);
583
				$isIndexed = false;
584
			} else
585
				$output .= $this->encode($input[$keys[$i]]);
586
587
			if ($i != count($keys) - 1)
588
				$output .= ',';
589
		}
590
591
		return $isIndexed ? '[' . $output . ']' : '{' . $output . '}';
592
	}
593
}
594
595
?>
596