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