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 = new String[] {};
033    private final int cIndex;
034    private final int c2Index;
035    private final int nIndex;
036    private final int tIndex;
037
038    /**
039     *
040     * @param C TODO
041     * @param F TODO
042     * @param C2 TODO
043     * @param N TODO
044     * @param cIndex the index of C in cpClass
045     * @param c2Index the index of C2 in cpClass, or -1 if C2 is null
046     * @param nIndex the index of N in cpUTF8, or -1 if N is null
047     * @param tIndex TODO
048     */
049    public IcTuple(final String C, final int F, final String C2, final String N, final int cIndex, final int c2Index,
050        final int nIndex, final int tIndex) {
051        this.C = C;
052        this.F = F;
053        this.C2 = C2;
054        this.N = N;
055        this.cIndex = cIndex;
056        this.c2Index = c2Index;
057        this.nIndex = nIndex;
058        this.tIndex = tIndex;
059        if (null == N) {
060            predictSimple = true;
061        }
062        if (null == C2) {
063            predictOuter = true;
064        }
065        initializeClassStrings();
066    }
067
068    public static final int NESTED_CLASS_FLAG = 0x00010000;
069    static final IcTuple[] EMPTY_ARRAY = {};
070
071    protected String C; // this class
072    protected int F; // flags
073    protected String C2; // outer class
074    protected String N; // name
075
076    private boolean predictSimple;
077    private boolean predictOuter;
078    private String cachedOuterClassString;
079    private String cachedSimpleClassName;
080    private boolean initialized;
081    private boolean anonymous;
082    private boolean outerIsAnonymous;
083    private boolean member = true;
084    private int cachedOuterClassIndex = -1;
085    private int cachedSimpleClassNameIndex = -1;
086
087    /**
088     * Answer true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and
089     * name fields.
090     *
091     * @return true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and
092     *         name fields.
093     */
094    public boolean predicted() {
095        return predictOuter || predictSimple;
096    }
097
098    /**
099     * Answer true if the receiver's bit 16 is set (indicating that explicit outer class and name fields are set).
100     *
101     * @return boolean
102     */
103    public boolean nestedExplicitFlagSet() {
104        return (F & NESTED_CLASS_FLAG) == NESTED_CLASS_FLAG;
105    }
106
107    /**
108     * Break the receiver into components at $ boundaries.
109     *
110     * @param className TODO
111     * @return TODO
112     */
113    public String[] innerBreakAtDollar(final String className) {
114        final List<String> resultList = new ArrayList<>();
115        int start = 0;
116        int index = 0;
117        while (index < className.length()) {
118            if (className.charAt(index) <= '$') {
119                resultList.add(className.substring(start, index));
120                start = index + 1;
121            }
122            index++;
123            if (index >= className.length()) {
124                // Add the last element
125                resultList.add(className.substring(start));
126            }
127        }
128        return resultList.toArray(EMPTY_STRING_ARRAY);
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 (String element : result) {
182            if (isAllDigits(element)) {
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 StringBuilder result = new StringBuilder();
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}