blackdrag
blackdrag

Reputation: 6508

make javac compile from a non-java dependency

The goal is to use javac (no other compiler) programatically to compile a Java class extending another class, that does not exist as java source or as bytecode and cannot be provided as these. The Java class exists as source file, or in memory as string (I know how to compile from strings).

I think hooking into the lookup for precompiled classes is what I need. But so far I was not able to locate this part. So if I would know how exactly javac looks up precompiled classes, how I can hook into that and provide my own representation, then this question is answered.

Or any other way that allows me to dynamically provide required dependencies...

EDIT: Since Groovy was mentioned and the purpose questioned, let me give an example... Imagine you have a class in Groovy named G, with a field of class J, which is a Java class and J extends G. I cannot compile G without J and I cannot compile J without G. However I have the AST of G and if I can hook up that AST with javac, I will be able to compile J and then later in the Groovy compiler G - or the other way around. Currently this is bypassed by producing stubs, but I am looking for a better solution.

EDIT 2: To make it absolutely clear. The ultimate goal of this question is to let the groovy compiler and the javac compiler speak to each other in a way, that they can tell to each other if they have a certain class, and then make that certain class known to the other compiler. And let me repeat, stubs in bytecode cannot work because of unresolved classes. In source they kind of work, when depending on imports resolved at least similar. But due to the nature of the Groovy compiler, which is actually kind of similar to the processing stuff javac does, we have to generate those source stubs in a pretty early phase, too early for most of the ast transforms you can apply in groovy. And that's a problem

Upvotes: 0

Views: 214

Answers (3)

blackdrag
blackdrag

Reputation: 6508

Looks like I can now answer this question on my own. By using com.sun.tools.javac.main.JavaCompiler directly I can provide a Context in which I set a custom ClassReader. There the loadClass method can be then overwritten, to provide my own ClassSymbol. I used this code to provide the type of the ClassSymbol

        // ClassSymbol cs is from super.loadClass
        // Context context is same as used for creating the compiler
        Type.ClassType newClassType = new Type.ClassType(Type.noType, List.<Type>nil(), cs);
        cs.type = newClassType;
        newClassType.tsym = cs;
        Names names = Names.instance(context);
        Symtab symtab = Symtab.instance(context);
        newClassType.supertype_field = symtab.objectType;
        cs.kind = Kinds.TYP;
        Scope members = new Scope(cs);
        cs.members_field = members;
        Type.MethodType mt = new Type.MethodType(List.<Type>nil(), symtab.voidType, List.<Type>nil(), symtab.methodClass);
        Symbol.MethodSymbol constructor = new Symbol.MethodSymbol(Flags.PUBLIC, names.init, mt, cs);
        members.enter(constructor);

The code will create a phantom type (which extends Object) for the missing type as well as providing a constructor without parameters and body. Not sure if this is the best way, but it was good enough to let javac run through

Upvotes: 0

AlexR
AlexR

Reputation: 115328

I can suggest the following. You indeed do not have to provide "real" base class. You have to provide base class with

  1. same fully qualified name as needed.
  2. same constructors if they are invoked from sub class
  3. same methods if they are invoked or overridden from subclass.

So, you can generate the class that will meet all these requirements compile your child class with this base class in classpath and then remove this fake base class.

How to create such class? You can either generate source code and compile it or generate byte code directly using ASM.

The question is just how can you get all elements of the class: required constructors and super class methods. I do not know Groovy but I hope you can get all these information from the Groovy class using reflection.

EDIT

Other solution is to work with interfaces and decouple Groovy and Java code. Just define interface in Java that will be referenced from Groovy. This interface will not have any other dependencies and can be compiled first. Then compile Groovy code. Then compile java class that extends Groovy class. What's wrong with this?

Upvotes: 0

arcy
arcy

Reputation: 13103

If the first comment is correct, and you're trying to compile class A that extends B when you don't have B's source or bytecode, then I think the answer is "You can't." If you think about what it means to compile a subclass, you will realize that the compiler needs details from the superclass -- what methods there are, what abstract methods there are, what to put in the jump table, what protected variables and methods might be referenced, etc. Java is still a strongly typed language (at least at this writing).

Upvotes: 3

Related Questions