BcBands.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.ByteArrayOutputStream;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.List;

  24. import org.apache.commons.compress.harmony.pack200.Codec;
  25. import org.apache.commons.compress.harmony.pack200.Pack200Exception;
  26. import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
  27. import org.apache.commons.compress.harmony.unpack200.bytecode.BCIRenumberedAttribute;
  28. import org.apache.commons.compress.harmony.unpack200.bytecode.ByteCode;
  29. import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
  30. import org.apache.commons.compress.harmony.unpack200.bytecode.CodeAttribute;
  31. import org.apache.commons.compress.harmony.unpack200.bytecode.ExceptionTableEntry;
  32. import org.apache.commons.compress.harmony.unpack200.bytecode.NewAttribute;
  33. import org.apache.commons.compress.harmony.unpack200.bytecode.OperandManager;

  34. /**
  35.  * Bytecode bands
  36.  */
  37. public class BcBands extends BandSet {

  38.     // The bytecodes for each method in each class as they come (i.e. in their
  39.     // packed format)
  40.     private byte[][][] methodByteCodePacked;

  41.     // The bands
  42.     // TODO: Haven't resolved references yet. Do we want to?
  43.     private int[] bcCaseCount;
  44.     private int[] bcCaseValue;
  45.     private int[] bcByte;
  46.     private int[] bcLocal;
  47.     private int[] bcShort;
  48.     private int[] bcLabel;
  49.     private int[] bcIntRef;
  50.     private int[] bcFloatRef;
  51.     private int[] bcLongRef;
  52.     private int[] bcDoubleRef;
  53.     private int[] bcStringRef;
  54.     private int[] bcClassRef;
  55.     private int[] bcFieldRef;
  56.     private int[] bcMethodRef;
  57.     private int[] bcIMethodRef;
  58.     private int[] bcThisField;
  59.     private int[] bcSuperField;
  60.     private int[] bcThisMethod;
  61.     private int[] bcSuperMethod;
  62.     private int[] bcInitRef;
  63.     private int[] bcEscRef;
  64.     private int[] bcEscRefSize;
  65.     private int[] bcEscSize;
  66.     private int[][] bcEscByte;

  67.     private List<Integer> wideByteCodes;

  68.     /**
  69.      * @param segment TODO
  70.      */
  71.     public BcBands(final Segment segment) {
  72.         super(segment);
  73.     }

  74.     private boolean endsWithLoad(final int codePacked) {
  75.         return codePacked >= 21 && codePacked <= 25;
  76.     }

  77.     private boolean endsWithStore(final int codePacked) {
  78.         return codePacked >= 54 && codePacked <= 58;
  79.     }

  80.     public int[] getBcByte() {
  81.         return bcByte;
  82.     }

  83.     public int[] getBcCaseCount() {
  84.         return bcCaseCount;
  85.     }

  86.     public int[] getBcCaseValue() {
  87.         return bcCaseValue;
  88.     }

  89.     public int[] getBcClassRef() {
  90.         return bcClassRef;
  91.     }

  92.     public int[] getBcDoubleRef() {
  93.         return bcDoubleRef;
  94.     }

  95.     public int[] getBcFieldRef() {
  96.         return bcFieldRef;
  97.     }

  98.     public int[] getBcFloatRef() {
  99.         return bcFloatRef;
  100.     }

  101.     public int[] getBcIMethodRef() {
  102.         return bcIMethodRef;
  103.     }

  104.     public int[] getBcInitRef() {
  105.         return bcInitRef;
  106.     }

  107.     public int[] getBcIntRef() {
  108.         return bcIntRef;
  109.     }

  110.     public int[] getBcLabel() {
  111.         return bcLabel;
  112.     }

  113.     public int[] getBcLocal() {
  114.         return bcLocal;
  115.     }

  116.     public int[] getBcLongRef() {
  117.         return bcLongRef;
  118.     }

  119.     public int[] getBcMethodRef() {
  120.         return bcMethodRef;
  121.     }

  122.     public int[] getBcShort() {
  123.         return bcShort;
  124.     }

  125.     public int[] getBcStringRef() {
  126.         return bcStringRef;
  127.     }

  128.     public int[] getBcSuperField() {
  129.         return bcSuperField;
  130.     }

  131.     public int[] getBcSuperMethod() {
  132.         return bcSuperMethod;
  133.     }

  134.     public int[] getBcThisField() {
  135.         return bcThisField;
  136.     }

  137.     public int[] getBcThisMethod() {
  138.         return bcThisMethod;
  139.     }

  140.     public byte[][][] getMethodByteCodePacked() {
  141.         return methodByteCodePacked;
  142.     }

  143.     /*
  144.      * (non-Javadoc)
  145.      *
  146.      * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
  147.      */
  148.     @Override
  149.     public void read(final InputStream in) throws IOException, Pack200Exception {

  150.         final AttributeLayoutMap attributeDefinitionMap = segment.getAttrDefinitionBands().getAttributeDefinitionMap();
  151.         final int classCount = header.getClassCount();
  152.         final long[][] methodFlags = segment.getClassBands().getMethodFlags();

  153.         int bcCaseCountCount = 0;
  154.         int bcByteCount = 0;
  155.         int bcShortCount = 0;
  156.         int bcLocalCount = 0;
  157.         int bcLabelCount = 0;
  158.         int bcIntRefCount = 0;
  159.         int bcFloatRefCount = 0;
  160.         int bcLongRefCount = 0;
  161.         int bcDoubleRefCount = 0;
  162.         int bcStringRefCount = 0;
  163.         int bcClassRefCount = 0;
  164.         int bcFieldRefCount = 0;
  165.         int bcMethodRefCount = 0;
  166.         int bcIMethodRefCount = 0;
  167.         int bcThisFieldCount = 0;
  168.         int bcSuperFieldCount = 0;
  169.         int bcThisMethodCount = 0;
  170.         int bcSuperMethodCount = 0;
  171.         int bcInitRefCount = 0;
  172.         int bcEscCount = 0;
  173.         int bcEscRefCount = 0;

  174.         final AttributeLayout abstractModifier = attributeDefinitionMap.getAttributeLayout(AttributeLayout.ACC_ABSTRACT, AttributeLayout.CONTEXT_METHOD);
  175.         final AttributeLayout nativeModifier = attributeDefinitionMap.getAttributeLayout(AttributeLayout.ACC_NATIVE, AttributeLayout.CONTEXT_METHOD);

  176.         methodByteCodePacked = new byte[classCount][][];

  177.         final List<Boolean> switchIsTableSwitch = new ArrayList<>();
  178.         wideByteCodes = new ArrayList<>();
  179.         for (int c = 0; c < classCount; c++) {
  180.             final int numberOfMethods = methodFlags[c].length;
  181.             methodByteCodePacked[c] = new byte[numberOfMethods][];
  182.             for (int m = 0; m < numberOfMethods; m++) {
  183.                 final long methodFlag = methodFlags[c][m];
  184.                 if (!abstractModifier.matches(methodFlag) && !nativeModifier.matches(methodFlag)) {
  185.                     final ByteArrayOutputStream codeBytes = new ByteArrayOutputStream();
  186.                     byte code;
  187.                     while ((code = (byte) (0xff & in.read())) != -1) {
  188.                         codeBytes.write(code);
  189.                     }
  190.                     methodByteCodePacked[c][m] = codeBytes.toByteArray();
  191.                     final int[] codes = new int[methodByteCodePacked[c][m].length];
  192.                     for (int i = 0; i < codes.length; i++) {
  193.                         codes[i] = methodByteCodePacked[c][m][i] & 0xff;
  194.                     }
  195.                     for (int i = 0; i < methodByteCodePacked[c][m].length; i++) {
  196.                         final int codePacked = 0xff & methodByteCodePacked[c][m][i];
  197.                         switch (codePacked) {
  198.                         case 16: // bipush
  199.                         case 188: // newarray
  200.                             bcByteCount++;
  201.                             break;
  202.                         case 17: // sipush
  203.                             bcShortCount++;
  204.                             break;
  205.                         case 18: // (a)ldc
  206.                         case 19: // aldc_w
  207.                             bcStringRefCount++;
  208.                             break;
  209.                         case 234: // ildc
  210.                         case 237: // ildc_w
  211.                             bcIntRefCount++;
  212.                             break;
  213.                         case 235: // fldc
  214.                         case 238: // fldc_w
  215.                             bcFloatRefCount++;
  216.                             break;
  217.                         case 197: // multianewarray
  218.                             bcByteCount++;
  219.                             // fallthrough intended
  220.                         case 233: // cldc
  221.                         case 236: // cldc_w
  222.                         case 187: // new
  223.                         case 189: // anewarray
  224.                         case 192: // checkcast
  225.                         case 193: // instanceof
  226.                             bcClassRefCount++;
  227.                             break;
  228.                         case 20: // lldc2_w
  229.                             bcLongRefCount++;
  230.                             break;
  231.                         case 239: // dldc2_w
  232.                             bcDoubleRefCount++;
  233.                             break;
  234.                         case 169: // ret
  235.                             bcLocalCount++;
  236.                             break;
  237.                         case 167: // goto
  238.                         case 168: // jsr
  239.                         case 200: // goto_w
  240.                         case 201: // jsr_w
  241.                             bcLabelCount++;
  242.                             break;
  243.                         case 170: // tableswitch
  244.                             switchIsTableSwitch.add(Boolean.TRUE);
  245.                             bcCaseCountCount++;
  246.                             bcLabelCount++;
  247.                             break;
  248.                         case 171: // lookupswitch
  249.                             switchIsTableSwitch.add(Boolean.FALSE);
  250.                             bcCaseCountCount++;
  251.                             bcLabelCount++;
  252.                             break;
  253.                         case 178: // getstatic
  254.                         case 179: // putstatic
  255.                         case 180: // getfield
  256.                         case 181: // putfield
  257.                             bcFieldRefCount++;
  258.                             break;
  259.                         case 182: // invokevirtual
  260.                         case 183: // invokespecial
  261.                         case 184: // invokestatic
  262.                             bcMethodRefCount++;
  263.                             break;
  264.                         case 185: // invokeinterface
  265.                             bcIMethodRefCount++;
  266.                             break;
  267.                         case 202: // getstatic_this
  268.                         case 203: // putstatic_this
  269.                         case 204: // getfield_this
  270.                         case 205: // putfield_this
  271.                         case 209: // aload_0_getstatic_this
  272.                         case 210: // aload_0_putstatic_this
  273.                         case 211: // aload_0_putfield_this
  274.                         case 212: // aload_0_putfield_this
  275.                             bcThisFieldCount++;
  276.                             break;
  277.                         case 206: // invokevirtual_this
  278.                         case 207: // invokespecial_this
  279.                         case 208: // invokestatic_this
  280.                         case 213: // aload_0_invokevirtual_this
  281.                         case 214: // aload_0_invokespecial_this
  282.                         case 215: // aload_0_invokestatic_this
  283.                             bcThisMethodCount++;
  284.                             break;
  285.                         case 216: // getstatic_super
  286.                         case 217: // putstatic_super
  287.                         case 218: // getfield_super
  288.                         case 219: // putfield_super
  289.                         case 223: // aload_0_getstatic_super
  290.                         case 224: // aload_0_putstatic_super
  291.                         case 225: // aload_0_getfield_super
  292.                         case 226: // aload_0_putfield_super
  293.                             bcSuperFieldCount++;
  294.                             break;
  295.                         case 220: // invokevirtual_super
  296.                         case 221: // invokespecial_super
  297.                         case 222: // invokestatic_super
  298.                         case 227: // aload_0_invokevirtual_super
  299.                         case 228: // aload_0_invokespecial_super
  300.                         case 229: // aload_0_invokestatic_super
  301.                             bcSuperMethodCount++;
  302.                             break;
  303.                         case 132: // iinc
  304.                             bcLocalCount++;
  305.                             bcByteCount++;
  306.                             break;
  307.                         case 196: // wide
  308.                             final int nextInstruction = 0xff & methodByteCodePacked[c][m][i + 1];
  309.                             wideByteCodes.add(Integer.valueOf(nextInstruction));
  310.                             if (nextInstruction == 132) { // iinc
  311.                                 bcLocalCount++;
  312.                                 bcShortCount++;
  313.                             } else if (endsWithLoad(nextInstruction) || endsWithStore(nextInstruction) || nextInstruction == 169) {
  314.                                 bcLocalCount++;
  315.                             } else {
  316.                                 segment.log(Segment.LOG_LEVEL_VERBOSE, "Found unhandled " + ByteCode.getByteCode(nextInstruction));
  317.                             }
  318.                             i++;
  319.                             break;
  320.                         case 230: // invokespecial_this_init
  321.                         case 231: // invokespecial_super_init
  322.                         case 232: // invokespecial_new_init
  323.                             bcInitRefCount++;
  324.                             break;
  325.                         case 253: // ref_escape
  326.                             bcEscRefCount++;
  327.                             break;
  328.                         case 254: // byte_escape
  329.                             bcEscCount++;
  330.                             break;
  331.                         default:
  332.                             if (endsWithLoad(codePacked) || endsWithStore(codePacked)) {
  333.                                 bcLocalCount++;
  334.                             } else if (startsWithIf(codePacked)) {
  335.                                 bcLabelCount++;
  336.                             }
  337.                         }
  338.                     }
  339.                 }
  340.             }
  341.         }
  342.         // other bytecode bands
  343.         bcCaseCount = decodeBandInt("bc_case_count", in, Codec.UNSIGNED5, bcCaseCountCount);
  344.         int bcCaseValueCount = 0;
  345.         for (int i = 0; i < bcCaseCount.length; i++) {
  346.             final boolean isTableSwitch = switchIsTableSwitch.get(i).booleanValue();
  347.             if (isTableSwitch) {
  348.                 bcCaseValueCount += 1;
  349.             } else {
  350.                 bcCaseValueCount += bcCaseCount[i];
  351.             }
  352.         }
  353.         bcCaseValue = decodeBandInt("bc_case_value", in, Codec.DELTA5, bcCaseValueCount);
  354.         // Every case value needs a label. We weren't able to count these
  355.         // above, because we didn't know how many cases there were.
  356.         // Have to correct it now.
  357.         for (int index = 0; index < bcCaseCountCount; index++) {
  358.             bcLabelCount += bcCaseCount[index];
  359.         }
  360.         bcByte = decodeBandInt("bc_byte", in, Codec.BYTE1, bcByteCount);
  361.         bcShort = decodeBandInt("bc_short", in, Codec.DELTA5, bcShortCount);
  362.         bcLocal = decodeBandInt("bc_local", in, Codec.UNSIGNED5, bcLocalCount);
  363.         bcLabel = decodeBandInt("bc_label", in, Codec.BRANCH5, bcLabelCount);
  364.         bcIntRef = decodeBandInt("bc_intref", in, Codec.DELTA5, bcIntRefCount);
  365.         bcFloatRef = decodeBandInt("bc_floatref", in, Codec.DELTA5, bcFloatRefCount);
  366.         bcLongRef = decodeBandInt("bc_longref", in, Codec.DELTA5, bcLongRefCount);
  367.         bcDoubleRef = decodeBandInt("bc_doubleref", in, Codec.DELTA5, bcDoubleRefCount);
  368.         bcStringRef = decodeBandInt("bc_stringref", in, Codec.DELTA5, bcStringRefCount);
  369.         bcClassRef = decodeBandInt("bc_classref", in, Codec.UNSIGNED5, bcClassRefCount);
  370.         bcFieldRef = decodeBandInt("bc_fieldref", in, Codec.DELTA5, bcFieldRefCount);
  371.         bcMethodRef = decodeBandInt("bc_methodref", in, Codec.UNSIGNED5, bcMethodRefCount);
  372.         bcIMethodRef = decodeBandInt("bc_imethodref", in, Codec.DELTA5, bcIMethodRefCount);
  373.         bcThisField = decodeBandInt("bc_thisfield", in, Codec.UNSIGNED5, bcThisFieldCount);
  374.         bcSuperField = decodeBandInt("bc_superfield", in, Codec.UNSIGNED5, bcSuperFieldCount);
  375.         bcThisMethod = decodeBandInt("bc_thismethod", in, Codec.UNSIGNED5, bcThisMethodCount);
  376.         bcSuperMethod = decodeBandInt("bc_supermethod", in, Codec.UNSIGNED5, bcSuperMethodCount);
  377.         bcInitRef = decodeBandInt("bc_initref", in, Codec.UNSIGNED5, bcInitRefCount);
  378.         bcEscRef = decodeBandInt("bc_escref", in, Codec.UNSIGNED5, bcEscRefCount);
  379.         bcEscRefSize = decodeBandInt("bc_escrefsize", in, Codec.UNSIGNED5, bcEscRefCount);
  380.         bcEscSize = decodeBandInt("bc_escsize", in, Codec.UNSIGNED5, bcEscCount);
  381.         bcEscByte = decodeBandInt("bc_escbyte", in, Codec.BYTE1, bcEscSize);
  382.     }

  383.     private boolean startsWithIf(final int codePacked) {
  384.         return codePacked >= 153 && codePacked <= 166 || codePacked == 198 || codePacked == 199;
  385.     }

  386.     @Override
  387.     public void unpack() throws Pack200Exception {
  388.         final int classCount = header.getClassCount();
  389.         final long[][] methodFlags = segment.getClassBands().getMethodFlags();
  390.         final int[] codeMaxNALocals = segment.getClassBands().getCodeMaxNALocals();
  391.         final int[] codeMaxStack = segment.getClassBands().getCodeMaxStack();
  392.         final ArrayList<Attribute>[][] methodAttributes = segment.getClassBands().getMethodAttributes();
  393.         final String[][] methodDescr = segment.getClassBands().getMethodDescr();

  394.         final AttributeLayoutMap attributeDefinitionMap = segment.getAttrDefinitionBands().getAttributeDefinitionMap();

  395.         final AttributeLayout abstractModifier = attributeDefinitionMap.getAttributeLayout(AttributeLayout.ACC_ABSTRACT, AttributeLayout.CONTEXT_METHOD);
  396.         final AttributeLayout nativeModifier = attributeDefinitionMap.getAttributeLayout(AttributeLayout.ACC_NATIVE, AttributeLayout.CONTEXT_METHOD);
  397.         final AttributeLayout staticModifier = attributeDefinitionMap.getAttributeLayout(AttributeLayout.ACC_STATIC, AttributeLayout.CONTEXT_METHOD);

  398.         final int[] wideByteCodeArray = new int[wideByteCodes.size()];
  399.         for (int index = 0; index < wideByteCodeArray.length; index++) {
  400.             wideByteCodeArray[index] = wideByteCodes.get(index).intValue();
  401.         }
  402.         final OperandManager operandManager = new OperandManager(bcCaseCount, bcCaseValue, bcByte, bcShort, bcLocal, bcLabel, bcIntRef, bcFloatRef, bcLongRef,
  403.                 bcDoubleRef, bcStringRef, bcClassRef, bcFieldRef, bcMethodRef, bcIMethodRef, bcThisField, bcSuperField, bcThisMethod, bcSuperMethod, bcInitRef,
  404.                 wideByteCodeArray);
  405.         operandManager.setSegment(segment);

  406.         int i = 0;
  407.         final ArrayList<List<Attribute>> orderedCodeAttributes = segment.getClassBands().getOrderedCodeAttributes();
  408.         int codeAttributeIndex = 0;

  409.         // Exception table fields
  410.         final int[] handlerCount = segment.getClassBands().getCodeHandlerCount();
  411.         final int[][] handlerStartPCs = segment.getClassBands().getCodeHandlerStartP();
  412.         final int[][] handlerEndPCs = segment.getClassBands().getCodeHandlerEndPO();
  413.         final int[][] handlerCatchPCs = segment.getClassBands().getCodeHandlerCatchPO();
  414.         final int[][] handlerClassTypes = segment.getClassBands().getCodeHandlerClassRCN();

  415.         final boolean allCodeHasFlags = segment.getSegmentHeader().getOptions().hasAllCodeFlags();
  416.         final boolean[] codeHasFlags = segment.getClassBands().getCodeHasAttributes();

  417.         for (int c = 0; c < classCount; c++) {
  418.             final int numberOfMethods = methodFlags[c].length;
  419.             for (int m = 0; m < numberOfMethods; m++) {
  420.                 final long methodFlag = methodFlags[c][m];
  421.                 if (!abstractModifier.matches(methodFlag) && !nativeModifier.matches(methodFlag)) {
  422.                     final int maxStack = codeMaxStack[i];
  423.                     int maxLocal = codeMaxNALocals[i];
  424.                     if (!staticModifier.matches(methodFlag)) {
  425.                         maxLocal++; // one for 'this' parameter
  426.                     }
  427.                     // I believe this has to take wide arguments into account
  428.                     maxLocal += SegmentUtils.countInvokeInterfaceArgs(methodDescr[c][m]);
  429.                     final String[] cpClass = segment.getCpBands().getCpClass();
  430.                     operandManager.setCurrentClass(cpClass[segment.getClassBands().getClassThisInts()[c]]);
  431.                     operandManager.setSuperClass(cpClass[segment.getClassBands().getClassSuperInts()[c]]);
  432.                     final List<ExceptionTableEntry> exceptionTable = new ArrayList<>();
  433.                     if (handlerCount != null) {
  434.                         for (int j = 0; j < handlerCount[i]; j++) {
  435.                             final int handlerClass = handlerClassTypes[i][j] - 1;
  436.                             CPClass cpHandlerClass = null;
  437.                             if (handlerClass != -1) {
  438.                                 // The handlerClass will be null if the
  439.                                 // catch is a finally (that is, the
  440.                                 // exception table catch_type should be 0
  441.                                 cpHandlerClass = segment.getCpBands().cpClassValue(handlerClass);
  442.                             }
  443.                             final ExceptionTableEntry entry = new ExceptionTableEntry(handlerStartPCs[i][j], handlerEndPCs[i][j], handlerCatchPCs[i][j],
  444.                                     cpHandlerClass);
  445.                             exceptionTable.add(entry);
  446.                         }
  447.                     }
  448.                     final CodeAttribute codeAttr = new CodeAttribute(maxStack, maxLocal, methodByteCodePacked[c][m], segment, operandManager, exceptionTable);
  449.                     final List<Attribute> methodAttributesList = methodAttributes[c][m];
  450.                     // Make sure we add the code attribute in the right place
  451.                     int indexForCodeAttr = 0;
  452.                     for (final Attribute attribute : methodAttributesList) {
  453.                         if (!(attribute instanceof NewAttribute) || ((NewAttribute) attribute).getLayoutIndex() >= 15) {
  454.                             break;
  455.                         }
  456.                         indexForCodeAttr++;
  457.                     }
  458.                     methodAttributesList.add(indexForCodeAttr, codeAttr);
  459.                     codeAttr.renumber(codeAttr.byteCodeOffsets);
  460.                     List<Attribute> currentAttributes;
  461.                     if (allCodeHasFlags) {
  462.                         currentAttributes = orderedCodeAttributes.get(i);
  463.                     } else if (codeHasFlags[i]) {
  464.                         currentAttributes = orderedCodeAttributes.get(codeAttributeIndex);
  465.                         codeAttributeIndex++;
  466.                     } else {
  467.                         currentAttributes = Collections.EMPTY_LIST;
  468.                     }
  469.                     for (final Attribute currentAttribute : currentAttributes) {
  470.                         codeAttr.addAttribute(currentAttribute);
  471.                         // Fix up the line numbers if needed
  472.                         if (currentAttribute.hasBCIRenumbering()) {
  473.                             ((BCIRenumberedAttribute) currentAttribute).renumber(codeAttr.byteCodeOffsets);
  474.                         }
  475.                     }
  476.                     i++;
  477.                 }
  478.             }
  479.         }
  480.     }
  481. }