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}