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.unpack200;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.StringReader;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.List;

  24. import org.apache.commons.compress.harmony.pack200.BHSDCodec;
  25. import org.apache.commons.compress.harmony.pack200.Codec;
  26. import org.apache.commons.compress.harmony.pack200.Pack200Exception;
  27. import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
  28. import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
  29. import org.apache.commons.compress.harmony.unpack200.bytecode.CPDouble;
  30. import org.apache.commons.compress.harmony.unpack200.bytecode.CPFieldRef;
  31. import org.apache.commons.compress.harmony.unpack200.bytecode.CPFloat;
  32. import org.apache.commons.compress.harmony.unpack200.bytecode.CPInteger;
  33. import org.apache.commons.compress.harmony.unpack200.bytecode.CPInterfaceMethodRef;
  34. import org.apache.commons.compress.harmony.unpack200.bytecode.CPLong;
  35. import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethodRef;
  36. import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType;
  37. import org.apache.commons.compress.harmony.unpack200.bytecode.CPString;
  38. import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
  39. import org.apache.commons.compress.harmony.unpack200.bytecode.NewAttribute;
  40. import org.apache.commons.compress.utils.ParsingUtils;

  41. /**
  42.  * Sets of bands relating to a non-predefined attribute
  43.  */
  44. public class NewAttributeBands extends BandSet {

  45.     /**
  46.      * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for
  47.      * successive Attributes of this type.
  48.      */
  49.     private interface AttributeLayoutElement {

  50.         /**
  51.          * Adds the band data for this element at the given index to the attribute.
  52.          *
  53.          * @param index     Index position to add the attribute.
  54.          * @param attribute The attribute to add.
  55.          */
  56.         void addToAttribute(int index, NewAttribute attribute);

  57.         /**
  58.          * Read the bands associated with this part of the layout.
  59.          *
  60.          * @param in    TODO
  61.          * @param count TODO
  62.          * @throws Pack200Exception Bad archive.
  63.          * @throws IOException      If an I/O error occurs.
  64.          */
  65.         void readBands(InputStream in, int count) throws IOException, Pack200Exception;

  66.     }

  67.     public class Call extends LayoutElement {

  68.         private final int callableIndex;
  69.         private Callable callable;

  70.         public Call(final int callableIndex) {
  71.             this.callableIndex = callableIndex;
  72.         }

  73.         @Override
  74.         public void addToAttribute(final int n, final NewAttribute attribute) {
  75.             callable.addNextToAttribute(attribute);
  76.         }

  77.         public Callable getCallable() {
  78.             return callable;
  79.         }

  80.         public int getCallableIndex() {
  81.             return callableIndex;
  82.         }

  83.         @Override
  84.         public void readBands(final InputStream in, final int count) {
  85.             /*
  86.              * 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
  87.              * transmitted directly in the attribute bands and so it is added later.
  88.              */
  89.             if (callableIndex > 0) {
  90.                 callable.addCount(count);
  91.             }
  92.         }

  93.         public void setCallable(final Callable callable) {
  94.             this.callable = callable;
  95.             if (callableIndex < 1) {
  96.                 callable.setBackwardsCallable();
  97.             }
  98.         }
  99.     }

  100.     public static class Callable implements AttributeLayoutElement {

  101.         private final List<LayoutElement> body;

  102.         private boolean isBackwardsCallable;

  103.         private boolean isFirstCallable;

  104.         private int count;

  105.         private int index;

  106.         public Callable(final List<LayoutElement> body) {
  107.             this.body = body;
  108.         }

  109.         /**
  110.          * Adds the count of a call to this callable (ie the number of calls)
  111.          *
  112.          * @param count TODO
  113.          */
  114.         public void addCount(final int count) {
  115.             this.count += count;
  116.         }

  117.         /**
  118.          * Used by calls when adding band contents to attributes, so they don't have to keep track of the internal index of the callable.
  119.          *
  120.          * @param attribute TODO
  121.          */
  122.         public void addNextToAttribute(final NewAttribute attribute) {
  123.             for (final LayoutElement element : body) {
  124.                 element.addToAttribute(index, attribute);
  125.             }
  126.             index++;
  127.         }

  128.         @Override
  129.         public void addToAttribute(final int n, final NewAttribute attribute) {
  130.             if (isFirstCallable) {
  131.                 // Ignore n because bands also contain element parts from calls
  132.                 for (final LayoutElement element : body) {
  133.                     element.addToAttribute(index, attribute);
  134.                 }
  135.                 index++;
  136.             }
  137.         }

  138.         public List<LayoutElement> getBody() {
  139.             return body;
  140.         }

  141.         public boolean isBackwardsCallable() {
  142.             return isBackwardsCallable;
  143.         }

  144.         @Override
  145.         public void readBands(final InputStream in, int count) throws IOException, Pack200Exception {
  146.             if (isFirstCallable) {
  147.                 count += this.count;
  148.             } else {
  149.                 count = this.count;
  150.             }
  151.             for (final LayoutElement element : body) {
  152.                 element.readBands(in, count);
  153.             }
  154.         }

  155.         /**
  156.          * Tells this Callable that it is a backwards callable
  157.          */
  158.         public void setBackwardsCallable() {
  159.             this.isBackwardsCallable = true;
  160.         }

  161.         public void setFirstCallable(final boolean isFirstCallable) {
  162.             this.isFirstCallable = isFirstCallable;
  163.         }
  164.     }

  165.     public class Integral extends LayoutElement {

  166.         private final String tag;

  167.         private int[] band;

  168.         public Integral(final String tag) {
  169.             this.tag = tag;
  170.         }

  171.         @Override
  172.         public void addToAttribute(final int n, final NewAttribute attribute) {
  173.             int value = band[n];
  174.             if (tag.equals("B") || tag.equals("FB")) {
  175.                 attribute.addInteger(1, value);
  176.             } else if (tag.equals("SB")) {
  177.                 attribute.addInteger(1, (byte) value);
  178.             } else if (tag.equals("H") || tag.equals("FH")) {
  179.                 attribute.addInteger(2, value);
  180.             } else if (tag.equals("SH")) {
  181.                 attribute.addInteger(2, (short) value);
  182.             } else if (tag.equals("I") || tag.equals("FI") || tag.equals("SI")) {
  183.                 attribute.addInteger(4, value);
  184.             } else if (tag.equals("V") || tag.equals("FV") || tag.equals("SV")) {
  185.                 // Don't add V's - they shouldn't be written out to the class
  186.                 // file
  187.             } else if (tag.startsWith("PO")) {
  188.                 final char uintType = tag.substring(2).toCharArray()[0];
  189.                 final int length = getLength(uintType);
  190.                 attribute.addBCOffset(length, value);
  191.             } else if (tag.startsWith("P")) {
  192.                 final char uintType = tag.substring(1).toCharArray()[0];
  193.                 final int length = getLength(uintType);
  194.                 attribute.addBCIndex(length, value);
  195.             } else if (tag.startsWith("OS")) {
  196.                 final char uintType = tag.substring(2).toCharArray()[0];
  197.                 final int length = getLength(uintType);
  198.                 switch (length) {
  199.                 case 1:
  200.                     value = (byte) value;
  201.                     break;
  202.                 case 2:
  203.                     value = (short) value;
  204.                     break;
  205.                 case 4:
  206.                     value = value;
  207.                     break;
  208.                 default:
  209.                     break;
  210.                 }
  211.                 attribute.addBCLength(length, value);
  212.             } else if (tag.startsWith("O")) {
  213.                 final char uintType = tag.substring(1).toCharArray()[0];
  214.                 final int length = getLength(uintType);
  215.                 attribute.addBCLength(length, value);
  216.             }
  217.         }

  218.         public String getTag() {
  219.             return tag;
  220.         }

  221.         int getValue(final int index) {
  222.             return band[index];
  223.         }

  224.         @Override
  225.         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
  226.             band = decodeBandInt(attributeLayout.getName() + "_" + tag, in, getCodec(tag), count);
  227.         }

  228.     }

  229.     private abstract static class LayoutElement implements AttributeLayoutElement {

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

  249.     /**
  250.      * Constant Pool Reference
  251.      */
  252.     public class Reference extends LayoutElement {

  253.         private final String tag;

  254.         private Object band;

  255.         private final int length;

  256.         public Reference(final String tag) {
  257.             this.tag = tag;
  258.             length = getLength(tag.charAt(tag.length() - 1));
  259.         }

  260.         @Override
  261.         public void addToAttribute(final int n, final NewAttribute attribute) {
  262.             if (tag.startsWith("KI")) { // Integer
  263.                 attribute.addToBody(length, ((CPInteger[]) band)[n]);
  264.             } else if (tag.startsWith("KJ")) { // Long
  265.                 attribute.addToBody(length, ((CPLong[]) band)[n]);
  266.             } else if (tag.startsWith("KF")) { // Float
  267.                 attribute.addToBody(length, ((CPFloat[]) band)[n]);
  268.             } else if (tag.startsWith("KD")) { // Double
  269.                 attribute.addToBody(length, ((CPDouble[]) band)[n]);
  270.             } else if (tag.startsWith("KS")) { // String
  271.                 attribute.addToBody(length, ((CPString[]) band)[n]);
  272.             } else if (tag.startsWith("RC")) { // Class
  273.                 attribute.addToBody(length, ((CPClass[]) band)[n]);
  274.             } else if (tag.startsWith("RS")) { // Signature
  275.                 attribute.addToBody(length, ((CPUTF8[]) band)[n]);
  276.             } else if (tag.startsWith("RD")) { // Descriptor
  277.                 attribute.addToBody(length, ((CPNameAndType[]) band)[n]);
  278.             } else if (tag.startsWith("RF")) { // Field Reference
  279.                 attribute.addToBody(length, ((CPFieldRef[]) band)[n]);
  280.             } else if (tag.startsWith("RM")) { // Method Reference
  281.                 attribute.addToBody(length, ((CPMethodRef[]) band)[n]);
  282.             } else if (tag.startsWith("RI")) { // Interface Method Reference
  283.                 attribute.addToBody(length, ((CPInterfaceMethodRef[]) band)[n]);
  284.             } else if (tag.startsWith("RU")) { // UTF8 String
  285.                 attribute.addToBody(length, ((CPUTF8[]) band)[n]);
  286.             }
  287.         }

  288.         public String getTag() {
  289.             return tag;
  290.         }

  291.         @Override
  292.         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
  293.             if (tag.startsWith("KI")) { // Integer
  294.                 band = parseCPIntReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  295.             } else if (tag.startsWith("KJ")) { // Long
  296.                 band = parseCPLongReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  297.             } else if (tag.startsWith("KF")) { // Float
  298.                 band = parseCPFloatReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  299.             } else if (tag.startsWith("KD")) { // Double
  300.                 band = parseCPDoubleReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  301.             } else if (tag.startsWith("KS")) { // String
  302.                 band = parseCPStringReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  303.             } else if (tag.startsWith("RC")) { // Class
  304.                 band = parseCPClassReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  305.             } else if (tag.startsWith("RS")) { // Signature
  306.                 band = parseCPSignatureReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  307.             } else if (tag.startsWith("RD")) { // Descriptor
  308.                 band = parseCPDescriptorReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  309.             } else if (tag.startsWith("RF")) { // Field Reference
  310.                 band = parseCPFieldRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  311.             } else if (tag.startsWith("RM")) { // Method Reference
  312.                 band = parseCPMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  313.             } else if (tag.startsWith("RI")) { // Interface Method Reference
  314.                 band = parseCPInterfaceMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  315.             } else if (tag.startsWith("RU")) { // UTF8 String
  316.                 band = parseCPUTF8References(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
  317.             }
  318.         }

  319.     }

  320.     /**
  321.      * A replication is an array of layout elements, with an associated count
  322.      */
  323.     public class Replication extends LayoutElement {

  324.         private final Integral countElement;

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

  326.         public Replication(final String tag, final String contents) throws IOException {
  327.             this.countElement = new Integral(tag);
  328.             final StringReader stream = new StringReader(contents);
  329.             LayoutElement e;
  330.             while ((e = readNextLayoutElement(stream)) != null) {
  331.                 layoutElements.add(e);
  332.             }
  333.         }

  334.         @Override
  335.         public void addToAttribute(final int index, final NewAttribute attribute) {
  336.             // Add the count value
  337.             countElement.addToAttribute(index, attribute);

  338.             // Add the corresponding array values
  339.             int offset = 0;
  340.             for (int i = 0; i < index; i++) {
  341.                 offset += countElement.getValue(i);
  342.             }
  343.             final long numElements = countElement.getValue(index);
  344.             for (int i = offset; i < offset + numElements; i++) {
  345.                 for (final LayoutElement layoutElement : layoutElements) {
  346.                     layoutElement.addToAttribute(i, attribute);
  347.                 }
  348.             }
  349.         }

  350.         public Integral getCountElement() {
  351.             return countElement;
  352.         }

  353.         public List<LayoutElement> getLayoutElements() {
  354.             return layoutElements;
  355.         }

  356.         @Override
  357.         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
  358.             countElement.readBands(in, count);
  359.             int arrayCount = 0;
  360.             for (int i = 0; i < count; i++) {
  361.                 arrayCount += countElement.getValue(i);
  362.             }
  363.             for (final LayoutElement layoutElement : layoutElements) {
  364.                 layoutElement.readBands(in, arrayCount);
  365.             }
  366.         }
  367.     }

  368.     /**
  369.      * A Union is a type of layout element where the tag value acts as a selector for one of the union cases
  370.      */
  371.     public class Union extends LayoutElement {

  372.         private final Integral unionTag;
  373.         private final List<UnionCase> unionCases;
  374.         private final List<LayoutElement> defaultCaseBody;
  375.         private int[] caseCounts;
  376.         private int defaultCount;

  377.         public Union(final String tag, final List<UnionCase> unionCases, final List<LayoutElement> body) {
  378.             this.unionTag = new Integral(tag);
  379.             this.unionCases = unionCases;
  380.             this.defaultCaseBody = body;
  381.         }

  382.         @Override
  383.         public void addToAttribute(final int n, final NewAttribute attribute) {
  384.             unionTag.addToAttribute(n, attribute);
  385.             int offset = 0;
  386.             final int[] tagBand = unionTag.band;
  387.             final int tag = unionTag.getValue(n);
  388.             boolean defaultCase = true;
  389.             for (final UnionCase unionCase : unionCases) {
  390.                 if (unionCase.hasTag(tag)) {
  391.                     defaultCase = false;
  392.                     for (int j = 0; j < n; j++) {
  393.                         if (unionCase.hasTag(tagBand[j])) {
  394.                             offset++;
  395.                         }
  396.                     }
  397.                     unionCase.addToAttribute(offset, attribute);
  398.                 }
  399.             }
  400.             if (defaultCase) {
  401.                 // default case
  402.                 int defaultOffset = 0;
  403.                 for (int j = 0; j < n; j++) {
  404.                     boolean found = false;
  405.                     for (final UnionCase unionCase : unionCases) {
  406.                         if (unionCase.hasTag(tagBand[j])) {
  407.                             found = true;
  408.                         }
  409.                     }
  410.                     if (!found) {
  411.                         defaultOffset++;
  412.                     }
  413.                 }
  414.                 if (defaultCaseBody != null) {
  415.                     for (final LayoutElement element : defaultCaseBody) {
  416.                         element.addToAttribute(defaultOffset, attribute);
  417.                     }
  418.                 }
  419.             }
  420.         }

  421.         public List<LayoutElement> getDefaultCaseBody() {
  422.             return defaultCaseBody;
  423.         }

  424.         public List<UnionCase> getUnionCases() {
  425.             return unionCases;
  426.         }

  427.         public Integral getUnionTag() {
  428.             return unionTag;
  429.         }

  430.         @Override
  431.         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
  432.             unionTag.readBands(in, count);
  433.             final int[] values = unionTag.band;
  434.             // Count the band size for each union case then read the bands
  435.             caseCounts = new int[unionCases.size()];
  436.             for (int i = 0; i < caseCounts.length; i++) {
  437.                 final UnionCase unionCase = unionCases.get(i);
  438.                 for (final int value : values) {
  439.                     if (unionCase.hasTag(value)) {
  440.                         caseCounts[i]++;
  441.                     }
  442.                 }
  443.                 unionCase.readBands(in, caseCounts[i]);
  444.             }
  445.             // Count number of default cases then read the default bands
  446.             for (final int value : values) {
  447.                 boolean found = false;
  448.                 for (final UnionCase unionCase : unionCases) {
  449.                     if (unionCase.hasTag(value)) {
  450.                         found = true;
  451.                     }
  452.                 }
  453.                 if (!found) {
  454.                     defaultCount++;
  455.                 }
  456.             }
  457.             if (defaultCaseBody != null) {
  458.                 for (final LayoutElement element : defaultCaseBody) {
  459.                     element.readBands(in, defaultCount);
  460.                 }
  461.             }
  462.         }

  463.     }

  464.     /**
  465.      * A Union case
  466.      */
  467.     public class UnionCase extends LayoutElement {

  468.         private List<LayoutElement> body;

  469.         private final List<Integer> tags;

  470.         public UnionCase(final List<Integer> tags) {
  471.             this.tags = tags;
  472.         }

  473.         public UnionCase(final List<Integer> tags, final List<LayoutElement> body) {
  474.             this.tags = tags;
  475.             this.body = body;
  476.         }

  477.         @Override
  478.         public void addToAttribute(final int index, final NewAttribute attribute) {
  479.             if (body != null) {
  480.                 for (final LayoutElement element : body) {
  481.                     element.addToAttribute(index, attribute);
  482.                 }
  483.             }
  484.         }

  485.         public List<LayoutElement> getBody() {
  486.             return body == null ? Collections.EMPTY_LIST : body;
  487.         }

  488.         public boolean hasTag(final int i) {
  489.             return tags.contains(Integer.valueOf(i));
  490.         }

  491.         public boolean hasTag(final long l) {
  492.             return tags.contains(Integer.valueOf((int) l));
  493.         }

  494.         @Override
  495.         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
  496.             if (body != null) {
  497.                 for (final LayoutElement element : body) {
  498.                     element.readBands(in, count);
  499.                 }
  500.             }
  501.         }
  502.     }

  503.     private final AttributeLayout attributeLayout;

  504.     private int backwardsCallCount;

  505.     protected List<AttributeLayoutElement> attributeLayoutElements;

  506.     public NewAttributeBands(final Segment segment, final AttributeLayout attributeLayout) throws IOException {
  507.         super(segment);
  508.         this.attributeLayout = attributeLayout;
  509.         parseLayout();
  510.         attributeLayout.setBackwardsCallCount(backwardsCallCount);
  511.     }

  512.     public int getBackwardsCallCount() {
  513.         return backwardsCallCount;
  514.     }

  515.     /**
  516.      * Returns the {@link BHSDCodec} that should be used for the given layout element.
  517.      *
  518.      * @param layoutElement TODO
  519.      * @return the {@link BHSDCodec} that should be used for the given layout element.
  520.      */
  521.     public BHSDCodec getCodec(final String layoutElement) {
  522.         if (layoutElement.indexOf('O') >= 0) {
  523.             return Codec.BRANCH5;
  524.         }
  525.         if (layoutElement.indexOf('P') >= 0) {
  526.             return Codec.BCI5;
  527.         }
  528.         if (layoutElement.indexOf('S') >= 0 && !layoutElement.contains("KS") //$NON-NLS-1$
  529.                 && !layoutElement.contains("RS")) { //$NON-NLS-1$
  530.             return Codec.SIGNED5;
  531.         }
  532.         if (layoutElement.indexOf('B') >= 0) {
  533.             return Codec.BYTE1;
  534.         }
  535.         return Codec.UNSIGNED5;
  536.     }

  537.     /**
  538.      * Gets one attribute at the given index from the various bands. The correct bands must have already been read in.
  539.      *
  540.      * @param index    TODO
  541.      * @param elements TODO
  542.      * @return attribute at the given index.
  543.      */
  544.     private Attribute getOneAttribute(final int index, final List<AttributeLayoutElement> elements) {
  545.         final NewAttribute attribute = new NewAttribute(segment.getCpBands().cpUTF8Value(attributeLayout.getName()), attributeLayout.getIndex());
  546.         for (final AttributeLayoutElement element : elements) {
  547.             element.addToAttribute(index, attribute);
  548.         }
  549.         return attribute;
  550.     }

  551.     /**
  552.      * Utility method to get the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]})
  553.      *
  554.      * @param stream
  555.      * @return
  556.      * @throws IOException If an I/O error occurs.
  557.      */
  558.     private StringReader getStreamUpToMatchingBracket(final StringReader stream) throws IOException {
  559.         final StringBuilder sb = new StringBuilder();
  560.         int foundBracket = -1;
  561.         while (foundBracket != 0) {
  562.             final int read = stream.read();
  563.             if (read == -1) {
  564.                 break;
  565.             }
  566.             final char c = (char) read;
  567.             if (c == ']') {
  568.                 foundBracket++;
  569.             }
  570.             if (c == '[') {
  571.                 foundBracket--;
  572.             }
  573.             if (!(foundBracket == 0)) {
  574.                 sb.append(c);
  575.             }
  576.         }
  577.         return new StringReader(sb.toString());
  578.     }

  579.     /**
  580.      * Parse the bands relating to this AttributeLayout and return the correct class file attributes as a List of {@link Attribute}.
  581.      *
  582.      * @param in              parse source.
  583.      * @param occurrenceCount TODO
  584.      * @return Class file attributes as a List of {@link Attribute}.
  585.      * @throws IOException      If an I/O error occurs.
  586.      * @throws Pack200Exception TODO
  587.      */
  588.     public List<Attribute> parseAttributes(final InputStream in, final int occurrenceCount) throws IOException, Pack200Exception {
  589.         for (final AttributeLayoutElement element : attributeLayoutElements) {
  590.             element.readBands(in, occurrenceCount);
  591.         }

  592.         final List<Attribute> attributes = new ArrayList<>(occurrenceCount);
  593.         for (int i = 0; i < occurrenceCount; i++) {
  594.             attributes.add(getOneAttribute(i, attributeLayoutElements));
  595.         }
  596.         return attributes;
  597.     }

  598.     /**
  599.      * Tokenize the layout into AttributeElements
  600.      *
  601.      * @throws IOException If an I/O error occurs.
  602.      */
  603.     private void parseLayout() throws IOException {
  604.         if (attributeLayoutElements == null) {
  605.             attributeLayoutElements = new ArrayList<>();
  606.             final StringReader stream = new StringReader(attributeLayout.getLayout());
  607.             AttributeLayoutElement e;
  608.             while ((e = readNextAttributeElement(stream)) != null) {
  609.                 attributeLayoutElements.add(e);
  610.             }
  611.             resolveCalls();
  612.         }
  613.     }

  614.     /*
  615.      * (non-Javadoc)
  616.      *
  617.      * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
  618.      */
  619.     @Override
  620.     public void read(final InputStream in) throws IOException, Pack200Exception {
  621.         // does nothing - use parseAttributes instead
  622.     }

  623.     /**
  624.      * Read a 'body' section of the layout from the given stream
  625.      *
  626.      * @param stream
  627.      * @return List of LayoutElements
  628.      * @throws IOException If an I/O error occurs.
  629.      */
  630.     private List<LayoutElement> readBody(final StringReader stream) throws IOException {
  631.         final List<LayoutElement> layoutElements = new ArrayList<>();
  632.         LayoutElement e;
  633.         while ((e = readNextLayoutElement(stream)) != null) {
  634.             layoutElements.add(e);
  635.         }
  636.         return layoutElements;
  637.     }

  638.     private AttributeLayoutElement readNextAttributeElement(final StringReader stream) throws IOException {
  639.         stream.mark(1);
  640.         final int next = stream.read();
  641.         if (next == -1) {
  642.             return null;
  643.         }
  644.         if (next == '[') {
  645.             return new Callable(readBody(getStreamUpToMatchingBracket(stream)));
  646.         }
  647.         stream.reset();
  648.         return readNextLayoutElement(stream);
  649.     }

  650.     private LayoutElement readNextLayoutElement(final StringReader stream) throws IOException {
  651.         final int nextChar = stream.read();
  652.         if (nextChar == -1) {
  653.             return null;
  654.         }
  655.         switch (nextChar) {
  656.         // Integrals
  657.         case 'B':
  658.         case 'H':
  659.         case 'I':
  660.         case 'V':
  661.             return new Integral(new String(new char[] { (char) nextChar }));
  662.         case 'S':
  663.         case 'F':
  664.             return new Integral(new String(new char[] { (char) nextChar, (char) stream.read() }));
  665.         case 'P':
  666.             stream.mark(1);
  667.             if (stream.read() != 'O') {
  668.                 stream.reset();
  669.                 return new Integral("P" + (char) stream.read());
  670.             }
  671.             return new Integral("PO" + (char) stream.read());
  672.         case 'O':
  673.             stream.mark(1);
  674.             if (stream.read() != 'S') {
  675.                 stream.reset();
  676.                 return new Integral("O" + (char) stream.read());
  677.             }
  678.             return new Integral("OS" + (char) stream.read());

  679.         // Replication
  680.         case 'N':
  681.             final char uintType = (char) stream.read();
  682.             stream.read(); // '['
  683.             final String str = readUpToMatchingBracket(stream);
  684.             return new Replication("" + uintType, str);

  685.         // Union
  686.         case 'T':
  687.             String intType = "" + (char) stream.read();
  688.             if (intType.equals("S")) {
  689.                 intType += (char) stream.read();
  690.             }
  691.             final List<UnionCase> unionCases = new ArrayList<>();
  692.             UnionCase c;
  693.             while ((c = readNextUnionCase(stream)) != null) {
  694.                 unionCases.add(c);
  695.             }
  696.             stream.read(); // '('
  697.             stream.read(); // ')'
  698.             stream.read(); // '['
  699.             List<LayoutElement> body = null;
  700.             stream.mark(1);
  701.             final char next = (char) stream.read();
  702.             if (next != ']') {
  703.                 stream.reset();
  704.                 body = readBody(getStreamUpToMatchingBracket(stream));
  705.             }
  706.             return new Union(intType, unionCases, body);

  707.         // Call
  708.         case '(':
  709.             final int number = readNumber(stream).intValue();
  710.             stream.read(); // ')'
  711.             return new Call(number);
  712.         // Reference
  713.         case 'K':
  714.         case 'R':
  715.             final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) stream.read());
  716.             final char nxt = (char) stream.read();
  717.             string.append(nxt);
  718.             if (nxt == 'N') {
  719.                 string.append((char) stream.read());
  720.             }
  721.             return new Reference(string.toString());
  722.         }
  723.         return null;
  724.     }

  725.     /**
  726.      * Read a UnionCase from the stream.
  727.      *
  728.      * @param stream source stream.
  729.      * @return A UnionCase from the stream.
  730.      * @throws IOException If an I/O error occurs.
  731.      */
  732.     private UnionCase readNextUnionCase(final StringReader stream) throws IOException {
  733.         stream.mark(2);
  734.         stream.read(); // '('
  735.         final int next = stream.read();
  736.         char ch = (char) next;
  737.         if (ch == ')' || next == -1) {
  738.             stream.reset();
  739.             return null;
  740.         }
  741.         stream.reset();
  742.         stream.read(); // '('
  743.         final List<Integer> tags = new ArrayList<>();
  744.         Integer nextTag;
  745.         do {
  746.             nextTag = readNumber(stream);
  747.             if (nextTag != null) {
  748.                 tags.add(nextTag);
  749.                 stream.read(); // ',' or ')'
  750.             }
  751.         } while (nextTag != null);
  752.         stream.read(); // '['
  753.         stream.mark(1);
  754.         ch = (char) stream.read();
  755.         if (ch == ']') {
  756.             return new UnionCase(tags);
  757.         }
  758.         stream.reset();
  759.         return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(stream)));
  760.     }

  761.     /**
  762.      * Read a number from the stream and return it
  763.      *
  764.      * @param stream
  765.      * @return
  766.      * @throws IOException If an I/O error occurs.
  767.      */
  768.     private Integer readNumber(final StringReader stream) throws IOException {
  769.         stream.mark(1);
  770.         final char first = (char) stream.read();
  771.         final boolean negative = first == '-';
  772.         if (!negative) {
  773.             stream.reset();
  774.         }
  775.         stream.mark(100);
  776.         int i;
  777.         int length = 0;
  778.         while ((i = stream.read()) != -1 && Character.isDigit((char) i)) {
  779.             length++;
  780.         }
  781.         stream.reset();
  782.         if (length == 0) {
  783.             return null;
  784.         }
  785.         final char[] digits = new char[length];
  786.         final int read = stream.read(digits);
  787.         if (read != digits.length) {
  788.             throw new IOException("Error reading from the input stream");
  789.         }
  790.         return ParsingUtils.parseIntValue((negative ? "-" : "") + new String(digits));
  791.     }

  792.     /**
  793.      * Gets the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]})
  794.      *
  795.      * @param stream input stream.
  796.      * @return the contents of the given stream.
  797.      * @throws IOException If an I/O error occurs.
  798.      */
  799.     private String readUpToMatchingBracket(final StringReader stream) throws IOException {
  800.         final StringBuilder sb = new StringBuilder();
  801.         int foundBracket = -1;
  802.         while (foundBracket != 0) {
  803.             final int read = stream.read();
  804.             if (read == -1) {
  805.                 break;
  806.             }
  807.             final char c = (char) read;
  808.             if (c == ']') {
  809.                 foundBracket++;
  810.             }
  811.             if (c == '[') {
  812.                 foundBracket--;
  813.             }
  814.             if (!(foundBracket == 0)) {
  815.                 sb.append(c);
  816.             }
  817.         }
  818.         return sb.toString();
  819.     }

  820.     /**
  821.      * Resolve calls in the attribute layout and returns the number of backwards calls
  822.      */
  823.     private void resolveCalls() {
  824.         int backwardsCalls = 0;
  825.         for (int i = 0; i < attributeLayoutElements.size(); i++) {
  826.             final AttributeLayoutElement element = attributeLayoutElements.get(i);
  827.             if (element instanceof Callable) {
  828.                 final Callable callable = (Callable) element;
  829.                 if (i == 0) {
  830.                     callable.setFirstCallable(true);
  831.                 }
  832.                 // Look for calls in the body
  833.                 for (final LayoutElement layoutElement : callable.body) {
  834.                     // Set the callable for each call
  835.                     backwardsCalls += resolveCallsForElement(i, callable, layoutElement);
  836.                 }
  837.             }
  838.         }
  839.         backwardsCallCount = backwardsCalls;
  840.     }

  841.     private int resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) {
  842.         int backwardsCalls = 0;
  843.         if (layoutElement instanceof Call) {
  844.             final Call call = (Call) layoutElement;
  845.             int index = call.callableIndex;
  846.             if (index == 0) { // Calls the parent callable
  847.                 backwardsCalls++;
  848.                 call.setCallable(currentCallable);
  849.             } else if (index > 0) { // Forwards call
  850.                 for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
  851.                     final AttributeLayoutElement el = attributeLayoutElements.get(k);
  852.                     if (el instanceof Callable) {
  853.                         index--;
  854.                         if (index == 0) {
  855.                             call.setCallable((Callable) el);
  856.                             break;
  857.                         }
  858.                     }
  859.                 }
  860.             } else { // Backwards call
  861.                 backwardsCalls++;
  862.                 for (int k = i - 1; k >= 0; k--) {
  863.                     final AttributeLayoutElement el = attributeLayoutElements.get(k);
  864.                     if (el instanceof Callable) {
  865.                         index++;
  866.                         if (index == 0) {
  867.                             call.setCallable((Callable) el);
  868.                             break;
  869.                         }
  870.                     }
  871.                 }
  872.             }
  873.         } else if (layoutElement instanceof Replication) {
  874.             final List<LayoutElement> children = ((Replication) layoutElement).layoutElements;
  875.             for (final LayoutElement child : children) {
  876.                 backwardsCalls += resolveCallsForElement(i, currentCallable, child);
  877.             }
  878.         }
  879.         return backwardsCalls;
  880.     }

  881.     /**
  882.      * 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
  883.      * to set this information.
  884.      *
  885.      * @param backwardsCalls one int for each backwards callable, which contains the number of times that callable is subject to a backwards call.
  886.      * @throws IOException If an I/O error occurs.
  887.      */
  888.     public void setBackwardsCalls(final int[] backwardsCalls) throws IOException {
  889.         int index = 0;
  890.         parseLayout();
  891.         for (final AttributeLayoutElement element : attributeLayoutElements) {
  892.             if (element instanceof Callable && ((Callable) element).isBackwardsCallable()) {
  893.                 ((Callable) element).addCount(backwardsCalls[index]);
  894.                 index++;
  895.             }
  896.         }
  897.     }

  898.     @Override
  899.     public void unpack() throws IOException, Pack200Exception {

  900.     }

  901. }