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