जावा
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;
}
यह वर्तमान में कुछ गोचरों के साथ काम करता है:
- यदि आप इसे संकलित करने के लिए IDE का उपयोग करते हैं, तो यह तब तक काम नहीं कर सकता जब तक कि इसे व्यवस्थापक के रूप में नहीं चलाया जाता है (यह निर्भर करता है कि अस्थायी कक्षा फ़ाइलें कहाँ सहेजी गई हैं)
- आपको ध्वज के
javac
साथ संकलन करना होगा -g
। यह संकलित वर्ग फ़ाइल में स्थानीय चर नामों सहित सभी डीबगिंग जानकारी उत्पन्न करता है।
- यह एक आंतरिक जावा एपीआई का उपयोग करता है
com.sun.tools.javap
जो एक क्लासफाइल के बाईटेकोड को पार्स करता है और एक मानव पठनीय परिणाम पैदा करता है। यह एपीआई केवल JDK पुस्तकालयों में उपलब्ध है, इसलिए आपको या तो JDK जावा रनटाइम का उपयोग करना होगा या अपने classpath में tools.jar को जोड़ना होगा।
यदि प्रोग्राम को कई बार विधि कहा जाता है तो भी यह काम करना चाहिए। दुर्भाग्य से यह तब तक काम नहीं करता है जब आपके पास एक ही लाइन पर कई इनवोकेशन हों। (जो करता है, उसके लिए नीचे देखें)
इसे ऑनलाइन आज़माएं!
व्याख्या
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));
इस पंक्ति में हम JDK के साथ आने वाले javap निष्पादन योग्य को निष्पादित कर रहे हैं। यह प्रोग्राम क्लास फ़ाइल (बाइटकोड) को पार्स करता है और मानव-पठनीय परिणाम प्रस्तुत करता है। हम इसका उपयोग अल्पविकसित "पार्सिंग" के लिए करेंगे।
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 आउटपुट लाइन पढ़ रहे हैं। दूसरा हम बायटेकोड लाइन इंडेक्स का एक नक्शा बना रहे हैं जो जेवैप लाइन इंडेक्स को दर्शाता है। यह हमें बाद में यह निर्धारित करने में मदद करता है कि हम किस पद्धति के आह्वान का विश्लेषण करना चाहते हैं। अंत में हम स्टैक ट्रेस से ज्ञात लाइन नंबर का उपयोग कर रहे हैं यह निर्धारित करने के लिए कि हम किस बायोटेक लाइन इंडेक्स को देखना चाहते हैं।
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;
अंत में हम स्थानीय चर तालिका में पंक्ति से चर के नाम को पार्स करते हैं। यदि यह नहीं मिला है, तो अशक्त लौटें, हालांकि मैंने ऐसा करने का कोई कारण नहीं देखा है।
यह सब एक साथ डालें
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 में लाइननंबरटेबल में दिखाया गया है कि उस लाइन की शुरुआत बायटेकोड लाइन इंडेक्स 18 है System.out
। यह भार है। फिर हमारे पास aload_2
मेथड कॉल से ठीक पहले है ताकि हम लोकल वियरेबलटेबल के स्लॉट 2 में वैरिएबल को देखें जो str
इस मामले में है।
मज़े के लिए, यहाँ एक ही लाइन पर एक से अधिक फ़ंक्शन कॉल संभालता है। यह फ़ंक्शन को बेकार नहीं होने का कारण बनता है लेकिन यह इस तरह का बिंदु है। इसे ऑनलाइन आज़माएं!