001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.harmony.unpack200; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.StringReader; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.List; 027 028import org.apache.commons.compress.harmony.pack200.BHSDCodec; 029import org.apache.commons.compress.harmony.pack200.Codec; 030import org.apache.commons.compress.harmony.pack200.Pack200Exception; 031import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute; 032import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass; 033import org.apache.commons.compress.harmony.unpack200.bytecode.CPDouble; 034import org.apache.commons.compress.harmony.unpack200.bytecode.CPFieldRef; 035import org.apache.commons.compress.harmony.unpack200.bytecode.CPFloat; 036import org.apache.commons.compress.harmony.unpack200.bytecode.CPInteger; 037import org.apache.commons.compress.harmony.unpack200.bytecode.CPInterfaceMethodRef; 038import org.apache.commons.compress.harmony.unpack200.bytecode.CPLong; 039import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethodRef; 040import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType; 041import org.apache.commons.compress.harmony.unpack200.bytecode.CPString; 042import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8; 043import org.apache.commons.compress.harmony.unpack200.bytecode.NewAttribute; 044import org.apache.commons.compress.utils.ParsingUtils; 045 046/** 047 * Sets of bands relating to a non-predefined attribute 048 */ 049public class NewAttributeBands extends BandSet { 050 051 /** 052 * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for 053 * successive Attributes of this type. 054 */ 055 private interface AttributeLayoutElement { 056 057 /** 058 * Adds the band data for this element at the given index to the attribute. 059 * 060 * @param index Index position to add the attribute. 061 * @param attribute The attribute to add. 062 */ 063 void addToAttribute(int index, NewAttribute attribute); 064 065 /** 066 * Reads the bands associated with this part of the layout. 067 * 068 * @param in TODO 069 * @param count TODO 070 * @throws Pack200Exception Bad archive. 071 * @throws IOException If an I/O error occurs. 072 */ 073 void readBands(InputStream in, int count) throws IOException, Pack200Exception; 074 075 } 076 077 public class Call extends LayoutElement { 078 079 private final int callableIndex; 080 private Callable callable; 081 082 public Call(final int callableIndex) { 083 this.callableIndex = callableIndex; 084 } 085 086 @Override 087 public void addToAttribute(final int n, final NewAttribute attribute) { 088 callable.addNextToAttribute(attribute); 089 } 090 091 public Callable getCallable() { 092 return callable; 093 } 094 095 public int getCallableIndex() { 096 return callableIndex; 097 } 098 099 @Override 100 public void readBands(final InputStream in, final int count) { 101 /* 102 * We don't read anything here, but we need to pass the extra count to the callable if it's a forwards call. For backwards callables the count is 103 * transmitted directly in the attribute bands and so it is added later. 104 */ 105 if (callableIndex > 0) { 106 callable.addCount(count); 107 } 108 } 109 110 public void setCallable(final Callable callable) { 111 this.callable = callable; 112 if (callableIndex < 1) { 113 callable.setBackwardsCallable(); 114 } 115 } 116 } 117 118 public static class Callable implements AttributeLayoutElement { 119 120 private final List<LayoutElement> body; 121 122 private boolean isBackwardsCallable; 123 124 private boolean isFirstCallable; 125 126 private int count; 127 128 private int index; 129 130 public Callable(final List<LayoutElement> body) { 131 this.body = body; 132 } 133 134 /** 135 * Adds the count of a call to this callable (ie the number of calls) 136 * 137 * @param count TODO 138 */ 139 public void addCount(final int count) { 140 this.count += count; 141 } 142 143 /** 144 * Used by calls when adding band contents to attributes, so they don't have to keep track of the internal index of the callable. 145 * 146 * @param attribute TODO 147 */ 148 public void addNextToAttribute(final NewAttribute attribute) { 149 for (final LayoutElement element : body) { 150 element.addToAttribute(index, attribute); 151 } 152 index++; 153 } 154 155 @Override 156 public void addToAttribute(final int n, final NewAttribute attribute) { 157 if (isFirstCallable) { 158 // Ignore n because bands also contain element parts from calls 159 for (final LayoutElement element : body) { 160 element.addToAttribute(index, attribute); 161 } 162 index++; 163 } 164 } 165 166 public List<LayoutElement> getBody() { 167 return body; 168 } 169 170 public boolean isBackwardsCallable() { 171 return isBackwardsCallable; 172 } 173 174 @Override 175 public void readBands(final InputStream in, int count) throws IOException, Pack200Exception { 176 if (isFirstCallable) { 177 count += this.count; 178 } else { 179 count = this.count; 180 } 181 for (final LayoutElement element : body) { 182 element.readBands(in, count); 183 } 184 } 185 186 /** 187 * Tells this Callable that it is a backwards callable 188 */ 189 public void setBackwardsCallable() { 190 this.isBackwardsCallable = true; 191 } 192 193 public void setFirstCallable(final boolean isFirstCallable) { 194 this.isFirstCallable = isFirstCallable; 195 } 196 } 197 198 public class Integral extends LayoutElement { 199 200 private final String tag; 201 202 private int[] band; 203 204 public Integral(final String tag) { 205 this.tag = tag; 206 } 207 208 @Override 209 public void addToAttribute(final int n, final NewAttribute attribute) { 210 int value = band[n]; 211 switch (tag) { 212 case "B": 213 case "FB": 214 attribute.addInteger(1, value); 215 break; 216 case "SB": 217 attribute.addInteger(1, (byte) value); 218 break; 219 case "H": 220 case "FH": 221 attribute.addInteger(2, value); 222 break; 223 case "SH": 224 attribute.addInteger(2, (short) value); 225 break; 226 case "I": 227 case "FI": 228 case "SI": 229 attribute.addInteger(4, value); 230 break; 231 case "V": 232 case "FV": 233 case "SV": 234 break; 235 default: 236 if (tag.startsWith("PO")) { 237 final char uintType = tag.substring(2).toCharArray()[0]; 238 final int length = getLength(uintType); 239 attribute.addBCOffset(length, value); 240 } else if (tag.startsWith("P")) { 241 final char uintType = tag.substring(1).toCharArray()[0]; 242 final int length = getLength(uintType); 243 attribute.addBCIndex(length, value); 244 } else if (tag.startsWith("OS")) { 245 final char uintType = tag.substring(2).toCharArray()[0]; 246 final int length = getLength(uintType); 247 switch (length) { 248 case 1: 249 value = (byte) value; 250 break; 251 case 2: 252 value = (short) value; 253 break; 254 case 4: 255 value = value; 256 break; 257 default: 258 break; 259 } 260 attribute.addBCLength(length, value); 261 } else if (tag.startsWith("O")) { 262 final char uintType = tag.substring(1).toCharArray()[0]; 263 final int length = getLength(uintType); 264 attribute.addBCLength(length, value); 265 } 266 break; 267 } 268 } 269 270 public String getTag() { 271 return tag; 272 } 273 274 int getValue(final int index) { 275 return band[index]; 276 } 277 278 @Override 279 public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception { 280 band = decodeBandInt(attributeLayout.getName() + "_" + tag, in, getCodec(tag), count); 281 } 282 283 } 284 285 private abstract static class LayoutElement implements AttributeLayoutElement { 286 287 protected int getLength(final char uintType) { 288 int length = 0; 289 switch (uintType) { 290 case 'B': 291 length = 1; 292 break; 293 case 'H': 294 length = 2; 295 break; 296 case 'I': 297 length = 4; 298 break; 299 case 'V': 300 length = 0; 301 break; 302 } 303 return length; 304 } 305 } 306 307 /** 308 * Constant Pool Reference 309 */ 310 public class Reference extends LayoutElement { 311 312 private final String tag; 313 314 private Object band; 315 316 private final int length; 317 318 public Reference(final String tag) { 319 this.tag = tag; 320 length = getLength(tag.charAt(tag.length() - 1)); 321 } 322 323 @Override 324 public void addToAttribute(final int n, final NewAttribute attribute) { 325 if (tag.startsWith("KI")) { // Integer 326 attribute.addToBody(length, ((CPInteger[]) band)[n]); 327 } else if (tag.startsWith("KJ")) { // Long 328 attribute.addToBody(length, ((CPLong[]) band)[n]); 329 } else if (tag.startsWith("KF")) { // Float 330 attribute.addToBody(length, ((CPFloat[]) band)[n]); 331 } else if (tag.startsWith("KD")) { // Double 332 attribute.addToBody(length, ((CPDouble[]) band)[n]); 333 } else if (tag.startsWith("KS")) { // String 334 attribute.addToBody(length, ((CPString[]) band)[n]); 335 } else if (tag.startsWith("RC")) { // Class 336 attribute.addToBody(length, ((CPClass[]) band)[n]); 337 } else if (tag.startsWith("RS")) { // Signature 338 attribute.addToBody(length, ((CPUTF8[]) band)[n]); 339 } else if (tag.startsWith("RD")) { // Descriptor 340 attribute.addToBody(length, ((CPNameAndType[]) band)[n]); 341 } else if (tag.startsWith("RF")) { // Field Reference 342 attribute.addToBody(length, ((CPFieldRef[]) band)[n]); 343 } else if (tag.startsWith("RM")) { // Method Reference 344 attribute.addToBody(length, ((CPMethodRef[]) band)[n]); 345 } else if (tag.startsWith("RI")) { // Interface Method Reference 346 attribute.addToBody(length, ((CPInterfaceMethodRef[]) band)[n]); 347 } else if (tag.startsWith("RU")) { // UTF8 String 348 attribute.addToBody(length, ((CPUTF8[]) band)[n]); 349 } 350 } 351 352 public String getTag() { 353 return tag; 354 } 355 356 @Override 357 public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception { 358 if (tag.startsWith("KI")) { // Integer 359 band = parseCPIntReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 360 } else if (tag.startsWith("KJ")) { // Long 361 band = parseCPLongReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 362 } else if (tag.startsWith("KF")) { // Float 363 band = parseCPFloatReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 364 } else if (tag.startsWith("KD")) { // Double 365 band = parseCPDoubleReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 366 } else if (tag.startsWith("KS")) { // String 367 band = parseCPStringReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 368 } else if (tag.startsWith("RC")) { // Class 369 band = parseCPClassReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 370 } else if (tag.startsWith("RS")) { // Signature 371 band = parseCPSignatureReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 372 } else if (tag.startsWith("RD")) { // Descriptor 373 band = parseCPDescriptorReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 374 } else if (tag.startsWith("RF")) { // Field Reference 375 band = parseCPFieldRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 376 } else if (tag.startsWith("RM")) { // Method Reference 377 band = parseCPMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 378 } else if (tag.startsWith("RI")) { // Interface Method Reference 379 band = parseCPInterfaceMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 380 } else if (tag.startsWith("RU")) { // UTF8 String 381 band = parseCPUTF8References(attributeLayout.getName(), in, Codec.UNSIGNED5, count); 382 } 383 } 384 385 } 386 387 /** 388 * A replication is an array of layout elements, with an associated count 389 */ 390 public class Replication extends LayoutElement { 391 392 private final Integral countElement; 393 394 private final List<LayoutElement> layoutElements = new ArrayList<>(); 395 396 public Replication(final String tag, final String contents) throws IOException { 397 this.countElement = new Integral(tag); 398 final StringReader stream = new StringReader(contents); 399 LayoutElement e; 400 while ((e = readNextLayoutElement(stream)) != null) { 401 layoutElements.add(e); 402 } 403 } 404 405 @Override 406 public void addToAttribute(final int index, final NewAttribute attribute) { 407 // Add the count value 408 countElement.addToAttribute(index, attribute); 409 410 // Add the corresponding array values 411 int offset = 0; 412 for (int i = 0; i < index; i++) { 413 offset += countElement.getValue(i); 414 } 415 final long numElements = countElement.getValue(index); 416 for (int i = offset; i < offset + numElements; i++) { 417 for (final LayoutElement layoutElement : layoutElements) { 418 layoutElement.addToAttribute(i, attribute); 419 } 420 } 421 } 422 423 public Integral getCountElement() { 424 return countElement; 425 } 426 427 public List<LayoutElement> getLayoutElements() { 428 return layoutElements; 429 } 430 431 @Override 432 public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception { 433 countElement.readBands(in, count); 434 int arrayCount = 0; 435 for (int i = 0; i < count; i++) { 436 arrayCount += countElement.getValue(i); 437 } 438 for (final LayoutElement layoutElement : layoutElements) { 439 layoutElement.readBands(in, arrayCount); 440 } 441 } 442 } 443 444 /** 445 * A Union is a type of layout element where the tag value acts as a selector for one of the union cases 446 */ 447 public class Union extends LayoutElement { 448 449 private final Integral unionTag; 450 private final List<UnionCase> unionCases; 451 private final List<LayoutElement> defaultCaseBody; 452 private int[] caseCounts; 453 private int defaultCount; 454 455 public Union(final String tag, final List<UnionCase> unionCases, final List<LayoutElement> body) { 456 this.unionTag = new Integral(tag); 457 this.unionCases = unionCases; 458 this.defaultCaseBody = body; 459 } 460 461 @Override 462 public void addToAttribute(final int n, final NewAttribute attribute) { 463 unionTag.addToAttribute(n, attribute); 464 int offset = 0; 465 final int[] tagBand = unionTag.band; 466 final int tag = unionTag.getValue(n); 467 boolean defaultCase = true; 468 for (final UnionCase unionCase : unionCases) { 469 if (unionCase.hasTag(tag)) { 470 defaultCase = false; 471 for (int j = 0; j < n; j++) { 472 if (unionCase.hasTag(tagBand[j])) { 473 offset++; 474 } 475 } 476 unionCase.addToAttribute(offset, attribute); 477 } 478 } 479 if (defaultCase) { 480 // default case 481 int defaultOffset = 0; 482 for (int j = 0; j < n; j++) { 483 boolean found = false; 484 for (final UnionCase unionCase : unionCases) { 485 if (unionCase.hasTag(tagBand[j])) { 486 found = true; 487 } 488 } 489 if (!found) { 490 defaultOffset++; 491 } 492 } 493 if (defaultCaseBody != null) { 494 for (final LayoutElement element : defaultCaseBody) { 495 element.addToAttribute(defaultOffset, attribute); 496 } 497 } 498 } 499 } 500 501 public List<LayoutElement> getDefaultCaseBody() { 502 return defaultCaseBody; 503 } 504 505 public List<UnionCase> getUnionCases() { 506 return unionCases; 507 } 508 509 public Integral getUnionTag() { 510 return unionTag; 511 } 512 513 @Override 514 public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception { 515 unionTag.readBands(in, count); 516 final int[] values = unionTag.band; 517 // Count the band size for each union case then read the bands 518 caseCounts = new int[unionCases.size()]; 519 for (int i = 0; i < caseCounts.length; i++) { 520 final UnionCase unionCase = unionCases.get(i); 521 for (final int value : values) { 522 if (unionCase.hasTag(value)) { 523 caseCounts[i]++; 524 } 525 } 526 unionCase.readBands(in, caseCounts[i]); 527 } 528 // Count number of default cases then read the default bands 529 for (final int value : values) { 530 boolean found = false; 531 for (final UnionCase unionCase : unionCases) { 532 if (unionCase.hasTag(value)) { 533 found = true; 534 } 535 } 536 if (!found) { 537 defaultCount++; 538 } 539 } 540 if (defaultCaseBody != null) { 541 for (final LayoutElement element : defaultCaseBody) { 542 element.readBands(in, defaultCount); 543 } 544 } 545 } 546 547 } 548 549 /** 550 * A Union case 551 */ 552 public class UnionCase extends LayoutElement { 553 554 private List<LayoutElement> body; 555 556 private final List<Integer> tags; 557 558 public UnionCase(final List<Integer> tags) { 559 this.tags = tags; 560 } 561 562 public UnionCase(final List<Integer> tags, final List<LayoutElement> body) { 563 this.tags = tags; 564 this.body = body; 565 } 566 567 @Override 568 public void addToAttribute(final int index, final NewAttribute attribute) { 569 if (body != null) { 570 for (final LayoutElement element : body) { 571 element.addToAttribute(index, attribute); 572 } 573 } 574 } 575 576 public List<LayoutElement> getBody() { 577 return body == null ? Collections.EMPTY_LIST : body; 578 } 579 580 public boolean hasTag(final int i) { 581 return tags.contains(Integer.valueOf(i)); 582 } 583 584 public boolean hasTag(final long l) { 585 return tags.contains(Integer.valueOf((int) l)); 586 } 587 588 @Override 589 public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception { 590 if (body != null) { 591 for (final LayoutElement element : body) { 592 element.readBands(in, count); 593 } 594 } 595 } 596 } 597 598 private final AttributeLayout attributeLayout; 599 600 private int backwardsCallCount; 601 602 protected List<AttributeLayoutElement> attributeLayoutElements; 603 604 public NewAttributeBands(final Segment segment, final AttributeLayout attributeLayout) throws IOException { 605 super(segment); 606 this.attributeLayout = attributeLayout; 607 parseLayout(); 608 attributeLayout.setBackwardsCallCount(backwardsCallCount); 609 } 610 611 public int getBackwardsCallCount() { 612 return backwardsCallCount; 613 } 614 615 /** 616 * Returns the {@link BHSDCodec} that should be used for the given layout element. 617 * 618 * @param layoutElement TODO 619 * @return the {@link BHSDCodec} that should be used for the given layout element. 620 */ 621 public BHSDCodec getCodec(final String layoutElement) { 622 if (layoutElement.indexOf('O') >= 0) { 623 return Codec.BRANCH5; 624 } 625 if (layoutElement.indexOf('P') >= 0) { 626 return Codec.BCI5; 627 } 628 if (layoutElement.indexOf('S') >= 0 && !layoutElement.contains("KS") //$NON-NLS-1$ 629 && !layoutElement.contains("RS")) { //$NON-NLS-1$ 630 return Codec.SIGNED5; 631 } 632 if (layoutElement.indexOf('B') >= 0) { 633 return Codec.BYTE1; 634 } 635 return Codec.UNSIGNED5; 636 } 637 638 /** 639 * Gets one attribute at the given index from the various bands. The correct bands must have already been read in. 640 * 641 * @param index TODO 642 * @param elements TODO 643 * @return attribute at the given index. 644 */ 645 private Attribute getOneAttribute(final int index, final List<AttributeLayoutElement> elements) { 646 final NewAttribute attribute = new NewAttribute(segment.getCpBands().cpUTF8Value(attributeLayout.getName()), attributeLayout.getIndex()); 647 for (final AttributeLayoutElement element : elements) { 648 element.addToAttribute(index, attribute); 649 } 650 return attribute; 651 } 652 653 /** 654 * Utility method to get the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]}) 655 * 656 * @param stream 657 * @return 658 * @throws IOException If an I/O error occurs. 659 */ 660 private StringReader getStreamUpToMatchingBracket(final StringReader stream) throws IOException { 661 final StringBuilder sb = new StringBuilder(); 662 int foundBracket = -1; 663 while (foundBracket != 0) { 664 final int read = stream.read(); 665 if (read == -1) { 666 break; 667 } 668 final char c = (char) read; 669 if (c == ']') { 670 foundBracket++; 671 } 672 if (c == '[') { 673 foundBracket--; 674 } 675 if (!(foundBracket == 0)) { 676 sb.append(c); 677 } 678 } 679 return new StringReader(sb.toString()); 680 } 681 682 /** 683 * Parse the bands relating to this AttributeLayout and return the correct class file attributes as a List of {@link Attribute}. 684 * 685 * @param in parse source. 686 * @param occurrenceCount TODO 687 * @return Class file attributes as a List of {@link Attribute}. 688 * @throws IOException If an I/O error occurs. 689 * @throws Pack200Exception If a Pack200 semantic error occurs. 690 */ 691 public List<Attribute> parseAttributes(final InputStream in, final int occurrenceCount) throws IOException, Pack200Exception { 692 for (final AttributeLayoutElement element : attributeLayoutElements) { 693 element.readBands(in, occurrenceCount); 694 } 695 696 final List<Attribute> attributes = new ArrayList<>(occurrenceCount); 697 for (int i = 0; i < occurrenceCount; i++) { 698 attributes.add(getOneAttribute(i, attributeLayoutElements)); 699 } 700 return attributes; 701 } 702 703 /** 704 * Tokenize the layout into AttributeElements 705 * 706 * @throws IOException If an I/O error occurs. 707 */ 708 private void parseLayout() throws IOException { 709 if (attributeLayoutElements == null) { 710 attributeLayoutElements = new ArrayList<>(); 711 final StringReader stream = new StringReader(attributeLayout.getLayout()); 712 AttributeLayoutElement e; 713 while ((e = readNextAttributeElement(stream)) != null) { 714 attributeLayoutElements.add(e); 715 } 716 resolveCalls(); 717 } 718 } 719 720 /* 721 * (non-Javadoc) 722 * 723 * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream) 724 */ 725 @Override 726 public void read(final InputStream in) throws IOException, Pack200Exception { 727 // does nothing - use parseAttributes instead 728 } 729 730 /** 731 * Reads a 'body' section of the layout from the given stream 732 * 733 * @param stream 734 * @return List of LayoutElements 735 * @throws IOException If an I/O error occurs. 736 */ 737 private List<LayoutElement> readBody(final StringReader stream) throws IOException { 738 final List<LayoutElement> layoutElements = new ArrayList<>(); 739 LayoutElement e; 740 while ((e = readNextLayoutElement(stream)) != null) { 741 layoutElements.add(e); 742 } 743 return layoutElements; 744 } 745 746 private AttributeLayoutElement readNextAttributeElement(final StringReader stream) throws IOException { 747 stream.mark(1); 748 final int next = stream.read(); 749 if (next == -1) { 750 return null; 751 } 752 if (next == '[') { 753 return new Callable(readBody(getStreamUpToMatchingBracket(stream))); 754 } 755 stream.reset(); 756 return readNextLayoutElement(stream); 757 } 758 759 private LayoutElement readNextLayoutElement(final StringReader stream) throws IOException { 760 final int nextChar = stream.read(); 761 if (nextChar == -1) { 762 return null; 763 } 764 switch (nextChar) { 765 // Integrals 766 case 'B': 767 case 'H': 768 case 'I': 769 case 'V': 770 return new Integral(new String(new char[] { (char) nextChar })); 771 case 'S': 772 case 'F': 773 return new Integral(new String(new char[] { (char) nextChar, (char) stream.read() })); 774 case 'P': 775 stream.mark(1); 776 if (stream.read() != 'O') { 777 stream.reset(); 778 return new Integral("P" + (char) stream.read()); 779 } 780 return new Integral("PO" + (char) stream.read()); 781 case 'O': 782 stream.mark(1); 783 if (stream.read() != 'S') { 784 stream.reset(); 785 return new Integral("O" + (char) stream.read()); 786 } 787 return new Integral("OS" + (char) stream.read()); 788 789 // Replication 790 case 'N': 791 final char uintType = (char) stream.read(); 792 stream.read(); // '[' 793 final String str = readUpToMatchingBracket(stream); 794 return new Replication("" + uintType, str); 795 796 // Union 797 case 'T': 798 String intType = "" + (char) stream.read(); 799 if (intType.equals("S")) { 800 intType += (char) stream.read(); 801 } 802 final List<UnionCase> unionCases = new ArrayList<>(); 803 UnionCase c; 804 while ((c = readNextUnionCase(stream)) != null) { 805 unionCases.add(c); 806 } 807 stream.read(); // '(' 808 stream.read(); // ')' 809 stream.read(); // '[' 810 List<LayoutElement> body = null; 811 stream.mark(1); 812 final char next = (char) stream.read(); 813 if (next != ']') { 814 stream.reset(); 815 body = readBody(getStreamUpToMatchingBracket(stream)); 816 } 817 return new Union(intType, unionCases, body); 818 819 // Call 820 case '(': 821 final int number = readNumber(stream).intValue(); 822 stream.read(); // ')' 823 return new Call(number); 824 // Reference 825 case 'K': 826 case 'R': 827 final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) stream.read()); 828 final char nxt = (char) stream.read(); 829 string.append(nxt); 830 if (nxt == 'N') { 831 string.append((char) stream.read()); 832 } 833 return new Reference(string.toString()); 834 } 835 return null; 836 } 837 838 /** 839 * Reads a UnionCase from the stream. 840 * 841 * @param stream source stream. 842 * @return A UnionCase from the stream. 843 * @throws IOException If an I/O error occurs. 844 */ 845 private UnionCase readNextUnionCase(final StringReader stream) throws IOException { 846 stream.mark(2); 847 stream.read(); // '(' 848 final int next = stream.read(); 849 char ch = (char) next; 850 if (ch == ')' || next == -1) { 851 stream.reset(); 852 return null; 853 } 854 stream.reset(); 855 stream.read(); // '(' 856 final List<Integer> tags = new ArrayList<>(); 857 Integer nextTag; 858 do { 859 nextTag = readNumber(stream); 860 if (nextTag != null) { 861 tags.add(nextTag); 862 stream.read(); // ',' or ')' 863 } 864 } while (nextTag != null); 865 stream.read(); // '[' 866 stream.mark(1); 867 ch = (char) stream.read(); 868 if (ch == ']') { 869 return new UnionCase(tags); 870 } 871 stream.reset(); 872 return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(stream))); 873 } 874 875 /** 876 * Reads a number from the stream and return it 877 * 878 * @param stream 879 * @return 880 * @throws IOException If an I/O error occurs. 881 */ 882 private Integer readNumber(final StringReader stream) throws IOException { 883 stream.mark(1); 884 final char first = (char) stream.read(); 885 final boolean negative = first == '-'; 886 if (!negative) { 887 stream.reset(); 888 } 889 stream.mark(100); 890 int i; 891 int length = 0; 892 while ((i = stream.read()) != -1 && Character.isDigit((char) i)) { 893 length++; 894 } 895 stream.reset(); 896 if (length == 0) { 897 return null; 898 } 899 final char[] digits = new char[length]; 900 final int read = stream.read(digits); 901 if (read != digits.length) { 902 throw new IOException("Error reading from the input stream"); 903 } 904 return ParsingUtils.parseIntValue((negative ? "-" : "") + new String(digits)); 905 } 906 907 /** 908 * Gets the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]}) 909 * 910 * @param stream input stream. 911 * @return the contents of the given stream. 912 * @throws IOException If an I/O error occurs. 913 */ 914 private String readUpToMatchingBracket(final StringReader stream) throws IOException { 915 final StringBuilder sb = new StringBuilder(); 916 int foundBracket = -1; 917 while (foundBracket != 0) { 918 final int read = stream.read(); 919 if (read == -1) { 920 break; 921 } 922 final char c = (char) read; 923 if (c == ']') { 924 foundBracket++; 925 } 926 if (c == '[') { 927 foundBracket--; 928 } 929 if (!(foundBracket == 0)) { 930 sb.append(c); 931 } 932 } 933 return sb.toString(); 934 } 935 936 /** 937 * Resolve calls in the attribute layout and returns the number of backwards calls 938 */ 939 private void resolveCalls() { 940 int backwardsCalls = 0; 941 for (int i = 0; i < attributeLayoutElements.size(); i++) { 942 final AttributeLayoutElement element = attributeLayoutElements.get(i); 943 if (element instanceof Callable) { 944 final Callable callable = (Callable) element; 945 if (i == 0) { 946 callable.setFirstCallable(true); 947 } 948 // Look for calls in the body 949 for (final LayoutElement layoutElement : callable.body) { 950 // Set the callable for each call 951 backwardsCalls += resolveCallsForElement(i, callable, layoutElement); 952 } 953 } 954 } 955 backwardsCallCount = backwardsCalls; 956 } 957 958 private int resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) { 959 int backwardsCalls = 0; 960 if (layoutElement instanceof Call) { 961 final Call call = (Call) layoutElement; 962 int index = call.callableIndex; 963 if (index == 0) { // Calls the parent callable 964 backwardsCalls++; 965 call.setCallable(currentCallable); 966 } else if (index > 0) { // Forwards call 967 for (int k = i + 1; k < attributeLayoutElements.size(); k++) { 968 final AttributeLayoutElement el = attributeLayoutElements.get(k); 969 if (el instanceof Callable) { 970 index--; 971 if (index == 0) { 972 call.setCallable((Callable) el); 973 break; 974 } 975 } 976 } 977 } else { // Backwards call 978 backwardsCalls++; 979 for (int k = i - 1; k >= 0; k--) { 980 final AttributeLayoutElement el = attributeLayoutElements.get(k); 981 if (el instanceof Callable) { 982 index++; 983 if (index == 0) { 984 call.setCallable((Callable) el); 985 break; 986 } 987 } 988 } 989 } 990 } else if (layoutElement instanceof Replication) { 991 final List<LayoutElement> children = ((Replication) layoutElement).layoutElements; 992 for (final LayoutElement child : children) { 993 backwardsCalls += resolveCallsForElement(i, currentCallable, child); 994 } 995 } 996 return backwardsCalls; 997 } 998 999 /** 1000 * Once the attribute bands have been read the callables can be informed about the number of times each is subject to a backwards call. This method is used 1001 * to set this information. 1002 * 1003 * @param backwardsCalls one int for each backwards callable, which contains the number of times that callable is subject to a backwards call. 1004 * @throws IOException If an I/O error occurs. 1005 */ 1006 public void setBackwardsCalls(final int[] backwardsCalls) throws IOException { 1007 int index = 0; 1008 parseLayout(); 1009 for (final AttributeLayoutElement element : attributeLayoutElements) { 1010 if (element instanceof Callable && ((Callable) element).isBackwardsCallable()) { 1011 ((Callable) element).addCount(backwardsCalls[index]); 1012 index++; 1013 } 1014 } 1015 } 1016 1017 @Override 1018 public void unpack() throws IOException, Pack200Exception { 1019 1020 } 1021 1022}