Java compile-time class resolution - java

How can I get a class reference/TypeElement of a specific identifier at compile time in Java?
Say I have the following source file, and I want to get a reference to the Pixel class so I can get a list of its member fields.
package com.foo.bar;
class Test {
class Pixel {
int x,y,r,g,b;
}
Pixel saturate(Pixel p, int value) {...}
}
The Pixel class definition could be nested inside the Test class, or included from a different package where the source is not available.
I am using the javax.tools API to compile the source files, and I define visitor methods so I can view the arguments to each function. The arguments of a function can be iterated using VariableTree var : node.getParameters(), but the type information from var.getType() only triggers visitIdentifier for class names. This identifier is only the simple name Pixel, not the fully-qualified com.foo.bar.Pixel.
I need a way to reverse this identifier into either Pixel.class or into the TypeElement for the definition of the Pixel class, or into the fully-qualified com.foo.bar.Pixel string so I can then use a ClassLoader on it.
A crude way would be to record all class definitions and then try to do compile-time type lookup, but this wouldn't work for externally-defined classes.

As far as I remember var.getType().toString() returns you the fully qualified class name. Unfortunately I cannot check it right now, but try it yourself.
Yes, I know, it is a very bad style to use toString() for something except logging but it seems they have not given us other choice.

I ended up creating my own class lookup tool. For those that are interested, I'll include it here.
Call this with the path name of every source file to be included in the search:
public void populateClassDefinitions(String path) {
Iterable<? extends JavaFileObject> files = fileManager.getJavaFileObjects(path);
CompilationTask task =
compiler.getTask(null, fileManager, diagnosticsCollector, null, null, files);
final JavacTask javacTask = (JavacTask) task;
parseResult = null;
try {
parseResult = javacTask.parse();
} catch (IOException e) {
e.printStackTrace();
return;
}
for (CompilationUnitTree tree : parseResult) {
tree.accept(new TreeScanner<Void, Void>() {
#Override
public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
currentPackage = "";
ExpressionTree packageName = node.getPackageName();
if (packageName != null) {
String packageNameString = String.valueOf(packageName);
if (packageNameString.length() > 0) {
currentPackage = packageNameString;
}
}
TreeScanner<Void, String> visitor = new TreeScanner<Void, String>() {
#Override
public Void visitClass(ClassTree node, String packagePrefix) {
if (classDefinitions.get(currentPackage) == null) {
classDefinitions.put(currentPackage, new HashMap<String, ClassTree>());
}
classDefinitions.get(currentPackage).put(packagePrefix + node.getSimpleName(), node);
return super.visitClass(node, packagePrefix + node.getSimpleName() + ".");
}
};
for (Tree decls : node.getTypeDecls()) {
decls.accept(visitor, "");
}
return super.visitCompilationUnit(node, p);
}
}, null);
}
}
Call this to search for classes.
/**
* Lookup the definition of a class.
*
* Lookup order: 1. Search in the current file: within the current class scope upwards to the
* root. 2. Search laterally across files with the same package value for implicitly included
* classes. 3. Check all import statements.
*
* #param pack
* Current package ex "edu.illinois.crhc"
* #param scope
* Current scope ex "Test.InnerClass"
* #param identifier
* The partial class name to search for
* #return ClassTree the definition of this class if found
*/
ClassLookup lookupClass(CompilationUnitTree packTree, String scope, String identifier) {
dumpClassTable();
String pack = packTree.getPackageName().toString();
System.out.println("Looking for class " + pack + " - " + scope + " - " + identifier);
// Search nested scope and within same package
HashMap<String, ClassTree> packClasses = classDefinitions.get(pack);
if (packClasses != null) {
String[] scopeWalk = scope.split("\\.");
for (int i = scopeWalk.length; i >= 0; i--) {
StringBuilder scopeTest = new StringBuilder();
for (int j = 0; j < i; j++) {
scopeTest.append(scopeWalk[j] + ".");
}
scopeTest.append(identifier);
System.out.println("Testing scope " + pack + " - " + scopeTest.toString());
if (packClasses.containsKey(scopeTest.toString())) {
return new ClassLookup(packClasses.get(scopeTest.toString()), pack.replace(".", "/")
+ "/" + scopeTest.toString().replace(".", "$"));
}
}
}
/*
* Check if fully-qualified identifier (foo.bar.Widget) is used. This needs to search all
* combinations of package and class nesting.
*/
StringBuilder packTest = new StringBuilder();
String[] qualifiedName = identifier.split("\\.");
for (int i = 0; i < qualifiedName.length - 1; i++) {
packTest.append(qualifiedName[i]);
if (i != qualifiedName.length - 2) {
packTest.append(".");
}
}
String clazz = qualifiedName[qualifiedName.length - 1];
System.out.println("Testing absolute identifier: " + packTest.toString() + " " + clazz);
if (classDefinitions.containsKey(packTest.toString())) {
HashMap<String, ClassTree> foundPack = classDefinitions.get(packTest.toString());
if (foundPack.containsKey(clazz)) {
return new ClassLookup(foundPack.get(clazz), packTest.toString().replace(".", "/") + "/"
+ clazz.replace(".", "$"));
}
}
/*
* Search import statements. Last identifier segment must be class name. Search all of the
* packages for the identifier by splitting off the class name. a.b.c.Tree Tree.Branch
* Tree.Branch.Leaf
*/
for (ImportTree imp : currentPackTree.getImports()) {
pack = imp.getQualifiedIdentifier().toString();
System.out.println(pack);
String[] importName = pack.split("\\.");
// Split off class name.
// TODO: (edge case) no package
StringBuilder importTest = new StringBuilder();
for (int i = 0; i < importName.length - 1; i++) {
importTest.append(importName[i]);
if (i != importName.length - 2) {
importTest.append(".");
}
}
// See if the last import segment is * or matches the first segment of the identifier.
System.out.println("Testing globally " + importTest.toString() + " - " + identifier);
if (classDefinitions.containsKey(importTest.toString())) {
HashMap<String, ClassTree> foundPack = classDefinitions.get(importTest.toString());
String[] identifierParts = identifier.split(".");
String importClass = importName[importName.length-1];
if (importClass.equals("*") || identifierParts[0].equals(importClass)) {
if (foundPack.containsKey(identifier)) {
return new ClassLookup(foundPack.get(identifier), importTest.toString().replace(".", "/")
+ "/" + identifier.replace(".", "$"));
}
}
}
}
return null;
}

Related

trying to use another properties key value in another properties key by using a subclass of Properties to override getProperty() method

im trying to use another properties key value in another properties key by using a subclass of Properties class to ovveride getProperty() method but im not able to get the expected key value i wanted from the properties file. this is where i got the sub class code https://coderanch.com/t/469713/java/reference-defined-property-key-properties
sub class
import java.util.Properties;
/**
*
* A subclass of Properties that allows recursive
* references for property values. For example,
*
* <pre><code>
* A=12345678
* B={A}90
* C={B} plus more
* </code></pre>
*
* will result in <code>getProperty("C")</code>
* returning the value "1234567890 plus more".
*
* #author: Chris Mair
*
* Copied from: http://www2.sys-con.com/ITSG/virtualcd/Java/archives/0612/mair/index.html
*/
//#SuppressWarnings("serial")
public class XProperties extends Properties {
private static final long serialVersionUID = 1L;
private static final String START_DELIMITER = "${";
private static final String END_DELIMITER = "}";
#Override
public String getProperty(String key) {
String value = super.getProperty(key);
if (value != null) {
int startIndex = 0;
int endIndex = 0;
while ( (startIndex = value.indexOf(START_DELIMITER, endIndex)) >= 0
&& (endIndex = value.indexOf(END_DELIMITER, startIndex) ) >= 0) {
String variableName = value.substring(startIndex + START_DELIMITER.length(), endIndex);
// now call getProperty recursively to have this looked up
String variableValue = null;
if (!variableName.equals(key)) {
// only recurse if the variable does not equal our own original key
variableValue = this.getProperty(variableName);
}
if (variableValue == null) {
// when unable to find the variable value, just return it as the variable name
variableValue = START_DELIMITER + variableName + END_DELIMITER;
}
value = value.replace(START_DELIMITER + variableName + END_DELIMITER, variableValue);
} // while matches.
}
return value;
}
}
Properties file
ROOTDIR=C:/test_PROJECT
LOCALDIRWEB=${ROOTDIR}/cfo_web
LOCALDIRSERVER=${ROOTDIR}/cfo_server
implemetation of the above class
import java.util.Properties;
public class GitClone {
static XProperties a = new XProperties();
Properties prop = new Properties();
try {
a.load(GitClone.class.getClassLoader().getResourceAsStream("config.properties"));
} catch (IOException e) {
e.printStackTrace();
}
File cfoWebDir = new File (a.getProperty("LOCALDIRWEB"));
System.out.println(cfoWebDir);
Output when printed out
NOTHING PRINTED OUT
Expected output
C:\test_PROJECT\cfo_web
Any help with this? Im i doing the implementation wrong?

Java ASM CheckClassAdapter on ClassNode

The ASM CheckClassAdapter is very useful for obtaining useful log output about why a class failed in the case of verify errors, however it cannot be used where the stackmap frames are invalid.
The CheckClassAdapter can't be used for these situations because of it accepting a ClassReader, meaning that to use it to check my transformed ClassNode I have to do the following:
ClassWriter verifyWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(verifyWriter);
CheckClassAdapter.verify(new ClassReader(verifyWriter.toByteArray()), true, printDumpLogFile);
When the ClassWriter fails because of the bytecode of my ClassNode being strangely invalid, the output passed into the CheckClassAdapter is invalid and very malformed with many empty frames and NOPs. Is there any way to pass a ClassNode into the CheckClassAdapter, avoiding having to use a ClassWriter which limits its usefulness?
If you have a way of obtaining the input class' byte code instead of a ClassNode, you can use something like this:
CheckClassAdapter.verify(
bytes,
true,
new PrintWriter(System.out)
);
For example, if the byte code is already stored in a class file, you can do this:
CheckClassAdapter.verify(
new ClassReader(new FileInputStream(args[0])),
true,
new PrintWriter(System.out)
);
But actually, if the stackmap frames are invalid, the CheckClassAdapter might not report anything. At least I have class files in which it does not. The same applies to org.apache.bcel.verifier.Verifier, by the way. Still, the JVM throws a VerifyError, i.e. loading the class into an actual JVM is the ultimate check.
I found a solution, creating a class that extends CheckClassAdapter and adding a method with the ClassNode parameter. Reading the CheckClassAdapter's source, I found that that ClassReader visits a class node anyway.
public static void verify(
final ClassReader classReader,
final ClassLoader loader,
final boolean printResults,
final PrintWriter printWriter) {
ClassNode classNode = new ClassNode();
classReader.accept(
new CheckClassAdapter(/*latest*/ Opcodes.ASM10_EXPERIMENTAL, classNode, false) {},
ClassReader.SKIP_DEBUG);
As you can see, ASM could expose a method to just input in the ClassNode, but for some reason has not.
Here is code to a class that extends CheckClassAdapter, adding this functionality:
public class CheckClassAdapterClassNode extends CheckClassAdapter {
public CheckClassAdapterClassNode(ClassVisitor classVisitor) {
super(classVisitor);
}
/**
* Checks the given class.
*
* #param classNode the class to be checked.
* #param loader a <code>ClassLoader</code> which will be used to load referenced classes. May be
* {#literal null}.
* #param printResults whether to print the results of the bytecode verification.
* #param printWriter where the results (or the stack trace in case of error) must be printed.
*/
public static void verify(
final ClassNode classNode,
final ClassLoader loader,
final boolean printResults,
final PrintWriter printWriter) {
Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName);
List<MethodNode> methods = classNode.methods;
List<Type> interfaces = new ArrayList<>();
for (String interfaceName : classNode.interfaces) {
interfaces.add(Type.getObjectType(interfaceName));
}
for (MethodNode method : methods) {
SimpleVerifier verifier =
new SimpleVerifier(
Type.getObjectType(classNode.name),
syperType,
interfaces,
(classNode.access & Opcodes.ACC_INTERFACE) != 0);
Analyzer<BasicValue> analyzer = new Analyzer<>(verifier);
if (loader != null) {
verifier.setClassLoader(loader);
}
try {
analyzer.analyze(classNode.name, method);
} catch (AnalyzerException e) {
e.printStackTrace(printWriter);
}
if (printResults) {
printAnalyzerResult(method, analyzer, printWriter);
}
}
printWriter.flush();
}
static void printAnalyzerResult(
final MethodNode method, final Analyzer<BasicValue> analyzer, final PrintWriter printWriter) {
Textifier textifier = new Textifier();
TraceMethodVisitor traceMethodVisitor = new TraceMethodVisitor(textifier);
printWriter.println(method.name + method.desc);
for (int i = 0; i < method.instructions.size(); ++i) {
if (method.instructions.get(i) == null) continue;
method.instructions.get(i).accept(traceMethodVisitor);
StringBuilder stringBuilder = new StringBuilder();
Frame<BasicValue> frame = analyzer.getFrames()[i];
if (frame == null) {
stringBuilder.append('?');
} else {
for (int j = 0; j < frame.getLocals(); ++j) {
stringBuilder.append(getUnqualifiedName(frame.getLocal(j).toString())).append(' ');
}
stringBuilder.append(" : ");
for (int j = 0; j < frame.getStackSize(); ++j) {
stringBuilder.append(getUnqualifiedName(frame.getStack(j).toString())).append(' ');
}
}
while (stringBuilder.length() < method.maxStack + method.maxLocals + 1) {
stringBuilder.append(' ');
}
printWriter.print(Integer.toString(i + 100000).substring(1));
printWriter.print(
" " + stringBuilder + " : " + textifier.text.get(textifier.text.size() - 1));
}
for (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks) {
tryCatchBlock.accept(traceMethodVisitor);
printWriter.print(" " + textifier.text.get(textifier.text.size() - 1));
}
printWriter.println();
}
private static String getUnqualifiedName(final String name) {
int lastSlashIndex = name.lastIndexOf('/');
if (lastSlashIndex == -1) {
return name;
} else {
int endIndex = name.length();
if (name.charAt(endIndex - 1) == ';') {
endIndex--;
}
return name.substring(lastSlashIndex + 1, endIndex);
}
}
}
I will likely create an issue on the ASM repo and add a pull request to add this.

Java Array Out of Bounds Exception from Properties

I am trying to utilize a .properties file to get a list of strings and transform them into an array to be added with javassist later in my program. However, I keep getting an arrayoutofboundsexception 0 with the following data in properties:
modify.spells.in.
void configure(Properties options)
{
String nameSpace;
ArrayList<String> spellList = new ArrayList<String>();
String[] tokens;
for(String key : options.stringPropertyNames()) {
if(key.startsWith("modify.spells.in.")) {
nameSpace = key.substring(17);
tokens = options.getProperty(key).split(",");
for(String token : tokens) {
if(spellList.contains(nameSpace + "." + token)) {
continue;
}
spellList.add(nameSpace + "." + token);
}
}
}
if(spellList.size() == 0) {
itemSpells = new String[]{
"Fire1", "Fire2", "Fire3", "Lit1", "Lit2", "Lit3", "Ice1", "Ice2",
"Ice3"
};
for(int i = 0; i < itemSpells.length; i++) {
itemSpells[i] = "com.ragdoll.spells." + itemSpells[i];
}
} else {
itemSpells = spellList.toArray(new String[0]);
}
The properties file string is as follows:
modify.spells.in.com.ragdoll.spells=Fire3,Lit3,Ice3
The expression to edit the existing code is as follows:
I have a HookManager to add in interceptSpells(itemSpells);
private void interceptSpells(String[] classes) throws NotFoundException, CannotCompileException
{
CtClass c;
ClassPool cp = HookManager.getInstance().getClassPool();
CtMethod m = null;
// CAST -
for(int i = 0; i < classes.length; i++) {
final int j = i;
c = cp.get(classes[i]);
m = c.getDeclaredMethods("doEffect")[0];
m.instrument(new ExprEditor() {
public void edit(MethodCall m) throws CannotCompileException {
if (m.getMethodName().equals("addSpellEffect")) {
logger.info("Intercepting (1) " + classes[j]);
m.replace(
"$_ = $proceed($$);"
+ "com.rev.spellmod.getInstance().addSpellEffectCaster($0, eff, performer); "
);
} else if(m.getMethodName().equals("improvePower")) {
logger.info("Intercepting (2) " + classes[j]);
m.replace(
"$_ = $proceed($$);"
+ "com.rev.spellmod.getInstance().replaceSpellEffectCaster($0, eff, performer); "
);
}
}
});
I feel as though I have declared the namespace and am splitting the list correctly. If I add just one spell in the .properties file, it works. As soon as I add more than one it points back to the intercept code with the arrayoutofboundsexception. If I leave it blank, it also works with the pre-determined spells, but I was hoping to be able to modify the spells on the go rather the recompiling each time.
Any thoughts or help would be appreciated!
I realized the doEffect was the issue. Some of the spells inherited doEffect from another class and so that was producing the ArrayOutOfBoundsException not the parsing of the string which was correct.

removeZero() method when creating linkedlist?

I am creating a LinkedList from scratch, in the form of a train. So I have a class called Domino which creates each node, and then I have the class Train, which includes the add, size, remove etc methods. My problem is:
removeZeros() method: I cannot have any parameters, but I must delete all the nodes with zeros in them. What my program does instead is find all the zeros in the list, and delete all the nodes up until there are no more zeros. The zeros were added in a client class.
here is my Train class:
public class Train{
private Domino engine;
private Domino caboose;
private int insertS;
public Train(){
engine = null;
caboose = engine;
}
/** WHERE IM HAVING TROUBLE
* removeZero() - remove any Dominos from the train that have one or more zero spots
* while maintaining the linked list structure.
*/
// method is now just getting the spot1 0 and printing that
public void removeZero(){
Domino current = engine;
Domino hold = caboose.next;
while (current != hold) {
if(current.spot1 == 0 ||current.spot2 == 0){
current = current.next;
engine = current;
System.out.println("b " + engine);
}else{
current = current.next;
}
}
public String toString(){
String ts = "{ empty }";
if (engine == null) {
return ts;
} else {
Domino hold = engine;
ts = "{ ";
while (hold != caboose) {
ts += hold + ", ";
hold = hold.next;
}
ts += hold + " }";
}
return ts;
}
/**
*
* add(spot1, spot2) - add a Domino to the end of the Train with the given spots
*/
public void add(int spot1, int spot2){
if (engine == null) {
engine = new Domino(spot1,spot2);
caboose = engine;
} else {
caboose.next = new Domino(spot1, spot2, null,caboose);
//tail.next.back = tail;
caboose = caboose.next;
}
}
}
/**
* reversePrint() - like toString, but provides a String that lists
* all of the Dominos that are in the Train in reverse order
*/
public String reversePrint () {
Domino hold = caboose;
String reverse = "{ empty }";
if (engine == null) {
System.out.println(reverse);
} else {
reverse = "{ ";
while (hold != engine){
reverse += hold + ", ";
hold = hold.back;
}
reverse += hold + " }";
}
return reverse;
}
/**
* size() - return the number of Dominos in the Train
*/
public int size(){
int count = 0;
Domino hold = engine;
while(hold != null){
hold = hold.next;
count++;
}
return count;
}
/** insert(spot1, spot2, next, back) - insert a Domino in the middle of
* the Train where spot2 is the same as the spot1 of the next Domino and spot1
* is the same as spot2 of the previous Domino.
* (private)
*/
private void insert(int spot1,int spot2){
if (!(insertS == search)) {
Domino hold = engine;
while (hold != caboose) {
if (hold.spot1 == search) {
Domino newDom = new Domino(spot1, spot2, null,caboose);
hold.next = newDom;
newDom.next.back = newDom;
hold = hold.next;
} else {
hold = hold.next;
}
}
if (hold.spot2 == search) {
add(spot1, spot2);
}
} else {
System.out.println(" ** Error Inserting these values will cause an infinite loop:");
System.out.println(" * * * " + insertS + " and " + search + " * * *");
}
}
/**
* build() - scans through the Train creating links, using insert(spot1, spot2), between
* existing Dominos where the second spot of the first Domino does not match the
* first spot of the second domino, no param
*/
public void build(){
insert(search, insertS);
}
}
here is my Domino class:
public class Domino{
public int spot1; // the leading half how many spots it has
public int spot2; // the trailing half how many spots it has
public Domino next; // a link to the next Domino (type)?
public Domino back; // a link to the previous Domino
private int zero;
/**
* Constructor
* Creates null Domino
*
*/
public Domino(){
this(0,0 , null, null);
}
/**
* Constructor
* a constructor for Domino with given spots
*/
public Domino( int spot1, int spot2){
this(spot1,spot2, null, null);
}
/**
* Constructor
* #param: all fields
* setting variables
*/
public Domino(int spot1, int spot2, Domino next, Domino back){
this.spot1 = spot1;
this.spot2 = spot2;
this.next = next;
this.back = back;
}
/**
* toString(): prints out single Domino
*
*/
public String toString(){
if(this == null){
return("[empty]");
}else{
return("[ " + spot1 + " | "+ spot2 + "]");
}
}
}
I've really been stuck on this for the past day or so and can't seem to figure it out. Any help would be great. If you need the client code please say so. Thanks!
In the case of encountering a zero domino, you assign engine to be the current domino. As engine is the head of the list, this is equivalent to deleting all the items preceding the one containing a zero. Deletion in linked list is usually accomplished by something like:
toDelete.back.next = toDelete.next;
toDelete.next.back = toDelete.back
where toDelete is a Domino object with a zero in this case. As no domino now has a reference to the toDelete domino, it is essentially deleted.

Java: How to get the caller function name

To fix a test case I need to identify whether the function is called from a particular caller function. I can't afford to add a boolean parameter because it would break the interfaces defined. How to go about this?
This is what I want to achieve. Here I can't change the parameters of operation() as it is an interface implementation.
operation()
{
if not called from performancetest() method
do expensive bookkeeping operation
...
}
You could try
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
StackTraceElement e = stacktrace[2];//maybe this number needs to be corrected
String methodName = e.getMethodName();
Here is code that is more modern (available in Java 9+) and better performing.
private static String getCallerMethodName()
{
return StackWalker.
getInstance().
walk(stream -> stream.skip(1).findFirst().get()).
getMethodName();
}
Change skip(1) to a larger number as needed to go higher on the stack.
This performs better than Thread.currentThread().getStackTrace() because it does not walk the entire stack and allocate all of the stack frames. It only walks two frames on the stack.
This method can be adapted to return StackWalker.StackFrame that has a lot of information about the method.
Here's a function I wrote to Log the function name of the function that calls it. It runs up the stack trace until it finds a function named logIt, then displays the next name. It's a dirty hack, so don't do it unless you're using it to debug.
private static void logIt() {
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
boolean logged = false;
boolean foundMe = false;
for(int i=0; i<stacktrace.length; i++) {
StackTraceElement e = stacktrace[i];
String methodName = e.getMethodName();
if (foundMe) {
if (!methodName.startsWith("access$")) {
Log.i(TAG, String.format(Locale.US, "%s.%s", e.getClassName(), methodName));
logged = true;
break;
}
} else {
if (methodName.equals("logIt")) {
foundMe = true;
}
}
}
if (!logged)
Log.e(TAG, "unlogged call");
}
I tweaked the code that is being discussed here and customized it to get the invoking method. What the code does here is to iterate over the stack trace elements and as soon as it finds the name of the method being invoked, it gets the name of the previous method, which in turn will be the method that is invoking this method.
private String method() {
String methodName=null;
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
for (int i = 0; i < stacktrace.length; i++) {
if(stacktrace[i].getMethodName().equals("method")) {
methodName = stacktrace[i+1].getMethodName();
break;
}
}
return methodName;
}
Another sample for android usage:
//package your.package.name;
import android.util.Log;
/*
File name: MyDebugLog.java
*/
public class MyDebugLog {
private static final int index = 4; // <== Index in call stack array
private static final String methodName = "Log"; // <== Name of method for public call
private static String getCallerName() {
String caller = "NONE";
final StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
for (int i = 0; i < stacktrace.length; i++) {
Log.e("Method ", "[" + i + "]" + stacktrace[i].getMethodName());
}
if (stacktrace.length >= index){
caller = stacktrace[index].getMethodName();
}
return caller;
}
private static String getTag() {
String tag = "NONE";
final StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
for (int i = 0; i < stacktrace.length; i++) {
Log.e("Method ", "[" + i + "]" + stacktrace[i].getMethodName());
if (stacktrace[i].getMethodName().equals(methodName)) {
tag = "("+stacktrace[i + 1].getFileName() + ":" + stacktrace[i + 1].getLineNumber()+")";
return tag;
}
}
return tag;
}
public static void Log(String message){
Log.v(getTag(), getCallerName() + " " + message);
}
}
Usage:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_main);
MyDebugLog.Log("XXXXX");
}
Output:
V/(MainActivity.java:117): onCreate XXXXX
Sample of arrays:
getTag Sample of stacktace array:
Method: [0]getThreadStackTrace
Method: [1]getStackTrace
Method: [2]getTag
Method: [3]Log <== Method for external call
...
getName Sample of stacktace array:
Method: [0]getThreadStackTrace
Method: [1]getStackTrace
Method: [2]getCallerName
Method: [3]Log
Method: [4]onCreate <== Our external method
Method: [5]performCreate
...
I sometimes want to make some outputs to the logcat. So I wrote a tiny class with some testing-methods:
public class Common {
// can be used as test-flag anywhere in the app (set to false, when release the app)
public static boolean b_TEST_MODE = true;
public static void echo(String message) {
if (b_TEST_MODE) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
// subtring(25) is to cut off app-name from output
System.out.println(">>> " + stackTraceElements[3].toString().substring(25) + ": " + message);
}
}
}
now you can call it from anywhere in the app to get some infos:
String sSQLQuery = "SELECT * FROM database WHERE id=23";
Common.echo(sSQLQuery);
The logcat prints out:
>>> MainActivity.onCreate(MainActivity.java:46): SELECT * FROM dateabase WHERE id=23
I have no idea why but in my shop the develop system differs from the test and production environments shifting position in the stack. I was forced to loop through the stack to find and get the calling method from the next element in the stack trace. A little clunkier but so far has been consistently returning the desired method. I use this as part of my error handling to identify where an exception was caught.
List<String> list = new ArrayList<String>();
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
for (int i = 0; i < Thread.currentThread().getStackTrace().length; i++) {
System.out.println("Stack: "
+ i
+ " Class: "
+ elements[i].getClassName()
+ " Method: "
+ elements[i].getMethodName());
if (elements[i].getMethodName().equals("<init>")) {
list.add(elements[i + 1].getClassName());
list.add(elements[i + 1].getMethodName());
break;
} // if
} // for

Categories