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}