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