diff --git a/src/main/javassist/bytecode/SignatureAttribute.java b/src/main/javassist/bytecode/SignatureAttribute.java index 6a483057..c17b3f29 100644 --- a/src/main/javassist/bytecode/SignatureAttribute.java +++ b/src/main/javassist/bytecode/SignatureAttribute.java @@ -19,6 +19,7 @@ import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -110,41 +111,84 @@ static String renameClass(String desc, String oldname, String newname) { } static String renameClass(String desc, Map map) { - if (map == null) + if (map == null || map.isEmpty()) return desc; StringBuilder newdesc = new StringBuilder(); int head = 0; int i = 0; for (;;) { - int j = desc.indexOf('L', i); + final int j = desc.indexOf('L', i); if (j < 0) break; - int k = desc.indexOf(';', j); - if (k < 0) - break; - - int l = desc.indexOf('<', j); - int classEndIndex; - char classEndChar; - if (l < 0 || k < l) { - classEndIndex = k; - classEndChar = ';'; - } else { - classEndIndex = l; - classEndChar = '<'; + StringBuilder nameBuf = new StringBuilder(); + StringBuilder genericParamBuf = new StringBuilder(); + final ArrayList nameBufs = new ArrayList<>(); + final ArrayList genericParamBufs = new ArrayList<>(); + int k = j; + char c; + try { + while ((c = desc.charAt(++k)) != ';') { + if (c == '<') { + genericParamBuf.append(c); + int level = 1; + while (level > 0) { + c = desc.charAt(++k); + genericParamBuf.append(c); + if (c == '<') ++level; + else if (c == '>') --level; + } + } else if (c == '.') { + nameBufs.add(nameBuf); + genericParamBufs.add(genericParamBuf); + nameBuf = new StringBuilder(); + genericParamBuf = new StringBuilder(); + } else { + nameBuf.append(c); + } + } } - i = classEndIndex + 1; - - String name = desc.substring(j + 1, classEndIndex); - String name2 = map.get(name); - if (name2 != null) { - newdesc.append(desc.substring(head, j)); - newdesc.append('L'); - newdesc.append(name2); - newdesc.append(classEndChar); - head = i; + catch (IndexOutOfBoundsException e) { break; } + + nameBufs.add(nameBuf); + genericParamBufs.add(genericParamBuf); + i = k + 1; + + String name = String.join("$", nameBufs.toArray(new StringBuilder[0])); + String newname = map.get(name); + if (newname != null) { + final String[] nameSplit = name.split("\\$"); + final String[] newnameSplit = newname.split("\\$"); + if (nameSplit.length == newnameSplit.length) { + final String[] newnames = new String[nameBufs.size()]; + for (int start = 0, z = 0; z < nameBufs.size(); ++z) { + final int toAggregate = (int) nameBufs.get(z).chars().filter(ch -> ch == '$').count() + 1; + String s = String.join("$", Arrays.copyOfRange(newnameSplit, start, start + toAggregate)); + start += toAggregate; + newnames[z] = s; + } + + + newdesc.append(desc.substring(head, j)); + newdesc.append('L'); + for (int z = 0; z < newnames.length; ++z) { + if (z > 0) { + newdesc.append('.'); + } + newdesc.append(newnames[z]); + final String newgenericParam; + final StringBuilder genericParamBufCurrent = genericParamBufs.get(z); + if (genericParamBufCurrent.length() > 0) { + newgenericParam = "<" + renameClass(genericParamBufCurrent.substring(1, genericParamBufCurrent.length() - 1), map) + ">"; + } else { + newgenericParam = genericParamBufCurrent.toString(); //empty string + } + newdesc.append(newgenericParam); + } + newdesc.append(c); //the final semicolon + head = i; + } } } diff --git a/src/test/javassist/bytecode/SignatureAttributeTest.java b/src/test/javassist/bytecode/SignatureAttributeTest.java new file mode 100644 index 00000000..1bf654d4 --- /dev/null +++ b/src/test/javassist/bytecode/SignatureAttributeTest.java @@ -0,0 +1,84 @@ +package javassist.bytecode; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; + +public class SignatureAttributeTest { + void test1() { + final String signature = "TX;TY;La/b/C$D$E$J$K;"; //a sequence of three ReferenceTypeSignature + final HashMap map = new HashMap<>(); + map.put("a/b/C$D$E$J$K", "o/p/Q$R$S$T$U"); + map.put("e/F$G$H$I", "v/W$X$Y$Z"); + final String signatureRenamed = SignatureAttribute.renameClass(signature, map); + assertEquals("TX;TY;Lo/p/Q$R$S$T$U;", signatureRenamed); + } + + void test2() { + final String signature = "La/b/C.D;"; //a ClassTypeSignature + final HashMap map = new HashMap<>(); + map.put("a/b/C$D", "o/p/Q$R"); + map.put("java/lang/Integer", "java/lang/Long"); + final String signatureRenamed = SignatureAttribute.renameClass(signature, map); + assertEquals("Lo/p/Q.R;", signatureRenamed); + } + + void test3() { + final String signature = "BJLB.F;TZ;"; //a sequence of four JavaTypeSignature + final HashMap map = new HashMap<>(); + map.put("B$F", "P$T"); + map.put("c/D$E", "q/R$S"); + final String signatureRenamed = SignatureAttribute.renameClass(signature, map); + assertEquals("BJLP.T;TZ;", signatureRenamed); + } + + void test4() { + final String signature = "La/b/C;[[Ld/E<+TY;-Ljava/lang/Object;*>;Z"; //a sequence of three JavaTypeSignature + final HashMap map = new HashMap<>(); + map.put("java/lang/Object", "java/util/Map"); + map.put("d/E", "F"); + final String signatureRenamed = SignatureAttribute.renameClass(signature, map); + assertEquals("La/b/C;[[LF<+TY;-Ljava/util/Map;*>;Z", signatureRenamed); + } + + void test5() { + final String signature = "La/b/C$D$E.H$I;>.J$K;"; //a ClassTypeSignature + final HashMap map = new HashMap<>(); + map.put("a/b/C$D$E$J$K", "o/p/Q$R$S$T$U"); + map.put("e/F$G$H$I", "v/W$X$Y$Z"); + final String signatureRenamed = SignatureAttribute.renameClass(signature, map); + assertEquals("Lo/p/Q$R$S.Y$Z;>.T$U;", signatureRenamed); + } + + void test6() { + final String signature = ".D;:Le/F$G;>Lh/I$J;"; //a ClassSignature + final HashMap map = new HashMap<>(); + map.put("a/B$C$D", "o/P$Q$R"); + map.put("e/F$G", "s/T$U"); + map.put("h/I$J", "v/W$X"); + final String signatureRenamed = SignatureAttribute.renameClass(signature, map); + assertEquals(".R;:Ls/T$U;>Lv/W$X;", signatureRenamed); + } + + void test7() { + final String signature = ".F;:TZ;B:Ljava/lang/Thread;>(LX;TA;LA;)V^Ljava/lang/Exception;"; //a MethodSignature + final HashMap map = new HashMap<>(); + map.put("A", "P"); + map.put("a/B$C", "s/T$U"); + map.put("d/E$F", "v/W$X"); + map.put("X", "V"); + map.put("java/lang/Exception", "java/lang/RuntimeException"); + final String signatureRenamed = SignatureAttribute.renameClass(signature, map); + assertEquals(".X;:TZ;B:Ljava/lang/Thread;>(LV;TA;LP;)V^Ljava/lang/RuntimeException;", signatureRenamed); + } + + public static void main(String[] s) { + new SignatureAttributeTest().test1(); + new SignatureAttributeTest().test2(); + new SignatureAttributeTest().test3(); + new SignatureAttributeTest().test4(); + new SignatureAttributeTest().test5(); + new SignatureAttributeTest().test6(); + new SignatureAttributeTest().test7(); + } +}