getJavaFileForOutput(...) method of custom JavaFileManager not called by compiler - java

I have a custom JavaFileManager that looks something like this:
public class InMemoryForwardingFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
private final Map<String, ByteArrayJavaFileObject> javaFileObjects = new HashMap<>();
#Override
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException{
JavaFileObject fileObject = new ByteArrayJavaFileObject( ... );
javaFileObjects.put(className, fileObject);
return fileObject;
}
#Override
public ClassLoader getClassLoader(Location location){
return new SecureClassLoader(InMemoryForwardingFileManager.class.getClassLoader()){
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
ByteArrayJavaFileObject fileObject = javaFileObjects.get(name);
if(fileObject != null){
byte[] bytes = fileObject.getBytes();
return defineClass(name, bytes, 0, bytes.length);
} else{
throw new ClassNotFoundException();
}
}
}
}
}
I have redacted a lot of code for readability purposes; the class also implements the list(...) method and the inferBinaryName(...) method.
In another area of my project I run something similar to the following:
InMemoryForwardingFileManager fileManager = // get singleton instance
... // declare compilation units, options, diagnostic listener, etc
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
CompilationTask compilerTask = compiler.getTask(null, fileManager, diagnostics, compilationOptions, null, compilationUnits);
compilerTask.call();
// load and instantiate the compiled class
ClassLoader classLoader = fileManager.getClassLoader(null);
MyGeneratedClass instance = (MyGeneratedClass) classLoader.loadClass(fullClassName).newInstance();
If I run some junit tests in eclipse on windows/mac/linux it works exactly as I would expect. If I run my project in glassfish on windows it also works exactly how I would expect. If I run the same project in glassfish on Mac OS X Mavericks or Centos 6.4 the getJavaFileForOutput(...) is simply never called! The getClassLoader(...) method of my file manager is eventually called but by then it is too late.
What is unique about the linux+glassfish environment that is preventing my getJavaFileForOuput method from being called?
I'm pretty sure I have all environments correctly set up to use the same jdk version: jdk1.7.0_45.
Any advice??

I'll answer my own question here.
I can't believe this went over my head but it turns out the Windows environment was using glassfish3 whereas the other environments were using glassfish4. Once I discovered this, I tested the project on windows using glassfish4 and I was able to replicate the same issue I experience on mac/linux.
While that vaguely answers my question, if I find out exactly why the above code does not work as expected in glassfish4, I will add an explanation here.

Related

Java JavaCompiler.run() compiling anonymous classes as well

I am trying to load in text files on the fly and compile them.
File file = new File("Files/"+fileName+".java");
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, errStream, file.getAbsolutePath());
I then will load the compiled .class files later:
public Class loadStrategyClass(File strategyClassFile) throws IOException
{
FileChannel roChannel = new RandomAccessFile(strategyClassFile, "r").getChannel();
ByteBuffer buffer = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, (int)roChannel.size());
return defineClass(strategyClassFile.getName(), buffer, (ProtectionDomain)null);
}
I am currently running into two issues:
The first is if the .java files I load in contain anonymous classes. It doesn't appear that the JavaCompiler class will compile these.
Exception in thread "main" java.lang.IllegalAccessException: Class Loader.ClassLoader can not access a member of class Files.myname.myclass$1 with modifiers ""
The second:
Is that sometimes I will get errors for NoClassDefFoundError:
Exception in thread "main" java.lang.NoClassDefFoundError: Files/myname/myclass
Despite the fact that other classes will load correctly and the .class file is in that path.
Apparently, your loadStrategyClass is defined within a custom ClassLoader. The problem is that it is not enough to call defineClass once for the class you’re interested in, your class loader must be able to resolve classes on demand, usually by implementing findClass, so the JVM can resolve dependencies, like the inner classes.
You didn’t specify, how you get the strategyClassFile argument for the loadStrategyClass method. Since you ran the compiler without any options, I suppose you simply looked up the file relative to the source file. To resolve other dependencies, the actual root of the class directory needs to be known. It becomes much easier when you define where to store the class files, e.g.
// customize these, if you want, null triggers default behavior
DiagnosticListener<JavaFileObject> diagnosticListener = null;
Locale locale = null;
JavaCompiler c = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm
= c.getStandardFileManager(diagnosticListener, locale, Charset.defaultCharset());
// define where to store compiled class files - use a temporary directory
Path binaryDirectory = Files.createTempDirectory("compile-test");
fm.setLocation(StandardLocation.CLASS_OUTPUT,
Collections.singleton(binaryDirectory.toFile()));
JavaCompiler.CompilationTask task = c.getTask(null, fm,
diagnosticListener, Collections.emptySet(), Collections.emptySet(),
// to make this a stand-alone example, I use embedded source code
Collections.singleton(new SimpleJavaFileObject(
URI.create("string:///Class1.java"), Kind.SOURCE) {
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return "package test;\npublic class Class1 { public class Inner {} }";
}
}));
if(task.call()) try {
URLClassLoader cl = new URLClassLoader(new URL[]{ binaryDirectory.toUri().toURL() });
Class<?> loadedClass = cl.loadClass("test.Class1");
System.out.println("loaded "+loadedClass);
System.out.println("inner classes: "+Arrays.toString(loadedClass.getClasses()));
} catch(ClassNotFoundException ex) {
ex.printStackTrace();
}
In the example above, we know the root of the class directory, because we have defined it. This allows to simply use the existing URLClassLoader rather than implementing a new type of class loader. Of course, using a custom file manager, we also could use an in-memory storage for rather than a temporary directory.
You may use this API to discover what has been generated, which enables you to use the resulting class without knowing beforehand, which package or inner class declarations exist in the source file you’re going to compile.
public static Class<?> compile(
DiagnosticListener<JavaFileObject> diagnosticListener,
Locale locale, String sourceFile) throws IOException, ClassNotFoundException {
JavaCompiler c = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm
= c.getStandardFileManager(diagnosticListener, locale, Charset.defaultCharset());
// define where to store compiled class files - use a temporary directory
Path binaryDirectory = Files.createTempDirectory("compile-test");
fm.setLocation(StandardLocation.CLASS_OUTPUT,
Collections.singleton(binaryDirectory.toFile()));
JavaCompiler.CompilationTask task = c.getTask(null, fm,
diagnosticListener, Collections.emptySet(), Collections.emptySet(),
fm.getJavaFileObjects(new File(sourceFile)));
if(task.call()) {
Class<?> clazz = null;
URLClassLoader cl = new URLClassLoader(new URL[]{binaryDirectory.toUri().toURL()});
for(JavaFileObject o: fm.list(
StandardLocation.CLASS_OUTPUT, "", Collections.singleton(Kind.CLASS), true)) {
String s = binaryDirectory.toUri().relativize(o.toUri()).toString();
s = s.substring(0, s.length()-6).replace('/', '.');
clazz = cl.loadClass(s);
while(clazz.getDeclaringClass() != null) clazz = clazz.getDeclaringClass();
if(Modifier.isPublic(clazz.getModifiers())) break;
}
if(clazz != null) return clazz;
throw new ClassNotFoundException(null,
new NoSuchElementException("no top level class generated"));
}
throw new ClassNotFoundException(null,
new NoSuchElementException("compilation failed"));
}
If you use this to dynamically bind plugins or modules, you may extend the search to look for a result class which implements a particular interface or has a certain annotation.

ClassTransformer not executing

USE CASE
I'm attempting to write a Java SE8 ClassFileTransformer implementation. The goal of this is Debugging. I'm well aware of BTrace but it doesn't really fit the bill for what I'm trying to do. What that is, is internal method level inspection. BTrace limits its breakpoints to entry/exit. I wanted to get into the details of this.
So I figured I'd do this myself with ASM now hard can it be?
(ASM is a byte code manipulation library used by ByteBuddy and BTrace)
PROBLEM
So I started by defining a simple ClassFileTransformer.
public class PreMainInjection {
public static void premain(String agentArgs,
Instrumentation inst) {
inst.addTransformer(new EntryPoint(), true);
}
}
public class EntryPoint implements ClassFileTransformer {
public EntryPoint() { }
#Override
public byte[] transform(ClassLoader classloader, String name,
Class<?> clazz, ProtectionDomain prot, byte[] data) {
System.out.printf("Loaded: %s\n", name);
}
}
And this worked perfectly :D I see a list of all the classes as the application I'm inspecting start.
So now I bring in ASM.
public class PreMainInjection {
public static void premain(String agentArgs,
Instrumentation inst) {
inst.addTransformer(new EntryPoint(), true);
}
}
public class EntryPoint implements ClassFileTransformer {
public EntryPoint() { }
#Override
public byte[] transform(ClassLoader classloader, String name,
Class<?> clazz, ProtectionDomain prot, byte[] data) {
ClassReader reader = new ClassReader(bytes);
ClassNode node = new ClassNode();
ClassWriter writer = new ClassWriter(
ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
reader.accept(node, ClassReader.EXPAND_FRAMES);
System.out.printf("Name: %s", node.name);
node.accept(writer);
return writer.toByteArray();
}
}
This works... so far as my app doesn't break. But I never see a single debug output.
So what is going on?
To expand further if my agent just does
System.out.printf("Loaded %s\n", name);
return null;
The app still works and I can see the outputs. So I'm deeply confused.
To answer your first question how hard ASM can be: very hard.
To get to your actual point: you are probably getting an exception before reaching the print output. With a class file transformer, exceptions are suppressed if they escape the transform method. Did you try to wrap the code in a try-catch block to see if an exception is raised?
There is also a chance that you did not bundle ASM with your agent. In this case, you would get an error rather than an exception during your transformer's execution. Finally, I would try your agent without frame computation as this requires access to all referenced class files which can also be a source of problems.
So I appear to be hitting a weird edge case
ClassNode's internal fields are only instantiated after a full visit of the ClassReader has been conducted. As that is the case it is statically provable System.out.printf("Name: %s", node.name); will always return a NullPointerException at compile time.
So I believe the JVM simply doesn't load my ClassFileTransformer.
Either way I have the system working.

Conevert string to java code [duplicate]

This question already has answers here:
How do I programmatically compile and instantiate a Java class?
(3 answers)
Closed 7 years ago.
Is there a way for a running Java program to compile Java source code (passed as a string)?
Class newClass = Compiler.compile ("class ABC { void xyz {etc. etc. } }");
Ideally, any classes referenced by the passed-in source code would be resolved by the program's class loader.
Does something like this exist?
Sure. Have a look at the JavaCompiler class and the other classes in the javax.tools package.
They've been around since Java 1.6.
Here is some example code.
(As pointed out by #Sergey Tachenov in the comments, it needs JDK to be installed as the necessary tools.jar file comes with JDK but not JRE.)
What you need is a class that extends JavaFileObject
import java.net.URI;
import javax.tools.SimpleJavaFileObject;
public class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
public JavaSourceFromString( String name, String code) {
super( URI.create("string:///" + name.replace('.','/')
+ Kind.SOURCE.extension),Kind.SOURCE);
this.code = code;
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
Which can be used as follows:
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
if( jc == null) throw new Exception( "Compiler unavailable");
String code = "public class CustomProcessor { /*custom stuff*/ }";
JavaSourceFromString jsfs = new JavaSourceFromString( "CustomProcessor", code);
Iterable<? extends JavaFileObject> fileObjects = Arrays.asList( jsfs);
List<String> options = new ArrayList<String>();
options.add("-d");
options.add( compilationPath);
options.add( "-classpath");
URLClassLoader urlClassLoader =
(URLClassLoader)Thread.currentThread().getContextClassLoader();
StringBuilder sb = new StringBuilder();
for (URL url : urlClassLoader.getURLs()) {
sb.append(url.getFile()).append(File.pathSeparator);
}
sb.append( compilationPath);
options.add(sb.toString());
StringWriter output = new StringWriter();
boolean success = jc.getTask( output, null, null, options, null, fileObjects).call();
if( success) {
logger.info( LOG_PREFIX + "Class has been successfully compiled");
} else {
throw new Exception( "Compilation failed :" + output);
}
Depends on what you want to do.
If you just want to run some code you could use BeanShell. It's not a java compiled class, but is very usefull to make something flexible
You could try my essence jcf library which does this. When running in debug you can have the source written to a file so you can step into the code. Otherwise, it does everything in memory. It wraps the JavaCompiler in tools.jar
It takes a String, compiles and loads it into the current class loader and returns the Class. It handles nested/inner classes.
http://vanillajava.blogspot.com/2010/11/more-uses-for-dynamic-code-in-java.html
Note: I haven't got this working in OSGi. ;)
Javassist can generate and load at runtime classes and methods from Strings of source code.
It is also possible to dump in the file system the generated class if you need to.
Currently there are minor limitations in the code you can pass in those strings, for example it cannot include generics, enumerations, or autoboxing and inboxing of primitives.
More information here:
http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/

Eclipse tracing.What is best practice?How to get instance of DebugTrace?

I'm working on RCP aplicattion, that works on Eclipse 4 platform(Luna). I neeed to find out how to get instance that implement tracing inside my Application. I found out the following ways
-
Using ILog interface, that can be get via Platform.getLog call. ILog has good point. It prints to .log file that located in .metadata, but
it get IStatus object as parameter. So for logging each line to log I have to created new instance of Status object. And it cannot put automatically information about caller like class name etc.(like Log4j, LogBack)
I found interface DebugTrace that provide good functionality for traicing. It can be obtained via DebugOptions inerface(DebugOptions.newDebugTrace()). To my regret I could not find appropriated way for getting instance of DebugOptions
To use DebugTrace, add this to the plugin Activator:
private DebugTrace tracer = null;
public static DebugTrace getTrace()
{
return plugin.tracer;
}
public void start(final BundleContext context) throws Exception
{
...
final Hashtable<String, String> properties = new Hashtable<String, String>(4);
properties.put(DebugOptions.LISTENER_SYMBOLICNAME, "org.eclipse.ui.trace"); //$NON-NLS-1$
context.registerService(
DebugOptionsListener.class.getName(),
new DebugOptionsListener()
{
#Override
public void optionsChanged(DebugOptions options)
{
tracer = options.newDebugTrace(context.getBundle().getSymbolicName());
}
}, properties);
You will need to add a .options file to the plugin project which will contain your logging options:
# Editor-related tracing
com.acme.atf.app/trace/editor=false
#Start-up tracing
com.acme.atf.app/trace/startup=true
org.eclipse.core.jobs/jobs=true
org.eclipse.core.jobs/jobs/beginend=true
org.eclipse.core.jobs/jobs/errorondeadlock=true
Hope this helps...

Java compiler API ClassLoader

I am trying to use Java Compiler API to compile some java class. That class imports some packages from the jar files which can be loaded by context ClassLoader, let's call him X, which is NOT the system classloader. When I run the compilation, the compiler complains about not recognizing the imports. I have tried to specify the fileManager to pass the classloader, but it does not help.
When compile method is called, it first prints "CLASS LOADED", so the context ClassLoader CAN find the dependency class. However, the compilation itself fails (I get "Compilation FAILED" message) and during the compilation I get errors like this:
/path/to/my/Source.java:3: package my.dependency does not exist
import my.dependency.MyClass;
^
What am I doing wrong? What's the correct way to pass custom classloader to the compilationTask? I can't extract the URLs from the ClassLoader since it's not URLClassLoader.
My methods are here:
public void compile(List<File> filesToCompile) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdFileManager =
compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> fileObjects = stdFileManager
.getJavaFileObjectsFromFiles(filesToCompile);
FileManagerImpl fileManager = new FileManagerImpl(stdFileManager);
CompilationTask task = compiler.getTask(null, fileManager, null, null, null, fileObjects);
Boolean result = task.call();
if (result == true) {
System.out.println("Compilation has succeeded");
} else {
System.out.println("Compilation FAILED");
}
}
private final class FileManagerImpl extends ForwardingJavaFileManager<JavaFileManager> {
public FileManagerImpl(JavaFileManager fileManager) {
super(fileManager);
}
#Override
public ClassLoader getClassLoader(JavaFileManager.Location location) {
ClassLoader def = getContextClassLoader();
try {
def.loadClass("my.dependency.MyClass");
System.out.println("CLASS LOADED");
} catch (ClassNotFoundException ex) {
System.out.println("NOT LOADED");
}
return def;
}
}
The main point is that, while a class loader loads classes, javac will call JavaFileManager#list() to get a listing of all the files in a package.
So to use a custom class loader you need to modify (or extend) it to override JavaFileManager#list(). Hopefully you can reuse some of the logic used for class loading.
You might want to use your own implementations of JavaFileObject to represent class objects. You will then need to override JavaFileManager#inferBinaryName() (else the javac version will crash). Your implementations of JavaFileObject also needs to override (at least) JavaFileObject#openInputStream.
Here are some pointers: http://atamur.blogspot.be/2009/10/using-built-in-javacompiler-with-custom.html
Also, don't make your life harder than it should and extend ForwardingJavaFileManager and SimpleJavaFileObject.
For reference, here is an example implementation:
#Override public Iterable<JavaFileObject> list(Location location,
String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse)
throws IOException
{
Iterable<JavaFileObject> stdResults =
fileManager.list(location, packageName, kinds, recurse);
if (location != StandardLocation.CLASS_PATH
|| !kinds.contains(JavaFileObject.Kind.CLASS))
{
return stdResults;
}
Set<JavaFileObject> additional = pkgObjects.get(packageName);
if (additional == null || additional.isEmpty()) {
return stdResults;
}
List<JavaFileObject> out = new ArrayList<>();
for (JavaFileObject obj : additional) {
out.add(obj);
}
for (JavaFileObject obj : stdResults) {
out.add(obj);
}
return out;
}
Where pkgObjects is a map from package names to JavaFileObject. The way you fill this map depends on how your class loader works.
This question has the answer. You'll have to set a classpath through an options list with the getTask() method (as described in detail in the accepted answer).
For loading the class from different jar file you could try with Reflection API it is easy way ..refer the following link http://download.oracle.com/javase/tutorial/reflect/index.html..

Categories