This question already has answers here:
Printing debug info on errors with java 8 lambda expressions
(2 answers)
How can I find the target of a Java8 method reference?
(2 answers)
Closed 3 years ago.
If a method in a library is called with a Java Lambda expression, these are often just wrapped method calls. Is it possible to find out what method that originally was, just for logging purposes? (Another question is about what object it applies to - this is specifically about the called method.)
class Foo {
private void doSomething() { ... }
public void doSomethingInTransaction() {
doInTransaction(this::doSomething);
}
private void doInTransaction(Runnable run) { ... }
}
When calling doSomethingInTransaction() the method doInTransaction is actually called with an Object of type Runnable. It would sometimes be nice to log the name and class of the method that was passed here (that is, Foo.doSomething), as well as the object. Is it somehow possible to find out what that was via reflection or whatnot? If that requires specific Java versions, that'd be an interesting answer, too.
(UPDATE: please note that this is not a duplicate of the related question Java 8 - how to access object and method encapsulated as lambda since I'm mostly asking for the method that is encapsulated there. That wasn't asked there.)
The following example shows how to get the method reference name from the runnable. As explained in the comments, the code may be unnecesserarily complex and only works for certain cases (including the one in the question). Also, it makes certain assumptions that don't work in the general case.
Example class:
public class Test {
public void callingMethod() {
this.acceptingMethod(this::methodReferenceMethod);
}
public void acceptingMethod(final Runnable runnable) {
final String name = Util.getRunnableName(runnable, "acceptingMethod");
System.out.println("Name is " + name);
}
public void methodReferenceMethod() {
}
public static void main(final String[] args) {
new Test().callingMethod();
}
}
Now the actual magic here:
class Util {
public static String getRunnableName(final Runnable runnable, final String calledMethodName) {
final String callSiteMethodName = getCallSiteMethodNameNotThreadSafe();
final Class<?> callSiteClass = getDeclaringClass(runnable);
final String runnableName = extractRunnableName(callSiteClass, callSiteMethodName, calledMethodName);
return runnableName;
}
private static String extractRunnableName(
final Class<?> callSiteClass,
final String callSiteMethodName,
final String calledMethodName) {
try {
final AtomicReference<String> result = new AtomicReference<>(null);
final ClassReader cr = new ClassReader(callSiteClass.getName());
final TraceClassVisitor traceVisitor = new TraceClassVisitor(new PrintWriter(System.out));
cr.accept(new CheckClassAdapter(Opcodes.ASM7, traceVisitor, false) {
#Override
public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
if (!name.equals(callSiteMethodName)) {
return super.visitMethod(access, calledMethodName, descriptor, signature, exceptions);
}
return new CheckMethodAdapter(Opcodes.ASM7, super.visitMethod(access, name, descriptor, signature, exceptions), new HashMap<>()) {
#Override
public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
final String invokeDynamic = ((Handle) bootstrapMethodArguments[1]).getName();
result.set(invokeDynamic);
}
};
}
}, 0);
return result.get();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
public static String getCallSiteMethodNameNotThreadSafe() {
final int depth = 4;
return Thread.currentThread().getStackTrace()[depth].getMethodName();
}
public static Class<?> getDeclaringClass(final Runnable runnable) {
return Arrays.stream(runnable.getClass().getDeclaredFields())
.filter(f -> f.getName().equals("arg$1"))
.map(f -> {
f.setAccessible(true);
try {
return f.get(runnable).getClass();
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.findFirst()
.orElseThrow(IllegalStateException::new);
}
}
The output is as expected "Name is methodReferenceMethod". I would probably never use this in any project, but I guess it is possible. Also, this only works for the given example, as there is only one INVOKEVIRTUAL in the calling method. For the general case, one would need to adjust the checkMethodVisitor and filter the calls to the "calledMethodName" only. Lastly, the code to get the calling method uses a fixed index for the stack trace element, which also does not generalize well.
Related
I need to get a list of all caller methods for a method of interest for me in Java. Is there a tool that can help me with this?
Edit: I forgot to mention that I need to do this from a program. I'm usig Java Pathfinder and I want to run it an all the methods that call my method of interest.
For analyzing bytecode, I would recommend ASM. Given a list of Classes to analyze, a visitor can be made which finds the method calls you're interested in. One implementation which analyses classes in a jar file is below.
Note that ASM uses internalNames with '/' instead of '.' as a separator. Specify the target method as a standard declaration without modifiers.
For example, to list methods that could be calling System.out.println("foo") in the java runtime jar:
java -cp "classes;asm-3.1.jar;asm-commons-3.1.jar" App \
c:/java/jdk/jre/lib/rt.jar \
java/io/PrintStream "void println(String)"
Edit: source and line numbers added: Note that this only indicates the last target method invocation per calling method - the original q only wanted to know which methods. I leave it as an exercise for the reader to show line numbers of the calling method declaration, or the line numbers of every target invocation, depending on what you're actually after. :)
results in:
LogSupport.java:44 com/sun/activation/registries/LogSupport log (Ljava/lang/String;)V
LogSupport.java:50 com/sun/activation/registries/LogSupport log (Ljava/lang/String;Ljava/lang/Throwable;)V
...
Throwable.java:498 java/lang/Throwable printStackTraceAsCause (Ljava/io/PrintStream;[Ljava/lang/StackTraceElement;)V
--
885 methods invoke java/io/PrintStream println (Ljava/lang/String;)V
source:
public class App {
private String targetClass;
private Method targetMethod;
private AppClassVisitor cv;
private ArrayList<Callee> callees = new ArrayList<Callee>();
private static class Callee {
String className;
String methodName;
String methodDesc;
String source;
int line;
public Callee(String cName, String mName, String mDesc, String src, int ln) {
className = cName; methodName = mName; methodDesc = mDesc; source = src; line = ln;
}
}
private class AppMethodVisitor extends MethodAdapter {
boolean callsTarget;
int line;
public AppMethodVisitor() { super(new EmptyVisitor()); }
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (owner.equals(targetClass)
&& name.equals(targetMethod.getName())
&& desc.equals(targetMethod.getDescriptor())) {
callsTarget = true;
}
}
public void visitCode() {
callsTarget = false;
}
public void visitLineNumber(int line, Label start) {
this.line = line;
}
public void visitEnd() {
if (callsTarget)
callees.add(new Callee(cv.className, cv.methodName, cv.methodDesc,
cv.source, line));
}
}
private class AppClassVisitor extends ClassAdapter {
private AppMethodVisitor mv = new AppMethodVisitor();
public String source;
public String className;
public String methodName;
public String methodDesc;
public AppClassVisitor() { super(new EmptyVisitor()); }
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
className = name;
}
public void visitSource(String source, String debug) {
this.source = source;
}
public MethodVisitor visitMethod(int access, String name,
String desc, String signature,
String[] exceptions) {
methodName = name;
methodDesc = desc;
return mv;
}
}
public void findCallingMethodsInJar(String jarPath, String targetClass,
String targetMethodDeclaration) throws Exception {
this.targetClass = targetClass;
this.targetMethod = Method.getMethod(targetMethodDeclaration);
this.cv = new AppClassVisitor();
JarFile jarFile = new JarFile(jarPath);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class")) {
InputStream stream = new BufferedInputStream(jarFile.getInputStream(entry), 1024);
ClassReader reader = new ClassReader(stream);
reader.accept(cv, 0);
stream.close();
}
}
}
public static void main( String[] args ) {
try {
App app = new App();
app.findCallingMethodsInJar(args[0], args[1], args[2]);
for (Callee c : app.callees) {
System.out.println(c.source+":"+c.line+" "+c.className+" "+c.methodName+" "+c.methodDesc);
}
System.out.println("--\n"+app.callees.size()+" methods invoke "+
app.targetClass+" "+
app.targetMethod.getName()+" "+app.targetMethod.getDescriptor());
} catch(Exception x) {
x.printStackTrace();
}
}
}
Edit: the original question was edited to indicate a runtime solution was needed - this answer was given before that edit and only indicates how to do it during development.
If you are using Eclipse you can right click the method and choose "Open call hierarchy" to get this information.
Updated after reading comments: Other IDEs support this as well in a similar fashion (at least Netbeans and IntelliJ do)
Annotate the method with #Deprecated ( or tag it with #deprecated ), turn on deprecation warnings, run your compile and see which warnings get triggered.
The run your compile bit can be done either by invoking an external ant process or by using the Java 6 compiler API.
right click on method
Go to references and (depending on your requirement)
choose workspace/project/Hierarchy.
This pops up a panel that shows all references to this functions. Eclipse FTW !
In eclipse, highlight the method name and then Ctrl+Shift+G
There isn't a way to do this (programmatically) via the Java reflection libraries - you can't ask a java.lang.reflect.Method "which methods do you call?"
That leaves two other options I can think of:
Static analysis of the source code. I'm sure this is what the Eclipse Java toolset does - you could look at the Eclipse source behind the JDT, and find what it does when you ask Eclipse to "Find References" to a method.
Bytecode analysis. You could inspect the bytecode for calls to the method. I'm not sure what libraries or examples are out there to help with this - but I can't imagine that something doesn't exist.
Yes, most modern IDE:s will let you either search for usages of a method or variable. Alternatively, you could use a debugger and set a trace point on the method entry, printing a stack trace or whatever every time the method is invoked.
Finally, you could use some simple shell util to just grep for the method, such as
find . -name '*.java' -exec grep -H methodName {} ;
The only method that will let you find invokations made through some reflection method, though, would be using the debugger.
I made a small example using #Chadwick's one. It's a test that assesses if calls to getDatabaseEngine() are made by methods that implement #Transaction.
/**
* Ensures that methods that call {#link DatabaseProvider#getDatabaseEngine()}
* implement the {#link #Transaction} annotation.
*
* #throws Exception If something occurs while testing.
*/
#Test
public void ensure() throws Exception {
final Method method = Method.getMethod(
DatabaseEngine.class.getCanonicalName() + " getDatabaseEngine()");
final ArrayList<java.lang.reflect.Method> faultyMethods = Lists.newArrayList();
for (Path p : getAllClasses()) {
try (InputStream stream = new BufferedInputStream(Files.newInputStream(p))) {
ClassReader reader = new ClassReader(stream);
reader.accept(new ClassAdapter(new EmptyVisitor()) {
#Override
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
return new MethodAdapter(new EmptyVisitor()) {
#Override
public void visitMethodInsn(int opcode, String owner, String nameCode, String descCode) {
try {
final Class<?> klass = Class.forName(Type.getObjectType(owner).getClassName());
if (DatabaseProvider.class.isAssignableFrom(klass) &&
nameCode.equals(method.getName()) &&
descCode.equals(method.getDescriptor())) {
final java.lang.reflect.Method method = klass.getDeclaredMethod(name,
getParameters(desc).toArray(new Class[]{}));
for (Annotation annotation : method.getDeclaredAnnotations()) {
if (annotation.annotationType().equals(Transaction.class)) {
return;
}
}
faultyMethods.add(method);
}
} catch (Exception e) {
Throwables.propagate(e);
}
}
};
}
}, 0);
}
}
if (!faultyMethods.isEmpty()) {
fail("\n\nThe following methods must implement #Transaction because they're calling getDatabaseEngine().\n\n" + Joiner.on("\n").join
(faultyMethods) + "\n\n");
}
}
/**
* Gets all the classes from target.
*
* #return The list of classes.
* #throws IOException If something occurs while collecting those classes.
*/
private List<Path> getAllClasses() throws IOException {
final ImmutableList.Builder<Path> builder = new ImmutableList.Builder<>();
Files.walkFileTree(Paths.get("target", "classes"), new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
if (file.getFileName().toString().endsWith(".class")) {
builder.add(file);
}
return FileVisitResult.CONTINUE;
}
});
return builder.build();
}
/**
* Gets the list of parameters given the description.
*
* #param desc The method description.
* #return The list of parameters.
* #throws Exception If something occurs getting the parameters.
*/
private List<Class<?>> getParameters(String desc) throws Exception {
ImmutableList.Builder<Class<?>> obj = new ImmutableList.Builder<>();
for (Type type : Type.getArgumentTypes(desc)) {
obj.add(ClassUtils.getClass(type.getClassName()));
}
return obj.build();
}
1)In eclipse it is ->right click on the method and select open call hierarchy or CLT+ALT+H
2)In jdeveloper it is -> right click on the method and select calls or ALT+SHIFT+H
The closest that I could find was the method described in this StackOverflow questions selected answer.check this out
You can do this with something in your IDE such as "Find Usages" (which is what it is called in Netbeans and JDeveloper). A couple of things to note:
If your method implements a method from an interface or base class, you can only know that your method is POSSIBLY called.
A lot of Java frameworks use Reflection to call your method (IE Spring, Hibernate, JSF, etc), so be careful of that.
On the same note, your method could be called by some framework, reflectively or not, so again be careful.
I am extracting concept from a single page and that page is being used in different functions, so i have created a function that assigns the value if value hasn't been assigned yet.
public String text() {
if (text.isPresent()) {
return text.get();
}
this.text = Optional.of(extractText(pdDocument));
return text.get();
}
I would like to create a test that checks that the function is being called once and the context is shared between the functions that doing some functionality in the same context
Here is an example when text() is being called twice
private Optional<String> packingListNet() {
return locatePattern(text(), PACKING_LIST_NET);
}
private Optional<String> packingListNumber() {
return locatePattern(text(), PACKING_LIST_NUMBER);
}
Would be grateful for any information, thank you
The technical answer: you could use a mocking library, such as PowerMockito, to create mocked instances of the Optional class. And when you have a mock object, you can instruct the mock how to react to method calls. Then you need to "get" the mocked Optional object into your class under test.
You could use that to cover the first if statement: you expect the mock to see the isPresent() call, to return true, and to then return a specific string. Your testcase could then check "that expected string came back". Similar for the other way round, when the mocked Optional "is empty", then you ensure another string is returned, and you check for that.
But honestly, it is doubtful if you should do that all.
You should focus on the public contract that your method there provides.
And that would mean:
Enable yourself that you can pass in a (real) Optional object into the class under test
When your Optional is not empty, your test expects ... what you put into it
When your Optional is empty, your test expects whatever extractText() will return
Of course, mocking is really problematic here: Optional is a final class. So you need to either use Mockito with "experimental support for final enabled", or you need to use PowerMock(ito) (which I strongly advise to never use).
So, as said: avoid mocking.
I think this method is badly conceived. It may rely on private, mutable state that will be a problem with multiple documents and threads accessing them.
A better approach would be to pass all the necessary information as method parameters. They are thread safe that way;
public static String getText(String textToSearchFor, Document pdfDocument) {
// extract here
}
Here's how I might write a JUnit test for a method like this:
public class TextMethodOwnerTest {
#Test
public void testGetText_Success() {
// setup
String expected = "text to find";
Document pdf; // Have to get this.
// exercise
String actual = TextMethodOwner.getText(expected, pdf);
// assert
Assert.assertEquals(expected, actual);
}
#Test
public void testGetText_PackingListNumber() {
// Add another case here
}
#Test
public void testGetText_PackingListNet() {
// Add another case here
}
}
I am not sure what are you trying to ask. Information is not clear but maybe this can help you:
Junit and mockito are mostly used togther. If you want to check any function gets called only one time we use verify() method of mockito with parameter atLeast(1)
For example: Example taken from (https://www.baeldung.com/mockito-verify)
List<String> mockedList = mock(MyList.class);
mockedList.clear();
mockedList.clear();
mockedList.clear();
verify(mockedList, atLeast(1)).clear();
Here is some pseudo code how you could achieve it:
public Class {
int counter = 0;
void test() {
counter++;
}
}
public ClassTest {
public Class class;
void shouldBeCalledOneTime() {
class.test();
AssertThat(class).hasFieldWithValue("counter", 1);
}
}
Since your question seemed to me mostly about reading the file only once, which is quite a genuine need for many, I wrote up this class using your code, but without JUnit.
This has a main method that calls the packingList*() method 100 times to different threads, but you will see that the extraction part is entered into only once in the beginning. For this, I have added a lock and used a synchronized block. I understand that this is basic, but thought I may share since it might help others.
Note the changes in the method public String text().
public class ReusedText{
private static final long PACKING_LIST_NET = 200;
private static final long PACKING_LIST_NUMBER = 120;
private Optional<String> text = Optional.ofNullable( null );
private Document pdDocument;
private static final Object LOCK = new Object();
private static final ExecutorService svc = Executors.newFixedThreadPool( 2 );
public ReusedText(Document pdDocument) {
super();
this.pdDocument = pdDocument;
}
public static void main( String[] args ){
ReusedText rt = new ReusedText( new Document( "/path/to/document/on/disk" ) );
for( int i = 0; i < 100; i++ ) {
svc.submit( () -> System.out.println( rt.packingListNet() ) );
svc.submit( () -> System.out.println( rt.packingListNumber() ) );
}
svc.shutdown();
}
public String text() {
if (text.isPresent()) {
return text.get();
}
else {
synchronized (LOCK) {
/* This repeated 'if' block is necessary because 'text' may have got populated while this thread was waiting for lock. */
if (text.isPresent()) return text.get();
else{
System.out.println( "Extracting text..." );
this.text = Optional.of( extractText( pdDocument ) );
return text.get();
}
}
}
}
private String extractText( Document doc ) {
//Read the file contents using some API like java.nio.file.Files or Apache Tika
return "file contents here!";
}
private Optional<String> packingListNet() {
return locatePattern(text(), PACKING_LIST_NET);
}
private Optional<String> packingListNumber() {
return locatePattern(text(), PACKING_LIST_NUMBER);
}
private Optional<String> locatePattern( String text, long packingListNumber ){
//Implement your logic with the text here.
return Optional.of( String.valueOf( packingListNumber ) );
}
private static class Document{
private String pathToText;
public Document(String pathToText) {
super();
this.pathToText = pathToText;
}
public String getPathToText(){
return pathToText;
}
}
}
So, I have the following classes:
public class MainClass{
public void run(String infoOne, String infoTwo, String infoThree, String infoFour, String infoFive, String infoSix){
SomeClass someClass = new SomeClass();
someClass.runSomeMethod();
someClass.runSomeMethodTwo( infoOne);
someClass.runSomeMethodThree( infoThree, infoOne, infoSix);
someClass.runSomeMethodFour( infoTwo, infoFive);
someClass.runSomeMethodFive(infoThree, infoFive, infoOne, infoSix);
}
}
public class SomeClass{
public boolean runSomeMethod(){
// do something
}
public boolean runSomeMethodTwo(String arg){
// do something
}
public boolean runSomeMethodThree(String argOne, String argTwo, String argThree){
// do something
}
public boolean runSomeMethodFour(String argOne, String argTwo){
// do something
}
public boolean runSomeMethodFive(String argOne, String argTwo, String argThree, String argFour){
// do something
}
}
As you can see it's a bunch of methods taking only Strings as parameters (but a different amount every time). What I want now is to wrap each single method in a try catch block and log some results. To do that I wanted to put a method in between that handles the logging:
log(SomeClass::runSomeMethodFour);
public void log(????? method, String...args){
try{
if(method.execute(args);
System.out.println("Success!");
} else {
System.out.println("Failed to execute!");
}
} catch (Exception e){
e.printStackTrace();
}
}
Is this possible in some way? To pass a dynamic number of arguments to a lambda function? Or could I do something with generics?
There is no need to create a complicated Reflection-based solution. Your problems stem from the unnecessary attempt to separate the method and the parameter arguments, instead of just encapsulating the entire action like
public class MainClass {
public void run(String infoOne, String infoTwo, String infoThree,
String infoFour, String infoFive, String infoSix) {
SomeClass someClass = new SomeClass();
log(() -> someClass.runSomeMethod());
log(() -> someClass.runSomeMethodTwo(infoOne));
log(() -> someClass.runSomeMethodThree(infoThree, infoOne, infoSix));
log(() -> someClass.runSomeMethodFour(infoTwo, infoFive));
log(() -> someClass.runSomeMethodFive(infoThree, infoFive, infoOne, infoSix));
}
public void log(BooleanSupplier method) {
try {
if(method.getAsBoolean()) {
System.out.println("Success!");
} else {
System.out.println("Failed to execute!");
}
} catch (Exception e ){
e.printStackTrace();
}
}
}
For the work of the log method, only the boolean return value is relevant, which matches the functional signature of BooleanSupplier.
JLS described Method Reference Expression:
The compile-time declaration of a method reference is the method to which the expression refers. In special cases, the compile-time declaration does not actually exist, but is a notional method that represents a class instance creation or an array creation. The choice of compile-time declaration depends on a function type targeted by the expression, just as the compile-time declaration of a method invocation depends on the invocation's arguments.
A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.
the method reference expression must be assign an exactly Function Interface at compile time. and Function Interface is a SAM Interface. so you can't bind a method handler dynamically by method reference expression at runtime.
but you can using reflection or invoke api to achieve it.
let see each method expression refer to a Function Interface in your SomeClass results in refers to different Function Interface type:
SomeClass it = new SomeClass();
BooleanSupplier first1 = it::runSomeMethod;//bound
Predicate<SomeClass> first2 = SomeClass::runSomeMethod;//unbound
Predicate<String> second1 = it::runSomeMethodTwo;//bound
BiPredicate<SomeClass, String> second2 = SomeClass::runSomeMethodTwo;//unbound
...
Hearing about "reflection" as a comment by Oliver Charlesworth I came up with the following solution:
public class Test {
static Test testLogger = new Test(); //This should be another class ofcourse, but it doesn't matter for this example
public static void main(String[] args) throws NoSuchMethodException, SecurityException{
Test test = new Test();
run(test, "something", "hi", "hai", "blaa");
}
public static void run(Object pageObjectModel, String methodName, String...arguments){
Class<String>[] args = new Class[arguments.length];
Arrays.fill(args, String.class);
try {
testLogger.log(pageObjectModel, pageObjectModel.getClass().getMethod(methodName, args), arguments);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
private void log(Object pageObjectModel, Method method, String...arguments) {
try {
if((Boolean)method.invoke(pageObjectModel, (Object[])arguments)){
System.out.println("Success!");
} else {
System.out.println("Fail!");
}
} catch (Exception e){
e.printStackTrace();
}
}
public boolean something(String one, String two, String three){
System.out.println(one+", "+two+", "+three);
return true;
}
}
This seems to be perfect for what I'm trying to achieve. Although I don't really like reflection due to having bad experiences with it (giving problems with obfuscated code) I think it's fine for this project.
Thanks for helping me in the right direction!
I am new to Java and started learning and exploring bit about language. Could anyone explain what is significance of _() in that constructor. Is that called constructor?
public class UserRequestCache {
private final static ThreadLocal <UserRequest> t = new ThreadLocal <UserRequest>();
private static UserRequestCache instance = new UserRequestCache();
public static UserRequestCache _() {
return instance;
}
private UserRequestCache() {
}
public void checkPoint() {
if (logDebug()) {
if (getUserRequest() != null) {
logDebug(getUserRequest().toString());
}
}
}
public UserRequest getCache() {
// checkPoint();
return getUserRequest();
}
private UserRequest getUserRequest() {
return t.get();
}
public void setCache(UserRequest value) {
t.set(value);
}
}
No, it's just a very poorly named method. I recall another similar question recently, that quoted some documentation saying that even though a single underscore is a legal name, it shouldn't be used.
In this case it seems that the class is a Singleton, and the method that's usually named getInstance() has been shortened to _().
It's a funny construct that you have here. the name of the function is '_'.
So you have something like UserRequestCache._() that return a UserRequestCache.
Nothing to do with some weird Java 'magic'
I want to transform a field of a class to be effectively a constant. I'm using ASM 5.0.3.
Here is the test class I have:
public class BytecodeUtilsTest {
#BeforeClass
public static void beforeClass(){
String replaceFieldClassName = "com.mypackage.ClassWithFieldToReplaceWithConstant";
String replaceFieldClassNameAsPath = replaceFieldClassName.replace('.', '/') + ".class";
// standard code to redefine class (inspired by ASM FAQ http://asm.ow2.org/doc/faq.html, sec. 5)
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
InputStream stream = contextClassLoader.getResourceAsStream(replaceFieldClassNameAsPath);
byte[] classBytes;
try {
classBytes = IOUtils.toByteArray(stream);
// here is the interesting part
byte[] patchedClassBytes = BytecodeUtils.patch(classBytes, "_fieldToReplace", true);
Reflection.invoke(contextClassLoader, "defineClass", Class.class,
new Class[]{String.class, byte[].class, int.class, int.class},
new Object[]{replaceFieldClassName, patchedClassBytes, 0, patchedClassBytes.length});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Test
public void testFieldReplace(){
Assert.assertTrue(new ClassWithFieldToReplaceWithConstant().getFieldToReplace());
Assert.assertTrue(new ClassWithFieldToReplaceWithConstant()._fieldToReplace);
}
}
Here is the test class to update:
public class ClassWithFieldToReplaceWithConstant {
boolean _fieldToReplace;
public boolean getFieldToReplace() {
return _fieldToReplace;
}
}
And here is the patcher:
public class BytecodeUtils {
public static byte[] patch(byte[] bytecode, final String fieldToReplace, final boolean value) {
ClassReader classReader = new ClassReader(bytecode);
final ClassWriter classWriter = new ClassWriter(classReader, 0);
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM4, classWriter) {
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM4, super.visitMethod(access, name, desc, signature, exceptions)) {
#Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.GETFIELD && name.equals(fieldToReplace)) {
mv.visitInsn(Opcodes.POP);
mv.visitInsn(value ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
} else {
super.visitFieldInsn(opcode, owner, name, desc);
}
}
};
}
};
classReader.accept(classVisitor, 0);
return classWriter.toByteArray();
}
}
The problem is test fails at the second assert. So if I use getter, it returns trueas expected, but if I read field directly it returns false. That's quite unexpected considering the fact that getter yields INVOKEVIRTUAL instruction and field read yields GETFIELD which is updated by invocation of visitInsn method.
What am I doing wrong and how to make direct field access return true?
For the second assert to work you need to patch BytecodeUtilsTest, not ClassWithFieldToReplaceWithConstant, since bytecode instruction to read field for the second case is actually in the BytecodeUtilsTest#testFieldReplace method.
Getter case works fine since instruction to read the field is inside the getter body, i.e, inside the ClassWithFieldToReplaceWithConstant class.
If in real scenario field is private, this code should be fine (since there will be no access to the field from outside of the class field is declared in). Otherwise, you will have to patch every class which reads or writes to this field.