rt/emul/compact/src/main/java/sun/invoke/anon/ConstantPoolPatch.java
branchjdk8-b132
changeset 1646 c880a8a8803b
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/rt/emul/compact/src/main/java/sun/invoke/anon/ConstantPoolPatch.java	Sat Aug 09 11:11:13 2014 +0200
     1.3 @@ -0,0 +1,503 @@
     1.4 +/*
     1.5 + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
     1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     1.7 + *
     1.8 + * This code is free software; you can redistribute it and/or modify it
     1.9 + * under the terms of the GNU General Public License version 2 only, as
    1.10 + * published by the Free Software Foundation.  Oracle designates this
    1.11 + * particular file as subject to the "Classpath" exception as provided
    1.12 + * by Oracle in the LICENSE file that accompanied this code.
    1.13 + *
    1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
    1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    1.16 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    1.17 + * version 2 for more details (a copy is included in the LICENSE file that
    1.18 + * accompanied this code).
    1.19 + *
    1.20 + * You should have received a copy of the GNU General Public License version
    1.21 + * 2 along with this work; if not, write to the Free Software Foundation,
    1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    1.23 + *
    1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    1.25 + * or visit www.oracle.com if you need additional information or have any
    1.26 + * questions.
    1.27 + */
    1.28 +
    1.29 +package sun.invoke.anon;
    1.30 +
    1.31 +import java.io.IOException;
    1.32 +import java.io.OutputStream;
    1.33 +import java.util.Arrays;
    1.34 +import java.util.HashSet;
    1.35 +import java.util.IdentityHashMap;
    1.36 +import java.util.Map;
    1.37 +
    1.38 +import static sun.invoke.anon.ConstantPoolVisitor.*;
    1.39 +
    1.40 +/** A class and its patched constant pool.
    1.41 + *
    1.42 + *  This class allow to modify (patch) a constant pool
    1.43 + *  by changing the value of its entry.
    1.44 + *  Entry are referenced using index that can be get
    1.45 + *  by parsing the constant pool using
    1.46 + *  {@link ConstantPoolParser#parse(ConstantPoolVisitor)}.
    1.47 + *
    1.48 + * @see ConstantPoolVisitor
    1.49 + * @see ConstantPoolParser#createPatch()
    1.50 + */
    1.51 +public class ConstantPoolPatch {
    1.52 +    final ConstantPoolParser outer;
    1.53 +    final Object[] patchArray;
    1.54 +
    1.55 +    ConstantPoolPatch(ConstantPoolParser outer) {
    1.56 +        this.outer      = outer;
    1.57 +        this.patchArray = new Object[outer.getLength()];
    1.58 +    }
    1.59 +
    1.60 +    /** Create a {@link ConstantPoolParser} and
    1.61 +     *  a {@link ConstantPoolPatch} in one step.
    1.62 +     *  Equivalent to {@code new ConstantPoolParser(classFile).createPatch()}.
    1.63 +     *
    1.64 +     * @param classFile an array of bytes containing a class.
    1.65 +     * @see #ConstantPoolParser(Class)
    1.66 +     */
    1.67 +    public ConstantPoolPatch(byte[] classFile) throws InvalidConstantPoolFormatException {
    1.68 +        this(new ConstantPoolParser(classFile));
    1.69 +    }
    1.70 +
    1.71 +    /** Create a {@link ConstantPoolParser} and
    1.72 +     *  a {@link ConstantPoolPatch} in one step.
    1.73 +     *  Equivalent to {@code new ConstantPoolParser(templateClass).createPatch()}.
    1.74 +     *
    1.75 +     * @param templateClass the class to parse.
    1.76 +     * @see #ConstantPoolParser(Class)
    1.77 +     */
    1.78 +    public ConstantPoolPatch(Class<?> templateClass) throws IOException, InvalidConstantPoolFormatException {
    1.79 +        this(new ConstantPoolParser(templateClass));
    1.80 +    }
    1.81 +
    1.82 +
    1.83 +    /** Creates a patch from an existing patch.
    1.84 +     *  All changes are copied from that patch.
    1.85 +     * @param patch a patch
    1.86 +     *
    1.87 +     * @see ConstantPoolParser#createPatch()
    1.88 +     */
    1.89 +    public ConstantPoolPatch(ConstantPoolPatch patch) {
    1.90 +        outer      = patch.outer;
    1.91 +        patchArray = patch.patchArray.clone();
    1.92 +    }
    1.93 +
    1.94 +    /** Which parser built this patch? */
    1.95 +    public ConstantPoolParser getParser() {
    1.96 +        return outer;
    1.97 +    }
    1.98 +
    1.99 +    /** Report the tag at the given index in the constant pool. */
   1.100 +    public byte getTag(int index) {
   1.101 +        return outer.getTag(index);
   1.102 +    }
   1.103 +
   1.104 +    /** Report the current patch at the given index of the constant pool.
   1.105 +     *  Null means no patch will be made.
   1.106 +     *  To observe the unpatched entry at the given index, use
   1.107 +     *  {@link #getParser()}{@code .}@link ConstantPoolParser#parse(ConstantPoolVisitor)}
   1.108 +     */
   1.109 +    public Object getPatch(int index) {
   1.110 +        Object value = patchArray[index];
   1.111 +        if (value == null)  return null;
   1.112 +        switch (getTag(index)) {
   1.113 +        case CONSTANT_Fieldref:
   1.114 +        case CONSTANT_Methodref:
   1.115 +        case CONSTANT_InterfaceMethodref:
   1.116 +            if (value instanceof String)
   1.117 +                value = stripSemis(2, (String) value);
   1.118 +            break;
   1.119 +        case CONSTANT_NameAndType:
   1.120 +            if (value instanceof String)
   1.121 +                value = stripSemis(1, (String) value);
   1.122 +            break;
   1.123 +        }
   1.124 +        return value;
   1.125 +    }
   1.126 +
   1.127 +    /** Clear all patches. */
   1.128 +    public void clear() {
   1.129 +        Arrays.fill(patchArray, null);
   1.130 +    }
   1.131 +
   1.132 +    /** Clear one patch. */
   1.133 +    public void clear(int index) {
   1.134 +        patchArray[index] = null;
   1.135 +    }
   1.136 +
   1.137 +    /** Produce the patches as an array. */
   1.138 +    public Object[] getPatches() {
   1.139 +        return patchArray.clone();
   1.140 +    }
   1.141 +
   1.142 +    /** Produce the original constant pool as an array. */
   1.143 +    public Object[] getOriginalCP() throws InvalidConstantPoolFormatException {
   1.144 +        return getOriginalCP(0, patchArray.length, -1);
   1.145 +    }
   1.146 +
   1.147 +    /** Walk the constant pool, applying patches using the given map.
   1.148 +     *
   1.149 +     * @param utf8Map Utf8 strings to modify, if encountered
   1.150 +     * @param classMap Classes (or their names) to modify, if encountered
   1.151 +     * @param valueMap Constant values to modify, if encountered
   1.152 +     * @param deleteUsedEntries if true, delete map entries that are used
   1.153 +     */
   1.154 +    public void putPatches(final Map<String,String> utf8Map,
   1.155 +                           final Map<String,Object> classMap,
   1.156 +                           final Map<Object,Object> valueMap,
   1.157 +                           boolean deleteUsedEntries) throws InvalidConstantPoolFormatException {
   1.158 +        final HashSet<String> usedUtf8Keys;
   1.159 +        final HashSet<String> usedClassKeys;
   1.160 +        final HashSet<Object> usedValueKeys;
   1.161 +        if (deleteUsedEntries) {
   1.162 +            usedUtf8Keys  = (utf8Map  == null) ? null : new HashSet<String>();
   1.163 +            usedClassKeys = (classMap == null) ? null : new HashSet<String>();
   1.164 +            usedValueKeys = (valueMap == null) ? null : new HashSet<Object>();
   1.165 +        } else {
   1.166 +            usedUtf8Keys = null;
   1.167 +            usedClassKeys = null;
   1.168 +            usedValueKeys = null;
   1.169 +        }
   1.170 +
   1.171 +        outer.parse(new ConstantPoolVisitor() {
   1.172 +
   1.173 +            @Override
   1.174 +            public void visitUTF8(int index, byte tag, String utf8) {
   1.175 +                putUTF8(index, utf8Map.get(utf8));
   1.176 +                if (usedUtf8Keys != null)  usedUtf8Keys.add(utf8);
   1.177 +            }
   1.178 +
   1.179 +            @Override
   1.180 +            public void visitConstantValue(int index, byte tag, Object value) {
   1.181 +                putConstantValue(index, tag, valueMap.get(value));
   1.182 +                if (usedValueKeys != null)  usedValueKeys.add(value);
   1.183 +            }
   1.184 +
   1.185 +            @Override
   1.186 +            public void visitConstantString(int index, byte tag, String name, int nameIndex) {
   1.187 +                if (tag == CONSTANT_Class) {
   1.188 +                    putConstantValue(index, tag, classMap.get(name));
   1.189 +                    if (usedClassKeys != null)  usedClassKeys.add(name);
   1.190 +                } else {
   1.191 +                    assert(tag == CONSTANT_String);
   1.192 +                    visitConstantValue(index, tag, name);
   1.193 +                }
   1.194 +            }
   1.195 +        });
   1.196 +        if (usedUtf8Keys != null)   utf8Map.keySet().removeAll(usedUtf8Keys);
   1.197 +        if (usedClassKeys != null)  classMap.keySet().removeAll(usedClassKeys);
   1.198 +        if (usedValueKeys != null)  valueMap.keySet().removeAll(usedValueKeys);
   1.199 +    }
   1.200 +
   1.201 +    Object[] getOriginalCP(final int startIndex,
   1.202 +                           final int endIndex,
   1.203 +                           final int tagMask) throws InvalidConstantPoolFormatException {
   1.204 +        final Object[] cpArray = new Object[endIndex - startIndex];
   1.205 +        outer.parse(new ConstantPoolVisitor() {
   1.206 +
   1.207 +            void show(int index, byte tag, Object value) {
   1.208 +                if (index < startIndex || index >= endIndex)  return;
   1.209 +                if (((1 << tag) & tagMask) == 0)  return;
   1.210 +                cpArray[index - startIndex] = value;
   1.211 +            }
   1.212 +
   1.213 +            @Override
   1.214 +            public void visitUTF8(int index, byte tag, String utf8) {
   1.215 +                show(index, tag, utf8);
   1.216 +            }
   1.217 +
   1.218 +            @Override
   1.219 +            public void visitConstantValue(int index, byte tag, Object value) {
   1.220 +                assert(tag != CONSTANT_String);
   1.221 +                show(index, tag, value);
   1.222 +            }
   1.223 +
   1.224 +            @Override
   1.225 +            public void visitConstantString(int index, byte tag,
   1.226 +                                            String value, int j) {
   1.227 +                show(index, tag, value);
   1.228 +            }
   1.229 +
   1.230 +            @Override
   1.231 +            public void visitMemberRef(int index, byte tag,
   1.232 +                    String className, String memberName,
   1.233 +                    String signature,
   1.234 +                    int j, int k) {
   1.235 +                show(index, tag, new String[]{ className, memberName, signature });
   1.236 +            }
   1.237 +
   1.238 +            @Override
   1.239 +            public void visitDescriptor(int index, byte tag,
   1.240 +                    String memberName, String signature,
   1.241 +                    int j, int k) {
   1.242 +                show(index, tag, new String[]{ memberName, signature });
   1.243 +            }
   1.244 +        });
   1.245 +        return cpArray;
   1.246 +    }
   1.247 +
   1.248 +    /** Write the head (header plus constant pool)
   1.249 +     *  of the patched class file to the indicated stream.
   1.250 +     */
   1.251 +    void writeHead(OutputStream out) throws IOException {
   1.252 +        outer.writePatchedHead(out, patchArray);
   1.253 +    }
   1.254 +
   1.255 +    /** Write the tail (everything after the constant pool)
   1.256 +     *  of the patched class file to the indicated stream.
   1.257 +     */
   1.258 +    void writeTail(OutputStream out) throws IOException {
   1.259 +        outer.writeTail(out);
   1.260 +    }
   1.261 +
   1.262 +    private void checkConstantTag(byte tag, Object value) {
   1.263 +        if (value == null)
   1.264 +            throw new IllegalArgumentException(
   1.265 +                    "invalid null constant value");
   1.266 +        if (classForTag(tag) != value.getClass())
   1.267 +            throw new IllegalArgumentException(
   1.268 +                    "invalid constant value"
   1.269 +                    + (tag == CONSTANT_None ? ""
   1.270 +                        : " for tag "+tagName(tag))
   1.271 +                    + " of class "+value.getClass());
   1.272 +    }
   1.273 +
   1.274 +    private void checkTag(int index, byte putTag) {
   1.275 +        byte tag = outer.tags[index];
   1.276 +        if (tag != putTag)
   1.277 +            throw new IllegalArgumentException(
   1.278 +                "invalid put operation"
   1.279 +                + " for " + tagName(putTag)
   1.280 +                + " at index " + index + " found " + tagName(tag));
   1.281 +    }
   1.282 +
   1.283 +    private void checkTagMask(int index, int tagBitMask) {
   1.284 +        byte tag = outer.tags[index];
   1.285 +        int tagBit = ((tag & 0x1F) == tag) ? (1 << tag) : 0;
   1.286 +        if ((tagBit & tagBitMask) == 0)
   1.287 +            throw new IllegalArgumentException(
   1.288 +                "invalid put operation"
   1.289 +                + " at index " + index + " found " + tagName(tag));
   1.290 +    }
   1.291 +
   1.292 +    private static void checkMemberName(String memberName) {
   1.293 +        if (memberName.indexOf(';') >= 0)
   1.294 +            throw new IllegalArgumentException("memberName " + memberName + " contains a ';'");
   1.295 +    }
   1.296 +
   1.297 +    /** Set the entry of the constant pool indexed by index to
   1.298 +     *  a new string.
   1.299 +     *
   1.300 +     * @param index an index to a constant pool entry containing a
   1.301 +     *        {@link ConstantPoolVisitor#CONSTANT_Utf8} value.
   1.302 +     * @param utf8 a string
   1.303 +     *
   1.304 +     * @see ConstantPoolVisitor#visitUTF8(int, byte, String)
   1.305 +     */
   1.306 +    public void putUTF8(int index, String utf8) {
   1.307 +        if (utf8 == null) { clear(index); return; }
   1.308 +        checkTag(index, CONSTANT_Utf8);
   1.309 +        patchArray[index] = utf8;
   1.310 +    }
   1.311 +
   1.312 +    /** Set the entry of the constant pool indexed by index to
   1.313 +     *  a new value, depending on its dynamic type.
   1.314 +     *
   1.315 +     * @param index an index to a constant pool entry containing a
   1.316 +     *        one of the following structures:
   1.317 +     *        {@link ConstantPoolVisitor#CONSTANT_Integer},
   1.318 +     *        {@link ConstantPoolVisitor#CONSTANT_Float},
   1.319 +     *        {@link ConstantPoolVisitor#CONSTANT_Long},
   1.320 +     *        {@link ConstantPoolVisitor#CONSTANT_Double},
   1.321 +     *        {@link ConstantPoolVisitor#CONSTANT_String}, or
   1.322 +     *        {@link ConstantPoolVisitor#CONSTANT_Class}
   1.323 +     * @param value a boxed int, float, long or double; or a string or class object
   1.324 +     * @throws IllegalArgumentException if the type of the constant does not
   1.325 +     *         match the constant pool entry type,
   1.326 +     *         as reported by {@link #getTag(int)}
   1.327 +     *
   1.328 +     * @see #putConstantValue(int, byte, Object)
   1.329 +     * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object)
   1.330 +     * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int)
   1.331 +     */
   1.332 +    public void putConstantValue(int index, Object value) {
   1.333 +        if (value == null) { clear(index); return; }
   1.334 +        byte tag = tagForConstant(value.getClass());
   1.335 +        checkConstantTag(tag, value);
   1.336 +        checkTag(index, tag);
   1.337 +        patchArray[index] = value;
   1.338 +    }
   1.339 +
   1.340 +    /** Set the entry of the constant pool indexed by index to
   1.341 +     *  a new value.
   1.342 +     *
   1.343 +     * @param index an index to a constant pool entry matching the given tag
   1.344 +     * @param tag one of the following values:
   1.345 +     *        {@link ConstantPoolVisitor#CONSTANT_Integer},
   1.346 +     *        {@link ConstantPoolVisitor#CONSTANT_Float},
   1.347 +     *        {@link ConstantPoolVisitor#CONSTANT_Long},
   1.348 +     *        {@link ConstantPoolVisitor#CONSTANT_Double},
   1.349 +     *        {@link ConstantPoolVisitor#CONSTANT_String}, or
   1.350 +     *        {@link ConstantPoolVisitor#CONSTANT_Class}
   1.351 +     * @param value a boxed number, string, or class object
   1.352 +     * @throws IllegalArgumentException if the type of the constant does not
   1.353 +     *         match the constant pool entry type, or if a class name contains
   1.354 +     *         '/' or ';'
   1.355 +     *
   1.356 +     * @see #putConstantValue(int, Object)
   1.357 +     * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object)
   1.358 +     * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int)
   1.359 +     */
   1.360 +    public void putConstantValue(int index, byte tag, Object value) {
   1.361 +        if (value == null) { clear(index); return; }
   1.362 +        checkTag(index, tag);
   1.363 +        if (tag == CONSTANT_Class && value instanceof String) {
   1.364 +            checkClassName((String) value);
   1.365 +        } else if (tag == CONSTANT_String) {
   1.366 +            // the JVM accepts any object as a patch for a string
   1.367 +        } else {
   1.368 +            // make sure the incoming value is the right type
   1.369 +            checkConstantTag(tag, value);
   1.370 +        }
   1.371 +        checkTag(index, tag);
   1.372 +        patchArray[index] = value;
   1.373 +    }
   1.374 +
   1.375 +    /** Set the entry of the constant pool indexed by index to
   1.376 +     *  a new {@link ConstantPoolVisitor#CONSTANT_NameAndType} value.
   1.377 +     *
   1.378 +     * @param index an index to a constant pool entry containing a
   1.379 +     *        {@link ConstantPoolVisitor#CONSTANT_NameAndType} value.
   1.380 +     * @param memberName a memberName
   1.381 +     * @param signature a signature
   1.382 +     * @throws IllegalArgumentException if memberName contains the character ';'
   1.383 +     *
   1.384 +     * @see ConstantPoolVisitor#visitDescriptor(int, byte, String, String, int, int)
   1.385 +     */
   1.386 +    public void putDescriptor(int index, String memberName, String signature) {
   1.387 +        checkTag(index, CONSTANT_NameAndType);
   1.388 +        checkMemberName(memberName);
   1.389 +        patchArray[index] = addSemis(memberName, signature);
   1.390 +    }
   1.391 +
   1.392 +    /** Set the entry of the constant pool indexed by index to
   1.393 +     *  a new {@link ConstantPoolVisitor#CONSTANT_Fieldref},
   1.394 +     *  {@link ConstantPoolVisitor#CONSTANT_Methodref}, or
   1.395 +     *  {@link ConstantPoolVisitor#CONSTANT_InterfaceMethodref} value.
   1.396 +     *
   1.397 +     * @param index an index to a constant pool entry containing a member reference
   1.398 +     * @param className a class name
   1.399 +     * @param memberName a field or method name
   1.400 +     * @param signature a field or method signature
   1.401 +     * @throws IllegalArgumentException if memberName contains the character ';'
   1.402 +     *             or signature is not a correct signature
   1.403 +     *
   1.404 +     * @see ConstantPoolVisitor#visitMemberRef(int, byte, String, String, String, int, int)
   1.405 +     */
   1.406 +    public void putMemberRef(int index, byte tag,
   1.407 +                    String className, String memberName, String signature) {
   1.408 +        checkTagMask(tag, CONSTANT_MemberRef_MASK);
   1.409 +        checkTag(index, tag);
   1.410 +        checkClassName(className);
   1.411 +        checkMemberName(memberName);
   1.412 +        if (signature.startsWith("(") == (tag == CONSTANT_Fieldref))
   1.413 +            throw new IllegalArgumentException("bad signature: "+signature);
   1.414 +        patchArray[index] = addSemis(className, memberName, signature);
   1.415 +    }
   1.416 +
   1.417 +    static private final int CONSTANT_MemberRef_MASK =
   1.418 +              CONSTANT_Fieldref
   1.419 +            | CONSTANT_Methodref
   1.420 +            | CONSTANT_InterfaceMethodref;
   1.421 +
   1.422 +    private static final Map<Class<?>, Byte> CONSTANT_VALUE_CLASS_TAG
   1.423 +        = new IdentityHashMap<Class<?>, Byte>();
   1.424 +    private static final Class<?>[] CONSTANT_VALUE_CLASS = new Class<?>[16];
   1.425 +    static {
   1.426 +        Object[][] values = {
   1.427 +            {Integer.class, CONSTANT_Integer},
   1.428 +            {Long.class, CONSTANT_Long},
   1.429 +            {Float.class, CONSTANT_Float},
   1.430 +            {Double.class, CONSTANT_Double},
   1.431 +            {String.class, CONSTANT_String},
   1.432 +            {Class.class, CONSTANT_Class}
   1.433 +        };
   1.434 +        for (Object[] value : values) {
   1.435 +            Class<?> cls = (Class<?>)value[0];
   1.436 +            Byte     tag = (Byte) value[1];
   1.437 +            CONSTANT_VALUE_CLASS_TAG.put(cls, tag);
   1.438 +            CONSTANT_VALUE_CLASS[(byte)tag] = cls;
   1.439 +        }
   1.440 +    }
   1.441 +
   1.442 +    static Class<?> classForTag(byte tag) {
   1.443 +        if ((tag & 0xFF) >= CONSTANT_VALUE_CLASS.length)
   1.444 +            return null;
   1.445 +        return CONSTANT_VALUE_CLASS[tag];
   1.446 +    }
   1.447 +
   1.448 +    static byte tagForConstant(Class<?> cls) {
   1.449 +        Byte tag = CONSTANT_VALUE_CLASS_TAG.get(cls);
   1.450 +        return (tag == null) ? CONSTANT_None : (byte)tag;
   1.451 +    }
   1.452 +
   1.453 +    private static void checkClassName(String className) {
   1.454 +        if (className.indexOf('/') >= 0 || className.indexOf(';') >= 0)
   1.455 +            throw new IllegalArgumentException("invalid class name " + className);
   1.456 +    }
   1.457 +
   1.458 +    static String addSemis(String name, String... names) {
   1.459 +        StringBuilder buf = new StringBuilder(name.length() * 5);
   1.460 +        buf.append(name);
   1.461 +        for (String name2 : names) {
   1.462 +            buf.append(';').append(name2);
   1.463 +        }
   1.464 +        String res = buf.toString();
   1.465 +        assert(stripSemis(names.length, res)[0].equals(name));
   1.466 +        assert(stripSemis(names.length, res)[1].equals(names[0]));
   1.467 +        assert(names.length == 1 ||
   1.468 +               stripSemis(names.length, res)[2].equals(names[1]));
   1.469 +        return res;
   1.470 +    }
   1.471 +
   1.472 +    static String[] stripSemis(int count, String string) {
   1.473 +        String[] res = new String[count+1];
   1.474 +        int pos = 0;
   1.475 +        for (int i = 0; i < count; i++) {
   1.476 +            int pos2 = string.indexOf(';', pos);
   1.477 +            if (pos2 < 0)  pos2 = string.length();  // yuck
   1.478 +            res[i] = string.substring(pos, pos2);
   1.479 +            pos = pos2;
   1.480 +        }
   1.481 +        res[count] = string.substring(pos);
   1.482 +        return res;
   1.483 +    }
   1.484 +
   1.485 +    public String toString() {
   1.486 +        StringBuilder buf = new StringBuilder(this.getClass().getName());
   1.487 +        buf.append("{");
   1.488 +        Object[] origCP = null;
   1.489 +        for (int i = 0; i < patchArray.length; i++) {
   1.490 +            if (patchArray[i] == null)  continue;
   1.491 +            if (origCP != null) {
   1.492 +                buf.append(", ");
   1.493 +            } else {
   1.494 +                try {
   1.495 +                    origCP = getOriginalCP();
   1.496 +                } catch (InvalidConstantPoolFormatException ee) {
   1.497 +                    origCP = new Object[0];
   1.498 +                }
   1.499 +            }
   1.500 +            Object orig = (i < origCP.length) ? origCP[i] : "?";
   1.501 +            buf.append(orig).append("=").append(patchArray[i]);
   1.502 +        }
   1.503 +        buf.append("}");
   1.504 +        return buf.toString();
   1.505 +    }
   1.506 +}