001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.harmony.unpack200; 020 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Objects; 024 025/** 026 * An IcTuple is the set of information that describes an inner class. 027 * 028 * C is the fully qualified class name<br> 029 * F is the flags<br> 030 * C2 is the outer class name, or null if it can be inferred from C<br> 031 * N is the inner class name, or null if it can be inferred from C<br> 032 */ 033public class IcTuple { 034 035 private static final String[] EMPTY_STRING_ARRAY = {}; 036 public static final int NESTED_CLASS_FLAG = 0x00010000; 037 static final IcTuple[] EMPTY_ARRAY = {}; 038 private final int cIndex; 039 private final int c2Index; 040 041 private final int nIndex; 042 043 private final int tIndex; 044 protected String C; // this class 045 046 protected int F; // flags 047 protected String C2; // outer class 048 protected String N; // name 049 private boolean predictSimple; 050 051 private boolean predictOuter; 052 private String cachedOuterClassString; 053 private String cachedSimpleClassName; 054 private boolean initialized; 055 private boolean anonymous; 056 private boolean outerIsAnonymous; 057 private boolean member = true; 058 private int cachedOuterClassIndex = -1; 059 private int cachedSimpleClassNameIndex = -1; 060 private boolean hashCodeComputed; 061 062 private int cachedHashCode; 063 064 /** 065 * 066 * @param C TODO 067 * @param F TODO 068 * @param C2 TODO 069 * @param N TODO 070 * @param cIndex the index of C in cpClass 071 * @param c2Index the index of C2 in cpClass, or -1 if C2 is null 072 * @param nIndex the index of N in cpUTF8, or -1 if N is null 073 * @param tIndex TODO 074 */ 075 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) { 076 this.C = C; 077 this.F = F; 078 this.C2 = C2; 079 this.N = N; 080 this.cIndex = cIndex; 081 this.c2Index = c2Index; 082 this.nIndex = nIndex; 083 this.tIndex = tIndex; 084 if (null == N) { 085 predictSimple = true; 086 } 087 if (null == C2) { 088 predictOuter = true; 089 } 090 initializeClassStrings(); 091 } 092 093 private boolean computeOuterIsAnonymous() { 094 final String[] result = innerBreakAtDollar(cachedOuterClassString); 095 if (result.length == 0) { 096 throw new Error("Should have an outer before checking if it's anonymous"); 097 } 098 099 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}