So, java has a built in library dedicated for compiling java source code into .class files, and it is in javax.tools. So, I was wondering how exactly you get it to work. I've read through the javadoc, and it gives some examples in there, but when I use those examples, I get errors.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
That is the example oracle gives in order to get an instance of the StandardJavaFileManager class from which you can do much more. However, I'm having some issues with the very first line of that code. When I attempt to do ToolProvider.getSystemJavaCompiler();, it always returns null. In the javadocs for that method, it says, "returns the compiler provided with this platform or null if no compiler is provided." But they never show any other way of getting an instance of a JavaCompiler. I've tried many other ways, such as using a ServiceLoader to find any reference of it that I could, but to no prevail. How might I go about getting this to work?
Chances are you're running Java from a JRE directory instead of a JDK directory - you need to run a version which "knows" where the Java compiler is.
So for example, on my Windows boxing, running a tiny test app like this:
import javax.tools.*;
class Test {
public static void main(String[] args) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
System.out.println(compiler);
}
}
The results are:
c:\Users\Jon\Test>"\Program Files\Java\jdk1.7.0_09"\bin\java Test
com.sun.tools.javac.api.JavacTool#1e0f2f6
c:\Users\Jon\Test>"\Program Files\Java\jre7\bin\java" Test
null
As you can see, it's fine when I specifically run the JDK version, but I get null when running the JRE version. Check how you're starting Java.
The compilation is often performed by directly invoking the javac compiler, which requires an installed Java Development Kit (JDK) or by calling com.sun.tools.javac.Main, which can be found in Sun's tools.jar. Sun's licensing allows tools.jar to be redistributed with the full Java Runtime Environment (JRE). Other ways to implement such dynamic capabilities include using an existing dynamic scripting language (such as JavaScript or Groovy) that integrates with the application's implementation language (see Resources) or writing a domain-specific language and associated language interpreter or compiler.
Related
Consider the following self-contained sample:
package bloopers;
import java.lang.annotation.Annotation;
public final class Blooper5
{
interface Converter<T,F>
{
T convert( F from );
}
interface Identifier<T>
{
}
static class ConvertingIdentifier<F,T> implements Identifier<F>
{
ConvertingIdentifier( Converter<T,F> converter )
{
}
}
static final class AnnotationIdentifier
{
Identifier<Annotation> I1 = new ConvertingIdentifier<>(
a -> a.annotationType() );
Identifier<Annotation> I2 = new ConvertingIdentifier<>(
Annotation::annotationType ); //<-- ERROR
Identifier<Annotation> I3 = new ConvertingIdentifier<>(
(Converter<Class<? extends Annotation>,Annotation>)
Annotation::annotationType );
}
}
The code above compiles just fine under the following:
javac from the command line.
IntelliJ IDEA configured to use the javac compiler.
But it fails to compile with the following:
Eclipse
IntelliJ IDEA configured to use the Eclipse compiler.
Eclipse fails to compile the line marked with <-- ERROR, giving the following message:
The constructor Blooper5.ConvertingIdentifier<Annotation,Class<capture#5-of ? extends Annotation>>(Blooper5.Converter<Class<? extends Annotation>,Annotation>) is undefined
Admittedly, this code really pushes the generic parameter type inference capabilities of the compiler, but still, I would like to know precisely what the discrepancy is, no matter how small.
Some exposure of my methods in case someone manages to see something wrong that I fail to see:
The command I used to compile with javac was "c:\Program Files\Java\jdk1.8.0_40\bin\javac" Blooper5.java.
I have version 14.1 of IntelliJ IDEA. Under Project Structure/SDKs I only have "1.8" which points to C:\Program Files\Java\jdk1.8.0_40 and under Project Structure/Modules the specific module is configured to use the "Project SDK (1.8)" which lists as 1.8 (java version "1.8.0_40").
As for Eclipse, I am using Eclipse for RCP and RAP Developers - Version: Luna Release (4.4.0) - Build id: 20140612-0600. Under Preferences/Java/Installed JREs I only have jdk1.8.0_40, and it is the default. Under Execution Environments it is also checked as a "Compatible JRE" of "JavaSE-1.8". And in my Project/Properties/Java Build Path/Libraries the "JRE System Library" is [jdk1.8.0_40].
More noteworthy facts:
It is not just me; it also fails on a colleague's (very similar) eclipse installation.
IntelliJ IDEA says that the lambda expression a -> a.annotationType() can be replaced with a method reference, but if asked to do so, it does not convert it to Annotation::annotationType; instead, it converts it to (Converter<Class<? extends Annotation>, Annotation>) Annotation:: annotationType.
So, the question:
What is causing these discrepancies between Eclipse and the others, and what can be done to eliminate these discrepancies?
(Obviously, the goal is to eliminate the unfortunately too frequently occurring scenario where one developer commits code which fails to compile on another developer's IDE.)
EDIT: When I originally posted this question I thought that IDEA using the Eclipse compiler also compiled fine, but I was wrong. It turns out that it is possible to get IDEA to fail to compile the above code by selecting the Eclipse compiler. Still, the question is why there is a discrepancy between eclipse and javac.
The answer to "why is there a discrepancy" is straightforward but perhaps not very satisfactory: because compilers have bugs and are furthermore open to interpretation of a very complex language specification. Determining whether it's a bug in javac or Eclipse is a difficult task; I've seen such discrepancies end up being declared both ways, sometimes as Eclipse compiler bugs, sometimes as javac bugs. That determination, especially when it involves generics and new language features (such as lambdas), can get quite tedious and arcane. For example, look at this one that turned out to be a javac bug but did uncover a related issue in Eclipse's compiler: https://bugs.eclipse.org/bugs/show_bug.cgi?id=456459
The best bet is to report it as an Eclipse bug as I did and see if the Eclipse compiler team can/will track it down.
I'm trying to compile a whole java project dynamically using the Compiler API. My initial thoughts of achieving this is to first know how to compile it in one-line using command line, then apply the same principle/parameters on the compiler object. Problem is, I never did, err, compiled using CLI. (Disadvantage of using an IDE? Haha)
So, am I on the right track? Moreover, can the project compilation achieved in one line execution? I'm having a hard time figuring this out because of the fact that it's a project, it contains packages galore.
So you either want to learn javac or Java Compiler API?
If you want CLI compilation look at javac (Linux | windows).
Alternatively for API, then programmatic use of Java's compiler API will definitely require more that one line, this will get you the compiler:
JavaCompilercompiler =ToolProvider.getSystemJavaCompiler();
Then you'll still need to load classes, write out byte code, and possibly package as a JAR.
You should use StandardJavaFileManager as you've probably many classes to manage, there's an example in the top of the JavaCompiler javadoc, but search for StandardJavaFileManager+JavaCompiler+example to find clearer examples like this blog post.
I found this code:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if(compiler.run(null, null, null, fileName) != 0) {
System.err.println("Could not compile.");
System.exit(0);
}
However, this returns a NullPointerException
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at net.foxycorndog.foxy.compiler.Compiler.compile(Compiler.java:25)
at net.foxycorndog.foxy.compiler.Parser.parse(Parser.java:41)
at net.foxycorndog.foxy.Foxy$ActionHandler.actionPerformed(Foxy.java:99)
I read that the JRE does not include the ability to compile within a java program, but the JDK does.
I don't want a program that only works on a few computers that have had to manually set the path to the JDK library instead of JRE. With this in mind, is there any work around for this problem?
I would also like it to work over cross platform if possible.
There's no workaround to the fact that the JRE doesn't contain a compiler, and the JDK does. If you want to use the Java compiler, then you'll need to run your program with the JDK.
There are other ways to create executable Java code at runtime, however; there are various bytecode assemblers that let you build your code dynamically without use of a compiler. ASM is one that rocks hard; the Apache BCEL is another.
Is it true that the rmi interface compiler is not needed for java 1.5 or newer and that just compiling the java file where the UnicastRemoteObject is defined with javac is enough?Rmic still works and it generates a stub class file if you give it the class file of the implementation of the remote object.
According to the RMI tutorial:
http://download.oracle.com/javase/tutorial/rmi/overview.html
Compiling Sources
As with any Java program, you use the
javac compiler to compile the source
files. The source files contain the
declarations of the remote interfaces,
their implementations, any other
server classes, and the client
classes. Note: With versions prior to
Java Platform, Standard Edition 5.0,
an additional step was required to
build stub classes, by using the rmic
compiler. However, this step is no
longer necessary.
See the preamble to the Javadoc for UnicastRemoteObiect. You can avoid using rmic under specific circumstances, i.e. when you construct or export the remote object providing a port number parameter (even zero), for reasons described in the documentation.
Eclipse is adding #Override annotations when I implement methods of an interface. Eclipse seems to have no problem with this. And our automated build process from Cruise Control seems to have no problem with this. But when I build from the command-line, with ant running javac, I get this error:
[javac] C:\path\project\src\com\us\MyClass.java:70: method does not override a method from its superclass
[javac] #Override
[javac] ^
[javac] 1 error
Eclipse is running under Java 1.6. Cruise Control is running Java 1.5. My ant build fails regardless of which version of Java I use.
The #Override annotation spec changed in Java 1.6. In Java 1.5, the compiler did not allow the #Override annotation on implemented interface methods, but in 1.6 it does. First search result I found is a blog post here.. It was not well documented, but it did change.
Eclipse is adding it because your Eclipse is set for 1.6 compliance. You should try to keep your build and eclipse environments on the same version of Java. It's unclear to me by your specifying Cruise Control is running Java 5 on whether or not it is compiling using a separate JDK6 or not.
Separate from the above 1.5 vs 1.6 #Override annotation rules, remember that Eclipse has its own compiler implementation (not javac) and will occasionally have different behavior. Whenever something compiles in Eclipse, but not Ant or Maven, you will need to find a way to make both compilers happy.
I can't really explain the problem you're seeing but it seems to be related to the fact that JDK 5 will not allow #Override on implemented methods of an interface, only on overridden methods present in a super class.
JDK 6 will allow #Override on any of them.
If your ant build fails it may be passing a source parameter to javac, asking for JDK 5 compliance.
The direct answer to the question "Why" an error is raised by javac when #Override is used in the context of a method implementation is actually in the java specifications:
"The rationale for this is that a concrete class that implements an interface will necessarily override all the interface's methods irrespective of the #Override annotation, and so it would be confusing to have the semantics of this annotation interact with the rules for implementing interfaces."
See http://java.sun.com/docs/books/jls/third_edition/html/interfaces.html#9.6.1.4
But apparently someone changed his mind for java 1.6 and 1.5 u21...
#Override tags for implemented methods are new to Java 1.6. In Java 1.5 #Override is only correct when overriding a method in a base class. Read more here and here.
A lot of people, including me, got busted by this. See here for a bigger SO discussion
Eclipse would be pointing to 1.6 version of Java rather than 1.5.
See here for configuring java version in eclipse.
Ensure that there is only one definition of that interface.
Example:
HttpServletRequest
This is an interface with different specs depending on provider.
Compare pax-web-jetty and apache-felix-jetty. They have different methods.
I have had the same problem when building a project with ANT. The solution to the problem was to change the following property inside the build.properties file:
javac.compiler=org.eclipse.jdt.core.JDTCompilerAdapter
to:
javac.compiler=modern
That solved the problem and the project got compiled and deployed successfully.