    001/*
    002 *  Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.commons.compress.harmony.unpack200;
    018
    019import java.util.ArrayList;
    020import java.util.List;
    021
    022/**
    023 * An IcTuple is the set of information that describes an inner class.
    024 *
    025 * C is the fully qualified class name<br>
    026 * F is the flags<br>
    027 * C2 is the outer class name, or null if it can be inferred from C<br>
    028 * N is the inner class name, or null if it can be inferred from C<br>
    029 */
    030public class IcTuple {
    031
    032    private static final String[] EMPTY_STRING_ARRAY = {};
    033    public static final int NESTED_CLASS_FLAG = 0x00010000;
    034    static final IcTuple[] EMPTY_ARRAY = {};
    035    private final int cIndex;
    036    private final int c2Index;
    037
    038    private final int nIndex;
    039
    040    private final int tIndex;
    041    protected String C; // this class
    042
    043    protected int F; // flags
    044    protected String C2; // outer class
    045    protected String N; // name
    046    private boolean predictSimple;
    047
    048    private boolean predictOuter;
    049    private String cachedOuterClassString;
    050    private String cachedSimpleClassName;
    051    private boolean initialized;
    052    private boolean anonymous;
    053    private boolean outerIsAnonymous;
    054    private boolean member = true;
    055    private int cachedOuterClassIndex = -1;
    056    private int cachedSimpleClassNameIndex = -1;
    057    private boolean hashCodeComputed;
    058
    059    private int cachedHashCode;
    060
    061    /**
    062     *
    063     * @param C TODO
    064     * @param F TODO
    065     * @param C2 TODO
    066     * @param N TODO
    067     * @param cIndex the index of C in cpClass
    068     * @param c2Index the index of C2 in cpClass, or -1 if C2 is null
    069     * @param nIndex the index of N in cpUTF8, or -1 if N is null
    070     * @param tIndex TODO
    071     */
    072    public IcTuple(final String C, final int F, final String C2, final String N, final int cIndex, final int c2Index,
    073        final int nIndex, final int tIndex) {
    074        this.C = C;
    075        this.F = F;
    076        this.C2 = C2;
    077        this.N = N;
    078        this.cIndex = cIndex;
    079        this.c2Index = c2Index;
    080        this.nIndex = nIndex;
    081        this.tIndex = tIndex;
    082        if (null == N) {
    083            predictSimple = true;
    084        }
    085        if (null == C2) {
    086            predictOuter = true;
    087        }
    088        initializeClassStrings();
    089    }
    090
    091    private boolean computeOuterIsAnonymous() {
    092        final String[] result = innerBreakAtDollar(cachedOuterClassString);
    093        if (result.length == 0) {
    094            throw new Error("Should have an outer before checking if it's anonymous");
    095        }
    096
    097        for (final String element : result) {
    098            if (isAllDigits(element)) {
    099                return true;
    100            }
    101        }
    102        return false;
    103    }
    104
    105    @Override
    106    public boolean equals(final Object object) {
    107        if ((object == null) || (object.getClass() != this.getClass())) {
    108            return false;
    109        }
    110        final IcTuple compareTuple = (IcTuple) object;
    111
    112        if (!nullSafeEquals(this.C, compareTuple.C)) {
    113            return false;
    114        }
    115
    116        if (!nullSafeEquals(this.C2, compareTuple.C2)) {
    117            return false;
    118        }
    119
    120        if (!nullSafeEquals(this.N, compareTuple.N)) {
    121            return false;
    122        }
    123        return true;
    124    }
    125
    126    private void generateHashCode() {
    127        hashCodeComputed = true;
    128        cachedHashCode = 17;
    129        if (C != null) {
    130            cachedHashCode = +C.hashCode();
    131        }
    132        if (C2 != null) {
    133            cachedHashCode = +C2.hashCode();
    134        }
    135        if (N != null) {
    136            cachedHashCode = +N.hashCode();
    137        }
    138    }
    139
    140    public String getC() {
    141        return C;
    142    }
    143
    144    public String getC2() {
    145        return C2;
    146    }
    147
    148    public int getF() {
    149        return F;
    150    }
    151
    152    public String getN() {
    153        return N;
    154    }
    155
    156    public int getTupleIndex() {
    157        return tIndex;
    158    }
    159
    160    @Override
    161    public int hashCode() {
    162        if (!hashCodeComputed) {
    163            generateHashCode();
    164        }
    165        return cachedHashCode;
    166    }
    167
    168    private void initializeClassStrings() {
    169        if (initialized) {
    170            return;
    171        }
    172        initialized = true;
    173
    174        if (!predictSimple) {
    175            cachedSimpleClassName = N;
    176        }
    177        if (!predictOuter) {
    178            cachedOuterClassString = C2;
    179        }
    180        // Class names must be calculated from
    181        // this class name.
    182        final String[] nameComponents = innerBreakAtDollar(C);
    183        if (nameComponents.length == 0) {
    184            // Unable to predict outer class
    185            // throw new Error("Unable to predict outer class name: " + C);
    186        }
    187        if (nameComponents.length == 1) {
    188            // Unable to predict simple class name
    189            // throw new Error("Unable to predict inner class name: " + C);
    190        }
    191        if (nameComponents.length < 2) {
    192            // If we get here, we hope cachedSimpleClassName
    193            // and cachedOuterClassString were caught by the
    194            // predictSimple / predictOuter code above.
    195            return;
    196        }
    197
    198        // If we get to this point, nameComponents.length must be >=2
    199        final int lastPosition = nameComponents.length - 1;
    200        cachedSimpleClassName = nameComponents[lastPosition];
    201        cachedOuterClassString = "";
    202        for (int index = 0; index < lastPosition; index++) {
    203            cachedOuterClassString += nameComponents[index];
    204            if (isAllDigits(nameComponents[index])) {
    205                member = false;
    206            }
    207            if (index + 1 != lastPosition) {
    208                // TODO: might need more logic to handle
    209                // classes with separators of non-$ characters
    210                // (ie Foo#Bar)
    211                cachedOuterClassString += '$';
    212            }
    213        }
    214        // TODO: these two blocks are the same as blocks
    215        // above. Can we eliminate some by reworking the logic?
    216        if (!predictSimple) {
    217            cachedSimpleClassName = N;
    218            cachedSimpleClassNameIndex = nIndex;
    219        }
    220        if (!predictOuter) {
    221            cachedOuterClassString = C2;
    222            cachedOuterClassIndex = c2Index;
    223        }
    224        if (isAllDigits(cachedSimpleClassName)) {
    225            anonymous = true;
    226            member = false;
    227            if (nestedExplicitFlagSet()) {
    228                // Predicted class - marking as member
    229                member = true;
    230            }
    231        }
    232
    233        outerIsAnonymous = computeOuterIsAnonymous();
    234    }
    235
    236    /**
    237     * Break the receiver into components at $ boundaries.
    238     *
    239     * @param className TODO
    240     * @return TODO
    241     */
    242    public String[] innerBreakAtDollar(final String className) {
    243        final List<String> resultList = new ArrayList<>();
    244        int start = 0;
    245        int index = 0;
    246        while (index < className.length()) {
    247            if (className.charAt(index) <= '$') {
    248                resultList.add(className.substring(start, index));
    249                start = index + 1;
    250            }
    251            index++;
    252            if (index >= className.length()) {
    253                // Add the last element
    254                resultList.add(className.substring(start));
    255            }
    256        }
    257        return resultList.toArray(EMPTY_STRING_ARRAY);
    258    }
    259
    260    private boolean isAllDigits(final String nameString) {
    261        // Answer true if the receiver is all digits; otherwise answer false.
    262        if (null == nameString) {
    263            return false;
    264        }
    265        for (int index = 0; index < nameString.length(); index++) {
    266            if (!Character.isDigit(nameString.charAt(index))) {
    267                return false;
    268            }
    269        }
    270        return true;
    271    }
    272
    273    public boolean isAnonymous() {
    274        return anonymous;
    275    }
    276
    277    public boolean isMember() {
    278        return member;
    279    }
    280    /**
    281     * Answer true if the receiver's bit 16 is set (indicating that explicit outer class and name fields are set).
    282     *
    283     * @return boolean
    284     */
    285    public boolean nestedExplicitFlagSet() {
    286        return (F & NESTED_CLASS_FLAG) == NESTED_CLASS_FLAG;
    287    }
    288
    289    public boolean nullSafeEquals(final String stringOne, final String stringTwo) {
    290        if (null == stringOne) {
    291            return null == stringTwo;
    292        }
    293        return stringOne.equals(stringTwo);
    294    }
    295
    296    public int outerClassIndex() {
    297        return cachedOuterClassIndex;
    298    }
    299
    300    /**
    301     * Answer the outer class name for the receiver. This may either be specified or inferred from inner class name.
    302     *
    303     * @return String name of outer class
    304     */
    305    public String outerClassString() {
    306        return cachedOuterClassString;
    307    }
    308
    309    public boolean outerIsAnonymous() {
    310        return outerIsAnonymous;
    311    }
    312
    313    /**
    314     * Answer true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and
    315     * name fields.
    316     *
    317     * @return true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and
    318     *         name fields.
    319     */
    320    public boolean predicted() {
    321        return predictOuter || predictSimple;
    322    }
    323
    324    /**
    325     * Answer the inner class name for the receiver.
    326     *
    327     * @return String name of inner class
    328     */
    329    public String simpleClassName() {
    330        return cachedSimpleClassName;
    331    }
    332
    333    public int simpleClassNameIndex() {
    334        return cachedSimpleClassNameIndex;
    335    }
    336
    337    public int thisClassIndex() {
    338        if (predicted()) {
    339            return cIndex;
    340        }
    341        return -1;
    342    }
    343
    344    /**
    345     * Answer the full name of the inner class represented by this tuple (including its outer component)
    346     *
    347     * @return String full name of inner class
    348     */
    349    public String thisClassString() {
    350        if (predicted()) {
    351            return C;
    352        }
    353        // TODO: this may not be right. What if I
    354        // get a class like Foo#Bar$Baz$Bug?
    355        return C2 + "$" + N;
    356    }
    357
    358    @Override
    359    public String toString() {
    360        final StringBuilder result = new StringBuilder();
    361        result.append("IcTuple ");
    362        result.append('(');
    363        result.append(simpleClassName());
    364        result.append(" in ");
    365        result.append(outerClassString());
    366        result.append(')');
    367        return result.toString();
    368    }
    369}