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; 021import java.util.Objects; 022 023/** 024 * An IcTuple is the set of information that describes an inner class. 025 * 026 * C is the fully qualified class name<br> 027 * F is the flags<br> 028 * C2 is the outer class name, or null if it can be inferred from C<br> 029 * N is the inner class name, or null if it can be inferred from C<br> 030 */ 031public class IcTuple { 032 033 private static final String[] EMPTY_STRING_ARRAY = {}; 034 public static final int NESTED_CLASS_FLAG = 0x00010000; 035 static final IcTuple[] EMPTY_ARRAY = {}; 036 private final int cIndex; 037 private final int c2Index; 038 039 private final int nIndex; 040 041 private final int tIndex; 042 protected String C; // this class 043 044 protected int F; // flags 045 protected String C2; // outer class 046 protected String N; // name 047 private boolean predictSimple; 048 049 private boolean predictOuter; 050 private String cachedOuterClassString; 051 private String cachedSimpleClassName; 052 private boolean initialized; 053 private boolean anonymous; 054 private boolean outerIsAnonymous; 055 private boolean member = true; 056 private int cachedOuterClassIndex = -1; 057 private int cachedSimpleClassNameIndex = -1; 058 private boolean hashCodeComputed; 059 060 private int cachedHashCode; 061 062 /** 063 * 064 * @param C TODO 065 * @param F TODO 066 * @param C2 TODO 067 * @param N TODO 068 * @param cIndex the index of C in cpClass 069 * @param c2Index the index of C2 in cpClass, or -1 if C2 is null 070 * @param nIndex the index of N in cpUTF8, or -1 if N is null 071 * @param tIndex TODO 072 */ 073 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) { 074 this.C = C; 075 this.F = F; 076 this.C2 = C2; 077 this.N = N; 078 this.cIndex = cIndex; 079 this.c2Index = c2Index; 080 this.nIndex = nIndex; 081 this.tIndex = tIndex; 082 if (null == N) { 083 predictSimple = true; 084 } 085 if (null == C2) { 086 predictOuter = true; 087 } 088 initializeClassStrings(); 089 } 090 091 private boolean computeOuterIsAnonymous() { 092 final String[] result = innerBreakAtDollar(cachedOuterClassString); 093 if (result.length == 0) { 094 throw new Error("Should have an outer before checking if it's anonymous"); 095 } 096 097 for (final String element : result) { 098 if (isAllDigits(element)) { 099 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}