Fix renaming of classes in presence of generic signatures and nested classes. This pull request has some problems but I'll fix them later.tags/rel_3_29_0_ga
@@ -19,6 +19,7 @@ package javassist.bytecode; | |||
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 @@ public class SignatureAttribute extends AttributeInfo { | |||
} | |||
static String renameClass(String desc, Map<String,String> 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<StringBuilder> nameBufs = new ArrayList<>(); | |||
final ArrayList<StringBuilder> 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; | |||
} | |||
} | |||
} | |||
@@ -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<String, String> 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<TA;TB;>.D<Ljava/lang/Integer;>;"; //a ClassTypeSignature | |||
final HashMap<String, String> 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<TA;TB;>.R<Ljava/lang/Long;>;", signatureRenamed); | |||
} | |||
void test3() { | |||
final String signature = "BJLB<TX;Lc/D$E;>.F<TY;>;TZ;"; //a sequence of four JavaTypeSignature | |||
final HashMap<String, String> 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<TX;Lq/R$S;>.T<TY;>;TZ;", signatureRenamed); | |||
} | |||
void test4() { | |||
final String signature = "La/b/C<TX;>;[[Ld/E<+TY;-Ljava/lang/Object;*>;Z"; //a sequence of three JavaTypeSignature | |||
final HashMap<String, String> 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<TX;>;[[LF<+TY;-Ljava/util/Map;*>;Z", signatureRenamed); | |||
} | |||
void test5() { | |||
final String signature = "La/b/C$D$E<TX;Le/F$G<TY;TZ;>.H$I<TU;TV;>;>.J$K;"; //a ClassTypeSignature | |||
final HashMap<String, String> 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<TX;Lv/W$X<TY;TZ;>.Y$Z<TU;TV;>;>.T$U;", signatureRenamed); | |||
} | |||
void test6() { | |||
final String signature = "<X:La/B$C<TY;>.D<TZ;>;:Le/F$G;>Lh/I$J;"; //a ClassSignature | |||
final HashMap<String, String> 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("<X:Lo/P$Q<TY;>.R<TZ;>;:Ls/T$U;>Lv/W$X;", signatureRenamed); | |||
} | |||
void test7() { | |||
final String signature = "<A:La/B$C;:Ld/E<TX;>.F<TY;>;:TZ;B:Ljava/lang/Thread;>(LX;TA;LA;)V^Ljava/lang/Exception;"; //a MethodSignature | |||
final HashMap<String, String> 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("<A:Ls/T$U;:Lv/W<TX;>.X<TY;>;: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(); | |||
} | |||
} |