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}