|
31 | 31 | import java.nio.charset.StandardCharsets; |
32 | 32 | import java.nio.file.Files; |
33 | 33 | import java.nio.file.Path; |
| 34 | +import java.util.HashMap; |
| 35 | +import java.util.HashSet; |
34 | 36 | import java.util.Objects; |
35 | 37 | import java.util.function.BiFunction; |
36 | 38 |
|
@@ -82,6 +84,7 @@ public final class BinaryPropertyListParser { |
82 | 84 | private int offsetSize; |
83 | 85 | private int numObjects; |
84 | 86 | private int offsetTableOffset; |
| 87 | + private HashMap<Integer, NSObject> parsedObjects = new HashMap<>(); |
85 | 88 |
|
86 | 89 | /** |
87 | 90 | * Protected constructor so that instantiation is fully controlled by the |
@@ -320,44 +323,66 @@ private NSObject doParse(byte[] data) throws PropertyListFormatException, Unsupp |
320 | 323 | */ |
321 | 324 | private NSObject parseObject(ParsedObjectStack stack, int obj) throws PropertyListFormatException, UnsupportedEncodingException { |
322 | 325 | stack = stack.push(obj); |
| 326 | + |
| 327 | + if (this.parsedObjects.containsKey(obj)) { |
| 328 | + return this.parsedObjects.get(obj); |
| 329 | + } |
| 330 | + |
323 | 331 | int offset = this.getObjectOffset(obj); |
324 | 332 | byte type = this.bytes[offset]; |
325 | 333 | int objType = (type & 0xF0) >> 4; |
326 | 334 | int objInfo = type & 0x0F; |
| 335 | + NSObject result; |
327 | 336 | switch (objType) { |
328 | 337 | case SIMPLE_TYPE: |
329 | | - return this.parseSimpleObject(offset, objInfo, objType, obj); |
| 338 | + result = this.parseSimpleObject(offset, objInfo, objType, obj); |
| 339 | + break; |
330 | 340 | case INT_TYPE: |
331 | | - return this.parseNumber(offset, objInfo, NSNumber.INTEGER); |
| 341 | + result = this.parseNumber(offset, objInfo, NSNumber.INTEGER); |
| 342 | + break; |
332 | 343 | case REAL_TYPE: |
333 | | - return this.parseNumber(offset, objInfo, NSNumber.REAL); |
| 344 | + result = this.parseNumber(offset, objInfo, NSNumber.REAL); |
| 345 | + break; |
334 | 346 | case DATE_TYPE: |
335 | | - return this.parseDate(offset, objInfo); |
| 347 | + result = this.parseDate(offset, objInfo); |
| 348 | + break; |
336 | 349 | case DATA_TYPE: |
337 | | - return this.parseData(offset, objInfo); |
| 350 | + result = this.parseData(offset, objInfo); |
| 351 | + break; |
338 | 352 | case ASCII_STRING_TYPE: |
339 | | - return this.parseString(offset, objInfo, (o, l) -> l, StandardCharsets.US_ASCII.name()); |
| 353 | + result = this.parseString(offset, objInfo, (o, l) -> l, StandardCharsets.US_ASCII.name()); |
| 354 | + break; |
340 | 355 | case UTF16_STRING_TYPE: |
341 | 356 | // UTF-16 characters can have variable length, but the Core Foundation reference implementation |
342 | 357 | // assumes 2 byte characters, thus only covering the Basic Multilingual Plane |
343 | | - return this.parseString(offset, objInfo, (o, l) -> 2 * l, StandardCharsets.UTF_16BE.name()); |
| 358 | + result = this.parseString(offset, objInfo, (o, l) -> 2 * l, StandardCharsets.UTF_16BE.name()); |
| 359 | + break; |
344 | 360 | case UTF8_STRING_TYPE: |
345 | 361 | // UTF-8 characters can have variable length, so we need to calculate the byte length dynamically |
346 | 362 | // by reading the UTF-8 characters one by one |
347 | | - return this.parseString(offset, objInfo, this::calculateUtf8StringLength, StandardCharsets.UTF_8.name()); |
| 363 | + result = this.parseString(offset, objInfo, this::calculateUtf8StringLength, StandardCharsets.UTF_8.name()); |
| 364 | + break; |
348 | 365 | case UID_TYPE: |
349 | | - return this.parseUid(obj, offset, objInfo + 1); |
| 366 | + result = this.parseUid(obj, offset, objInfo + 1); |
| 367 | + break; |
350 | 368 | case ARRAY_TYPE: |
351 | | - return this.parseArray(offset, objInfo, stack); |
| 369 | + result = this.parseArray(offset, objInfo, stack); |
| 370 | + break; |
352 | 371 | case ORDERED_SET_TYPE: |
353 | | - return this.parseSet(offset, objInfo, true, stack); |
| 372 | + result = this.parseSet(offset, objInfo, true, stack); |
| 373 | + break; |
354 | 374 | case SET_TYPE: |
355 | | - return this.parseSet(offset, objInfo, false, stack); |
| 375 | + result = this.parseSet(offset, objInfo, false, stack); |
| 376 | + break; |
356 | 377 | case DICTIONARY_TYPE: |
357 | | - return this.parseDictionary(offset, objInfo, stack); |
| 378 | + result = this.parseDictionary(offset, objInfo, stack); |
| 379 | + break; |
358 | 380 | default: |
359 | 381 | throw new PropertyListFormatException("The given binary property list contains an object of unknown type (" + objType + ")"); |
360 | 382 | } |
| 383 | + |
| 384 | + this.parsedObjects.put(obj, result); |
| 385 | + return result; |
361 | 386 | } |
362 | 387 |
|
363 | 388 | private NSDate parseDate(int offset, int objInfo) throws PropertyListFormatException { |
@@ -450,9 +475,12 @@ private NSSet parseSet(int offset, int objInfo, boolean ordered, ParsedObjectSta |
450 | 475 | int setOffset = offset + lengthAndOffset[1]; |
451 | 476 |
|
452 | 477 | NSSet set = new NSSet(ordered); |
| 478 | + HashSet<Integer> addedObjectReferences = new HashSet<>(); |
453 | 479 | for (int i = 0; i < length; i++) { |
454 | 480 | int objRef = this.parseObjectReferenceFromList(setOffset, i); |
455 | | - set.addObject(this.parseObject(stack, objRef)); |
| 481 | + if (addedObjectReferences.add(objRef)) { |
| 482 | + set.addObject(this.parseObject(stack, objRef)); |
| 483 | + } |
456 | 484 | } |
457 | 485 |
|
458 | 486 | return set; |
|
0 commit comments