B4J Question Similar to php's magic variable, gets the name of the current sub in the code

Daestrum

Expert
Licensed User
Longtime User
In java 9+ use StackWalker (with some inline java + JavaObject)

B4X:
...
Sub Button1_Click
    WhereAmINow
End Sub

Sub WhereAmINow
    Dim mName As String = (Me).As(JavaObject).RunMethod("findMe",Null)
    Log("In sub : " & mName)
End Sub

#if java
import java.lang.StackWalker;
import java.util.List;
import java.util.stream.Collectors;

public static String findMe() {
      List<StackWalker.StackFrame> stack = StackWalker.getInstance().walk(s -> s.collect(Collectors.toList()));
        // jump over findMe and javaobject runmethod and WhereAmINow
      return stack.get(3).getMethodName();
   }
#End If

If you need to know the module name too change
B4X:
      return stack.get(3).getMethodName();
to
B4X:
return stack.get(3).getMethodName() + " in module " + stack.get(3).getClassName();
 
Last edited:
Upvote 0

hzq200409

Member
Licensed User
That's great. I just tried it in my jserver and it worked pretty well. Thank you very much!
In java 9+ use StackWalker (with some inline java + JavaObject)

B4X:
...
Sub Button1_Click
    WhereAmINow
End Sub

Sub WhereAmINow
    Dim mName As String = (Me).As(JavaObject).RunMethod("findMe",Null)
    Log("In sub : " & mName)
End Sub

#if java
import java.lang.StackWalker;
import java.util.List;
import java.util.stream.Collectors;

public static String findMe() {
      List<StackWalker.StackFrame> stack = StackWalker.getInstance().walk(s -> s.collect(Collectors.toList()));
        // jump over findMe and javaobject runmethod and WhereAmINow
      return stack.get(3).getMethodName();
   }
#End If

If you need to know the module name too change
B4X:
      return stack.get(3).getMethodName();

B4X:
return stack.get(3).getMethodName() + " in module " + stack.get(3).getClassName();
That's great. I just tried it in my jserver and it worked pretty well. Thank you very much!
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
I expanded the code a bit so it can now print the following when called from inside the IDE.

Called in method <_button1_click> at B4X line 25 in module b4j.example.main (JAVA) line 90 in source file [main.java]
 

Attachments

  • currentMethodTest.zip
    3 KB · Views: 118
Last edited:
Upvote 0

Sandman

Expert
Licensed User
Longtime User
And if I've compile obfuscated, it's the generated sub name showing up, I assume? Might be good to know so one isn't surprised.
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
Not tried obfuscated - I would guess the mangled name is shown as that gets compiled.

**Edit: just tried and it is the mangled name that shows. If there were a way to detect Obfuscation like #If Obfuscated then it would be easy to get the real name.
Just trying best guess at deciding if method name if mangled and retrieving the real name for the output.

**Edit 2
Ok, adding this after the #If Release .. #End If catches Obfuscated names (mostly). The last line below is a modified line not one to be added

It probably needs some error handling in case Obfuscated release has never been done and it fails to find the map file. ( I leave that to you)

B4X:
        Dim obsinp As InputStream
        obsinp = File.OpenInput("../objects/","ObfuscatorMap.txt")
        Dim obsreader As TextReader
        obsreader.Initialize(obsinp)
        Dim obslist As List = obsreader.ReadList
        obsinp.Close
        For Each item As String In obslist
            If item.StartsWith(d.method.SubString(1)) Then 'd.method has leading _ in name
                mname = item.SubString(item.IndexOf("=")+1)
                Exit
            End If
        Next

        If mname = "" Then mname = d.method

    Log($"Called in method <${mname}> at B4X line ${d.b4xlinenumber} in module ${d.clss} (JAVA) line ${d.javalinenumber} in source file [${d.sourcefile}] ${IIf(d.method = mname,"",$"Obfuscated name is ${d.method}"$)}"$)

This is the sample output

Called in method <where> at B4X line 11 in module b4j.example.mod1 (JAVA) line 44 in source file [mod1.java] Obfuscated name is _v0
 
Last edited:
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
Final version with some error checking (uses ObfuscatorMap as a properties file so only reads what it needs)

NOTE: It only works inside the IDE, if you compile to standalone it will break ( no source files to read from )
 

Attachments

  • currentMethodTest.zip
    3.6 KB · Views: 114
Last edited:
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
From my (limited) testing it works in Debug , Release and Obfuscated Release.

The only difference is the line number in debug is not shown for the B4X code.

I can 'sort of', in debug, get the line number, but it's not exact like in release. I can determine which line within a method it is called, but not the actual line number in the B4X source code.
So it would print something like

Called in method <where> around method line 5 in module b4j.example.mod1 (JAVA) line 44 in source file [mod1.java]
 
Last edited:
Upvote 0

TelKel81

Active Member
Licensed User
I made a simplified version for those interested. Note that it won't return the correct class/method name if you put a breakpoint (in debug mode) in the method that you want to investigate.
B4X:
Sub GetClassNameAndMethodName As String()
    Dim list As List = (Me).As(JavaObject).RunMethod("getStackFrame", Null)
    Dim item As JavaObject = list.Get(3)
    Dim className As String = item.RunMethod("getClassName", Null)
    className = className.SubString(className.LastIndexOf(".") + 1)
    Dim methodName As String = item.RunMethod("getMethodName", Null).As(String).SubString(1)
    Return Array As String(className, methodName)
End Sub

#if java
import java.lang.StackWalker;
import java.util.List;
import java.util.stream.Collectors;

public static List<StackWalker.StackFrame> getStackFrame() {
      return StackWalker.getInstance().walk(s -> s.collect(Collectors.toList()));
   }
#End If
I put the above code in my Util module.

Trivial usage, from within amethod you want to get info about :
B4X:
Log(Util.GetClassNameAndMethodName(0) & " -> " & Util.GetClassNameAndMethodName(1))

More practical usage in my case : change list.Get(3) to list.Get(4), create a logging module such as "Logger", create a method for output such as "Say".
B4X:
Sub Say(Something As String)
    Dim nfo() As String = Util.GetClassNameAndMethodName
    Log(nfo(0) & " -> " & nfo(1) & " -> " & Something)
End Sub

And finally, from any method :
B4X:
Logger.Say("This is some text passed as input and I won't have to specify class/module because Logger.Say will find it...")
'Output : main -> appstart -> This is some text passed as input and I won't have to specify class/module because Logger.Say will find it...
 
Last edited:
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
B4X:
Sub Say(Something As String)
    Dim nfo() As String = Util.GetClassNameAndMethodName
    Log(nfo(0) & " -> " & nfo(1) & " -> " & Something)
End Sub
I don't understand the benefit of writing the above code instead of directly:
B4X:
Sub Say(Something As String)
    Log("NameOfThisClass -> Say " & Something)
End Sub
 
Upvote 0

TelKel81

Active Member
Licensed User
I'm not sure you understand the last line of my post ? "And finally, from any method :"... but let's assume that you do.

Benefits :
1) If at some point I change a class name or method name, I won't have to update the Log string
2) In fact, I will never have to type the method/class name, ever, when logging anything. How is that not a benefit ?
3) In the Say method, I can immediately update the output format (e.g. different separator, colors, etc), as opposed to : going through ALL The methods to make changes.

I'm sure there's little benefit in a small application, but I'm making a game with a lot of classes and methods, so... I need consistency :)
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
I'm not sure you understand the last line of my post ? "And finally, from any method :"... but let's assume that you do.

Benefits :
1) If at some point I change a class name or method name, I won't have to update the Log string
2) In fact, I will never have to type the method/class name, ever, when logging anything. How is that not a benefit ?
3) In the Say method, I can immediately update the output format (e.g. different separator, colors, etc), as opposed to : going through ALL The methods to make changes.

I'm sure there's little benefit in a small application, but I'm making a game with a lot of classes and methods, so... I need consistency :)
I agree with point 1 but it is certainly not fundamental given that usually at most the names of variables are changed, not of classes.

That functionality would be useful if a log of that kind, reporting class and method, was triggered in the event of a crash exception; if it was always listening, let's say.
 
Upvote 0

TelKel81

Active Member
Licensed User
My game is multiplayer, sub calls often correlate with game events / user actions, therefore timestamps + class/method names is quite relevant.

Example:
NetClient -> Message IN -> Move OK
Data_Unit -> Move -> at xy
GFX_Unit -> Move -> at xy
Etc

But in the typical context of a small / simple app, I agree, it's not fundamental at all, probably even a sad waste of time :)
 
Upvote 0
Top