ชวา
String getParamName(String param) throws Exception {
StackTraceElement[] strace = new Exception().getStackTrace();
String methodName = strace[0].getMethodName();
int lineNum = strace[1].getLineNumber();
String className = strace[1].getClassName().replaceAll(".{5}$", "");
String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
List<String> javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
int byteCodeStart = -1;
Map<Integer, Integer> byteCodePointerToJavaPLine = new HashMap<Integer, Integer>();
Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
for (int n = 0;n < javapLines.size();n++) {
String javapLine = javapLines.get(n);
if (byteCodeStart > -1 && (javapLine == null || "".equals(javapLine))) {
break;
}
Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
if (byteCodeIndexMatcher.find()) {
byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
} else if (javapLine.contains("line " + lineNum + ":")) {
byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
}
}
int varLoadIndex = -1;
int varTableIndex = -1;
for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i < javapLines.size();i++) {
if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) {
varLoadIndex = i;
continue;
}
if (varLoadIndex > -1 && javapLines.get(i).contains("LocalVariableTable:")) {
varTableIndex = i;
break;
}
}
String loadLine = javapLines.get(varLoadIndex - 1).trim();
int varNumber;
try {
varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
} catch (NumberFormatException e) {
return null;
}
int j = varTableIndex + 2;
while(!"".equals(javapLines.get(j))) {
Matcher varName = Pattern.compile("\\s*" + varNumber + "\\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));
if (varName.find()) {
return varName.group(1);
}
j++;
}
return null;
}
ปัจจุบันนี้ใช้งานได้กับ gotchas สองสาม:
- ถ้าคุณใช้ IDE เพื่อคอมไพล์สิ่งนี้มันอาจไม่ทำงานจนกว่าจะถูกเรียกใช้ในฐานะผู้ดูแลระบบ (ขึ้นอยู่กับตำแหน่งที่บันทึกไฟล์คลาสชั่วคราว)
- คุณต้องรวบรวมโดยใช้
javac
กับ-g
ธง สิ่งนี้สร้างข้อมูลการดีบักทั้งหมดรวมถึงชื่อตัวแปรโลคัลในไฟล์คลาสที่คอมไพล์
- สิ่งนี้ใช้ Java API ภายใน
com.sun.tools.javap
ซึ่งแยกวิเคราะห์ bytecode ของ classfile และสร้างผลลัพธ์ที่มนุษย์สามารถอ่านได้ API นี้สามารถเข้าถึงได้ในไลบรารี JDK เท่านั้นดังนั้นคุณต้องใช้ JDK java runtime หรือเพิ่ม tools.jar ใน classpath ของคุณ
ตอนนี้ควรใช้งานได้แม้ว่าวิธีการนั้นจะเรียกว่าหลายครั้งในโปรแกรม น่าเสียดายที่มันยังไม่สามารถใช้งานได้หากคุณมีการเรียกใช้หลายรายการในบรรทัดเดียว (สำหรับสิ่งที่ทำโปรดดูด้านล่าง)
ลองออนไลน์!
คำอธิบาย
StackTraceElement[] strace = new Exception().getStackTrace();
String methodName = strace[0].getMethodName();
int lineNum = strace[1].getLineNumber();
String className = strace[1].getClassName().replaceAll(".{5}$", "");
String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";
ส่วนแรกนี้รับข้อมูลทั่วไปเกี่ยวกับคลาสที่เราอยู่และชื่อของฟังก์ชันคืออะไร สิ่งนี้สามารถทำได้โดยการสร้างข้อยกเว้นและแยก 2 รายการแรกของการติดตามสแต็ก
java.lang.Exception
at E.getParamName(E.java:28)
at E.main(E.java:17)
รายการแรกคือบรรทัดที่มีข้อยกเว้นเกิดขึ้นซึ่งเราสามารถคว้าเมธอดจากและรายการที่สองคือตำแหน่งที่เรียกใช้ฟังก์ชัน
StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] {"-l", "-c", classPath}, new PrintWriter(javapOut));
ในบรรทัดนี้เรากำลังเรียกใช้งาน javap ที่มากับ JDK โปรแกรมนี้แยกวิเคราะห์ไฟล์คลาส (bytecode) และนำเสนอผลลัพธ์ที่มนุษย์สามารถอ่านได้ เราจะใช้สิ่งนี้เป็น "การแยกวิเคราะห์" ขั้นพื้นฐาน
List<String> javapLines = Arrays.asList(javapOut.toString().split("\\r?\\n"));
int byteCodeStart = -1;
Map<Integer, Integer> byteCodePointerToJavaPLine = new HashMap<Integer, Integer>();
Pattern byteCodeIndexPattern = Pattern.compile("^\\s*(\\d+): ");
for (int n = 0;n < javapLines.size();n++) {
String javapLine = javapLines.get(n);
if (byteCodeStart > -1 && (javapLine == null || "".equals(javapLine))) {
break;
}
Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
if (byteCodeIndexMatcher.find()) {
byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
} else if (javapLine.contains("line " + lineNum + ":")) {
byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
}
}
เรากำลังทำสองสิ่งที่แตกต่างกันที่นี่ อันดับแรกเรากำลังอ่าน javap เอาต์พุตบรรทัดโดยบรรทัดลงในรายการ ประการที่สองเรากำลังสร้างแผนที่ของดัชนีเส้น bytecode เพื่อดัชนี javap line สิ่งนี้จะช่วยเราในการพิจารณาการเรียกใช้เมธอดที่เราต้องการวิเคราะห์ ในที่สุดเรากำลังใช้หมายเลขบรรทัดที่รู้จักจากการติดตามสแต็กเพื่อกำหนดดัชนีบรรทัด bytecode ที่เราต้องการดู
int varLoadIndex = -1;
int varTableIndex = -1;
for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i < javapLines.size();i++) {
if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) {
varLoadIndex = i;
continue;
}
if (varLoadIndex > -1 && javapLines.get(i).contains("LocalVariableTable:")) {
varTableIndex = i;
break;
}
}
ที่นี่เรากำลังวนซ้ำ javap line อีกครั้งเพื่อค้นหาจุดที่วิธีการของเราถูกเรียกใช้และจุดเริ่มต้นของ Local Variable Table เราต้องการบรรทัดที่เรียกใช้เมธอดเนื่องจากบรรทัดก่อนที่จะมีการเรียกเพื่อโหลดตัวแปรและระบุว่าตัวแปรใด (โดยดัชนี) ที่จะโหลด Local Variable Table ช่วยให้เราค้นหาชื่อของตัวแปรตามดัชนีที่เราคว้า
String loadLine = javapLines.get(varLoadIndex - 1).trim();
int varNumber;
try {
varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
} catch (NumberFormatException e) {
return null;
}
ส่วนนี้แยกวิเคราะห์การเรียกเพื่อรับดัชนีตัวแปร สิ่งนี้อาจทำให้เกิดข้อยกเว้นถ้าฟังก์ชั่นไม่ได้ถูกเรียกใช้พร้อมกับตัวแปรดังนั้นเราจึงสามารถคืนค่า Null ได้ที่นี่
int j = varTableIndex + 2;
while(!"".equals(javapLines.get(j))) {
Matcher varName = Pattern.compile("\\s*" + varNumber + "\\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));
if (varName.find()) {
return varName.group(1);
}
j++;
}
return null;
ในที่สุดเราก็แยกชื่อของตัวแปรออกจากบรรทัดใน Local Variable Table ส่งคืน null หากไม่พบแม้ว่าฉันจะไม่เห็นเหตุผลว่าทำไมสิ่งนี้จึงเกิดขึ้น
วางมันทั้งหมดเข้าด้วยกัน
public static void main(java.lang.String[]);
Code:
...
18: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_1
22: aload_2
23: invokevirtual #25 // Method getParamName:(Ljava/lang/String;)Ljava/lang/String;
...
LineNumberTable:
...
line 17: 18
line 18: 29
line 19: 40
...
LocalVariableTable:
Start Length Slot Name Signature
0 83 0 args [Ljava/lang/String;
8 75 1 e LE;
11 72 2 str Ljava/lang/String;
14 69 3 str2 Ljava/lang/String;
18 65 4 str4 Ljava/lang/String;
77 5 5 e1 Ljava/lang/Exception;
นี่คือสิ่งที่เรากำลังมองหา ในโค้ดตัวอย่างการเรียกใช้ครั้งแรกคือบรรทัดที่ 17 บรรทัดที่ 17 ใน LineNumberTable แสดงให้เห็นว่าจุดเริ่มต้นของบรรทัดนั้นคือดัชนีบรรทัด bytecode 18 นั่นคือการSystem.out
โหลด จากนั้นเรามีaload_2
สิทธิ์ก่อนการเรียกเมธอดดังนั้นเราจึงค้นหาตัวแปรในช่องที่ 2 ของ LocalVariableTable ซึ่งอยู่str
ในกรณีนี้
เพื่อความสนุกนี่คือสายที่รองรับการใช้งานหลายฟังก์ชั่นในสายเดียวกัน สิ่งนี้ทำให้ฟังก์ชันไม่เป็น idempotent แต่เป็นจุดสำคัญ ลองออนไลน์!