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