Because a single Java file can compile into any number of class files, is there some way from the compiler API to find out which class files were generated? It's outputting to a directory that may have other files in it, so I can't just look at that.
I figured out something that appears to work. The *FileManager has callbacks to get the locations for things, including things for output. You can wrap it using the ForwardingJavaFileManager, override, and store the values from the calls.
final List<String> classFiles = new ArrayList<>();
StandardJavaFileManager inner = compiler.getStandardFileManager(null, null, null);
JavaFileManager fileManager = new ForwardingJavaFileManager(inner) {
#Override
public JavaFileObject getJavaFileForOutput(Location location, String className,
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
JavaFileObject o = super.getJavaFileForOutput(location, className, kind, sibling);
classFiles.add(o.getName());
return o;
}
};
javax.tools package shows several ways to manage compilation units.
See JavaFileObject or JavaFileManager
I don't think we can find the non public classes created by Java Compiler API using any of the given references. You would need to parse (or apply reg expression ) on the input java files to identify the available class names. Once you get the name of the classes, you should be able to load them using custom class loaders.
Satheesh
Related
I'm trying to use the Java TreePathScanner API to determine the list of class files that will be generated from a given compilation. For example, the following source code:
public class InnerClass {
private final InnerInnerClass clazz = new InnerInnerClass();
private class InnerInnerClass {
}
}
will generate the following files:
InnerClass.class
InnerClass$1.class
InnerClass$InnerInnerClass.class
However, in my TreePathScanner subclass, visitClass is only called twice, for the InnerClass class, and the InnerInnerClass classes, but not the anonymously named class created from the new class statement. Changing the source to the following works as expected:
public class InnerClass {
private final InnerInnerClass clazz = new InnerInnerClass() { };
private class InnerInnerClass {
}
}
My tool's full source code is available here for reference, specifically ArtifactScanner.java.
Either this is a bug or a flaw in the API as there doesn't seem to be any other way to get all of the binary names that will be generated from a given compilation unit's source code. Am I missing something?
One of the JDK developers explained on this bug report that the observed behavior really is not a bug, and that the additional class files are generated as a result of the Java compiler backend which rewrites more complex language constructs into simpler ones before generating class files.
The TreePathScanner API therefore does produce the correct output in this case, and the short of it is that TreePathScanner is the wrong solution to use for my use case (determining the list of class files that will be produced) and that com.sun.source.util.TaskListener, TaskEvent, and TaskEvent.Kind, and JavaFileManager.inferBinaryName should be used instead.
My code:
FileChooser prompt = new FileChooser();
prompt.setTitle("Odaberi fajl");
source = (Source) prompt.showOpenDialog(new Stage());
where source is a class that extends File as such:
import java.io.File;
public class Source extends File {
public Source(String pathname) {
super(pathname);
}
}
returns an error when trying to cast to Source. I have no idea what is causing this.
FileChooser returns a File. What makes you think you can cast it to Source? It is not a Source.
What you want to do is one of:
Make Source encasulate a File and provide whatever custom methods you need, delegating to the contained File as appropriate.
public class Source {
private File f;
public Source(File f) {
this.f = f;
}
// Custom methods
...
// Delegating methods
public boolean exists() {
return f.exists();
}
...
}
Extend File as you are doing, but provide a constructor that takes another File (i.e. a copy constructor) and instantiates the Source using the data from the passed File.
public class Source extends File {
public Source(File f) {
super(f.getAbsolutePath());
}
// Custom methods
...
}
Then instantiate as follows:
FileChooser prompt = new FileChooser();
prompt.setTitle("Odaberi fajl");
source = new Source(prompt.showOpenDialog(new Stage()));
You need to understand what casting does.
Casting is a compile-time directive; it tells the compiler that the operation being performed (showOpenDialog() in your case) is going to return an object that matches the cast. If that turns out not to be true, then the program will throw an IllegalCastException at runtime. It is up to the programmer who writes the cast to ensure that the cast is going to be correct at runtime. The compiler will tell you if there is no possible way for it to be correct, in many cases, but cannot tell you in all cases.
FileChooser was written without knowledge of your Source class, so it is not possible for it to return a Source object. If the cast had succeeded, you would be allowed (by the compiler) to call methods from Source on the resulting object, and that would clearly be incorrect.
One thing to realize about casting is that, except in very limited circumstances involving boxed primitives, casting does not change the object referred to at all. Its purpose is to let the compiler know that operations will be legal on the resulting class.
As an aside, it helps enormously when asking a question to say what happens, not just 'returns an Error'. Is that a compile or runtime error? What does the error say? Please remember that for questions you ask in the future.
I've got a library that allows clients to provide a list of text files, each of which contains groovy code for a class that extends java class Z. For instance file 'A.groovy' contains
package com.mypkg;
public class A extends Z {
#Override
public void someMethod() {
// do something A-ish
}
}
etc.
The library compiles each of these and (in this case) would return to the clients an instance of type Z.
My issue comes when a client needs something like this:
package com.mypkg;
public class B extends A { // extends A!
#Override
public void someMethod() {
// do something B-ish instead of A-ish
}
}
where B extends A, and class A was parsed before class B.
The issue is that the GroovyClassLoader can't seem to find class A, even though it just parsed A. Here's the code that compiles the scripts and creates the instances:
for (String fileName : listOfScriptFiles) {
InputStream in = getInputStreamFromFile(fileName);
CompilerConfiguration compConfig = new CompilerConfiguration();
GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread()
.getContextClassLoader(), compConfig);
Z service = null;
Class clazz = classLoader.parseClass(in);
service = (Z) clazz.newInstance();
return service;
}
Is there a way to 'register' class A with the runtime so that when Groovy tries to compile class B it will not complain that class A doesn't exist?
UPDATE
I was actually able to solve this by instantiating the GroovyClassLoader outside the loop that iterates through the client's code list, so the classloader that parses A is the same that parses B.
The question still stands, though, because I could envision a case where in one part of someone's code they parse A, and then in a completely different part, where the same classloader is unavailable, they parse B.
In my experience with the Groovy classloader (which is similar in behavior with Ant and beanshell's classloader in this respect) , you have to decide up front whether you are going to use the default system classloader, in which case you would build the classpath into the command that launches the Groovy script, OR on the other hand, you specify ONLY the groovy jar on the command line classpath and then you dynamically add classes at the beginning of your Groovy script on the custom classloader.
You aren't providing much information in your question, but my guess is that you put class "A" on the classpath before you launched the script and then your trying to load class "B" dynamically. That wouldn't work as far as I know.
NOTE: I myself have been trying to figure out how to do this kind of thing. It seems it would be possible but I still haven't figured it out.
I'm trying to read a java file and display in console the package, class and method name. something like this:
File: Test.java
package tspec.test;
public class Test {
public void addTest () {}
public void deleteTest () {}
}
Output:
package name: tspec.test
class name: Test
method name:
addTest
deleteTest
Thanks in advance :)
This can be accomplished using the Java Compiler API (introduced in Java 6). Unfortunately, this solution is limited to Sun's JDK. Therefore, you will have to have that JDK installed and you must include its tools.jar file in your class path.
public void displayInformation(File javaSourceFile) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// The file manager locates your Java source file for the compiler. Null arguments indicate I am comfortable with its default behavior.
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
// These will be parsed by the compiler
Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjects(javaSourceFile);
// Creates a new compilation task. This doesn't actually start the compilation process.
// Null arguments indicate I am comfortable with its default behavior.
CompilationTask task = compiler.getTask(null, null, null, null, null, fileObjects);
// Cast to the Sun-specific CompilationTask.
com.sun.tools.javac.api.JavacTaskImpl javacTask = (com.sun.tools.javac.api.JavacTaskImpl) task;
// The Sun-specific JavacTaskImpl can parse the source file without compiling it, returning
// one CompilationUnitTree for each JavaFileObject given to the compiler.getTask call (only one in our case).
Iterable<? extends CompilationUnitTree> trees = javacTask.parse();
CompilationUnitTree tree = trees.iterator().next();
// Create a class that implements the com.sun.source.tree.TreeVisitor interface.
// The com.sun.source.util.TreeScanner is a good choice because it already implements most of the logic.
// We just override the methods we're interested in.
class MyTreeVisitor extends TreeScanner<Void, Void> {
#Override
public Void visitClass(ClassTree classTree, Void p) {
System.out.println("class name: " + classTree.getSimpleName());
System.out.println("method name:");
return super.visitClass(classTree, p);
}
#Override
public Void visitMethod(MethodTree methodTree, Void p) {
System.out.println(methodTree.getName());
return super.visitMethod(methodTree, p);
}
}
tree.accept(new MyTreeVisitor(), null);
}
When I pass this method a File whose content is your sample, I receive this output:
class name: Test
method name:
addTest
deleteTest
Unfortunately, I haven't yet figured out where the package name is stored.
Reflection and Introspection Java API's.
It's purpose is to introspect Java code and report back about it's contents. With Reflection you can do things like :
Class.forName(className).getDeclaredMethods();
Java also has the Java Mirror API with similiar functionality, but is not as commonly used.
Both of these solutions require no 3rd party libraries or tools.
The only difficult is the java code may not be well formatted. like the function declaration can be spread on multiple lines.
The ultimate solution is to create an automata to tokenize the source code first and then apply some compiler technique to grab what you want from the parsed data.
We use PMD Java code analyzer to solve similar problem.
It is useful.
http://pmd.sourceforge.net/
You don't have to do this by parsing the Java file yourself! Java already contains a way of getting information about its own classes, methods, and packages: it's called reflection.
Have a look at the java.lang.Class class. Each instance of this class represents a particular Java class, and contains methods to return the class name, the package it lives in, the methods it contains, and lots more information.
Also worth looking at is the java.lang.reflect package, since some of the methods of Class return types from this package. The package contains classes to represent things like methods, types, fields, and so on.
To obtain a Class instance of your Test class, you can use the following code:
Class<?> testclass = Class.forName("tspec.test.Test");
This returns a class of an unknown type, which is what the question mark inside the angle brackets means if you're not familiar with generics. Why the type of the class instance is unknown is because you specify the class name with a string, which is parsed at runtime. At compile-time, Java cannot be sure that the string passed to forName even represent a valid class at all.
However, testclass as defined above will be fine for getting the class's name, methods, and containing package.
I am developing a web application.
The web application generates java classes on the fly. For example it generates class com.people.Customer.java
In my code, I dynamically compile this to get com.people.Customer.class and store in some directory say repository/com/people/Customer.class which is not on the classpath of my application server.My application server(I am using WebSphere Application Server/Apache Tomcat etc) picks up the classes from the WEB-INF/classes directory. The Classloader would use this to load the classes.
After compilation I need to load this class so that it becomes accessible to other classes using it after its creation.
When I use Thread.currentThread().getContextClassLoader().loadClass(com.people.Customer) obviously the Classloader is not able to load the class, since its not on the classpath(not in WEB-INF/classes). Due to similar reasons, getResource(..) or getResourceAsStream(..) also does not work.
I need a way to :
Read the class Customer.class maybe as a stream (or any other way would do) and then load it. Following are the constraints:
I cannot add the repository folder to the WEB-INF/classes folder.
I cannot create a new Custom ClassLoader. If I create a new ClassLoader and this loads the class, it will not be accessible to its parent ClassLoader.
Is there any way of achieving this?
If not this, in the worse case, is there a way of overriding the default class loader with a custom class loader for web applications the same classloader should be used to load applications throughout entire lifecycle of my web application.
Appreciate any solution :)
You need a custom class loader to do this, and in this classloader you need to re-define a method findClass(String name)
An example:
public class CustomClassLoader extends ClassLoader {
final String basePath = "/your/base/path/to/directory/named/repository/";
#Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
String fullName = name.replace('.', '/');
fullName += ".class";
String path = basePath + fullName ;
try {
FileInputStream fis = new FileInputStream(path);
byte[] data = new byte[fis.available()];
fis.read(data);
Class<?> res = defineClass(name, data, 0, data.length);
fis.close();
return res;
} catch(Exception e) {
return super.findClass(name);
}
}
}
Then, you'll be load classes from custom location. For example:
Class<?> clazz = Class.forName("my.pretty.Clazz", true, new CustomClassLoader());
Object obj = clazz.newInstance();
Doing this, you tell JVM that class named my.pretty.Clazz should be loaded by your custom class loader, which knows how and where from to load your custom class.
It resolves full class name (like my.pretty.Clazz) to file name (in our case: /your/base/path/to/directory/named/repository/my/pretty/Clazz.class), then loads obtained resource as a byte array, and finally converts this array to a Class instance.
This example is very simple and demonstrates a general technique about how to load custom classes as in your case.
I suggest you to read some articles about class loading, for example this one.
Short answer: No
Without a custom ClassLoader, you cannot dynamically load classes.
However, your assumption that you cannot use a custom ClassLoader because your other objects loaded by the WebApp ClassLoader would be unable to use these newly loaded classes is incorrect. All you need is a generic way to use these newly created classes - like a common interface or a meta-description (Beans Introspector for accessing bean properties).
But if you are using third-party libraries like Hibernate and you are dynamically loading entities at runtime which are to be persisted, then you will have a hard time, but imho it is possible.
Sure you can do this. Just get the web classloader and call the defineClass() method using reflection (it is protected, so be sure to call setAccessible(true) on the method. defineClass() takes a byte array, so it doesn't make any difference where you class is from. Make sure that the class name is unique and you're loading it only once, or you'll have complicated classloading problems.