Showing the changes to java/pkg/sun/invoke/anon/ConstantPoolPatch.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,503 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | package sun.invoke.anon; | ||
| 27 | |||
| 28 | import java.io.IOException; | ||
| 29 | import java.io.OutputStream; | ||
| 30 | import java.util.Arrays; | ||
| 31 | import java.util.HashSet; | ||
| 32 | import java.util.IdentityHashMap; | ||
| 33 | import java.util.Map; | ||
| 34 | |||
| 35 | import static sun.invoke.anon.ConstantPoolVisitor.*; | ||
| 36 | |||
| 37 | /** A class and its patched constant pool. | ||
| 38 | * | ||
| 39 | * This class allow to modify (patch) a constant pool | ||
| 40 | * by changing the value of its entry. | ||
| 41 | * Entry are referenced using index that can be get | ||
| 42 | * by parsing the constant pool using | ||
| 43 | * {@link ConstantPoolParser#parse(ConstantPoolVisitor)}. | ||
| 44 | * | ||
| 45 | * @see ConstantPoolVisitor | ||
| 46 | * @see ConstantPoolParser#createPatch() | ||
| 47 | */ | ||
| 48 | public class ConstantPoolPatch { | ||
| 49 | final ConstantPoolParser outer; | ||
| 50 | final Object[] patchArray; | ||
| 51 | |||
| 52 | ConstantPoolPatch(ConstantPoolParser outer) { | ||
| 53 | this.outer = outer; | ||
| 54 | this.patchArray = new Object[outer.getLength()]; | ||
| 55 | } | ||
| 56 | |||
| 57 | /** Create a {@link ConstantPoolParser} and | ||
| 58 | * a {@link ConstantPoolPatch} in one step. | ||
| 59 | * Equivalent to {@code new ConstantPoolParser(classFile).createPatch()}. | ||
| 60 | * | ||
| 61 | * @param classFile an array of bytes containing a class. | ||
| 62 | * @see #ConstantPoolParser(Class) | ||
| 63 | */ | ||
| 64 | public ConstantPoolPatch(byte[] classFile) throws InvalidConstantPoolFormatException { | ||
| 65 | this(new ConstantPoolParser(classFile)); | ||
| 66 | } | ||
| 67 | |||
| 68 | /** Create a {@link ConstantPoolParser} and | ||
| 69 | * a {@link ConstantPoolPatch} in one step. | ||
| 70 | * Equivalent to {@code new ConstantPoolParser(templateClass).createPatch()}. | ||
| 71 | * | ||
| 72 | * @param templateClass the class to parse. | ||
| 73 | * @see #ConstantPoolParser(Class) | ||
| 74 | */ | ||
| 75 | public ConstantPoolPatch(Class<?> templateClass) throws IOException, InvalidConstantPoolFormatException { | ||
| 76 | this(new ConstantPoolParser(templateClass)); | ||
| 77 | } | ||
| 78 | |||
| 79 | |||
| 80 | /** Creates a patch from an existing patch. | ||
| 81 | * All changes are copied from that patch. | ||
| 82 | * @param patch a patch | ||
| 83 | * | ||
| 84 | * @see ConstantPoolParser#createPatch() | ||
| 85 | */ | ||
| 86 | public ConstantPoolPatch(ConstantPoolPatch patch) { | ||
| 87 | outer = patch.outer; | ||
| 88 | patchArray = patch.patchArray.clone(); | ||
| 89 | } | ||
| 90 | |||
| 91 | /** Which parser built this patch? */ | ||
| 92 | public ConstantPoolParser getParser() { | ||
| 93 | return outer; | ||
| 94 | } | ||
| 95 | |||
| 96 | /** Report the tag at the given index in the constant pool. */ | ||
| 97 | public byte getTag(int index) { | ||
| 98 | return outer.getTag(index); | ||
| 99 | } | ||
| 100 | |||
| 101 | /** Report the current patch at the given index of the constant pool. | ||
| 102 | * Null means no patch will be made. | ||
| 103 | * To observe the unpatched entry at the given index, use | ||
| 104 | * {@link #getParser()}{@code .}@link ConstantPoolParser#parse(ConstantPoolVisitor)} | ||
| 105 | */ | ||
| 106 | public Object getPatch(int index) { | ||
| 107 | Object value = patchArray[index]; | ||
| 108 | if (value == null) return null; | ||
| 109 | switch (getTag(index)) { | ||
| 110 | case CONSTANT_Fieldref: | ||
| 111 | case CONSTANT_Methodref: | ||
| 112 | case CONSTANT_InterfaceMethodref: | ||
| 113 | if (value instanceof String) | ||
| 114 | value = stripSemis(2, (String) value); | ||
| 115 | break; | ||
| 116 | case CONSTANT_NameAndType: | ||
| 117 | if (value instanceof String) | ||
| 118 | value = stripSemis(1, (String) value); | ||
| 119 | break; | ||
| 120 | } | ||
| 121 | return value; | ||
| 122 | } | ||
| 123 | |||
| 124 | /** Clear all patches. */ | ||
| 125 | public void clear() { | ||
| 126 | Arrays.fill(patchArray, null); | ||
| 127 | } | ||
| 128 | |||
| 129 | /** Clear one patch. */ | ||
| 130 | public void clear(int index) { | ||
| 131 | patchArray[index] = null; | ||
| 132 | } | ||
| 133 | |||
| 134 | /** Produce the patches as an array. */ | ||
| 135 | public Object[] getPatches() { | ||
| 136 | return patchArray.clone(); | ||
| 137 | } | ||
| 138 | |||
| 139 | /** Produce the original constant pool as an array. */ | ||
| 140 | public Object[] getOriginalCP() throws InvalidConstantPoolFormatException { | ||
| 141 | return getOriginalCP(0, patchArray.length, -1); | ||
| 142 | } | ||
| 143 | |||
| 144 | /** Walk the constant pool, applying patches using the given map. | ||
| 145 | * | ||
| 146 | * @param utf8Map Utf8 strings to modify, if encountered | ||
| 147 | * @param classMap Classes (or their names) to modify, if encountered | ||
| 148 | * @param valueMap Constant values to modify, if encountered | ||
| 149 | * @param deleteUsedEntries if true, delete map entries that are used | ||
| 150 | */ | ||
| 151 | public void putPatches(final Map<String,String> utf8Map, | ||
| 152 | final Map<String,Object> classMap, | ||
| 153 | final Map<Object,Object> valueMap, | ||
| 154 | boolean deleteUsedEntries) throws InvalidConstantPoolFormatException { | ||
| 155 | final HashSet<String> usedUtf8Keys; | ||
| 156 | final HashSet<String> usedClassKeys; | ||
| 157 | final HashSet<Object> usedValueKeys; | ||
| 158 | if (deleteUsedEntries) { | ||
| 159 | usedUtf8Keys = (utf8Map == null) ? null : new HashSet<String>(); | ||
| 160 | usedClassKeys = (classMap == null) ? null : new HashSet<String>(); | ||
| 161 | usedValueKeys = (valueMap == null) ? null : new HashSet<Object>(); | ||
| 162 | } else { | ||
| 163 | usedUtf8Keys = null; | ||
| 164 | usedClassKeys = null; | ||
| 165 | usedValueKeys = null; | ||
| 166 | } | ||
| 167 | |||
| 168 | outer.parse(new ConstantPoolVisitor() { | ||
| 169 | |||
| 170 | @Override | ||
| 171 | public void visitUTF8(int index, byte tag, String utf8) { | ||
| 172 | putUTF8(index, utf8Map.get(utf8)); | ||
| 173 | if (usedUtf8Keys != null) usedUtf8Keys.add(utf8); | ||
| 174 | } | ||
| 175 | |||
| 176 | @Override | ||
| 177 | public void visitConstantValue(int index, byte tag, Object value) { | ||
| 178 | putConstantValue(index, tag, valueMap.get(value)); | ||
| 179 | if (usedValueKeys != null) usedValueKeys.add(value); | ||
| 180 | } | ||
| 181 | |||
| 182 | @Override | ||
| 183 | public void visitConstantString(int index, byte tag, String name, int nameIndex) { | ||
| 184 | if (tag == CONSTANT_Class) { | ||
| 185 | putConstantValue(index, tag, classMap.get(name)); | ||
| 186 | if (usedClassKeys != null) usedClassKeys.add(name); | ||
| 187 | } else { | ||
| 188 | assert(tag == CONSTANT_String); | ||
| 189 | visitConstantValue(index, tag, name); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | }); | ||
| 193 | if (usedUtf8Keys != null) utf8Map.keySet().removeAll(usedUtf8Keys); | ||
| 194 | if (usedClassKeys != null) classMap.keySet().removeAll(usedClassKeys); | ||
| 195 | if (usedValueKeys != null) valueMap.keySet().removeAll(usedValueKeys); | ||
| 196 | } | ||
| 197 | |||
| 198 | Object[] getOriginalCP(final int startIndex, | ||
| 199 | final int endIndex, | ||
| 200 | final int tagMask) throws InvalidConstantPoolFormatException { | ||
| 201 | final Object[] cpArray = new Object[endIndex - startIndex]; | ||
| 202 | outer.parse(new ConstantPoolVisitor() { | ||
| 203 | |||
| 204 | void show(int index, byte tag, Object value) { | ||
| 205 | if (index < startIndex || index >= endIndex) return; | ||
| 206 | if (((1 << tag) & tagMask) == 0) return; | ||
| 207 | cpArray[index - startIndex] = value; | ||
| 208 | } | ||
| 209 | |||
| 210 | @Override | ||
| 211 | public void visitUTF8(int index, byte tag, String utf8) { | ||
| 212 | show(index, tag, utf8); | ||
| 213 | } | ||
| 214 | |||
| 215 | @Override | ||
| 216 | public void visitConstantValue(int index, byte tag, Object value) { | ||
| 217 | assert(tag != CONSTANT_String); | ||
| 218 | show(index, tag, value); | ||
| 219 | } | ||
| 220 | |||
| 221 | @Override | ||
| 222 | public void visitConstantString(int index, byte tag, | ||
| 223 | String value, int j) { | ||
| 224 | show(index, tag, value); | ||
| 225 | } | ||
| 226 | |||
| 227 | @Override | ||
| 228 | public void visitMemberRef(int index, byte tag, | ||
| 229 | String className, String memberName, | ||
| 230 | String signature, | ||
| 231 | int j, int k) { | ||
| 232 | show(index, tag, new String[]{ className, memberName, signature }); | ||
| 233 | } | ||
| 234 | |||
| 235 | @Override | ||
| 236 | public void visitDescriptor(int index, byte tag, | ||
| 237 | String memberName, String signature, | ||
| 238 | int j, int k) { | ||
| 239 | show(index, tag, new String[]{ memberName, signature }); | ||
| 240 | } | ||
| 241 | }); | ||
| 242 | return cpArray; | ||
| 243 | } | ||
| 244 | |||
| 245 | /** Write the head (header plus constant pool) | ||
| 246 | * of the patched class file to the indicated stream. | ||
| 247 | */ | ||
| 248 | void writeHead(OutputStream out) throws IOException { | ||
| 249 | outer.writePatchedHead(out, patchArray); | ||
| 250 | } | ||
| 251 | |||
| 252 | /** Write the tail (everything after the constant pool) | ||
| 253 | * of the patched class file to the indicated stream. | ||
| 254 | */ | ||
| 255 | void writeTail(OutputStream out) throws IOException { | ||
| 256 | outer.writeTail(out); | ||
| 257 | } | ||
| 258 | |||
| 259 | private void checkConstantTag(byte tag, Object value) { | ||
| 260 | if (value == null) | ||
| 261 | throw new IllegalArgumentException( | ||
| 262 | "invalid null constant value"); | ||
| 263 | if (classForTag(tag) != value.getClass()) | ||
| 264 | throw new IllegalArgumentException( | ||
| 265 | "invalid constant value" | ||
| 266 | + (tag == CONSTANT_None ? "" | ||
| 267 | : " for tag "+tagName(tag)) | ||
| 268 | + " of class "+value.getClass()); | ||
| 269 | } | ||
| 270 | |||
| 271 | private void checkTag(int index, byte putTag) { | ||
| 272 | byte tag = outer.tags[index]; | ||
| 273 | if (tag != putTag) | ||
| 274 | throw new IllegalArgumentException( | ||
| 275 | "invalid put operation" | ||
| 276 | + " for " + tagName(putTag) | ||
| 277 | + " at index " + index + " found " + tagName(tag)); | ||
| 278 | } | ||
| 279 | |||
| 280 | private void checkTagMask(int index, int tagBitMask) { | ||
| 281 | byte tag = outer.tags[index]; | ||
| 282 | int tagBit = ((tag & 0x1F) == tag) ? (1 << tag) : 0; | ||
| 283 | if ((tagBit & tagBitMask) == 0) | ||
| 284 | throw new IllegalArgumentException( | ||
| 285 | "invalid put operation" | ||
| 286 | + " at index " + index + " found " + tagName(tag)); | ||
| 287 | } | ||
| 288 | |||
| 289 | private static void checkMemberName(String memberName) { | ||
| 290 | if (memberName.indexOf(';') >= 0) | ||
| 291 | throw new IllegalArgumentException("memberName " + memberName + " contains a ';'"); | ||
| 292 | } | ||
| 293 | |||
| 294 | /** Set the entry of the constant pool indexed by index to | ||
| 295 | * a new string. | ||
| 296 | * | ||
| 297 | * @param index an index to a constant pool entry containing a | ||
| 298 | * {@link ConstantPoolVisitor#CONSTANT_Utf8} value. | ||
| 299 | * @param utf8 a string | ||
| 300 | * | ||
| 301 | * @see ConstantPoolVisitor#visitUTF8(int, byte, String) | ||
| 302 | */ | ||
| 303 | public void putUTF8(int index, String utf8) { | ||
| 304 | if (utf8 == null) { clear(index); return; } | ||
| 305 | checkTag(index, CONSTANT_Utf8); | ||
| 306 | patchArray[index] = utf8; | ||
| 307 | } | ||
| 308 | |||
| 309 | /** Set the entry of the constant pool indexed by index to | ||
| 310 | * a new value, depending on its dynamic type. | ||
| 311 | * | ||
| 312 | * @param index an index to a constant pool entry containing a | ||
| 313 | * one of the following structures: | ||
| 314 | * {@link ConstantPoolVisitor#CONSTANT_Integer}, | ||
| 315 | * {@link ConstantPoolVisitor#CONSTANT_Float}, | ||
| 316 | * {@link ConstantPoolVisitor#CONSTANT_Long}, | ||
| 317 | * {@link ConstantPoolVisitor#CONSTANT_Double}, | ||
| 318 | * {@link ConstantPoolVisitor#CONSTANT_String}, or | ||
| 319 | * {@link ConstantPoolVisitor#CONSTANT_Class} | ||
| 320 | * @param value a boxed int, float, long or double; or a string or class object | ||
| 321 | * @throws IllegalArgumentException if the type of the constant does not | ||
| 322 | * match the constant pool entry type, | ||
| 323 | * as reported by {@link #getTag(int)} | ||
| 324 | * | ||
| 325 | * @see #putConstantValue(int, byte, Object) | ||
| 326 | * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object) | ||
| 327 | * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int) | ||
| 328 | */ | ||
| 329 | public void putConstantValue(int index, Object value) { | ||
| 330 | if (value == null) { clear(index); return; } | ||
| 331 | byte tag = tagForConstant(value.getClass()); | ||
| 332 | checkConstantTag(tag, value); | ||
| 333 | checkTag(index, tag); | ||
| 334 | patchArray[index] = value; | ||
| 335 | } | ||
| 336 | |||
| 337 | /** Set the entry of the constant pool indexed by index to | ||
| 338 | * a new value. | ||
| 339 | * | ||
| 340 | * @param index an index to a constant pool entry matching the given tag | ||
| 341 | * @param tag one of the following values: | ||
| 342 | * {@link ConstantPoolVisitor#CONSTANT_Integer}, | ||
| 343 | * {@link ConstantPoolVisitor#CONSTANT_Float}, | ||
| 344 | * {@link ConstantPoolVisitor#CONSTANT_Long}, | ||
| 345 | * {@link ConstantPoolVisitor#CONSTANT_Double}, | ||
| 346 | * {@link ConstantPoolVisitor#CONSTANT_String}, or | ||
| 347 | * {@link ConstantPoolVisitor#CONSTANT_Class} | ||
| 348 | * @param value a boxed number, string, or class object | ||
| 349 | * @throws IllegalArgumentException if the type of the constant does not | ||
| 350 | * match the constant pool entry type, or if a class name contains | ||
| 351 | * '/' or ';' | ||
| 352 | * | ||
| 353 | * @see #putConstantValue(int, Object) | ||
| 354 | * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object) | ||
| 355 | * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int) | ||
| 356 | */ | ||
| 357 | public void putConstantValue(int index, byte tag, Object value) { | ||
| 358 | if (value == null) { clear(index); return; } | ||
| 359 | checkTag(index, tag); | ||
| 360 | if (tag == CONSTANT_Class && value instanceof String) { | ||
| 361 | checkClassName((String) value); | ||
| 362 | } else if (tag == CONSTANT_String) { | ||
| 363 | // the JVM accepts any object as a patch for a string | ||
| 364 | } else { | ||
| 365 | // make sure the incoming value is the right type | ||
| 366 | checkConstantTag(tag, value); | ||
| 367 | } | ||
| 368 | checkTag(index, tag); | ||
| 369 | patchArray[index] = value; | ||
| 370 | } | ||
| 371 | |||
| 372 | /** Set the entry of the constant pool indexed by index to | ||
| 373 | * a new {@link ConstantPoolVisitor#CONSTANT_NameAndType} value. | ||
| 374 | * | ||
| 375 | * @param index an index to a constant pool entry containing a | ||
| 376 | * {@link ConstantPoolVisitor#CONSTANT_NameAndType} value. | ||
| 377 | * @param memberName a memberName | ||
| 378 | * @param signature a signature | ||
| 379 | * @throws IllegalArgumentException if memberName contains the character ';' | ||
| 380 | * | ||
| 381 | * @see ConstantPoolVisitor#visitDescriptor(int, byte, String, String, int, int) | ||
| 382 | */ | ||
| 383 | public void putDescriptor(int index, String memberName, String signature) { | ||
| 384 | checkTag(index, CONSTANT_NameAndType); | ||
| 385 | checkMemberName(memberName); | ||
| 386 | patchArray[index] = addSemis(memberName, signature); | ||
| 387 | } | ||
| 388 | |||
| 389 | /** Set the entry of the constant pool indexed by index to | ||
| 390 | * a new {@link ConstantPoolVisitor#CONSTANT_Fieldref}, | ||
| 391 | * {@link ConstantPoolVisitor#CONSTANT_Methodref}, or | ||
| 392 | * {@link ConstantPoolVisitor#CONSTANT_InterfaceMethodref} value. | ||
| 393 | * | ||
| 394 | * @param index an index to a constant pool entry containing a member reference | ||
| 395 | * @param className a class name | ||
| 396 | * @param memberName a field or method name | ||
| 397 | * @param signature a field or method signature | ||
| 398 | * @throws IllegalArgumentException if memberName contains the character ';' | ||
| 399 | * or signature is not a correct signature | ||
| 400 | * | ||
| 401 | * @see ConstantPoolVisitor#visitMemberRef(int, byte, String, String, String, int, int) | ||
| 402 | */ | ||
| 403 | public void putMemberRef(int index, byte tag, | ||
| 404 | String className, String memberName, String signature) { | ||
| 405 | checkTagMask(tag, CONSTANT_MemberRef_MASK); | ||
| 406 | checkTag(index, tag); | ||
| 407 | checkClassName(className); | ||
| 408 | checkMemberName(memberName); | ||
| 409 | if (signature.startsWith("(") == (tag == CONSTANT_Fieldref)) | ||
| 410 | throw new IllegalArgumentException("bad signature: "+signature); | ||
| 411 | patchArray[index] = addSemis(className, memberName, signature); | ||
| 412 | } | ||
| 413 | |||
| 414 | static private final int CONSTANT_MemberRef_MASK = | ||
| 415 | CONSTANT_Fieldref | ||
| 416 | | CONSTANT_Methodref | ||
| 417 | | CONSTANT_InterfaceMethodref; | ||
| 418 | |||
| 419 | private static final Map<Class<?>, Byte> CONSTANT_VALUE_CLASS_TAG | ||
| 420 | = new IdentityHashMap<Class<?>, Byte>(); | ||
| 421 | private static final Class[] CONSTANT_VALUE_CLASS = new Class[16]; | ||
| 422 | static { | ||
| 423 | Object[][] values = { | ||
| 424 | {Integer.class, CONSTANT_Integer}, | ||
| 425 | {Long.class, CONSTANT_Long}, | ||
| 426 | {Float.class, CONSTANT_Float}, | ||
| 427 | {Double.class, CONSTANT_Double}, | ||
| 428 | {String.class, CONSTANT_String}, | ||
| 429 | {Class.class, CONSTANT_Class} | ||
| 430 | }; | ||
| 431 | for (Object[] value : values) { | ||
| 432 | Class<?> cls = (Class<?>)value[0]; | ||
| 433 | Byte tag = (Byte) value[1]; | ||
| 434 | CONSTANT_VALUE_CLASS_TAG.put(cls, tag); | ||
| 435 | CONSTANT_VALUE_CLASS[(byte)tag] = cls; | ||
| 436 | } | ||
| 437 | } | ||
| 438 | |||
| 439 | static Class<?> classForTag(byte tag) { | ||
| 440 | if ((tag & 0xFF) >= CONSTANT_VALUE_CLASS.length) | ||
| 441 | return null; | ||
| 442 | return CONSTANT_VALUE_CLASS[tag]; | ||
| 443 | } | ||
| 444 | |||
| 445 | static byte tagForConstant(Class<?> cls) { | ||
| 446 | Byte tag = CONSTANT_VALUE_CLASS_TAG.get(cls); | ||
| 447 | return (tag == null) ? CONSTANT_None : (byte)tag; | ||
| 448 | } | ||
| 449 | |||
| 450 | private static void checkClassName(String className) { | ||
| 451 | if (className.indexOf('/') >= 0 || className.indexOf(';') >= 0) | ||
| 452 | throw new IllegalArgumentException("invalid class name " + className); | ||
| 453 | } | ||
| 454 | |||
| 455 | static String addSemis(String name, String... names) { | ||
| 456 | StringBuilder buf = new StringBuilder(name.length() * 5); | ||
| 457 | buf.append(name); | ||
| 458 | for (String name2 : names) { | ||
| 459 | buf.append(';').append(name2); | ||
| 460 | } | ||
| 461 | String res = buf.toString(); | ||
| 462 | assert(stripSemis(names.length, res)[0].equals(name)); | ||
| 463 | assert(stripSemis(names.length, res)[1].equals(names[0])); | ||
| 464 | assert(names.length == 1 || | ||
| 465 | stripSemis(names.length, res)[2].equals(names[1])); | ||
| 466 | return res; | ||
| 467 | } | ||
| 468 | |||
| 469 | static String[] stripSemis(int count, String string) { | ||
| 470 | String[] res = new String[count+1]; | ||
| 471 | int pos = 0; | ||
| 472 | for (int i = 0; i < count; i++) { | ||
| 473 | int pos2 = string.indexOf(';', pos); | ||
| 474 | if (pos2 < 0) pos2 = string.length(); // yuck | ||
| 475 | res[i] = string.substring(pos, pos2); | ||
| 476 | pos = pos2; | ||
| 477 | } | ||
| 478 | res[count] = string.substring(pos); | ||
| 479 | return res; | ||
| 480 | } | ||
| 481 | |||
| 482 | public String toString() { | ||
| 483 | StringBuilder buf = new StringBuilder(this.getClass().getName()); | ||
| 484 | buf.append("{"); | ||
| 485 | Object[] origCP = null; | ||
| 486 | for (int i = 0; i < patchArray.length; i++) { | ||
| 487 | if (patchArray[i] == null) continue; | ||
| 488 | if (origCP != null) { | ||
| 489 | buf.append(", "); | ||
| 490 | } else { | ||
| 491 | try { | ||
| 492 | origCP = getOriginalCP(); | ||
| 493 | } catch (InvalidConstantPoolFormatException ee) { | ||
| 494 | origCP = new Object[0]; | ||
| 495 | } | ||
| 496 | } | ||
| 497 | Object orig = (i < origCP.length) ? origCP[i] : "?"; | ||
| 498 | buf.append(orig).append("=").append(patchArray[i]); | ||
| 499 | } | ||
| 500 | buf.append("}"); | ||
| 501 | return buf.toString(); | ||
| 502 | } | ||
| 503 | } | ||