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,
073        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 compareTuple = (IcTuple) object;
111
112        if (!nullSafeEquals(this.C, compareTuple.C)) {
113            return false;
114        }
115
116        if (!nullSafeEquals(this.C2, compareTuple.C2)) {
117            return false;
118        }
119
120        if (!nullSafeEquals(this.N, compareTuple.N)) {
121            return false;
122        }
123        return true;
124    }
125
126    private void generateHashCode() {
127        hashCodeComputed = true;
128        cachedHashCode = 17;
129        if (C != null) {
130            cachedHashCode = +C.hashCode();
131        }
132        if (C2 != null) {
133            cachedHashCode = +C2.hashCode();
134        }
135        if (N != null) {
136            cachedHashCode = +N.hashCode();
137        }
138    }
139
140    public String getC() {
141        return C;
142    }
143
144    public String getC2() {
145        return C2;
146    }
147
148    public int getF() {
149        return F;
150    }
151
152    public String getN() {
153        return N;
154    }
155
156    public int getTupleIndex() {
157        return tIndex;
158    }
159
160    @Override
161    public int hashCode() {
162        if (!hashCodeComputed) {
163            generateHashCode();
164        }
165        return cachedHashCode;
166    }
167
168    private void initializeClassStrings() {
169        if (initialized) {
170            return;
171        }
172        initialized = true;
173
174        if (!predictSimple) {
175            cachedSimpleClassName = N;
176        }
177        if (!predictOuter) {
178            cachedOuterClassString = C2;
179        }
180        // Class names must be calculated from
181        // this class name.
182        final String[] nameComponents = innerBreakAtDollar(C);
183        if (nameComponents.length == 0) {
184            // Unable to predict outer class
185            // throw new Error("Unable to predict outer class name: " + C);
186        }
187        if (nameComponents.length == 1) {
188            // Unable to predict simple class name
189            // throw new Error("Unable to predict inner class name: " + C);
190        }
191        if (nameComponents.length < 2) {
192            // If we get here, we hope cachedSimpleClassName
193            // and cachedOuterClassString were caught by the
194            // predictSimple / predictOuter code above.
195            return;
196        }
197
198        // If we get to this point, nameComponents.length must be >=2
199        final int lastPosition = nameComponents.length - 1;
200        cachedSimpleClassName = nameComponents[lastPosition];
201        cachedOuterClassString = "";
202        for (int index = 0; index < lastPosition; index++) {
203            cachedOuterClassString += nameComponents[index];
204            if (isAllDigits(nameComponents[index])) {
205                member = false;
206            }
207            if (index + 1 != lastPosition) {
208                // TODO: might need more logic to handle
209                // classes with separators of non-$ characters
210                // (ie Foo#Bar)
211                cachedOuterClassString += '$';
212            }
213        }
214        // TODO: these two blocks are the same as blocks
215        // above. Can we eliminate some by reworking the logic?
216        if (!predictSimple) {
217            cachedSimpleClassName = N;
218            cachedSimpleClassNameIndex = nIndex;
219        }
220        if (!predictOuter) {
221            cachedOuterClassString = C2;
222            cachedOuterClassIndex = c2Index;
223        }
224        if (isAllDigits(cachedSimpleClassName)) {
225            anonymous = true;
226            member = false;
227            if (nestedExplicitFlagSet()) {
228                // Predicted class - marking as member
229                member = true;
230            }
231        }
232
233        outerIsAnonymous = computeOuterIsAnonymous();
234    }
235
236    /**
237     * Break the receiver into components at $ boundaries.
238     *
239     * @param className TODO
240     * @return TODO
241     */
242    public String[] innerBreakAtDollar(final String className) {
243        final List<String> resultList = new ArrayList<>();
244        int start = 0;
245        int index = 0;
246        while (index < className.length()) {
247            if (className.charAt(index) <= '$') {
248                resultList.add(className.substring(start, index));
249                start = index + 1;
250            }
251            index++;
252            if (index >= className.length()) {
253                // Add the last element
254                resultList.add(className.substring(start));
255            }
256        }
257        return resultList.toArray(EMPTY_STRING_ARRAY);
258    }
259
260    private boolean isAllDigits(final String nameString) {
261        // Answer true if the receiver is all digits; otherwise answer false.
262        if (null == nameString) {
263            return false;
264        }
265        for (int index = 0; index < nameString.length(); index++) {
266            if (!Character.isDigit(nameString.charAt(index))) {
267                return false;
268            }
269        }
270        return true;
271    }
272
273    public boolean isAnonymous() {
274        return anonymous;
275    }
276
277    public boolean isMember() {
278        return member;
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
315     * name fields.
316     *
317     * @return true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and
318     *         name fields.
319     */
320    public boolean predicted() {
321        return predictOuter || predictSimple;
322    }
323
324    /**
325     * Answer the inner class name for the receiver.
326     *
327     * @return String name of inner class
328     */
329    public String simpleClassName() {
330        return cachedSimpleClassName;
331    }
332
333    public int simpleClassNameIndex() {
334        return cachedSimpleClassNameIndex;
335    }
336
337    public int thisClassIndex() {
338        if (predicted()) {
339            return cIndex;
340        }
341        return -1;
342    }
343
344    /**
345     * Answer the full name of the inner class represented by this tuple (including its outer component)
346     *
347     * @return String full name of inner class
348     */
349    public String thisClassString() {
350        if (predicted()) {
351            return C;
352        }
353        // TODO: this may not be right. What if I
354        // get a class like Foo#Bar$Baz$Bug?
355        return C2 + "$" + N;
356    }
357
358    @Override
359    public String toString() {
360        final StringBuilder result = new StringBuilder();
361        result.append("IcTuple ");
362        result.append('(');
363        result.append(simpleClassName());
364        result.append(" in ");
365        result.append(outerClassString());
366        result.append(')');
367        return result.toString();
368    }
369}