    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;
    021import java.util.Objects;
    022
    023/**
    024 * An IcTuple is the set of information that describes an inner class.
    025 *
    026 * C is the fully qualified class name<br>
    027 * F is the flags<br>
    028 * C2 is the outer class name, or null if it can be inferred from C<br>
    029 * N is the inner class name, or null if it can be inferred from C<br>
    030 */
    031public class IcTuple {
    032
    033    private static final String[] EMPTY_STRING_ARRAY = {};
    034    public static final int NESTED_CLASS_FLAG = 0x00010000;
    035    static final IcTuple[] EMPTY_ARRAY = {};
    036    private final int cIndex;
    037    private final int c2Index;
    038
    039    private final int nIndex;
    040
    041    private final int tIndex;
    042    protected String C; // this class
    043
    044    protected int F; // flags
    045    protected String C2; // outer class
    046    protected String N; // name
    047    private boolean predictSimple;
    048
    049    private boolean predictOuter;
    050    private String cachedOuterClassString;
    051    private String cachedSimpleClassName;
    052    private boolean initialized;
    053    private boolean anonymous;
    054    private boolean outerIsAnonymous;
    055    private boolean member = true;
    056    private int cachedOuterClassIndex = -1;
    057    private int cachedSimpleClassNameIndex = -1;
    058    private boolean hashCodeComputed;
    059
    060    private int cachedHashCode;
    061
    062    /**
    063     *
    064     * @param C       TODO
    065     * @param F       TODO
    066     * @param C2      TODO
    067     * @param N       TODO
    068     * @param cIndex  the index of C in cpClass
    069     * @param c2Index the index of C2 in cpClass, or -1 if C2 is null
    070     * @param nIndex  the index of N in cpUTF8, or -1 if N is null
    071     * @param tIndex  TODO
    072     */
    073    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) {
    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 other = (IcTuple) object;
    111        return Objects.equals(C, other.C)
    112                && Objects.equals(C2, other.C2)
    113                && Objects.equals(N, other.N);
    114    }
    115
    116    private void generateHashCode() {
    117        hashCodeComputed = true;
    118        cachedHashCode = 17;
    119        if (C != null) {
    120            cachedHashCode = +C.hashCode();
    121        }
    122        if (C2 != null) {
    123            cachedHashCode = +C2.hashCode();
    124        }
    125        if (N != null) {
    126            cachedHashCode = +N.hashCode();
    127        }
    128    }
    129
    130    public String getC() {
    131        return C;
    132    }
    133
    134    public String getC2() {
    135        return C2;
    136    }
    137
    138    public int getF() {
    139        return F;
    140    }
    141
    142    public String getN() {
    143        return N;
    144    }
    145
    146    public int getTupleIndex() {
    147        return tIndex;
    148    }
    149
    150    @Override
    151    public int hashCode() {
    152        if (!hashCodeComputed) {
    153            generateHashCode();
    154        }
    155        return cachedHashCode;
    156    }
    157
    158    private void initializeClassStrings() {
    159        if (initialized) {
    160            return;
    161        }
    162        initialized = true;
    163
    164        if (!predictSimple) {
    165            cachedSimpleClassName = N;
    166        }
    167        if (!predictOuter) {
    168            cachedOuterClassString = C2;
    169        }
    170        // Class names must be calculated from
    171        // this class name.
    172        final String[] nameComponents = innerBreakAtDollar(C);
    173        if (nameComponents.length == 0) {
    174            // Unable to predict outer class
    175            // throw new Error("Unable to predict outer class name: " + C);
    176        }
    177        if (nameComponents.length == 1) {
    178            // Unable to predict simple class name
    179            // throw new Error("Unable to predict inner class name: " + C);
    180        }
    181        if (nameComponents.length < 2) {
    182            // If we get here, we hope cachedSimpleClassName
    183            // and cachedOuterClassString were caught by the
    184            // predictSimple / predictOuter code above.
    185            return;
    186        }
    187
    188        // If we get to this point, nameComponents.length must be >=2
    189        final int lastPosition = nameComponents.length - 1;
    190        cachedSimpleClassName = nameComponents[lastPosition];
    191        cachedOuterClassString = "";
    192        for (int index = 0; index < lastPosition; index++) {
    193            cachedOuterClassString += nameComponents[index];
    194            if (isAllDigits(nameComponents[index])) {
    195                member = false;
    196            }
    197            if (index + 1 != lastPosition) {
    198                // TODO: might need more logic to handle
    199                // classes with separators of non-$ characters
    200                // (ie Foo#Bar)
    201                cachedOuterClassString += '$';
    202            }
    203        }
    204        // TODO: these two blocks are the same as blocks
    205        // above. Can we eliminate some by reworking the logic?
    206        if (!predictSimple) {
    207            cachedSimpleClassName = N;
    208            cachedSimpleClassNameIndex = nIndex;
    209        }
    210        if (!predictOuter) {
    211            cachedOuterClassString = C2;
    212            cachedOuterClassIndex = c2Index;
    213        }
    214        if (isAllDigits(cachedSimpleClassName)) {
    215            anonymous = true;
    216            member = false;
    217            if (nestedExplicitFlagSet()) {
    218                // Predicted class - marking as member
    219                member = true;
    220            }
    221        }
    222
    223        outerIsAnonymous = computeOuterIsAnonymous();
    224    }
    225
    226    /**
    227     * Break the receiver into components at $ boundaries.
    228     *
    229     * @param className TODO
    230     * @return TODO
    231     */
    232    public String[] innerBreakAtDollar(final String className) {
    233        final List<String> resultList = new ArrayList<>();
    234        int start = 0;
    235        int index = 0;
    236        while (index < className.length()) {
    237            if (className.charAt(index) <= '$') {
    238                resultList.add(className.substring(start, index));
    239                start = index + 1;
    240            }
    241            index++;
    242            if (index >= className.length()) {
    243                // Add the last element
    244                resultList.add(className.substring(start));
    245            }
    246        }
    247        return resultList.toArray(EMPTY_STRING_ARRAY);
    248    }
    249
    250    private boolean isAllDigits(final String nameString) {
    251        // Answer true if the receiver is all digits; otherwise answer false.
    252        if (null == nameString) {
    253            return false;
    254        }
    255        for (int index = 0; index < nameString.length(); index++) {
    256            if (!Character.isDigit(nameString.charAt(index))) {
    257                return false;
    258            }
    259        }
    260        return true;
    261    }
    262
    263    public boolean isAnonymous() {
    264        return anonymous;
    265    }
    266
    267    public boolean isMember() {
    268        return member;
    269    }
    270
    271    /**
    272     * Answer true if the receiver's bit 16 is set (indicating that explicit outer class and name fields are set).
    273     *
    274     * @return boolean
    275     */
    276    public boolean nestedExplicitFlagSet() {
    277        return (F & NESTED_CLASS_FLAG) == NESTED_CLASS_FLAG;
    278    }
    279
    280    public boolean nullSafeEquals(final String stringOne, final String stringTwo) {
    281        if (null == stringOne) {
    282            return null == stringTwo;
    283        }
    284        return stringOne.equals(stringTwo);
    285    }
    286
    287    public int outerClassIndex() {
    288        return cachedOuterClassIndex;
    289    }
    290
    291    /**
    292     * Answer the outer class name for the receiver. This may either be specified or inferred from inner class name.
    293     *
    294     * @return String name of outer class
    295     */
    296    public String outerClassString() {
    297        return cachedOuterClassString;
    298    }
    299
    300    public boolean outerIsAnonymous() {
    301        return outerIsAnonymous;
    302    }
    303
    304    /**
    305     * Answer true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and name fields.
    306     *
    307     * @return true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and name fields.
    308     */
    309    public boolean predicted() {
    310        return predictOuter || predictSimple;
    311    }
    312
    313    /**
    314     * Answer the inner class name for the receiver.
    315     *
    316     * @return String name of inner class
    317     */
    318    public String simpleClassName() {
    319        return cachedSimpleClassName;
    320    }
    321
    322    public int simpleClassNameIndex() {
    323        return cachedSimpleClassNameIndex;
    324    }
    325
    326    public int thisClassIndex() {
    327        if (predicted()) {
    328            return cIndex;
    329        }
    330        return -1;
    331    }
    332
    333    /**
    334     * Answer the full name of the inner class represented by this tuple (including its outer component)
    335     *
    336     * @return String full name of inner class
    337     */
    338    public String thisClassString() {
    339        if (predicted()) {
    340            return C;
    341        }
    342        // TODO: this may not be right. What if I
    343        // get a class like Foo#Bar$Baz$Bug?
    344        return C2 + "$" + N;
    345    }
    346
    347    @Override
    348    public String toString() {
    349        // @formatter:off
    350        return new StringBuilder()
    351            .append("IcTuple ")
    352            .append('(')
    353            .append(simpleClassName())
    354            .append(" in ")
    355            .append(outerClassString())
    356            .append(')')
    357            .toString();
    358        // @formatter:on
    359    }
    360}