    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, final int nIndex, final int tIndex) {
    073        this.C = C;
    074        this.F = F;
    075        this.C2 = C2;
    076        this.N = N;
    077        this.cIndex = cIndex;
    078        this.c2Index = c2Index;
    079        this.nIndex = nIndex;
    080        this.tIndex = tIndex;
    081        if (null == N) {
    082            predictSimple = true;
    083        }
    084        if (null == C2) {
    085            predictOuter = true;
    086        }
    087        initializeClassStrings();
    088    }
    089
    090    private boolean computeOuterIsAnonymous() {
    091        final String[] result = innerBreakAtDollar(cachedOuterClassString);
    092        if (result.length == 0) {
    093            throw new Error("Should have an outer before checking if it's anonymous");
    094        }
    095
    096        for (final String element : result) {
    097            if (isAllDigits(element)) {
    098                return true;
    099            }
    100        }
    101        return false;
    102    }
    103
    104    @Override
    105    public boolean equals(final Object object) {
    106        if (object == null || object.getClass() != this.getClass()) {
    107            return false;
    108        }
    109        final IcTuple compareTuple = (IcTuple) object;
    110
    111        if (!nullSafeEquals(this.C, compareTuple.C)) {
    112            return false;
    113        }
    114
    115        if (!nullSafeEquals(this.C2, compareTuple.C2)) {
    116            return false;
    117        }
    118
    119        if (!nullSafeEquals(this.N, compareTuple.N)) {
    120            return false;
    121        }
    122        return true;
    123    }
    124
    125    private void generateHashCode() {
    126        hashCodeComputed = true;
    127        cachedHashCode = 17;
    128        if (C != null) {
    129            cachedHashCode = +C.hashCode();
    130        }
    131        if (C2 != null) {
    132            cachedHashCode = +C2.hashCode();
    133        }
    134        if (N != null) {
    135            cachedHashCode = +N.hashCode();
    136        }
    137    }
    138
    139    public String getC() {
    140        return C;
    141    }
    142
    143    public String getC2() {
    144        return C2;
    145    }
    146
    147    public int getF() {
    148        return F;
    149    }
    150
    151    public String getN() {
    152        return N;
    153    }
    154
    155    public int getTupleIndex() {
    156        return tIndex;
    157    }
    158
    159    @Override
    160    public int hashCode() {
    161        if (!hashCodeComputed) {
    162            generateHashCode();
    163        }
    164        return cachedHashCode;
    165    }
    166
    167    private void initializeClassStrings() {
    168        if (initialized) {
    169            return;
    170        }
    171        initialized = true;
    172
    173        if (!predictSimple) {
    174            cachedSimpleClassName = N;
    175        }
    176        if (!predictOuter) {
    177            cachedOuterClassString = C2;
    178        }
    179        // Class names must be calculated from
    180        // this class name.
    181        final String[] nameComponents = innerBreakAtDollar(C);
    182        if (nameComponents.length == 0) {
    183            // Unable to predict outer class
    184            // throw new Error("Unable to predict outer class name: " + C);
    185        }
    186        if (nameComponents.length == 1) {
    187            // Unable to predict simple class name
    188            // throw new Error("Unable to predict inner class name: " + C);
    189        }
    190        if (nameComponents.length < 2) {
    191            // If we get here, we hope cachedSimpleClassName
    192            // and cachedOuterClassString were caught by the
    193            // predictSimple / predictOuter code above.
    194            return;
    195        }
    196
    197        // If we get to this point, nameComponents.length must be >=2
    198        final int lastPosition = nameComponents.length - 1;
    199        cachedSimpleClassName = nameComponents[lastPosition];
    200        cachedOuterClassString = "";
    201        for (int index = 0; index < lastPosition; index++) {
    202            cachedOuterClassString += nameComponents[index];
    203            if (isAllDigits(nameComponents[index])) {
    204                member = false;
    205            }
    206            if (index + 1 != lastPosition) {
    207                // TODO: might need more logic to handle
    208                // classes with separators of non-$ characters
    209                // (ie Foo#Bar)
    210                cachedOuterClassString += '$';
    211            }
    212        }
    213        // TODO: these two blocks are the same as blocks
    214        // above. Can we eliminate some by reworking the logic?
    215        if (!predictSimple) {
    216            cachedSimpleClassName = N;
    217            cachedSimpleClassNameIndex = nIndex;
    218        }
    219        if (!predictOuter) {
    220            cachedOuterClassString = C2;
    221            cachedOuterClassIndex = c2Index;
    222        }
    223        if (isAllDigits(cachedSimpleClassName)) {
    224            anonymous = true;
    225            member = false;
    226            if (nestedExplicitFlagSet()) {
    227                // Predicted class - marking as member
    228                member = true;
    229            }
    230        }
    231
    232        outerIsAnonymous = computeOuterIsAnonymous();
    233    }
    234
    235    /**
    236     * Break the receiver into components at $ boundaries.
    237     *
    238     * @param className TODO
    239     * @return TODO
    240     */
    241    public String[] innerBreakAtDollar(final String className) {
    242        final List<String> resultList = new ArrayList<>();
    243        int start = 0;
    244        int index = 0;
    245        while (index < className.length()) {
    246            if (className.charAt(index) <= '$') {
    247                resultList.add(className.substring(start, index));
    248                start = index + 1;
    249            }
    250            index++;
    251            if (index >= className.length()) {
    252                // Add the last element
    253                resultList.add(className.substring(start));
    254            }
    255        }
    256        return resultList.toArray(EMPTY_STRING_ARRAY);
    257    }
    258
    259    private boolean isAllDigits(final String nameString) {
    260        // Answer true if the receiver is all digits; otherwise answer false.
    261        if (null == nameString) {
    262            return false;
    263        }
    264        for (int index = 0; index < nameString.length(); index++) {
    265            if (!Character.isDigit(nameString.charAt(index))) {
    266                return false;
    267            }
    268        }
    269        return true;
    270    }
    271
    272    public boolean isAnonymous() {
    273        return anonymous;
    274    }
    275
    276    public boolean isMember() {
    277        return member;
    278    }
    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 name fields.
    315     *
    316     * @return true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and name fields.
    317     */
    318    public boolean predicted() {
    319        return predictOuter || predictSimple;
    320    }
    321
    322    /**
    323     * Answer the inner class name for the receiver.
    324     *
    325     * @return String name of inner class
    326     */
    327    public String simpleClassName() {
    328        return cachedSimpleClassName;
    329    }
    330
    331    public int simpleClassNameIndex() {
    332        return cachedSimpleClassNameIndex;
    333    }
    334
    335    public int thisClassIndex() {
    336        if (predicted()) {
    337            return cIndex;
    338        }
    339        return -1;
    340    }
    341
    342    /**
    343     * Answer the full name of the inner class represented by this tuple (including its outer component)
    344     *
    345     * @return String full name of inner class
    346     */
    347    public String thisClassString() {
    348        if (predicted()) {
    349            return C;
    350        }
    351        // TODO: this may not be right. What if I
    352        // get a class like Foo#Bar$Baz$Bug?
    353        return C2 + "$" + N;
    354    }
    355
    356    @Override
    357    public String toString() {
    358        final StringBuilder result = new StringBuilder();
    359        result.append("IcTuple ");
    360        result.append('(');
    361        result.append(simpleClassName());
    362        result.append(" in ");
    363        result.append(outerClassString());
    364        result.append(')');
    365        return result.toString();
    366    }
    367}