|
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