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