IcTuple.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.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Objects;

  21. /**
  22.  * An IcTuple is the set of information that describes an inner class.
  23.  *
  24.  * C is the fully qualified class name<br>
  25.  * F is the flags<br>
  26.  * C2 is the outer class name, or null if it can be inferred from C<br>
  27.  * N is the inner class name, or null if it can be inferred from C<br>
  28.  */
  29. public class IcTuple {

  30.     private static final String[] EMPTY_STRING_ARRAY = {};
  31.     public static final int NESTED_CLASS_FLAG = 0x00010000;
  32.     static final IcTuple[] EMPTY_ARRAY = {};
  33.     private final int cIndex;
  34.     private final int c2Index;

  35.     private final int nIndex;

  36.     private final int tIndex;
  37.     protected String C; // this class

  38.     protected int F; // flags
  39.     protected String C2; // outer class
  40.     protected String N; // name
  41.     private boolean predictSimple;

  42.     private boolean predictOuter;
  43.     private String cachedOuterClassString;
  44.     private String cachedSimpleClassName;
  45.     private boolean initialized;
  46.     private boolean anonymous;
  47.     private boolean outerIsAnonymous;
  48.     private boolean member = true;
  49.     private int cachedOuterClassIndex = -1;
  50.     private int cachedSimpleClassNameIndex = -1;
  51.     private boolean hashCodeComputed;

  52.     private int cachedHashCode;

  53.     /**
  54.      *
  55.      * @param C       TODO
  56.      * @param F       TODO
  57.      * @param C2      TODO
  58.      * @param N       TODO
  59.      * @param cIndex  the index of C in cpClass
  60.      * @param c2Index the index of C2 in cpClass, or -1 if C2 is null
  61.      * @param nIndex  the index of N in cpUTF8, or -1 if N is null
  62.      * @param tIndex  TODO
  63.      */
  64.     public IcTuple(final String C, final int F, final String C2, final String N, final int cIndex, final int c2Index, final int nIndex, final int tIndex) {
  65.         this.C = C;
  66.         this.F = F;
  67.         this.C2 = C2;
  68.         this.N = N;
  69.         this.cIndex = cIndex;
  70.         this.c2Index = c2Index;
  71.         this.nIndex = nIndex;
  72.         this.tIndex = tIndex;
  73.         if (null == N) {
  74.             predictSimple = true;
  75.         }
  76.         if (null == C2) {
  77.             predictOuter = true;
  78.         }
  79.         initializeClassStrings();
  80.     }

  81.     private boolean computeOuterIsAnonymous() {
  82.         final String[] result = innerBreakAtDollar(cachedOuterClassString);
  83.         if (result.length == 0) {
  84.             throw new Error("Should have an outer before checking if it's anonymous");
  85.         }

  86.         for (final String element : result) {
  87.             if (isAllDigits(element)) {
  88.                 return true;
  89.             }
  90.         }
  91.         return false;
  92.     }

  93.     @Override
  94.     public boolean equals(final Object object) {
  95.         if (object == null || object.getClass() != this.getClass()) {
  96.             return false;
  97.         }
  98.         final IcTuple other = (IcTuple) object;
  99.         return Objects.equals(C, other.C)
  100.                 && Objects.equals(C2, other.C2)
  101.                 && Objects.equals(N, other.N);
  102.     }

  103.     private void generateHashCode() {
  104.         hashCodeComputed = true;
  105.         cachedHashCode = 17;
  106.         if (C != null) {
  107.             cachedHashCode = +C.hashCode();
  108.         }
  109.         if (C2 != null) {
  110.             cachedHashCode = +C2.hashCode();
  111.         }
  112.         if (N != null) {
  113.             cachedHashCode = +N.hashCode();
  114.         }
  115.     }

  116.     public String getC() {
  117.         return C;
  118.     }

  119.     public String getC2() {
  120.         return C2;
  121.     }

  122.     public int getF() {
  123.         return F;
  124.     }

  125.     public String getN() {
  126.         return N;
  127.     }

  128.     public int getTupleIndex() {
  129.         return tIndex;
  130.     }

  131.     @Override
  132.     public int hashCode() {
  133.         if (!hashCodeComputed) {
  134.             generateHashCode();
  135.         }
  136.         return cachedHashCode;
  137.     }

  138.     private void initializeClassStrings() {
  139.         if (initialized) {
  140.             return;
  141.         }
  142.         initialized = true;

  143.         if (!predictSimple) {
  144.             cachedSimpleClassName = N;
  145.         }
  146.         if (!predictOuter) {
  147.             cachedOuterClassString = C2;
  148.         }
  149.         // Class names must be calculated from
  150.         // this class name.
  151.         final String[] nameComponents = innerBreakAtDollar(C);
  152.         if (nameComponents.length == 0) {
  153.             // Unable to predict outer class
  154.             // throw new Error("Unable to predict outer class name: " + C);
  155.         }
  156.         if (nameComponents.length == 1) {
  157.             // Unable to predict simple class name
  158.             // throw new Error("Unable to predict inner class name: " + C);
  159.         }
  160.         if (nameComponents.length < 2) {
  161.             // If we get here, we hope cachedSimpleClassName
  162.             // and cachedOuterClassString were caught by the
  163.             // predictSimple / predictOuter code above.
  164.             return;
  165.         }

  166.         // If we get to this point, nameComponents.length must be >=2
  167.         final int lastPosition = nameComponents.length - 1;
  168.         cachedSimpleClassName = nameComponents[lastPosition];
  169.         cachedOuterClassString = "";
  170.         for (int index = 0; index < lastPosition; index++) {
  171.             cachedOuterClassString += nameComponents[index];
  172.             if (isAllDigits(nameComponents[index])) {
  173.                 member = false;
  174.             }
  175.             if (index + 1 != lastPosition) {
  176.                 // TODO: might need more logic to handle
  177.                 // classes with separators of non-$ characters
  178.                 // (ie Foo#Bar)
  179.                 cachedOuterClassString += '$';
  180.             }
  181.         }
  182.         // TODO: these two blocks are the same as blocks
  183.         // above. Can we eliminate some by reworking the logic?
  184.         if (!predictSimple) {
  185.             cachedSimpleClassName = N;
  186.             cachedSimpleClassNameIndex = nIndex;
  187.         }
  188.         if (!predictOuter) {
  189.             cachedOuterClassString = C2;
  190.             cachedOuterClassIndex = c2Index;
  191.         }
  192.         if (isAllDigits(cachedSimpleClassName)) {
  193.             anonymous = true;
  194.             member = false;
  195.             if (nestedExplicitFlagSet()) {
  196.                 // Predicted class - marking as member
  197.                 member = true;
  198.             }
  199.         }

  200.         outerIsAnonymous = computeOuterIsAnonymous();
  201.     }

  202.     /**
  203.      * Break the receiver into components at $ boundaries.
  204.      *
  205.      * @param className TODO
  206.      * @return TODO
  207.      */
  208.     public String[] innerBreakAtDollar(final String className) {
  209.         final List<String> resultList = new ArrayList<>();
  210.         int start = 0;
  211.         int index = 0;
  212.         while (index < className.length()) {
  213.             if (className.charAt(index) <= '$') {
  214.                 resultList.add(className.substring(start, index));
  215.                 start = index + 1;
  216.             }
  217.             index++;
  218.             if (index >= className.length()) {
  219.                 // Add the last element
  220.                 resultList.add(className.substring(start));
  221.             }
  222.         }
  223.         return resultList.toArray(EMPTY_STRING_ARRAY);
  224.     }

  225.     private boolean isAllDigits(final String nameString) {
  226.         // Answer true if the receiver is all digits; otherwise answer false.
  227.         if (null == nameString) {
  228.             return false;
  229.         }
  230.         for (int index = 0; index < nameString.length(); index++) {
  231.             if (!Character.isDigit(nameString.charAt(index))) {
  232.                 return false;
  233.             }
  234.         }
  235.         return true;
  236.     }

  237.     public boolean isAnonymous() {
  238.         return anonymous;
  239.     }

  240.     public boolean isMember() {
  241.         return member;
  242.     }

  243.     /**
  244.      * Answer true if the receiver's bit 16 is set (indicating that explicit outer class and name fields are set).
  245.      *
  246.      * @return boolean
  247.      */
  248.     public boolean nestedExplicitFlagSet() {
  249.         return (F & NESTED_CLASS_FLAG) == NESTED_CLASS_FLAG;
  250.     }

  251.     public boolean nullSafeEquals(final String stringOne, final String stringTwo) {
  252.         if (null == stringOne) {
  253.             return null == stringTwo;
  254.         }
  255.         return stringOne.equals(stringTwo);
  256.     }

  257.     public int outerClassIndex() {
  258.         return cachedOuterClassIndex;
  259.     }

  260.     /**
  261.      * Answer the outer class name for the receiver. This may either be specified or inferred from inner class name.
  262.      *
  263.      * @return String name of outer class
  264.      */
  265.     public String outerClassString() {
  266.         return cachedOuterClassString;
  267.     }

  268.     public boolean outerIsAnonymous() {
  269.         return outerIsAnonymous;
  270.     }

  271.     /**
  272.      * Answer true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and name fields.
  273.      *
  274.      * @return true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and name fields.
  275.      */
  276.     public boolean predicted() {
  277.         return predictOuter || predictSimple;
  278.     }

  279.     /**
  280.      * Answer the inner class name for the receiver.
  281.      *
  282.      * @return String name of inner class
  283.      */
  284.     public String simpleClassName() {
  285.         return cachedSimpleClassName;
  286.     }

  287.     public int simpleClassNameIndex() {
  288.         return cachedSimpleClassNameIndex;
  289.     }

  290.     public int thisClassIndex() {
  291.         if (predicted()) {
  292.             return cIndex;
  293.         }
  294.         return -1;
  295.     }

  296.     /**
  297.      * Answer the full name of the inner class represented by this tuple (including its outer component)
  298.      *
  299.      * @return String full name of inner class
  300.      */
  301.     public String thisClassString() {
  302.         if (predicted()) {
  303.             return C;
  304.         }
  305.         // TODO: this may not be right. What if I
  306.         // get a class like Foo#Bar$Baz$Bug?
  307.         return C2 + "$" + N;
  308.     }

  309.     @Override
  310.     public String toString() {
  311.         // @formatter:off
  312.         return new StringBuilder()
  313.             .append("IcTuple ")
  314.             .append('(')
  315.             .append(simpleClassName())
  316.             .append(" in ")
  317.             .append(outerClassString())
  318.             .append(')')
  319.             .toString();
  320.         // @formatter:on
  321.     }
  322. }