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, final int nIndex, final int tIndex) { 073 this.C = C; 074 this.F = F; 075 this.C2 = C2; 076 this.N = N; 077 this.cIndex = cIndex; 078 this.c2Index = c2Index; 079 this.nIndex = nIndex; 080 this.tIndex = tIndex; 081 if (null == N) { 082 predictSimple = true; 083 } 084 if (null == C2) { 085 predictOuter = true; 086 } 087 initializeClassStrings(); 088 } 089 090 private boolean computeOuterIsAnonymous() { 091 final String[] result = innerBreakAtDollar(cachedOuterClassString); 092 if (result.length == 0) { 093 throw new Error("Should have an outer before checking if it's anonymous"); 094 } 095 096 for (final String element : result) { 097 if (isAllDigits(element)) { 098 return true; 099 } 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}