View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.unpack200;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  /**
23   * An IcTuple is the set of information that describes an inner class.
24   *
25   * C is the fully qualified class name<br>
26   * F is the flags<br>
27   * C2 is the outer class name, or null if it can be inferred from C<br>
28   * N is the inner class name, or null if it can be inferred from C<br>
29   */
30  public class IcTuple {
31  
32      private static final String[] EMPTY_STRING_ARRAY = {};
33      public static final int NESTED_CLASS_FLAG = 0x00010000;
34      static final IcTuple[] EMPTY_ARRAY = {};
35      private final int cIndex;
36      private final int c2Index;
37  
38      private final int nIndex;
39  
40      private final int tIndex;
41      protected String C; // this class
42  
43      protected int F; // flags
44      protected String C2; // outer class
45      protected String N; // name
46      private boolean predictSimple;
47  
48      private boolean predictOuter;
49      private String cachedOuterClassString;
50      private String cachedSimpleClassName;
51      private boolean initialized;
52      private boolean anonymous;
53      private boolean outerIsAnonymous;
54      private boolean member = true;
55      private int cachedOuterClassIndex = -1;
56      private int cachedSimpleClassNameIndex = -1;
57      private boolean hashCodeComputed;
58  
59      private int cachedHashCode;
60  
61      /**
62       *
63       * @param C       TODO
64       * @param F       TODO
65       * @param C2      TODO
66       * @param N       TODO
67       * @param cIndex  the index of C in cpClass
68       * @param c2Index the index of C2 in cpClass, or -1 if C2 is null
69       * @param nIndex  the index of N in cpUTF8, or -1 if N is null
70       * @param tIndex  TODO
71       */
72      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) {
73          this.C = C;
74          this.F = F;
75          this.C2 = C2;
76          this.N = N;
77          this.cIndex = cIndex;
78          this.c2Index = c2Index;
79          this.nIndex = nIndex;
80          this.tIndex = tIndex;
81          if (null == N) {
82              predictSimple = true;
83          }
84          if (null == C2) {
85              predictOuter = true;
86          }
87          initializeClassStrings();
88      }
89  
90      private boolean computeOuterIsAnonymous() {
91          final String[] result = innerBreakAtDollar(cachedOuterClassString);
92          if (result.length == 0) {
93              throw new Error("Should have an outer before checking if it's anonymous");
94          }
95  
96          for (final String element : result) {
97              if (isAllDigits(element)) {
98                  return true;
99              }
100         }
101         return false;
102     }
103 
104     @Override
105     public boolean equals(final Object object) {
106         if (object == null || object.getClass() != this.getClass()) {
107             return false;
108         }
109         final IcTuple compareTuple = (IcTuple) object;
110 
111         if (!nullSafeEquals(this.C, compareTuple.C)) {
112             return false;
113         }
114 
115         if (!nullSafeEquals(this.C2, compareTuple.C2)) {
116             return false;
117         }
118 
119         if (!nullSafeEquals(this.N, compareTuple.N)) {
120             return false;
121         }
122         return true;
123     }
124 
125     private void generateHashCode() {
126         hashCodeComputed = true;
127         cachedHashCode = 17;
128         if (C != null) {
129             cachedHashCode = +C.hashCode();
130         }
131         if (C2 != null) {
132             cachedHashCode = +C2.hashCode();
133         }
134         if (N != null) {
135             cachedHashCode = +N.hashCode();
136         }
137     }
138 
139     public String getC() {
140         return C;
141     }
142 
143     public String getC2() {
144         return C2;
145     }
146 
147     public int getF() {
148         return F;
149     }
150 
151     public String getN() {
152         return N;
153     }
154 
155     public int getTupleIndex() {
156         return tIndex;
157     }
158 
159     @Override
160     public int hashCode() {
161         if (!hashCodeComputed) {
162             generateHashCode();
163         }
164         return cachedHashCode;
165     }
166 
167     private void initializeClassStrings() {
168         if (initialized) {
169             return;
170         }
171         initialized = true;
172 
173         if (!predictSimple) {
174             cachedSimpleClassName = N;
175         }
176         if (!predictOuter) {
177             cachedOuterClassString = C2;
178         }
179         // Class names must be calculated from
180         // this class name.
181         final String[] nameComponents = innerBreakAtDollar(C);
182         if (nameComponents.length == 0) {
183             // Unable to predict outer class
184             // throw new Error("Unable to predict outer class name: " + C);
185         }
186         if (nameComponents.length == 1) {
187             // Unable to predict simple class name
188             // throw new Error("Unable to predict inner class name: " + C);
189         }
190         if (nameComponents.length < 2) {
191             // If we get here, we hope cachedSimpleClassName
192             // and cachedOuterClassString were caught by the
193             // predictSimple / predictOuter code above.
194             return;
195         }
196 
197         // If we get to this point, nameComponents.length must be >=2
198         final int lastPosition = nameComponents.length - 1;
199         cachedSimpleClassName = nameComponents[lastPosition];
200         cachedOuterClassString = "";
201         for (int index = 0; index < lastPosition; index++) {
202             cachedOuterClassString += nameComponents[index];
203             if (isAllDigits(nameComponents[index])) {
204                 member = false;
205             }
206             if (index + 1 != lastPosition) {
207                 // TODO: might need more logic to handle
208                 // classes with separators of non-$ characters
209                 // (ie Foo#Bar)
210                 cachedOuterClassString += '$';
211             }
212         }
213         // TODO: these two blocks are the same as blocks
214         // above. Can we eliminate some by reworking the logic?
215         if (!predictSimple) {
216             cachedSimpleClassName = N;
217             cachedSimpleClassNameIndex = nIndex;
218         }
219         if (!predictOuter) {
220             cachedOuterClassString = C2;
221             cachedOuterClassIndex = c2Index;
222         }
223         if (isAllDigits(cachedSimpleClassName)) {
224             anonymous = true;
225             member = false;
226             if (nestedExplicitFlagSet()) {
227                 // Predicted class - marking as member
228                 member = true;
229             }
230         }
231 
232         outerIsAnonymous = computeOuterIsAnonymous();
233     }
234 
235     /**
236      * Break the receiver into components at $ boundaries.
237      *
238      * @param className TODO
239      * @return TODO
240      */
241     public String[] innerBreakAtDollar(final String className) {
242         final List<String> resultList = new ArrayList<>();
243         int start = 0;
244         int index = 0;
245         while (index < className.length()) {
246             if (className.charAt(index) <= '$') {
247                 resultList.add(className.substring(start, index));
248                 start = index + 1;
249             }
250             index++;
251             if (index >= className.length()) {
252                 // Add the last element
253                 resultList.add(className.substring(start));
254             }
255         }
256         return resultList.toArray(EMPTY_STRING_ARRAY);
257     }
258 
259     private boolean isAllDigits(final String nameString) {
260         // Answer true if the receiver is all digits; otherwise answer false.
261         if (null == nameString) {
262             return false;
263         }
264         for (int index = 0; index < nameString.length(); index++) {
265             if (!Character.isDigit(nameString.charAt(index))) {
266                 return false;
267             }
268         }
269         return true;
270     }
271 
272     public boolean isAnonymous() {
273         return anonymous;
274     }
275 
276     public boolean isMember() {
277         return member;
278     }
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 name fields.
315      *
316      * @return true if the receiver is predicted; answer false if the receiver is specified explicitly in the outer and name fields.
317      */
318     public boolean predicted() {
319         return predictOuter || predictSimple;
320     }
321 
322     /**
323      * Answer the inner class name for the receiver.
324      *
325      * @return String name of inner class
326      */
327     public String simpleClassName() {
328         return cachedSimpleClassName;
329     }
330 
331     public int simpleClassNameIndex() {
332         return cachedSimpleClassNameIndex;
333     }
334 
335     public int thisClassIndex() {
336         if (predicted()) {
337             return cIndex;
338         }
339         return -1;
340     }
341 
342     /**
343      * Answer the full name of the inner class represented by this tuple (including its outer component)
344      *
345      * @return String full name of inner class
346      */
347     public String thisClassString() {
348         if (predicted()) {
349             return C;
350         }
351         // TODO: this may not be right. What if I
352         // get a class like Foo#Bar$Baz$Bug?
353         return C2 + "$" + N;
354     }
355 
356     @Override
357     public String toString() {
358         final StringBuilder result = new StringBuilder();
359         result.append("IcTuple ");
360         result.append('(');
361         result.append(simpleClassName());
362         result.append(" in ");
363         result.append(outerClassString());
364         result.append(')');
365         return result.toString();
366     }
367 }