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; 042 043/** 044 * Sets of bands relating to a non-predefined attribute 045 */ 046public class NewAttributeBands extends BandSet { 047 048 /** 049 * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which 050 * transmit the AttributeElement data for successive Attributes of this type. 051 */ 052 private interface AttributeLayoutElement { 053 054 /** 055 * Adds the band data for this element at the given index to the attribute. 056 * 057 * @param index Index position to add the attribute. 058 * @param attribute The attribute to add. 059 */ 060 void addToAttribute(int index, NewAttribute attribute); 061 062 /** 063 * Read the bands associated with this part of the layout. 064 * 065 * @param in TODO 066 * @param count TODO 067 * @throws Pack200Exception Bad archive. 068 * @throws IOException If an I/O error occurs. 069 */ 070 void readBands(InputStream in, int count) throws IOException, Pack200Exception; 071 072 } 073 074 public class Call extends LayoutElement { 075 076 private final int callableIndex; 077 private Callable callable; 078 079 public Call(final int callableIndex) { 080 this.callableIndex = callableIndex; 081 } 082 083 @Override 084 public void addToAttribute(final int n, final NewAttribute attribute) { 085 callable.addNextToAttribute(attribute); 086 } 087 088 public Callable getCallable() { 089 return callable; 090 } 091 092 public int getCallableIndex() { 093 return callableIndex; 094 } 095 096 @Override 097 public void readBands(final InputStream in, final int count) { 098 /* 099 * We don't read anything here, but we need to pass the extra count to the callable if it's a forwards call. 100 * For backwards callables the count is transmitted directly in the attribute bands and so it is added 101 * 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 public Callable(final List<LayoutElement> body) { 128 this.body = body; 129 } 130 131 /** 132 * Adds the count of a call to this callable (ie the number of calls) 133 * 134 * @param count TODO 135 */ 136 public void addCount(final int count) { 137 this.count += count; 138 } 139 140 /** 141 * Used by calls when adding band contents to attributes, so they don't have to keep track of the internal index 142 * 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.indexOf("KS") < 0 //$NON-NLS-1$ 613 && layoutElement.indexOf("RS") < 0) { //$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()), 631 attributeLayout.getIndex()); 632 for (final AttributeLayoutElement element : elements) { 633 element.addToAttribute(index, attribute); 634 } 635 return attribute; 636 } 637 638 /** 639 * Utility method to get the contents of the given stream, up to the next {@code ]}, 640 * (ignoring pairs of brackets {@code [} and {@code ]}) 641 * 642 * @param stream 643 * @return 644 * @throws IOException If an I/O error occurs. 645 */ 646 private StringReader getStreamUpToMatchingBracket(final StringReader stream) throws IOException { 647 final StringBuilder sb = new StringBuilder(); 648 int foundBracket = -1; 649 while (foundBracket != 0) { 650 final int read = stream.read(); 651 if (read == -1) { 652 break; 653 } 654 final char c = (char) read; 655 if (c == ']') { 656 foundBracket++; 657 } 658 if (c == '[') { 659 foundBracket--; 660 } 661 if (!(foundBracket == 0)) { 662 sb.append(c); 663 } 664 } 665 return new StringReader(sb.toString()); 666 } 667 668 /** 669 * Parse the bands relating to this AttributeLayout and return the correct class file attributes as a List of 670 * {@link Attribute}. 671 * 672 * @param in parse source. 673 * @param occurrenceCount TODO 674 * @return Class file attributes as a List of {@link Attribute}. 675 * @throws IOException If an I/O error occurs. 676 * @throws Pack200Exception TODO 677 */ 678 public List<Attribute> parseAttributes(final InputStream in, final int occurrenceCount) throws IOException, Pack200Exception { 679 for (final AttributeLayoutElement element : attributeLayoutElements) { 680 element.readBands(in, occurrenceCount); 681 } 682 683 final List<Attribute> attributes = new ArrayList<>(occurrenceCount); 684 for (int i = 0; i < occurrenceCount; i++) { 685 attributes.add(getOneAttribute(i, attributeLayoutElements)); 686 } 687 return attributes; 688 } 689 690 /** 691 * Tokenize the layout into AttributeElements 692 * 693 * @throws IOException If an I/O error occurs. 694 */ 695 private void parseLayout() throws IOException { 696 if (attributeLayoutElements == null) { 697 attributeLayoutElements = new ArrayList<>(); 698 final StringReader stream = new StringReader(attributeLayout.getLayout()); 699 AttributeLayoutElement e; 700 while ((e = readNextAttributeElement(stream)) != null) { 701 attributeLayoutElements.add(e); 702 } 703 resolveCalls(); 704 } 705 } 706 707 /* 708 * (non-Javadoc) 709 * 710 * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream) 711 */ 712 @Override 713 public void read(final InputStream in) throws IOException, Pack200Exception { 714 // does nothing - use parseAttributes instead 715 } 716 717 /** 718 * Read a 'body' section of the layout from the given stream 719 * 720 * @param stream 721 * @return List of LayoutElements 722 * @throws IOException If an I/O error occurs. 723 */ 724 private List<LayoutElement> readBody(final StringReader stream) throws IOException { 725 final List<LayoutElement> layoutElements = new ArrayList<>(); 726 LayoutElement e; 727 while ((e = readNextLayoutElement(stream)) != null) { 728 layoutElements.add(e); 729 } 730 return layoutElements; 731 } 732 733 private AttributeLayoutElement readNextAttributeElement(final StringReader stream) throws IOException { 734 stream.mark(1); 735 final int next = stream.read(); 736 if (next == -1) { 737 return null; 738 } 739 if (next == '[') { 740 return new Callable(readBody(getStreamUpToMatchingBracket(stream))); 741 } 742 stream.reset(); 743 return readNextLayoutElement(stream); 744 } 745 746 private LayoutElement readNextLayoutElement(final StringReader stream) throws IOException { 747 final int nextChar = stream.read(); 748 if (nextChar == -1) { 749 return null; 750 } 751 switch (nextChar) { 752 // Integrals 753 case 'B': 754 case 'H': 755 case 'I': 756 case 'V': 757 return new Integral(new String(new char[] {(char) nextChar})); 758 case 'S': 759 case 'F': 760 return new Integral(new String(new char[] {(char) nextChar, (char) stream.read()})); 761 case 'P': 762 stream.mark(1); 763 if (stream.read() != 'O') { 764 stream.reset(); 765 return new Integral("P" + (char) stream.read()); 766 } 767 return new Integral("PO" + (char) stream.read()); 768 case 'O': 769 stream.mark(1); 770 if (stream.read() != 'S') { 771 stream.reset(); 772 return new Integral("O" + (char) stream.read()); 773 } 774 return new Integral("OS" + (char) stream.read()); 775 776 // Replication 777 case 'N': 778 final char uintType = (char) stream.read(); 779 stream.read(); // '[' 780 final String str = readUpToMatchingBracket(stream); 781 return new Replication("" + uintType, str); 782 783 // Union 784 case 'T': 785 String intType = "" + (char) stream.read(); 786 if (intType.equals("S")) { 787 intType += (char) stream.read(); 788 } 789 final List<UnionCase> unionCases = new ArrayList<>(); 790 UnionCase c; 791 while ((c = readNextUnionCase(stream)) != null) { 792 unionCases.add(c); 793 } 794 stream.read(); // '(' 795 stream.read(); // ')' 796 stream.read(); // '[' 797 List<LayoutElement> body = null; 798 stream.mark(1); 799 final char next = (char) stream.read(); 800 if (next != ']') { 801 stream.reset(); 802 body = readBody(getStreamUpToMatchingBracket(stream)); 803 } 804 return new Union(intType, unionCases, body); 805 806 // Call 807 case '(': 808 final int number = readNumber(stream).intValue(); 809 stream.read(); // ')' 810 return new Call(number); 811 // Reference 812 case 'K': 813 case 'R': 814 final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) stream.read()); 815 final char nxt = (char) stream.read(); 816 string.append(nxt); 817 if (nxt == 'N') { 818 string.append((char) stream.read()); 819 } 820 return new Reference(string.toString()); 821 } 822 return null; 823 } 824 825 /** 826 * Read a UnionCase from the stream. 827 * 828 * @param stream source stream. 829 * @return A UnionCase from the stream. 830 * @throws IOException If an I/O error occurs. 831 */ 832 private UnionCase readNextUnionCase(final StringReader stream) throws IOException { 833 stream.mark(2); 834 stream.read(); // '(' 835 final int next = stream.read(); 836 char ch = (char) next; 837 if (ch == ')'|| next == -1) { 838 stream.reset(); 839 return null; 840 } 841 stream.reset(); 842 stream.read(); // '(' 843 final List<Integer> tags = new ArrayList<>(); 844 Integer nextTag; 845 do { 846 nextTag = readNumber(stream); 847 if (nextTag != null) { 848 tags.add(nextTag); 849 stream.read(); // ',' or ')' 850 } 851 } while (nextTag != null); 852 stream.read(); // '[' 853 stream.mark(1); 854 ch = (char) stream.read(); 855 if (ch == ']') { 856 return new UnionCase(tags); 857 } 858 stream.reset(); 859 return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(stream))); 860 } 861 862 /** 863 * Read a number from the stream and return it 864 * 865 * @param stream 866 * @return 867 * @throws IOException If an I/O error occurs. 868 */ 869 private Integer readNumber(final StringReader stream) throws IOException { 870 stream.mark(1); 871 final char first = (char) stream.read(); 872 final boolean negative = first == '-'; 873 if (!negative) { 874 stream.reset(); 875 } 876 stream.mark(100); 877 int i; 878 int length = 0; 879 while ((i = stream.read()) != -1 && Character.isDigit((char) i)) { 880 length++; 881 } 882 stream.reset(); 883 if (length == 0) { 884 return null; 885 } 886 final char[] digits = new char[length]; 887 final int read = stream.read(digits); 888 if (read != digits.length) { 889 throw new IOException("Error reading from the input stream"); 890 } 891 return Integer.valueOf(Integer.parseInt((negative ? "-" : "") + new String(digits))); 892 } 893 894 /** 895 * Gets the contents of the given stream, up to the next {@code ]}, 896 * (ignoring pairs of brackets {@code [} and {@code ]}) 897 * 898 * @param stream input stream. 899 * @return the contents of the given stream. 900 * @throws IOException If an I/O error occurs. 901 */ 902 private String readUpToMatchingBracket(final StringReader stream) throws IOException { 903 final StringBuilder sb = new StringBuilder(); 904 int foundBracket = -1; 905 while (foundBracket != 0) { 906 final int read = stream.read(); 907 if (read == -1) { 908 break; 909 } 910 final char c = (char) read; 911 if (c == ']') { 912 foundBracket++; 913 } 914 if (c == '[') { 915 foundBracket--; 916 } 917 if (!(foundBracket == 0)) { 918 sb.append(c); 919 } 920 } 921 return sb.toString(); 922 } 923 924 /** 925 * Resolve calls in the attribute layout and returns the number of backwards calls 926 */ 927 private void resolveCalls() { 928 int backwardsCalls = 0; 929 for (int i = 0; i < attributeLayoutElements.size(); i++) { 930 final AttributeLayoutElement element = attributeLayoutElements.get(i); 931 if (element instanceof Callable) { 932 final Callable callable = (Callable) element; 933 if (i == 0) { 934 callable.setFirstCallable(true); 935 } 936 // Look for calls in the body 937 for (final LayoutElement layoutElement : callable.body) { 938 // Set the callable for each call 939 backwardsCalls += resolveCallsForElement(i, callable, layoutElement); 940 } 941 } 942 } 943 backwardsCallCount = backwardsCalls; 944 } 945 946 private int resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) { 947 int backwardsCalls = 0; 948 if (layoutElement instanceof Call) { 949 final Call call = (Call) layoutElement; 950 int index = call.callableIndex; 951 if (index == 0) { // Calls the parent callable 952 backwardsCalls++; 953 call.setCallable(currentCallable); 954 } else if (index > 0) { // Forwards call 955 for (int k = i + 1; k < attributeLayoutElements.size(); k++) { 956 final AttributeLayoutElement el = attributeLayoutElements.get(k); 957 if (el instanceof Callable) { 958 index--; 959 if (index == 0) { 960 call.setCallable((Callable) el); 961 break; 962 } 963 } 964 } 965 } else { // Backwards call 966 backwardsCalls++; 967 for (int k = i - 1; k >= 0; 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 } 978 } else if (layoutElement instanceof Replication) { 979 final List<LayoutElement> children = ((Replication) layoutElement).layoutElements; 980 for (final LayoutElement child : children) { 981 backwardsCalls += resolveCallsForElement(i, currentCallable, child); 982 } 983 } 984 return backwardsCalls; 985 } 986 987 /** 988 * Once the attribute bands have been read the callables can be informed about the number of times each is subject 989 * to a backwards call. This method is used to set this information. 990 * 991 * @param backwardsCalls one int for each backwards callable, which contains the number of times that callable is 992 * subject to a backwards call. 993 * @throws IOException If an I/O error occurs. 994 */ 995 public void setBackwardsCalls(final int[] backwardsCalls) throws IOException { 996 int index = 0; 997 parseLayout(); 998 for (final AttributeLayoutElement element : attributeLayoutElements) { 999 if (element instanceof Callable && ((Callable) element).isBackwardsCallable()) { 1000 ((Callable) element).addCount(backwardsCalls[index]); 1001 index++; 1002 } 1003 } 1004 } 1005 1006 @Override 1007 public void unpack() throws IOException, Pack200Exception { 1008 1009 } 1010 1011}