NewAttributeBands.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.harmony.pack200;

  18. import java.io.ByteArrayInputStream;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.io.StringReader;
  23. import java.io.UncheckedIOException;
  24. import java.util.ArrayList;
  25. import java.util.Collections;
  26. import java.util.List;
  27. import java.util.Map;

  28. import org.apache.commons.compress.harmony.pack200.AttributeDefinitionBands.AttributeDefinition;
  29. import org.apache.commons.compress.utils.ParsingUtils;
  30. import org.objectweb.asm.Label;

  31. /**
  32.  * Sets of bands relating to a non-predefined attribute that has had a layout definition given to pack200 (e.g. via one of the -C, -M, -F or -D command line
  33.  * options)
  34.  */
  35. public class NewAttributeBands extends BandSet {

  36.     /**
  37.      * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for
  38.      * successive Attributes of this type.
  39.      */
  40.     public interface AttributeLayoutElement {

  41.         void addAttributeToBand(NewAttribute attribute, InputStream inputStream);

  42.         void pack(OutputStream ouputStream) throws IOException, Pack200Exception;

  43.         void renumberBci(IntList bciRenumbering, Map<Label, Integer> labelsToOffsets);

  44.     }

  45.     public class Call extends LayoutElement {

  46.         private final int callableIndex;
  47.         private Callable callable;

  48.         public Call(final int callableIndex) {
  49.             this.callableIndex = callableIndex;
  50.         }

  51.         @Override
  52.         public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
  53.             callable.addAttributeToBand(attribute, inputStream);
  54.             if (callableIndex < 1) {
  55.                 callable.addBackwardsCall();
  56.             }
  57.         }

  58.         public Callable getCallable() {
  59.             return callable;
  60.         }

  61.         public int getCallableIndex() {
  62.             return callableIndex;
  63.         }

  64.         @Override
  65.         public void pack(final OutputStream outputStream) {
  66.             // do nothing here as pack will be called on the callable at another time
  67.         }

  68.         @Override
  69.         public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  70.             // do nothing here as renumberBci will be called on the callable at another time
  71.         }

  72.         public void setCallable(final Callable callable) {
  73.             this.callable = callable;
  74.             if (callableIndex < 1) {
  75.                 callable.setBackwardsCallable();
  76.             }
  77.         }
  78.     }

  79.     public class Callable implements AttributeLayoutElement {

  80.         private final List<LayoutElement> body;

  81.         private boolean isBackwardsCallable;

  82.         private int backwardsCallableIndex;

  83.         public Callable(final List<LayoutElement> body) {
  84.             this.body = body;
  85.         }

  86.         @Override
  87.         public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
  88.             for (final AttributeLayoutElement element : body) {
  89.                 element.addAttributeToBand(attribute, inputStream);
  90.             }
  91.         }

  92.         public void addBackwardsCall() {
  93.             backwardsCallCounts[backwardsCallableIndex]++;
  94.         }

  95.         public List<LayoutElement> getBody() {
  96.             return body;
  97.         }

  98.         public boolean isBackwardsCallable() {
  99.             return isBackwardsCallable;
  100.         }

  101.         @Override
  102.         public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
  103.             for (final AttributeLayoutElement element : body) {
  104.                 element.pack(outputStream);
  105.             }
  106.         }

  107.         @Override
  108.         public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  109.             for (final AttributeLayoutElement element : body) {
  110.                 element.renumberBci(bciRenumbering, labelsToOffsets);
  111.             }
  112.         }

  113.         /**
  114.          * Tells this Callable that it is a backwards callable
  115.          */
  116.         public void setBackwardsCallable() {
  117.             this.isBackwardsCallable = true;
  118.         }

  119.         public void setBackwardsCallableIndex(final int backwardsCallableIndex) {
  120.             this.backwardsCallableIndex = backwardsCallableIndex;
  121.         }
  122.     }

  123.     public class Integral extends LayoutElement {

  124.         private final String tag;

  125.         private final List band = new ArrayList();
  126.         private final BHSDCodec defaultCodec;

  127.         // used for bytecode offsets (OH and POH)
  128.         private Integral previousIntegral;
  129.         private int previousPValue;

  130.         public Integral(final String tag) {
  131.             this.tag = tag;
  132.             this.defaultCodec = getCodec(tag);
  133.         }

  134.         public Integral(final String tag, final Integral previousIntegral) {
  135.             this.tag = tag;
  136.             this.defaultCodec = getCodec(tag);
  137.             this.previousIntegral = previousIntegral;
  138.         }

  139.         @Override
  140.         public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
  141.             Object val = null;
  142.             int value = 0;
  143.             if (tag.equals("B") || tag.equals("FB")) {
  144.                 value = readInteger(1, inputStream) & 0xFF; // unsigned byte
  145.             } else if (tag.equals("SB")) {
  146.                 value = readInteger(1, inputStream);
  147.             } else if (tag.equals("H") || tag.equals("FH")) {
  148.                 value = readInteger(2, inputStream) & 0xFFFF; // unsigned short
  149.             } else if (tag.equals("SH")) {
  150.                 value = readInteger(2, inputStream);
  151.             } else if (tag.equals("I") || tag.equals("FI") || tag.equals("SI")) {
  152.                 value = readInteger(4, inputStream);
  153.             } else if (tag.equals("V") || tag.equals("FV") || tag.equals("SV")) {
  154.                 // Not currently supported
  155.             } else if (tag.startsWith("PO") || tag.startsWith("OS")) {
  156.                 final char uint_type = tag.substring(2).toCharArray()[0];
  157.                 final int length = getLength(uint_type);
  158.                 value = readInteger(length, inputStream);
  159.                 value += previousIntegral.previousPValue;
  160.                 val = attribute.getLabel(value);
  161.                 previousPValue = value;
  162.             } else if (tag.startsWith("P")) {
  163.                 final char uint_type = tag.substring(1).toCharArray()[0];
  164.                 final int length = getLength(uint_type);
  165.                 value = readInteger(length, inputStream);
  166.                 val = attribute.getLabel(value);
  167.                 previousPValue = value;
  168.             } else if (tag.startsWith("O")) {
  169.                 final char uint_type = tag.substring(1).toCharArray()[0];
  170.                 final int length = getLength(uint_type);
  171.                 value = readInteger(length, inputStream);
  172.                 value += previousIntegral.previousPValue;
  173.                 val = attribute.getLabel(value);
  174.                 previousPValue = value;
  175.             }
  176.             if (val == null) {
  177.                 val = Integer.valueOf(value);
  178.             }
  179.             band.add(val);
  180.         }

  181.         public String getTag() {
  182.             return tag;
  183.         }

  184.         public int latestValue() {
  185.             return ((Integer) band.get(band.size() - 1)).intValue();
  186.         }

  187.         @Override
  188.         public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
  189.             PackingUtils.log("Writing new attribute bands...");
  190.             final byte[] encodedBand = encodeBandInt(tag, integerListToArray(band), defaultCodec);
  191.             outputStream.write(encodedBand);
  192.             PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + tag + "[" + band.size() + "]");
  193.         }

  194.         @Override
  195.         public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  196.             if (tag.startsWith("O") || tag.startsWith("PO")) {
  197.                 renumberOffsetBci(previousIntegral.band, bciRenumbering, labelsToOffsets);
  198.             } else if (tag.startsWith("P")) {
  199.                 for (int i = band.size() - 1; i >= 0; i--) {
  200.                     final Object label = band.get(i);
  201.                     if (label instanceof Integer) {
  202.                         break;
  203.                     }
  204.                     if (label instanceof Label) {
  205.                         band.remove(i);
  206.                         final Integer bytecodeIndex = labelsToOffsets.get(label);
  207.                         band.add(i, Integer.valueOf(bciRenumbering.get(bytecodeIndex.intValue())));
  208.                     }
  209.                 }
  210.             }
  211.         }

  212.         private void renumberOffsetBci(final List relative, final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  213.             for (int i = band.size() - 1; i >= 0; i--) {
  214.                 final Object label = band.get(i);
  215.                 if (label instanceof Integer) {
  216.                     break;
  217.                 }
  218.                 if (label instanceof Label) {
  219.                     band.remove(i);
  220.                     final Integer bytecodeIndex = labelsToOffsets.get(label);
  221.                     final Integer renumberedOffset = Integer.valueOf(bciRenumbering.get(bytecodeIndex.intValue()) - ((Integer) relative.get(i)).intValue());
  222.                     band.add(i, renumberedOffset);
  223.                 }
  224.             }
  225.         }

  226.     }

  227.     public abstract class LayoutElement implements AttributeLayoutElement {

  228.         protected int getLength(final char uint_type) {
  229.             int length = 0;
  230.             switch (uint_type) {
  231.             case 'B':
  232.                 length = 1;
  233.                 break;
  234.             case 'H':
  235.                 length = 2;
  236.                 break;
  237.             case 'I':
  238.                 length = 4;
  239.                 break;
  240.             case 'V':
  241.                 length = 0;
  242.                 break;
  243.             }
  244.             return length;
  245.         }
  246.     }

  247.     /**
  248.      * Constant Pool Reference
  249.      */
  250.     public class Reference extends LayoutElement {

  251.         private final String tag;

  252.         private final List<ConstantPoolEntry> band = new ArrayList<>();

  253.         private final boolean nullsAllowed;

  254.         public Reference(final String tag) {
  255.             this.tag = tag;
  256.             nullsAllowed = tag.indexOf('N') != -1;
  257.         }

  258.         @Override
  259.         public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
  260.             final int index = readInteger(4, inputStream);
  261.             if (tag.startsWith("RC")) { // Class
  262.                 band.add(cpBands.getCPClass(attribute.readClass(index)));
  263.             } else if (tag.startsWith("RU")) { // UTF8 String
  264.                 band.add(cpBands.getCPUtf8(attribute.readUTF8(index)));
  265.             } else if (tag.startsWith("RS")) { // Signature
  266.                 band.add(cpBands.getCPSignature(attribute.readUTF8(index)));
  267.             } else { // Constant
  268.                 band.add(cpBands.getConstant(attribute.readConst(index)));
  269.             }
  270.             // TODO method and field references
  271.         }

  272.         public String getTag() {
  273.             return tag;
  274.         }

  275.         @Override
  276.         public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
  277.             int[] ints;
  278.             if (nullsAllowed) {
  279.                 ints = cpEntryOrNullListToArray(band);
  280.             } else {
  281.                 ints = cpEntryListToArray(band);
  282.             }
  283.             final byte[] encodedBand = encodeBandInt(tag, ints, Codec.UNSIGNED5);
  284.             outputStream.write(encodedBand);
  285.             PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + tag + "[" + ints.length + "]");
  286.         }

  287.         @Override
  288.         public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  289.             // nothing to do here
  290.         }

  291.     }

  292.     /**
  293.      * A replication is an array of layout elements, with an associated count
  294.      */
  295.     public class Replication extends LayoutElement {

  296.         private final Integral countElement;

  297.         private final List<LayoutElement> layoutElements = new ArrayList<>();

  298.         public Replication(final String tag, final String contents) throws IOException {
  299.             this.countElement = new Integral(tag);
  300.             final StringReader stream = new StringReader(contents);
  301.             LayoutElement e;
  302.             while ((e = readNextLayoutElement(stream)) != null) {
  303.                 layoutElements.add(e);
  304.             }
  305.         }

  306.         @Override
  307.         public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
  308.             countElement.addAttributeToBand(attribute, inputStream);
  309.             final int count = countElement.latestValue();
  310.             for (int i = 0; i < count; i++) {
  311.                 for (final AttributeLayoutElement layoutElement : layoutElements) {
  312.                     layoutElement.addAttributeToBand(attribute, inputStream);
  313.                 }
  314.             }
  315.         }

  316.         public Integral getCountElement() {
  317.             return countElement;
  318.         }

  319.         public List<LayoutElement> getLayoutElements() {
  320.             return layoutElements;
  321.         }

  322.         @Override
  323.         public void pack(final OutputStream out) throws IOException, Pack200Exception {
  324.             countElement.pack(out);
  325.             for (final AttributeLayoutElement layoutElement : layoutElements) {
  326.                 layoutElement.pack(out);
  327.             }
  328.         }

  329.         @Override
  330.         public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  331.             for (final AttributeLayoutElement layoutElement : layoutElements) {
  332.                 layoutElement.renumberBci(bciRenumbering, labelsToOffsets);
  333.             }
  334.         }
  335.     }

  336.     /**
  337.      * A Union is a type of layout element where the tag value acts as a selector for one of the union cases
  338.      */
  339.     public class Union extends LayoutElement {

  340.         private final Integral unionTag;
  341.         private final List<UnionCase> unionCases;
  342.         private final List<LayoutElement> defaultCaseBody;

  343.         public Union(final String tag, final List<UnionCase> unionCases, final List<LayoutElement> body) {
  344.             this.unionTag = new Integral(tag);
  345.             this.unionCases = unionCases;
  346.             this.defaultCaseBody = body;
  347.         }

  348.         @Override
  349.         public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
  350.             unionTag.addAttributeToBand(attribute, inputStream);
  351.             final long tag = unionTag.latestValue();
  352.             boolean defaultCase = true;
  353.             for (final UnionCase unionCase : unionCases) {
  354.                 if (unionCase.hasTag(tag)) {
  355.                     defaultCase = false;
  356.                     unionCase.addAttributeToBand(attribute, inputStream);
  357.                 }
  358.             }
  359.             if (defaultCase) {
  360.                 for (final LayoutElement layoutElement : defaultCaseBody) {
  361.                     layoutElement.addAttributeToBand(attribute, inputStream);
  362.                 }
  363.             }
  364.         }

  365.         public List<LayoutElement> getDefaultCaseBody() {
  366.             return defaultCaseBody;
  367.         }

  368.         public List<UnionCase> getUnionCases() {
  369.             return unionCases;
  370.         }

  371.         public Integral getUnionTag() {
  372.             return unionTag;
  373.         }

  374.         @Override
  375.         public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
  376.             unionTag.pack(outputStream);
  377.             for (final UnionCase unionCase : unionCases) {
  378.                 unionCase.pack(outputStream);
  379.             }
  380.             for (final LayoutElement element : defaultCaseBody) {
  381.                 element.pack(outputStream);
  382.             }
  383.         }

  384.         @Override
  385.         public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  386.             for (final UnionCase unionCase : unionCases) {
  387.                 unionCase.renumberBci(bciRenumbering, labelsToOffsets);
  388.             }
  389.             for (final LayoutElement element : defaultCaseBody) {
  390.                 element.renumberBci(bciRenumbering, labelsToOffsets);
  391.             }
  392.         }
  393.     }

  394.     /**
  395.      * A Union case
  396.      */
  397.     public class UnionCase extends LayoutElement {

  398.         private final List<LayoutElement> body;

  399.         private final List<Integer> tags;

  400.         public UnionCase(final List<Integer> tags) {
  401.             this.tags = tags;
  402.             this.body = Collections.EMPTY_LIST;
  403.         }

  404.         public UnionCase(final List<Integer> tags, final List<LayoutElement> body) {
  405.             this.tags = tags;
  406.             this.body = body;
  407.         }

  408.         @Override
  409.         public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
  410.             for (final LayoutElement element : body) {
  411.                 element.addAttributeToBand(attribute, inputStream);
  412.             }
  413.         }

  414.         public List<LayoutElement> getBody() {
  415.             return body;
  416.         }

  417.         public boolean hasTag(final long l) {
  418.             return tags.contains(Integer.valueOf((int) l));
  419.         }

  420.         @Override
  421.         public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
  422.             for (final LayoutElement element : body) {
  423.                 element.pack(outputStream);
  424.             }
  425.         }

  426.         @Override
  427.         public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  428.             for (final LayoutElement element : body) {
  429.                 element.renumberBci(bciRenumbering, labelsToOffsets);
  430.             }
  431.         }
  432.     }

  433.     protected List<AttributeLayoutElement> attributeLayoutElements;

  434.     private int[] backwardsCallCounts;

  435.     private final CpBands cpBands;

  436.     private final AttributeDefinition def;

  437.     private boolean usedAtLeastOnce;

  438.     // used when parsing
  439.     private Integral lastPIntegral;

  440.     public NewAttributeBands(final int effort, final CpBands cpBands, final SegmentHeader header, final AttributeDefinition def) throws IOException {
  441.         super(effort, header);
  442.         this.def = def;
  443.         this.cpBands = cpBands;
  444.         parseLayout();
  445.     }

  446.     public void addAttribute(final NewAttribute attribute) {
  447.         usedAtLeastOnce = true;
  448.         final InputStream stream = new ByteArrayInputStream(attribute.getBytes());
  449.         for (final AttributeLayoutElement attributeLayoutElement : attributeLayoutElements) {
  450.             attributeLayoutElement.addAttributeToBand(attribute, stream);
  451.         }
  452.     }

  453.     public String getAttributeName() {
  454.         return def.name.getUnderlyingString();
  455.     }

  456.     /**
  457.      * Returns the {@link BHSDCodec} that should be used for the given layout element
  458.      *
  459.      * @param layoutElement
  460.      */
  461.     private BHSDCodec getCodec(final String layoutElement) {
  462.         if (layoutElement.indexOf('O') >= 0) {
  463.             return Codec.BRANCH5;
  464.         }
  465.         if (layoutElement.indexOf('P') >= 0) {
  466.             return Codec.BCI5;
  467.         }
  468.         if (layoutElement.indexOf('S') >= 0 && !layoutElement.contains("KS") //$NON-NLS-1$
  469.                 && !layoutElement.contains("RS")) { //$NON-NLS-1$
  470.             return Codec.SIGNED5;
  471.         }
  472.         if (layoutElement.indexOf('B') >= 0) {
  473.             return Codec.BYTE1;
  474.         }
  475.         return Codec.UNSIGNED5;
  476.     }

  477.     public int getFlagIndex() {
  478.         return def.index;
  479.     }

  480.     /**
  481.      * Utility method to get the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]})
  482.      *
  483.      * @param reader
  484.      * @return
  485.      * @throws IOException If an I/O error occurs.
  486.      */
  487.     private StringReader getStreamUpToMatchingBracket(final StringReader reader) throws IOException {
  488.         final StringBuilder sb = new StringBuilder();
  489.         int foundBracket = -1;
  490.         while (foundBracket != 0) {
  491.             final int read = reader.read();
  492.             if (read == -1) {
  493.                 break;
  494.             }
  495.             final char c = (char) read;
  496.             if (c == ']') {
  497.                 foundBracket++;
  498.             }
  499.             if (c == '[') {
  500.                 foundBracket--;
  501.             }
  502.             if (!(foundBracket == 0)) {
  503.                 sb.append(c);
  504.             }
  505.         }
  506.         return new StringReader(sb.toString());
  507.     }

  508.     public boolean isUsedAtLeastOnce() {
  509.         return usedAtLeastOnce;
  510.     }

  511.     public int[] numBackwardsCalls() {
  512.         return backwardsCallCounts;
  513.     }

  514.     @Override
  515.     public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
  516.         for (final AttributeLayoutElement attributeLayoutElement : attributeLayoutElements) {
  517.             attributeLayoutElement.pack(outputStream);
  518.         }
  519.     }

  520.     private void parseLayout() throws IOException {
  521.         final String layout = def.layout.getUnderlyingString();
  522.         if (attributeLayoutElements == null) {
  523.             attributeLayoutElements = new ArrayList<>();
  524.             final StringReader reader = new StringReader(layout);
  525.             AttributeLayoutElement e;
  526.             while ((e = readNextAttributeElement(reader)) != null) {
  527.                 attributeLayoutElements.add(e);
  528.             }
  529.             resolveCalls();
  530.         }
  531.     }

  532.     /**
  533.      * Read a 'body' section of the layout from the given stream
  534.      *
  535.      * @param reader
  536.      * @return List of LayoutElements
  537.      * @throws IOException If an I/O error occurs.
  538.      */
  539.     private List<LayoutElement> readBody(final StringReader reader) throws IOException {
  540.         final List<LayoutElement> layoutElements = new ArrayList<>();
  541.         LayoutElement e;
  542.         while ((e = readNextLayoutElement(reader)) != null) {
  543.             layoutElements.add(e);
  544.         }
  545.         return layoutElements;
  546.     }

  547.     private int readInteger(final int i, final InputStream inputStream) {
  548.         int result = 0;
  549.         for (int j = 0; j < i; j++) {
  550.             try {
  551.                 result = result << 8 | inputStream.read();
  552.             } catch (final IOException e) {
  553.                 throw new UncheckedIOException("Error reading unknown attribute", e);
  554.             }
  555.         }
  556.         // use casting to preserve sign
  557.         if (i == 1) {
  558.             result = (byte) result;
  559.         }
  560.         if (i == 2) {
  561.             result = (short) result;
  562.         }
  563.         return result;
  564.     }

  565.     private AttributeLayoutElement readNextAttributeElement(final StringReader reader) throws IOException {
  566.         reader.mark(1);
  567.         final int next = reader.read();
  568.         if (next == -1) {
  569.             return null;
  570.         }
  571.         if (next == '[') {
  572.             return new Callable(readBody(getStreamUpToMatchingBracket(reader)));
  573.         }
  574.         reader.reset();
  575.         return readNextLayoutElement(reader);
  576.     }

  577.     private LayoutElement readNextLayoutElement(final StringReader reader) throws IOException {
  578.         final int nextChar = reader.read();
  579.         if (nextChar == -1) {
  580.             return null;
  581.         }

  582.         switch (nextChar) {
  583.         // Integrals
  584.         case 'B':
  585.         case 'H':
  586.         case 'I':
  587.         case 'V':
  588.             return new Integral(new String(new char[] { (char) nextChar }));
  589.         case 'S':
  590.         case 'F':
  591.             return new Integral(new String(new char[] { (char) nextChar, (char) reader.read() }));
  592.         case 'P':
  593.             reader.mark(1);
  594.             if (reader.read() != 'O') {
  595.                 reader.reset();
  596.                 lastPIntegral = new Integral("P" + (char) reader.read());
  597.                 return lastPIntegral;
  598.             }
  599.             lastPIntegral = new Integral("PO" + (char) reader.read(), lastPIntegral);
  600.             return lastPIntegral;
  601.         case 'O':
  602.             reader.mark(1);
  603.             if (reader.read() != 'S') {
  604.                 reader.reset();
  605.                 return new Integral("O" + (char) reader.read(), lastPIntegral);
  606.             }
  607.             return new Integral("OS" + (char) reader.read(), lastPIntegral);

  608.         // Replication
  609.         case 'N':
  610.             final char uint_type = (char) reader.read();
  611.             reader.read(); // '['
  612.             final String str = readUpToMatchingBracket(reader);
  613.             return new Replication("" + uint_type, str);

  614.         // Union
  615.         case 'T':
  616.             String int_type = String.valueOf((char) reader.read());
  617.             if (int_type.equals("S")) {
  618.                 int_type += (char) reader.read();
  619.             }
  620.             final List<UnionCase> unionCases = new ArrayList<>();
  621.             UnionCase c;
  622.             while ((c = readNextUnionCase(reader)) != null) {
  623.                 unionCases.add(c);
  624.             }
  625.             reader.read(); // '('
  626.             reader.read(); // ')'
  627.             reader.read(); // '['
  628.             List<LayoutElement> body = null;
  629.             reader.mark(1);
  630.             final char next = (char) reader.read();
  631.             if (next != ']') {
  632.                 reader.reset();
  633.                 body = readBody(getStreamUpToMatchingBracket(reader));
  634.             }
  635.             return new Union(int_type, unionCases, body);

  636.         // Call
  637.         case '(':
  638.             final int number = readNumber(reader).intValue();
  639.             reader.read(); // ')'
  640.             return new Call(number);
  641.         // Reference
  642.         case 'K':
  643.         case 'R':
  644.             final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) reader.read());
  645.             final char nxt = (char) reader.read();
  646.             string.append(nxt);
  647.             if (nxt == 'N') {
  648.                 string.append((char) reader.read());
  649.             }
  650.             return new Reference(string.toString());
  651.         }
  652.         return null;
  653.     }

  654.     /**
  655.      * Read a UnionCase from the stream
  656.      *
  657.      * @param reader
  658.      * @return
  659.      * @throws IOException If an I/O error occurs.
  660.      */
  661.     private UnionCase readNextUnionCase(final StringReader reader) throws IOException {
  662.         reader.mark(2);
  663.         reader.read(); // '('
  664.         final int next = reader.read();
  665.         char ch = (char) next;
  666.         if (ch == ')' || next == -1) {
  667.             reader.reset();
  668.             return null;
  669.         }
  670.         reader.reset();
  671.         reader.read(); // '('
  672.         final List<Integer> tags = new ArrayList<>();
  673.         Integer nextTag;
  674.         do {
  675.             nextTag = readNumber(reader);
  676.             if (nextTag != null) {
  677.                 tags.add(nextTag);
  678.                 reader.read(); // ',' or ')'
  679.             }
  680.         } while (nextTag != null);
  681.         reader.read(); // '['
  682.         reader.mark(1);
  683.         ch = (char) reader.read();
  684.         if (ch == ']') {
  685.             return new UnionCase(tags);
  686.         }
  687.         reader.reset();
  688.         return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(reader)));
  689.     }

  690.     /**
  691.      * Read a number from the stream and return it
  692.      *
  693.      * @param stream
  694.      * @return
  695.      * @throws IOException If an I/O error occurs.
  696.      */
  697.     private Integer readNumber(final StringReader stream) throws IOException {
  698.         stream.mark(1);
  699.         final char first = (char) stream.read();
  700.         final boolean negative = first == '-';
  701.         if (!negative) {
  702.             stream.reset();
  703.         }
  704.         stream.mark(100);
  705.         int i;
  706.         int length = 0;
  707.         while ((i = stream.read()) != -1 && Character.isDigit((char) i)) {
  708.             length++;
  709.         }
  710.         stream.reset();
  711.         if (length == 0) {
  712.             return null;
  713.         }
  714.         final char[] digits = new char[length];
  715.         final int read = stream.read(digits);
  716.         if (read != digits.length) {
  717.             throw new IOException("Error reading from the input stream");
  718.         }
  719.         return ParsingUtils.parseIntValue((negative ? "-" : "") + new String(digits));
  720.     }

  721.     /**
  722.      * Utility method to get the contents of the given stream, up to the next ']', (ignoring pairs of brackets '[' and ']')
  723.      *
  724.      * @param reader
  725.      * @return
  726.      * @throws IOException If an I/O error occurs.
  727.      */
  728.     private String readUpToMatchingBracket(final StringReader reader) throws IOException {
  729.         final StringBuilder sb = new StringBuilder();
  730.         int foundBracket = -1;
  731.         while (foundBracket != 0) {
  732.             final int read = reader.read();
  733.             if (read == -1) {
  734.                 break;
  735.             }
  736.             final char c = (char) read;
  737.             if (c == ']') {
  738.                 foundBracket++;
  739.             }
  740.             if (c == '[') {
  741.                 foundBracket--;
  742.             }
  743.             if (!(foundBracket == 0)) {
  744.                 sb.append(c);
  745.             }
  746.         }
  747.         return sb.toString();
  748.     }

  749.     /**
  750.      * Renumber any bytecode indexes or offsets as described in section 5.5.2 of the pack200 specification
  751.      *
  752.      * @param bciRenumbering  TODO
  753.      * @param labelsToOffsets TODO
  754.      */
  755.     public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
  756.         for (final AttributeLayoutElement attributeLayoutElement : attributeLayoutElements) {
  757.             attributeLayoutElement.renumberBci(bciRenumbering, labelsToOffsets);
  758.         }
  759.     }

  760.     /**
  761.      * Resolve calls in the attribute layout and returns the number of backwards callables
  762.      *
  763.      * @param tokens   the attribute layout as a List of AttributeElements
  764.      */
  765.     private void resolveCalls() {
  766.         for (int i = 0; i < attributeLayoutElements.size(); i++) {
  767.             final AttributeLayoutElement element = attributeLayoutElements.get(i);
  768.             if (element instanceof Callable) {
  769.                 final Callable callable = (Callable) element;
  770.                 final List<LayoutElement> body = callable.body; // Look for calls in the body
  771.                 for (final LayoutElement layoutElement : body) {
  772.                     // Set the callable for each call
  773.                     resolveCallsForElement(i, callable, layoutElement);
  774.                 }
  775.             }
  776.         }
  777.         int backwardsCallableIndex = 0;
  778.         for (final AttributeLayoutElement attributeLayoutElement : attributeLayoutElements) {
  779.             if (attributeLayoutElement instanceof Callable) {
  780.                 final Callable callable = (Callable) attributeLayoutElement;
  781.                 if (callable.isBackwardsCallable) {
  782.                     callable.setBackwardsCallableIndex(backwardsCallableIndex);
  783.                     backwardsCallableIndex++;
  784.                 }
  785.             }
  786.         }
  787.         backwardsCallCounts = new int[backwardsCallableIndex];
  788.     }

  789.     private void resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) {
  790.         if (layoutElement instanceof Call) {
  791.             final Call call = (Call) layoutElement;
  792.             int index = call.callableIndex;
  793.             if (index == 0) { // Calls the parent callable
  794.                 call.setCallable(currentCallable);
  795.             } else if (index > 0) { // Forwards call
  796.                 for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
  797.                     final AttributeLayoutElement el = attributeLayoutElements.get(k);
  798.                     if (el instanceof Callable) {
  799.                         index--;
  800.                         if (index == 0) {
  801.                             call.setCallable((Callable) el);
  802.                             break;
  803.                         }
  804.                     }
  805.                 }
  806.             } else { // Backwards call
  807.                 for (int k = i - 1; k >= 0; k--) {
  808.                     final AttributeLayoutElement el = attributeLayoutElements.get(k);
  809.                     if (el instanceof Callable) {
  810.                         index++;
  811.                         if (index == 0) {
  812.                             call.setCallable((Callable) el);
  813.                             break;
  814.                         }
  815.                     }
  816.                 }
  817.             }
  818.         } else if (layoutElement instanceof Replication) {
  819.             final List<LayoutElement> children = ((Replication) layoutElement).layoutElements;
  820.             for (final LayoutElement child : children) {
  821.                 resolveCallsForElement(i, currentCallable, child);
  822.             }
  823.         }
  824.     }

  825. }