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  import java.util.Objects;
22  
23  /**
24   * An IcTuple is the set of information that describes an inner class.
25   *
26   * C is the fully qualified class name<br>
27   * F is the flags<br>
28   * C2 is the outer class name, or null if it can be inferred from C<br>
29   * N is the inner class name, or null if it can be inferred from C<br>
30   */
31  public class IcTuple {
32  
33      private static final String[] EMPTY_STRING_ARRAY = {};
34      public static final int NESTED_CLASS_FLAG = 0x00010000;
35      static final IcTuple[] EMPTY_ARRAY = {};
36      private final int cIndex;
37      private final int c2Index;
38  
39      private final int nIndex;
40  
41      private final int tIndex;
42      protected String C; // this class
43  
44      protected int F; // flags
45      protected String C2; // outer class
46      protected String N; // name
47      private boolean predictSimple;
48  
49      private boolean predictOuter;
50      private String cachedOuterClassString;
51      private String cachedSimpleClassName;
52      private boolean initialized;
53      private boolean anonymous;
54      private boolean outerIsAnonymous;
55      private boolean member = true;
56      private int cachedOuterClassIndex = -1;
57      private int cachedSimpleClassNameIndex = -1;
58      private boolean hashCodeComputed;
59  
60      private int cachedHashCode;
61  
62      /**
63       *
64       * @param C       TODO
65       * @param F       TODO
66       * @param C2      TODO
67       * @param N       TODO
68       * @param cIndex  the index of C in cpClass
69       * @param c2Index the index of C2 in cpClass, or -1 if C2 is null
70       * @param nIndex  the index of N in cpUTF8, or -1 if N is null
71       * @param tIndex  TODO
72       */
73      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) {
74          this.C = C;
75          this.F = F;
76          this.C2 = C2;
77          this.N = N;
78          this.cIndex = cIndex;
79          this.c2Index = c2Index;
80          this.nIndex = nIndex;
81          this.tIndex = tIndex;
82          if (null == N) {
83              predictSimple = true;
84          }
85          if (null == C2) {
86              predictOuter = true;
87          }
88          initializeClassStrings();
89      }
90  
91      private boolean computeOuterIsAnonymous() {
92          final String[] result = innerBreakAtDollar(cachedOuterClassString);
93          if (result.length == 0) {
94              throw new Error("Should have an outer before checking if it's anonymous");
95          }
96  
97          for (final String element : result) {
98              if (isAllDigits(element)) {
99                  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 }