I am looking for a way to debug an advice class within IntelliJ.
In the following code sample, ByteBuddy is replacing the constructor of FileInputStream.
The method FileInputStreamCtorString.onEnter is being called as expected, right before the call to the constructor.
However, there is a problem during the execution of the onEnter method, apparently related to calling some static method and setting static field.
I would like to put a breakpoint inside the onEnter method for further investigation.
How should I configure IntelliJ to make the breakpoint stop as expected?
package com.example.javaagent.instrumentation;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.*;
import java.util.logging.Logger;
import static java.util.logging.Level.FINE;
import static net.bytebuddy.matcher.ElementMatchers.*;
public class FileInputStreamConstructorInstrumentationTests2 {
#Test
public void testByteBuddyfileInputStreamConstructor() {
ByteBuddyAgent.install();
AgentBuilder.Identified.Extendable extendableAgentBuilder = new AgentBuilder.Default()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
.with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
.ignore(none())
.type(named("java.io.FileInputStream"))
.transform(new AgentBuilder.Transformer.ForAdvice());
extendableAgentBuilder = extendableAgentBuilder.transform(
new AgentBuilder.Transformer.ForAdvice()
.include(Thread.currentThread().getContextClassLoader())
.advice(
isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)),
this.getClass().getName() + "$FileInputStreamCtorString"));
extendableAgentBuilder.installOnByteBuddyAgent();
String filename = "example.txt";
createFile(filename);
try {
FileInputStream inputStream = new FileInputStream(filename);
int data = inputStream.read();
while (data != -1) {
System.out.print((char) data);
data = inputStream.read();
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
deleteFile(filename);
}
public static class FileInputStreamCtorString {
public static boolean iveBeenHere = false;
#Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(#Advice.Argument(0) String name) {
System.out.println("FileInputStreamCtorString: entered with param "+ name);
System.out.println("FileInputStreamCtorString: second log line");
JustPrint.sayHello();
System.out.println("FileInputStreamCtorString: after calling static method");
iveBeenHere = true;
System.out.println("FileInputStreamCtorString: after setting static field");
}
}
public static class JustPrint{
public static void sayHello(){
System.out.println("Did I say 'hello'?");
}
}
private static void deleteFile(String filename) {
File file = new File(filename);
// Check if the file exists
if (file.exists()) {
// If the file exists, delete it
if (file.delete()) {
System.out.println("File deleted successfully");
} else {
System.out.println("Failed to delete file");
}
} else {
System.out.println("File does not exist");
}
}
private static void createFile(String filename) {
File file = new File(filename);
// Check if the file already exists
if (!file.exists()) {
// If the file does not exist, create a new file
try {
file.createNewFile();
System.out.println("File created successfully");
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("File already exists");
}
}
}
When running the test testByteBuddyfileInputStreamConstructor, I get the following output:
[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport#f9879ac on sun.instrument.InstrumentationImpl#37f21974
[Byte Buddy] REDEFINE BATCH #0 [1 of 1 type(s)]
[Byte Buddy] TRANSFORM java.io.FileInputStream [null, module java.base, Thread[Test worker,5,main], loaded=true]
[Byte Buddy] REDEFINE COMPLETE 1 batch(es) containing 1 types [0 failed batch(es)]
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport#f9879ac on sun.instrument.InstrumentationImpl#37f21974
File created successfully
FileInputStreamCtorString: entered with param example.txt
FileInputStreamCtorString: second log line
File deleted successfully
So, I'm sure that the following source line is reached and I would like the debugger to stop there:
System.out.println("FileInputStreamCtorString: entered with param "+ name);
Byte Buddy inlines advice code, meaning that your code is never executed, it only serves as a copy-paste template. You can set the delegate property in the enter and exit annotations to true if you wanted to invoke the advice methods instead.
Related
Please take a look at the code I have so far and if possible explain what I'm doing wrong. I'm trying to learn.
I made a little program to search for a type of file in a directory and all its sub-directories and copy them into another folder.
Code
import java.util.ArrayList;
import java.util.List;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class FandFandLoop {
public static void main(String[] args) {
final File folder = new File("C:/Users/ina/src");
List<String> result = new ArrayList<>();
search(".*\\.txt", folder, result);
File to = new File("C:/Users/ina/dest");
for (String s : result) {
System.out.println(s);
File from = new File(s);
try {
copyDir(from.toPath(), to.toPath());
System.out.println("done");
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
public static void copyDir(Path src, Path dest) throws IOException {
Files.walk(src)
.forEach(source -> {
try {
Files.copy(source, dest.resolve(src.relativize(source)),
StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
});
}
public static void search(final String pattern, final File folder, List<String> result) {
for (final File f : folder.listFiles()) {
if (f.isDirectory()) {
search(pattern, f, result);
}
if (f.isFile()) {
if (f.getName().matches(pattern)) {
result.add(f.getAbsolutePath());
}
}
}
}
}
It works, but what it actually does is to take my .txt files and write them into another file named dest without extension.
And at some point, it deletes the folder dest.
The deletion happens because of StandardCopyOption.REPLACE_EXISTING, if I understand this, but what I would have liked to obtain was that if several files had the same name then only one copy of it should be kept.
There is no need to call Files.walk on the matched source files.
You can improve this code by switching completely to using java.nio.file.Path and not mixing string paths and File objects. Additionally instead of calling File.listFiles() recursively you can use Files.walk or even better Files.find.
So you could instead use the following:
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
public class CopyFiles {
public static void copyFiles(Path src, Path dest, PathMatcher matcher, CopyOption... copyOptions) throws IOException {
// Argument validation
if (!Files.isDirectory(src)) {
throw new IllegalArgumentException("Source '" + src + "' is not a directory");
}
if (!Files.isDirectory(dest)) {
throw new IllegalArgumentException("Destination '" + dest + "' is not a directory");
}
Objects.requireNonNull(matcher);
Objects.requireNonNull(copyOptions);
BiPredicate<Path, BasicFileAttributes> filter = (path, attributes) -> attributes.isRegularFile() && matcher.matches(path);
// Use try-with-resources to close stream as soon as it is not longer needed
try (Stream<Path> files = Files.find(src, Integer.MAX_VALUE, filter)) {
files.forEach(file -> {
Path destFile = dest.resolve(src.relativize(file));
try {
copyFile(file, destFile, copyOptions);
}
// Stream methods do not allow checked exceptions, have to wrap it
catch (IOException ioException) {
throw new UncheckedIOException(ioException);
}
});
}
// Wrap UncheckedIOException; cannot unwrap it to get actual IOException
// because then information about the location where the exception was wrapped
// will get lost, see Files.find doc
catch (UncheckedIOException uncheckedIoException) {
throw new IOException(uncheckedIoException);
}
}
private static void copyFile(Path srcFile, Path destFile, CopyOption... copyOptions) throws IOException {
Path destParent = destFile.getParent();
// Parent might be null if dest is empty path
if (destParent != null) {
// Create parent directories before copying file
Files.createDirectories(destParent);
}
Files.copy(srcFile, destFile, copyOptions);
}
public static void main(String[] args) throws IOException {
Path srcDir = Paths.get("path/to/src");
Path destDir = Paths.get("path/to/dest");
// Could also use FileSystem.getPathMatcher
PathMatcher matcher = file -> file.getFileName().toString().endsWith(".txt");
copyFiles(srcDir, destDir, matcher);
}
}
I was trying to perform dynamic class loading from a jar file, unfortunately there was an error:
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 1347093252 in class file com/life/Life
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
at gloria.MyClassLoader.loadClass(MyClassLoader.java:38)
at gloria.Gloria.main(Gloria.java:9)
Java Result: 1
Here's my code:
MainClass.java
public class MainClass {
public static void main(String[] args) {
try{
ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
Class myObjectClass = classLoader.loadClass("com.life.Life");
//create new class loader so classes can be reloaded.
classLoader = new MyClassLoader(parentClassLoader);
myObjectClass = classLoader.loadClass("com.life.Life");
Life life = (Life) myObjectClass.newInstance();
System.out.println("Message: " + life.getMessage());
}catch(Exception e){
e.printStackTrace();
}
}
}
MyClassLoader.java
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class MyClassLoader extends ClassLoader{
public MyClassLoader(ClassLoader parent) {
super(parent);
}
#Override
public Class loadClass(String name) throws ClassNotFoundException {
if(!"com.life.Life".equals(name))
return super.loadClass(name);
try {
String url = "http://192.168.1.229:8081/downloads/Life.jar";
URL myUrl = new URL(url);
URLConnection connection = myUrl.openConnection();
InputStream input = connection.getInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int data = input.read();
while(data != -1){
buffer.write(data);
data = input.read();
}
input.close();
byte[] classData = buffer.toByteArray();
return defineClass("com.life.Life", classData, 0, classData.length);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Life.java
public interface Life {
public String getMessage();
}
What Im doing right here is to instantiate an object that from a jar file and load it in runtime. What's wrong with my code? Any idea?
0xCAFEBABE is the usual first 4 bytes of a Java file.
Your value 1347093252 is 0x504B0304 in hex, which is the magic value for a ZIP file.
As jar is also a zip file. This means that your jar can be corrupt. Try re-building the jar.
You are trying to use a JAR/ZIP file as a CLASS file; a JAR file contains JAR files, but isn't one.
BTW, why won't something like new UrlClassLoader("jar:http://http://192.168.1.229:8081/downloads/!/") work? This will also transparently handle loading classes that Life needs and are located in that JAR.
So I have to make a program in java that automatically runs in the background and looks for a new .dat file and when it sees the new .dat file it then runs a .bat file to load data into a database. So far I have a program that watches for new file creation, modification, and deletion. I also have a script that runs the .bat file and loads the data into the database now i just need to connect the two but I am not sure how to go about this, If someone could point me in the right direction I would greatly appreciate it.
Below is the code I have so far.
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import java.io.*;
import java.util.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
public class Order_Processing {
public static void watchDirectoryPath(Path path)
{
try {
Boolean isFolder = (Boolean) Files.getAttribute(path,
"basic:isDirectory", NOFOLLOW_LINKS);
if (!isFolder)
{
throw new IllegalArgumentException("Path: " + path
+ " is not a folder");
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
System.out.println("Watching path: "+ path);
FileSystem fs = path.getFileSystem();
try (WatchService service = fs.newWatchService())
{
path.register(service, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
WatchKey key = null;
while (true)
{
key = service.take();
Kind<?> kind = null;
for (WatchEvent<?> watchEvent : key.pollEvents())
{
kind = watchEvent.kind();
if (OVERFLOW == kind)
{
continue;
}
else if (ENTRY_CREATE == kind)
{
Path newPath = ((WatchEvent<Path>) watchEvent)
.context();
System.out.println("New Path Created: " + newPath);
}
else if (ENTRY_MODIFY == kind)
{
Path newPath = ((WatchEvent<Path>) watchEvent)
.context();
System.out.println("New path modified: "+ newPath);
}
else if (ENTRY_DELETE == kind)
{
Path newPath = ((WatchEvent<Path>) watchEvent)
.context();
System.out.println("New path deleted: "+ newPath);
}
}
if (!key.reset())
{
break;
}
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
catch (InterruptedException ie)
{
ie.printStackTrace();
}
}
public static void main(String[] args)
throws FileNotFoundException
{
File dir = new File("C:\\Paradigm");
watchDirectoryPath(dir.toPath());
//below is the script that runs the .bat file and it works if by itself
//with out all the other watch code.
try {
String[] command = {"cmd.exe", "/C", "Start", "C:\\Try.bat"};
Process p = Runtime.getRuntime().exec(command);
}
catch (IOException ex) {
}
}
}
This doesn't work because you have a while (true). This makes sense because you are listening and want the to happen continuously; however, the bat call will never be executed because watchDirectory(...) will never terminate. To solve this, pull the rest of the main out into its own function like so
public static void executeBat() {
try {
String[] command = {"cmd.exe", "/C", "Start", "C:\\Try.bat"};
Process p = Runtime.getRuntime().exec(command);
}
catch (IOException ex) {
// You should do something with this.
// DON'T JUST IGNORE FAILURES
}
so that upon file creation, you can call that bat script
...
else if (ENTRY_CREATE == kind)
{
Path newPath = ((WatchEvent<Path>) watchEvent).context();
executeBat();
}
...
public class Fileverifynanoha
{
private File fileext;
private Path filepath;
public Fileverifynanoha()//this class wants to create a file, write something, and close it.
{
filepath = Paths.get("./txttest.txt");
Charset charset = Charset.forName("US-ASCII");
String s = "Takamachi Nanoha. Shirasaki Tsugumi.!";
try (BufferedWriter filewriter = Files.newBufferedWriter(filepath,charset))
{
filewriter.write(s,0,s.length()-1);
}
catch(IOException e)
{
System.err.println(e);
}
}//end of this class
/**
* #param args the command line arguments
*/
public static void main(String[] args)//the main method will check if this file contains(created), if so, return exist. if not, return doesnt exist.
{
if (filetxt.exists()&&!filetxt.isDirectory())//object does not create any real thing, therefore nothing true will return.
{
System.out.println("File exist.");
}
else
{
System.out.println("File does not exist.");
}
}
}
Here is the code. I want to use the class I create to create a file, write something. Then, I use main class to check if that file exist.
However, I don't know why, but the main class does not recognise my (maybe) created file. Could anyone tell me how to link them together?
I know there may be some minor bugs in this program. I will fix that later.
Thanks.
You never called your constructor.
public static void main(String[] args)//the main method will check if this file contains(created), if so, return exist. if not, return doesnt exist.
{
Fileverifynanoha fvn = new Fileverifynanoha();
if (fvn.filetxt.exists()&&!fvn.filetxt.isDirectory())
{
System.out.println("File exist.");
}
else
{
System.out.println("File does not exist.");
}
}
}
Your issues:
Didn't create instance of class.
Didn't init File file, so it would be null always.
Better use utf-8 for plain text file.
Try this:
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Fileverifynanoha {
private File file;
private Path path;
public Fileverifynanoha(String fp) {
this.path = Paths.get(fp);
this.file = path.toFile();
}
public void createFile()// this class wants to create a file, write something, and close it.
{
Charset charset = Charset.forName("UTF-8");
String s = "Takamachi Nanoha. Shirasaki Tsugumi.!";
BufferedWriter filewriter = null;
try {
filewriter = Files.newBufferedWriter(path, charset);
filewriter.write(s, 0, s.length() - 1);
filewriter.close();
} catch (IOException e) {
System.err.println(e);
}
}// end of this class
/**
* #param args
* the command line arguments
*/
public static void main(String[] args)// the main method will check if this file contains(created), if so, return exist. if not, return doesnt exist.
{
Fileverifynanoha f = new Fileverifynanoha("./txttest.txt");
f.createFile();
if (f.file.exists() && !f.file.isDirectory())// object does not create any real thing, therefore nothing true will return.
{
System.out.println("File exist.");
} else {
System.out.println("File does not exist.");
}
}
}
I have a simple .txt file which has pure Java code inside it like
public class C {
public static void main(String[] args ) {
System.out.println("This is executed");
}
}
The file is named C.txt. Now I want to write Java code that will read the code in C.txt and will compile and run the read code as a pure Java file. Note, I can easily rename C.txt to C.java and compile and run the code manually. However, this is not my intention. I want to read the .txt file as is and execute the code directly. Is this possible somehow?
You can use the javax.tools api form Java 6 to compile the code on the fly. However since your extension is illegal it will complain with a error: C.txt Class names are only accepted if annotation processing is explicitly requested.
To get around this (as mentioned in the comments) you must first load the code into a String and then execute it:
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyCompiler2 {
public static void main(String[] args) throws Exception {
String program = "";
try {
BufferedReader in = new BufferedReader(new FileReader("C.txt"));
String str;
while ((str = in.readLine()) != null) {
program += str;
}
in.close();
} catch (IOException e) {
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Iterable<? extends JavaFileObject> fileObjects;
fileObjects = getJavaSourceFromString(program);
compiler.getTask(null, null, null, null, null, fileObjects).call();
Class<?> clazz = Class.forName("C");
Method m = clazz.getMethod("main", new Class[]{String[].class});
Object[] _args = new Object[]{new String[0]};
m.invoke(null, _args);
}
static Iterable<JavaSourceFromString> getJavaSourceFromString(String code) {
final JavaSourceFromString jsfs;
jsfs = new JavaSourceFromString("code", code);
return new Iterable<JavaSourceFromString>() {
public Iterator<JavaSourceFromString> iterator() {
return new Iterator<JavaSourceFromString>() {
boolean isNext = true;
public boolean hasNext() {
return isNext;
}
public JavaSourceFromString next() {
if (!isNext)
throw new NoSuchElementException();
isNext = false;
return jsfs;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
Notice how you need to explicitly provide the method and class name in order for reflection to execute your code.
I think I'd start with BeanShell, which allows you to compile and execute Java source held in a string.
Check out this thread for how to start the compile from within Java...
How to set the source for compilation by a CompilationTask