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}