jaroslav@1646: /* jaroslav@1646: * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. jaroslav@1646: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jaroslav@1646: * jaroslav@1646: * This code is free software; you can redistribute it and/or modify it jaroslav@1646: * under the terms of the GNU General Public License version 2 only, as jaroslav@1646: * published by the Free Software Foundation. Oracle designates this jaroslav@1646: * particular file as subject to the "Classpath" exception as provided jaroslav@1646: * by Oracle in the LICENSE file that accompanied this code. jaroslav@1646: * jaroslav@1646: * This code is distributed in the hope that it will be useful, but WITHOUT jaroslav@1646: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jaroslav@1646: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jaroslav@1646: * version 2 for more details (a copy is included in the LICENSE file that jaroslav@1646: * accompanied this code). jaroslav@1646: * jaroslav@1646: * You should have received a copy of the GNU General Public License version jaroslav@1646: * 2 along with this work; if not, write to the Free Software Foundation, jaroslav@1646: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jaroslav@1646: * jaroslav@1646: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jaroslav@1646: * or visit www.oracle.com if you need additional information or have any jaroslav@1646: * questions. jaroslav@1646: */ jaroslav@1646: jaroslav@1646: package sun.invoke.anon; jaroslav@1646: jaroslav@1646: import java.io.IOException; jaroslav@1646: import java.io.OutputStream; jaroslav@1646: import java.util.Arrays; jaroslav@1646: import java.util.HashSet; jaroslav@1646: import java.util.IdentityHashMap; jaroslav@1646: import java.util.Map; jaroslav@1646: jaroslav@1646: import static sun.invoke.anon.ConstantPoolVisitor.*; jaroslav@1646: jaroslav@1646: /** A class and its patched constant pool. jaroslav@1646: * jaroslav@1646: * This class allow to modify (patch) a constant pool jaroslav@1646: * by changing the value of its entry. jaroslav@1646: * Entry are referenced using index that can be get jaroslav@1646: * by parsing the constant pool using jaroslav@1646: * {@link ConstantPoolParser#parse(ConstantPoolVisitor)}. jaroslav@1646: * jaroslav@1646: * @see ConstantPoolVisitor jaroslav@1646: * @see ConstantPoolParser#createPatch() jaroslav@1646: */ jaroslav@1646: public class ConstantPoolPatch { jaroslav@1646: final ConstantPoolParser outer; jaroslav@1646: final Object[] patchArray; jaroslav@1646: jaroslav@1646: ConstantPoolPatch(ConstantPoolParser outer) { jaroslav@1646: this.outer = outer; jaroslav@1646: this.patchArray = new Object[outer.getLength()]; jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Create a {@link ConstantPoolParser} and jaroslav@1646: * a {@link ConstantPoolPatch} in one step. jaroslav@1646: * Equivalent to {@code new ConstantPoolParser(classFile).createPatch()}. jaroslav@1646: * jaroslav@1646: * @param classFile an array of bytes containing a class. jaroslav@1646: * @see #ConstantPoolParser(Class) jaroslav@1646: */ jaroslav@1646: public ConstantPoolPatch(byte[] classFile) throws InvalidConstantPoolFormatException { jaroslav@1646: this(new ConstantPoolParser(classFile)); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Create a {@link ConstantPoolParser} and jaroslav@1646: * a {@link ConstantPoolPatch} in one step. jaroslav@1646: * Equivalent to {@code new ConstantPoolParser(templateClass).createPatch()}. jaroslav@1646: * jaroslav@1646: * @param templateClass the class to parse. jaroslav@1646: * @see #ConstantPoolParser(Class) jaroslav@1646: */ jaroslav@1646: public ConstantPoolPatch(Class templateClass) throws IOException, InvalidConstantPoolFormatException { jaroslav@1646: this(new ConstantPoolParser(templateClass)); jaroslav@1646: } jaroslav@1646: jaroslav@1646: jaroslav@1646: /** Creates a patch from an existing patch. jaroslav@1646: * All changes are copied from that patch. jaroslav@1646: * @param patch a patch jaroslav@1646: * jaroslav@1646: * @see ConstantPoolParser#createPatch() jaroslav@1646: */ jaroslav@1646: public ConstantPoolPatch(ConstantPoolPatch patch) { jaroslav@1646: outer = patch.outer; jaroslav@1646: patchArray = patch.patchArray.clone(); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Which parser built this patch? */ jaroslav@1646: public ConstantPoolParser getParser() { jaroslav@1646: return outer; jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Report the tag at the given index in the constant pool. */ jaroslav@1646: public byte getTag(int index) { jaroslav@1646: return outer.getTag(index); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Report the current patch at the given index of the constant pool. jaroslav@1646: * Null means no patch will be made. jaroslav@1646: * To observe the unpatched entry at the given index, use jaroslav@1646: * {@link #getParser()}{@code .}@link ConstantPoolParser#parse(ConstantPoolVisitor)} jaroslav@1646: */ jaroslav@1646: public Object getPatch(int index) { jaroslav@1646: Object value = patchArray[index]; jaroslav@1646: if (value == null) return null; jaroslav@1646: switch (getTag(index)) { jaroslav@1646: case CONSTANT_Fieldref: jaroslav@1646: case CONSTANT_Methodref: jaroslav@1646: case CONSTANT_InterfaceMethodref: jaroslav@1646: if (value instanceof String) jaroslav@1646: value = stripSemis(2, (String) value); jaroslav@1646: break; jaroslav@1646: case CONSTANT_NameAndType: jaroslav@1646: if (value instanceof String) jaroslav@1646: value = stripSemis(1, (String) value); jaroslav@1646: break; jaroslav@1646: } jaroslav@1646: return value; jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Clear all patches. */ jaroslav@1646: public void clear() { jaroslav@1646: Arrays.fill(patchArray, null); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Clear one patch. */ jaroslav@1646: public void clear(int index) { jaroslav@1646: patchArray[index] = null; jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Produce the patches as an array. */ jaroslav@1646: public Object[] getPatches() { jaroslav@1646: return patchArray.clone(); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Produce the original constant pool as an array. */ jaroslav@1646: public Object[] getOriginalCP() throws InvalidConstantPoolFormatException { jaroslav@1646: return getOriginalCP(0, patchArray.length, -1); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Walk the constant pool, applying patches using the given map. jaroslav@1646: * jaroslav@1646: * @param utf8Map Utf8 strings to modify, if encountered jaroslav@1646: * @param classMap Classes (or their names) to modify, if encountered jaroslav@1646: * @param valueMap Constant values to modify, if encountered jaroslav@1646: * @param deleteUsedEntries if true, delete map entries that are used jaroslav@1646: */ jaroslav@1646: public void putPatches(final Map utf8Map, jaroslav@1646: final Map classMap, jaroslav@1646: final Map valueMap, jaroslav@1646: boolean deleteUsedEntries) throws InvalidConstantPoolFormatException { jaroslav@1646: final HashSet usedUtf8Keys; jaroslav@1646: final HashSet usedClassKeys; jaroslav@1646: final HashSet usedValueKeys; jaroslav@1646: if (deleteUsedEntries) { jaroslav@1646: usedUtf8Keys = (utf8Map == null) ? null : new HashSet(); jaroslav@1646: usedClassKeys = (classMap == null) ? null : new HashSet(); jaroslav@1646: usedValueKeys = (valueMap == null) ? null : new HashSet(); jaroslav@1646: } else { jaroslav@1646: usedUtf8Keys = null; jaroslav@1646: usedClassKeys = null; jaroslav@1646: usedValueKeys = null; jaroslav@1646: } jaroslav@1646: jaroslav@1646: outer.parse(new ConstantPoolVisitor() { jaroslav@1646: jaroslav@1646: @Override jaroslav@1646: public void visitUTF8(int index, byte tag, String utf8) { jaroslav@1646: putUTF8(index, utf8Map.get(utf8)); jaroslav@1646: if (usedUtf8Keys != null) usedUtf8Keys.add(utf8); jaroslav@1646: } jaroslav@1646: jaroslav@1646: @Override jaroslav@1646: public void visitConstantValue(int index, byte tag, Object value) { jaroslav@1646: putConstantValue(index, tag, valueMap.get(value)); jaroslav@1646: if (usedValueKeys != null) usedValueKeys.add(value); jaroslav@1646: } jaroslav@1646: jaroslav@1646: @Override jaroslav@1646: public void visitConstantString(int index, byte tag, String name, int nameIndex) { jaroslav@1646: if (tag == CONSTANT_Class) { jaroslav@1646: putConstantValue(index, tag, classMap.get(name)); jaroslav@1646: if (usedClassKeys != null) usedClassKeys.add(name); jaroslav@1646: } else { jaroslav@1646: assert(tag == CONSTANT_String); jaroslav@1646: visitConstantValue(index, tag, name); jaroslav@1646: } jaroslav@1646: } jaroslav@1646: }); jaroslav@1646: if (usedUtf8Keys != null) utf8Map.keySet().removeAll(usedUtf8Keys); jaroslav@1646: if (usedClassKeys != null) classMap.keySet().removeAll(usedClassKeys); jaroslav@1646: if (usedValueKeys != null) valueMap.keySet().removeAll(usedValueKeys); jaroslav@1646: } jaroslav@1646: jaroslav@1646: Object[] getOriginalCP(final int startIndex, jaroslav@1646: final int endIndex, jaroslav@1646: final int tagMask) throws InvalidConstantPoolFormatException { jaroslav@1646: final Object[] cpArray = new Object[endIndex - startIndex]; jaroslav@1646: outer.parse(new ConstantPoolVisitor() { jaroslav@1646: jaroslav@1646: void show(int index, byte tag, Object value) { jaroslav@1646: if (index < startIndex || index >= endIndex) return; jaroslav@1646: if (((1 << tag) & tagMask) == 0) return; jaroslav@1646: cpArray[index - startIndex] = value; jaroslav@1646: } jaroslav@1646: jaroslav@1646: @Override jaroslav@1646: public void visitUTF8(int index, byte tag, String utf8) { jaroslav@1646: show(index, tag, utf8); jaroslav@1646: } jaroslav@1646: jaroslav@1646: @Override jaroslav@1646: public void visitConstantValue(int index, byte tag, Object value) { jaroslav@1646: assert(tag != CONSTANT_String); jaroslav@1646: show(index, tag, value); jaroslav@1646: } jaroslav@1646: jaroslav@1646: @Override jaroslav@1646: public void visitConstantString(int index, byte tag, jaroslav@1646: String value, int j) { jaroslav@1646: show(index, tag, value); jaroslav@1646: } jaroslav@1646: jaroslav@1646: @Override jaroslav@1646: public void visitMemberRef(int index, byte tag, jaroslav@1646: String className, String memberName, jaroslav@1646: String signature, jaroslav@1646: int j, int k) { jaroslav@1646: show(index, tag, new String[]{ className, memberName, signature }); jaroslav@1646: } jaroslav@1646: jaroslav@1646: @Override jaroslav@1646: public void visitDescriptor(int index, byte tag, jaroslav@1646: String memberName, String signature, jaroslav@1646: int j, int k) { jaroslav@1646: show(index, tag, new String[]{ memberName, signature }); jaroslav@1646: } jaroslav@1646: }); jaroslav@1646: return cpArray; jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Write the head (header plus constant pool) jaroslav@1646: * of the patched class file to the indicated stream. jaroslav@1646: */ jaroslav@1646: void writeHead(OutputStream out) throws IOException { jaroslav@1646: outer.writePatchedHead(out, patchArray); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Write the tail (everything after the constant pool) jaroslav@1646: * of the patched class file to the indicated stream. jaroslav@1646: */ jaroslav@1646: void writeTail(OutputStream out) throws IOException { jaroslav@1646: outer.writeTail(out); jaroslav@1646: } jaroslav@1646: jaroslav@1646: private void checkConstantTag(byte tag, Object value) { jaroslav@1646: if (value == null) jaroslav@1646: throw new IllegalArgumentException( jaroslav@1646: "invalid null constant value"); jaroslav@1646: if (classForTag(tag) != value.getClass()) jaroslav@1646: throw new IllegalArgumentException( jaroslav@1646: "invalid constant value" jaroslav@1646: + (tag == CONSTANT_None ? "" jaroslav@1646: : " for tag "+tagName(tag)) jaroslav@1646: + " of class "+value.getClass()); jaroslav@1646: } jaroslav@1646: jaroslav@1646: private void checkTag(int index, byte putTag) { jaroslav@1646: byte tag = outer.tags[index]; jaroslav@1646: if (tag != putTag) jaroslav@1646: throw new IllegalArgumentException( jaroslav@1646: "invalid put operation" jaroslav@1646: + " for " + tagName(putTag) jaroslav@1646: + " at index " + index + " found " + tagName(tag)); jaroslav@1646: } jaroslav@1646: jaroslav@1646: private void checkTagMask(int index, int tagBitMask) { jaroslav@1646: byte tag = outer.tags[index]; jaroslav@1646: int tagBit = ((tag & 0x1F) == tag) ? (1 << tag) : 0; jaroslav@1646: if ((tagBit & tagBitMask) == 0) jaroslav@1646: throw new IllegalArgumentException( jaroslav@1646: "invalid put operation" jaroslav@1646: + " at index " + index + " found " + tagName(tag)); jaroslav@1646: } jaroslav@1646: jaroslav@1646: private static void checkMemberName(String memberName) { jaroslav@1646: if (memberName.indexOf(';') >= 0) jaroslav@1646: throw new IllegalArgumentException("memberName " + memberName + " contains a ';'"); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Set the entry of the constant pool indexed by index to jaroslav@1646: * a new string. jaroslav@1646: * jaroslav@1646: * @param index an index to a constant pool entry containing a jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Utf8} value. jaroslav@1646: * @param utf8 a string jaroslav@1646: * jaroslav@1646: * @see ConstantPoolVisitor#visitUTF8(int, byte, String) jaroslav@1646: */ jaroslav@1646: public void putUTF8(int index, String utf8) { jaroslav@1646: if (utf8 == null) { clear(index); return; } jaroslav@1646: checkTag(index, CONSTANT_Utf8); jaroslav@1646: patchArray[index] = utf8; jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Set the entry of the constant pool indexed by index to jaroslav@1646: * a new value, depending on its dynamic type. jaroslav@1646: * jaroslav@1646: * @param index an index to a constant pool entry containing a jaroslav@1646: * one of the following structures: jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Integer}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Float}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Long}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Double}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_String}, or jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Class} jaroslav@1646: * @param value a boxed int, float, long or double; or a string or class object jaroslav@1646: * @throws IllegalArgumentException if the type of the constant does not jaroslav@1646: * match the constant pool entry type, jaroslav@1646: * as reported by {@link #getTag(int)} jaroslav@1646: * jaroslav@1646: * @see #putConstantValue(int, byte, Object) jaroslav@1646: * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object) jaroslav@1646: * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int) jaroslav@1646: */ jaroslav@1646: public void putConstantValue(int index, Object value) { jaroslav@1646: if (value == null) { clear(index); return; } jaroslav@1646: byte tag = tagForConstant(value.getClass()); jaroslav@1646: checkConstantTag(tag, value); jaroslav@1646: checkTag(index, tag); jaroslav@1646: patchArray[index] = value; jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Set the entry of the constant pool indexed by index to jaroslav@1646: * a new value. jaroslav@1646: * jaroslav@1646: * @param index an index to a constant pool entry matching the given tag jaroslav@1646: * @param tag one of the following values: jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Integer}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Float}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Long}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Double}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_String}, or jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Class} jaroslav@1646: * @param value a boxed number, string, or class object jaroslav@1646: * @throws IllegalArgumentException if the type of the constant does not jaroslav@1646: * match the constant pool entry type, or if a class name contains jaroslav@1646: * '/' or ';' jaroslav@1646: * jaroslav@1646: * @see #putConstantValue(int, Object) jaroslav@1646: * @see ConstantPoolVisitor#visitConstantValue(int, byte, Object) jaroslav@1646: * @see ConstantPoolVisitor#visitConstantString(int, byte, String, int) jaroslav@1646: */ jaroslav@1646: public void putConstantValue(int index, byte tag, Object value) { jaroslav@1646: if (value == null) { clear(index); return; } jaroslav@1646: checkTag(index, tag); jaroslav@1646: if (tag == CONSTANT_Class && value instanceof String) { jaroslav@1646: checkClassName((String) value); jaroslav@1646: } else if (tag == CONSTANT_String) { jaroslav@1646: // the JVM accepts any object as a patch for a string jaroslav@1646: } else { jaroslav@1646: // make sure the incoming value is the right type jaroslav@1646: checkConstantTag(tag, value); jaroslav@1646: } jaroslav@1646: checkTag(index, tag); jaroslav@1646: patchArray[index] = value; jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Set the entry of the constant pool indexed by index to jaroslav@1646: * a new {@link ConstantPoolVisitor#CONSTANT_NameAndType} value. jaroslav@1646: * jaroslav@1646: * @param index an index to a constant pool entry containing a jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_NameAndType} value. jaroslav@1646: * @param memberName a memberName jaroslav@1646: * @param signature a signature jaroslav@1646: * @throws IllegalArgumentException if memberName contains the character ';' jaroslav@1646: * jaroslav@1646: * @see ConstantPoolVisitor#visitDescriptor(int, byte, String, String, int, int) jaroslav@1646: */ jaroslav@1646: public void putDescriptor(int index, String memberName, String signature) { jaroslav@1646: checkTag(index, CONSTANT_NameAndType); jaroslav@1646: checkMemberName(memberName); jaroslav@1646: patchArray[index] = addSemis(memberName, signature); jaroslav@1646: } jaroslav@1646: jaroslav@1646: /** Set the entry of the constant pool indexed by index to jaroslav@1646: * a new {@link ConstantPoolVisitor#CONSTANT_Fieldref}, jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_Methodref}, or jaroslav@1646: * {@link ConstantPoolVisitor#CONSTANT_InterfaceMethodref} value. jaroslav@1646: * jaroslav@1646: * @param index an index to a constant pool entry containing a member reference jaroslav@1646: * @param className a class name jaroslav@1646: * @param memberName a field or method name jaroslav@1646: * @param signature a field or method signature jaroslav@1646: * @throws IllegalArgumentException if memberName contains the character ';' jaroslav@1646: * or signature is not a correct signature jaroslav@1646: * jaroslav@1646: * @see ConstantPoolVisitor#visitMemberRef(int, byte, String, String, String, int, int) jaroslav@1646: */ jaroslav@1646: public void putMemberRef(int index, byte tag, jaroslav@1646: String className, String memberName, String signature) { jaroslav@1646: checkTagMask(tag, CONSTANT_MemberRef_MASK); jaroslav@1646: checkTag(index, tag); jaroslav@1646: checkClassName(className); jaroslav@1646: checkMemberName(memberName); jaroslav@1646: if (signature.startsWith("(") == (tag == CONSTANT_Fieldref)) jaroslav@1646: throw new IllegalArgumentException("bad signature: "+signature); jaroslav@1646: patchArray[index] = addSemis(className, memberName, signature); jaroslav@1646: } jaroslav@1646: jaroslav@1646: static private final int CONSTANT_MemberRef_MASK = jaroslav@1646: CONSTANT_Fieldref jaroslav@1646: | CONSTANT_Methodref jaroslav@1646: | CONSTANT_InterfaceMethodref; jaroslav@1646: jaroslav@1646: private static final Map, Byte> CONSTANT_VALUE_CLASS_TAG jaroslav@1646: = new IdentityHashMap, Byte>(); jaroslav@1646: private static final Class[] CONSTANT_VALUE_CLASS = new Class[16]; jaroslav@1646: static { jaroslav@1646: Object[][] values = { jaroslav@1646: {Integer.class, CONSTANT_Integer}, jaroslav@1646: {Long.class, CONSTANT_Long}, jaroslav@1646: {Float.class, CONSTANT_Float}, jaroslav@1646: {Double.class, CONSTANT_Double}, jaroslav@1646: {String.class, CONSTANT_String}, jaroslav@1646: {Class.class, CONSTANT_Class} jaroslav@1646: }; jaroslav@1646: for (Object[] value : values) { jaroslav@1646: Class cls = (Class)value[0]; jaroslav@1646: Byte tag = (Byte) value[1]; jaroslav@1646: CONSTANT_VALUE_CLASS_TAG.put(cls, tag); jaroslav@1646: CONSTANT_VALUE_CLASS[(byte)tag] = cls; jaroslav@1646: } jaroslav@1646: } jaroslav@1646: jaroslav@1646: static Class classForTag(byte tag) { jaroslav@1646: if ((tag & 0xFF) >= CONSTANT_VALUE_CLASS.length) jaroslav@1646: return null; jaroslav@1646: return CONSTANT_VALUE_CLASS[tag]; jaroslav@1646: } jaroslav@1646: jaroslav@1646: static byte tagForConstant(Class cls) { jaroslav@1646: Byte tag = CONSTANT_VALUE_CLASS_TAG.get(cls); jaroslav@1646: return (tag == null) ? CONSTANT_None : (byte)tag; jaroslav@1646: } jaroslav@1646: jaroslav@1646: private static void checkClassName(String className) { jaroslav@1646: if (className.indexOf('/') >= 0 || className.indexOf(';') >= 0) jaroslav@1646: throw new IllegalArgumentException("invalid class name " + className); jaroslav@1646: } jaroslav@1646: jaroslav@1646: static String addSemis(String name, String... names) { jaroslav@1646: StringBuilder buf = new StringBuilder(name.length() * 5); jaroslav@1646: buf.append(name); jaroslav@1646: for (String name2 : names) { jaroslav@1646: buf.append(';').append(name2); jaroslav@1646: } jaroslav@1646: String res = buf.toString(); jaroslav@1646: assert(stripSemis(names.length, res)[0].equals(name)); jaroslav@1646: assert(stripSemis(names.length, res)[1].equals(names[0])); jaroslav@1646: assert(names.length == 1 || jaroslav@1646: stripSemis(names.length, res)[2].equals(names[1])); jaroslav@1646: return res; jaroslav@1646: } jaroslav@1646: jaroslav@1646: static String[] stripSemis(int count, String string) { jaroslav@1646: String[] res = new String[count+1]; jaroslav@1646: int pos = 0; jaroslav@1646: for (int i = 0; i < count; i++) { jaroslav@1646: int pos2 = string.indexOf(';', pos); jaroslav@1646: if (pos2 < 0) pos2 = string.length(); // yuck jaroslav@1646: res[i] = string.substring(pos, pos2); jaroslav@1646: pos = pos2; jaroslav@1646: } jaroslav@1646: res[count] = string.substring(pos); jaroslav@1646: return res; jaroslav@1646: } jaroslav@1646: jaroslav@1646: public String toString() { jaroslav@1646: StringBuilder buf = new StringBuilder(this.getClass().getName()); jaroslav@1646: buf.append("{"); jaroslav@1646: Object[] origCP = null; jaroslav@1646: for (int i = 0; i < patchArray.length; i++) { jaroslav@1646: if (patchArray[i] == null) continue; jaroslav@1646: if (origCP != null) { jaroslav@1646: buf.append(", "); jaroslav@1646: } else { jaroslav@1646: try { jaroslav@1646: origCP = getOriginalCP(); jaroslav@1646: } catch (InvalidConstantPoolFormatException ee) { jaroslav@1646: origCP = new Object[0]; jaroslav@1646: } jaroslav@1646: } jaroslav@1646: Object orig = (i < origCP.length) ? origCP[i] : "?"; jaroslav@1646: buf.append(orig).append("=").append(patchArray[i]); jaroslav@1646: } jaroslav@1646: buf.append("}"); jaroslav@1646: return buf.toString(); jaroslav@1646: } jaroslav@1646: }