Custom compile-time class loading in Eclipse? - java

Is there a way to hook into the Eclipse compiler to specify custom class reading/resolving/loading logic while a Java source file is being compiled? I'm not sure what the correct term is, but essentially the compile-time equivalent of "class loading" that happens at run-time.
For example, say I have the Java source:
package foo;
import bar.Bar;
public final class Foo {
// getQux() returns type: qux.Qux
private final Bar bar = baz.Baz.getQux().getBar();
[...]
}
The compiler should request that 3 classes are read while compiling the source file foo/Foo.java:
bar.Bar - It is specified as an import.
baz.Baz - It is used in its fully qualified form (... = baz.Baz.getQux()...).
qux.Qux - It is an "indirect" dependency (it is returned by the call to baz.Baz.getQux(), which in turn is used to access a bar.Bar through the call to its getBar() method).
I'd like to be able intercept each of these "class requests" so that I can provide custom logic to obtain the class in question (perhaps it lives in a database, perhaps it it served up by some server somewhere, etc).
Also, I'd like it if no attempt was made to compile any of the source files in the Eclipse project until they are explicitly opened by the user. So in the example above, the 3 class requests (bar.Bar, baz.Baz, qux.Qux) aren't made until the user actually opens the source file foo/Foo.java. Ideally the list of source files in the project needn't be actual files on the filesystem (perhaps they too live in a database, etc) and a compile attempt is made only when a user opens/loads a source file.
I realize that, if possible, this has some drawbacks. For example, if I edit source file foo/Foo.java to make the class "package private", this will silently break any class that depends on foo.Foo until a "full" compile is done of the project. For now, that is fine for my purposes (there are things that I can do later to solve this).
Any ideas/suggestions?
Thank you!

Probably not, this would fall under the Java build path part of the JDT and I don't think it has that level of customization. There does not appear to be a documented extension point for this. To get a definitive answer you would need to look at the source. You could probably add this capability and it would mean that your would need to use an alternate version of the JDT, which might be difficult or impossible.

Related

Annotation Processing get element subclass/subtypes

While processing an annotation, I want to get all the subclass/subtypes of an element.
The element will be a Movie so I would like to get Elements or TypeMirrors of TerrorMovie and ComedyMovie.
interface Movie {}
class TerrorMovie implements Movie {}
class ComedyMovie implements Movie {}
Getting the superType is easy as I can use the Types.directSupertypes() but how can I get the subtypes?
Thanks
You can't. You can work around it though. You need to understand the significant limitations fundamental to the question 'We are compiling class X; can I get a list of all subtypes of this class?', because otherwise you're going to shoot yourself in the foot. Then I'll get to the workarounds.
The issue is, a superclass is fine. After all, this:
class Bob extends Alice {}
is not even going to compile if Alice is not available either as source file or as compiled class file when the compiler attempts to compile Bob. Therefore, asking the annotation processing (AP) tools to give you a TypeMirror object representing Alice is fine. It'll be there.
However, given that somewhere else in the codebase, class Carol extends Bob {} exists, is irrelevant, and if neither that source file nor that class file is around, Bob.java still compiles just fine. That's why the AP tools API doesn't give you a .getSubtypes() method. Because the docs would have to say something like this:
/**
* Finds all subtypes that are mirrorable in the current compilation context
* <b>but this does not do what you think it does and will miss a ton of classes,
* please read a page worth of caveats or prepare to shoot your foot off</b>:
*
* <ul><li>loooong list of caveats here</li></ul>
*/
Whereas the same list of caveats aren't necessary for getSupertypes(), because the cases where supertypes cannot be found are rare, are going to stop compilation anyway, and can thus don't apply (APs don't run when compilation errors occur, or if they do, it's not likely to be important that they can't do the job right).
You have 2 crucial issues:
In any case, 'search the universe is impossible'
I can write a class that extends AbstractList. I can do it right now: class ReiniersList extends AbstractList<String> {}. That was easy. When Team OpenJDK compiles AbstractList.java, of course ReiniersList cannot be returned when they run an AP that calls a hypothetical .getSubtypes().
So, you need to figure out what kind of subtype do you want. "Anywhere in this package?" "Anywhere in the source and classpath?" That last one is impossible - the classpath abstract simply does not support a 'list' directive. And yet that's probably what you intended to do here. The best option, in the sense that it really is the only thing that the AP API can meaningfully give you, is 'please give me all subclasses of this class that are part of the current compilation run, and only in source file form'. In other words, if you run javac *.java in a package and this hits AbstractFoo.java, FooImpl1.java, and FooImpl2.java, then asking 'getSubtypes()of the TypeMirror representingAbstractFoo, you would be able to get FooImpl1andFooImpl2` typemirrors. That's possible.
The problem is, programming teams split projects, and other code that doesn't even exist yet could extend AbstractFoo 5 years from now and you can't get those in the list. You're going to have to make crystal clear what you actually 'mean' when you ask for subtypes, and then check if that definition is something that the AP tools can even provide for you.
Inners
method-local classes can extend a type but aren't part of the type mirror infrastructure. They cannot have an impact on type resolution. This is a rarely used java feature, perhaps you don't know what I'm talking about. It's this:
class Example {
void foo() {
class MethodLocalClass extends AbstractList<String> {}
}
}
is legal java, actually. Yes, I stuck a class def in the middle of a method declaration. Outside of the method decl this class is utterly invisible. You can't write Example.MethodLocalClass anywhere, except inside foo(). Because of this, this class does not exist at annotation processing time, effectively. You can't ask for it. It has no accessible fully qualified name. Nevertheless, it could be a subclass. Point is, getSubtypes() cannot return it, ever. If that is a problem, then you can't do this at all. Hopefully, you're okay with this.
Incremental compilation
That problem of 'search the universe is impossible' takes on an acute role in making what you want completely impossible, at least as far as the AP tools are concerned, when you factor in that most compilation is done incrementally: Only the source files that need recompilation (because they were modified) are recompiled. That means if you have AbstractFoo, FooImpl1 and FooImpl2 all in the same package, and you edit AbstractFoo and FooImpl1, but don't touch FooImpl2, and then save and recompile, your compiler infra (be it an IDE, or maven, or gradle, or some other build tool) is rather likely to compile only AbstractFoo and FooImpl2 which means it is not possible for the infra to give you FooImpl1.
Even though FooImpl1 is in the same project (in the same package, even!). And there is no way to ask: It's simply not in the 'source set' of the compiler, and the classpath does not support a 'list' directive (where FooImpl2.class lives - after all, that's why it's not part of the source set, it already exists and does not need to be recreated).
Solutions
The main solution is to do a lot of bookkeeping yourself:
First, understand that you cannot ever get a list of stuff that lives outside your project. Only files that, on a complete clean recompilation run, would be touched, can be found. If it's in a different project built by the same team, you can find it, but stuff others write, or stuff that will be written in the future, you can never find that. Ensure you don't need these, or we're done.
Bookkeep every class you touch. Make a text file and write this (using the Filer, you can write non-java files too) into the output directory, listing all types you 'touched'. Then, on init, read this file, and for each class in it, ask the type tools for this type. This 'solves' the "classpaths cannot list" problem: Now you're not 'list all types and find me every type that extends X', now you are simply asking: "Please give me a TypeMirror representing class X, get it from the source path or the class path, and tell me what types that type extends". Crucially, that second thing is something the AP infra can do. You then do a quick 'cache clear' - whatever classes do not exist anywhere, you now know - I guess someone deleted that file. For everything that does exist, then that is one of the subtypes, though they might not be part of this compilation 'source set'.
You read the file during init, then you go through the rounds, extending this Set<String> (representing fully qualified types that are subtypes, automatically getting rid of duplicate names), and then on the last round (when roundEnv.isLastRound() is true), you check each type if it still exists and is a subtype of what you intended to be. And now you have your list of subtypes. Write this pruned list back for future runs, and do whatever you want to do with 'you now have a list of type names that extend the type you are interested in'.
I'm not aware of any libraries that can help you with this. It's not too hard to write it yourself.
This principle of 'write a file that contains, per line, the fully qualified type of some relevant type' smacks a lot of the SPI system, but crucially this one runs during compilation whereas SPI is designed as a runtime discovery system.

Java reflect, edit code in runtime

I am currently trying to edit a class file in runtime, Example:
Example.java with this code:
public static void execute(){
System.out.println("hello worl");
}
There is no easy way to edit the text in this example, Now i need code that edits the "hello worl" to "hello world" without having access to the Example.java and without restarting the program to edit byte code, Is this possible? I have searched many articles and have not found a defined answer.
This depends on how much access you have.
This simplest way is to alert that class before it loads and force JVM to load your version of it, but then it must be loaded by proper ClassLoader, by your nickname I can assume that you are trying to do some magic using Spigot minecraft engine? Then if you want to change class from other plugin all you need to do is to actually copy this class to your project and load this class in static block of your main class - just make sure that your plugin will load before that other one.
This will cause JVM to load this class before original one, and because of how spigot class loader works - it will be added to global map of plugin classes, so other classes with that name will not be loaded, that other plugin will use your class instead.
This is also possible in other places, not just spigot - but not in every application, as it must have similar class loading - with shared storage of classes for your plugin/jar and plugin/jar that you want to edit.
Other way is to use Javassist library to do something similar but in runtime:
ClassPool classPool = ClassPool.getDefault();
CtClass ctToEdit = classPool.getCtClass("my.class.to.Edit");
CtMethod execute = ctToEdit.getDeclaredMethod("execute");
execute.setBody("{System.out.println(\"hello world\");}");
ctToEdit.toClass(); // force load that class
Javassist will find .class file for that class and allow you to edit it, and then inject it to selected class loader (I think it is system one by default, you can use .toClass(ClassLoader) method too)
Most important part of this trick is that class can't be loaded before this code executes. This is why you need to provide class name manually, never do something like MyClass.class.getName(), this will break this trick.
Note that javassist java compiler is a bit tricky and limited, see their webpage to find more informations and useful tricks.
If class is already loaded... then you have last option.
You can do it using java agents via Instrumentation class - as it allows to reload classes in runtime (with few limitations, just like debugger, you can't change class schema, but you can change anything you want using ClassFileTransformer before class is loaded).
So you need to create special java agent that will edit this class before it loads, or after it load (but then with that few limitations) using https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses(java.lang.instrument.ClassDefinition...)
But normally to do so you need to add special flags or manifest entries to runnable .jar, if you are launching this code on JDK, then it is much easier - with few tricks you can create agent and attach it to your VM in runtime, there is a bit of code needed for that so I will just suggest to use byte-buddy-agent library, then you can get instrumentation with single line of code:
Instrumentation install = ByteBuddyAgent.install();
And you can redefine classes all you want, in your case I would also suggest to use Javassist library to edit code - as it is the easiest of available ones, but you can also use ByteBuddy or raw ASM, for javassist your code would look like this:
Instrumentation instrumentation = ByteBuddyAgent.install();
CtClass ctClass = ClassPool.getDefault().getCtClass(Main.class.getCanonicalName());
ctClass.defrost(); // as this class is already loaded, javassist tries to protect it.
CtMethod execute = ctClass.getDeclaredMethod("execute");
execute.setBody("{System.out.println(\"hello world\");}");
ClassDefinition classDefinition = new ClassDefinition(Main.class, ctClass.toBytecode());
instrumentation.redefineClasses(classDefinition);
If class is already loaded and you are on JRE and you can't change app start arguments for some reasons - so just any of that methods are not possible - write comment and describe way - I know some other magic, but it would take much more time to describe it, so I would need more information.

Matlab's Tab completion for classes

I have the following project on my hands, and I am banging my head to the wall for this "little" caveat.
In the project Matlab classes are used. Due to the structure of the project, I have the folders structured as follows:
+a/+b/+c/
Then, on c there are a bunch of other subfolders declared:
+a/+b/+c/+d
+a/+b/+c/+e
+a/+b/+c/+f
+a/+b/+c/+g
On one of those folders (let's sat +e) is where I am implementing my .m classes, which contain properties, as well as Static methods:
+a/+b/+c/+e/my_class_1.m
+a/+b/+c/+e/my_class_2.m
+a/+b/+c/+e/my_class_3.m
+a/+b/+c/+e/my_class_4.m
So let's take a look into my startup.m file:
% add the path to the class
addpath(genpath('<previous_path_to_a>'));
% import the module
import a.b.c.e.*
What I would like to do now is to be able to press my_class_1. + Tab on the Matlab prompt and be shown the properties and methods available for that given class.
I know I could just use Matlab's methods() function for this, or the properties() one, but it would be really nice to be able to just type:
help my_class_1. + Tab
to be able to select the given method and see it's documentation.
Otherwise I have, as I said, to call methods() first to see what the names of the class's own methods are for this particular class, to be able to access its documentation.
Edit:
Of course, what does work is typing the whole path, in my example:
help a.b.c.e.my_class_1. + Tab
The question is how to get rid off those previously annoying a.b.c.e.
Hmm, looks like you're right. Tab completion of methods and properties only seems to work with fully qualified class names, even if the class is on the path and imported.
I don't know of a workaround. If I were you, I'd enter an enhancement request with MathWorks for that. It would seem like an obvious and nice thing to have.

If a referenced Java class is not found, or blacklisted, when is this detected?

I wanted to use, inside Google appengine, a small library; one of the methods of one of its classes (say MyClass.writeToFile()) uses java.io.FileOutputStream, which is among the blacklisted classes (well, not white-listed).
Does this imply that MyClass will fail at classloading time, or just when (if) I try to invoke the offending method? At what point is that checking ("FileOutputStream not allowed") done? When loading MyClass file and detecting that it "refers to/depends on" FileOutputStream (I dont know if this is declared in the MyClass bytecode)? Or when trying to load FileOutputStream, while invoking MyClass.writeToFile() for the first time?
Further, assuming that the method MyClass.writeToFile() is not essential for my library (but MyClass is), is there some workaround, or should one refactor the library (and build, say two different jars, one full fledged and other sandbox-friendly) ?
Do a test deploy. Deployment should fail if I remember it correctly. Classes that are used by a certain class is part of the byte code, and google is verifying it.
Edit: I'm contradicting myself :) This thread indicates that deployment only fail if the FileOutputStream class is loaded:
GoogleAppEngine : possible to disable FileUpload?
Edit2: And this indicates that it is the class loader that is checking / stopping loading of forbidden classes: The Sandbox
Classes are lazyload upon first reference. If your code never tries to use FileOutputStream then there should be no problem.

Do I have any method to override System Properties in Java?

I am getting a practical issue and the issue can be dascribed as follows.
We are developing a component (Say a plugin) to do some task when an event is triggered within an external CMS using the API provided by them. They have provided some jar libraries, So what we are doing is implementing an Interface provided by them. Then an internal method is called when an event is triggered. (The CMS is creating only one instance of class when the first event triggers, then it just executes the method with each event trigger)
The function can be summarized as follows,
import com.external.ProvidedInterface;
public class MonitorProgram implements ProvidedInterface{
public void process(){
//This method is called when an event is triggered in CMS
}
}
Within our class we are using "javax.net.ssl.HttpsURLConnection" (JAVA 1.5). But HttpsURLConnection migrated to javax.net.ssl from com.sun.net.ssl for 1.4. But it seems the CMS I am referring to (We dont know their implementation actually) uses something like this
System.setProperty("java.protocol.handler.pkgs","com.sun.net.ssl.internal.www.protocol");
leading to a ClassCastException in our code.
I think my question is clear. In our case we cant set VM parameters,
-Djava.protocol.handler.pkgs=
Also we cant set it back using,
System.setProperty("")
because the VM instance is same for CMS and our program.
What can I do for get this problem resolved? And idea or experiences?
This is not clear for me.
Do you want to overwrite a system property?
You can do this.
Overwrite the System.property before calling the external library method and when the method returns you can set the old System.property back
final String propertyName = "Property";
String oldProperty = System.getProperty(propertyName);
System.setProperty(propertyName,"NEW_VALUE");
monitorProgram.process();
System.setProperty(propertyName,oldProperty);
Or do you want to prevent, that the called process overwrites the system.property?
And why you can not set the system property by hand?
I don't think you are going to have much success getting two pieces of code to use different properties.
In your own code however, you can define your own URLStreamHandlerFactory. Doing this will allow you to create a javax.net.ssl.HttpsURLConnection from a URL. While protocol handlers aren't the easiest thing to figure out, I think you can get them to do the job.
See http://java.sun.com/developer/onlineTraining/protocolhandlers/
Find the offending class in the stack trace
Use jad or a similar tool to decompile it.
Fix the name of the property
Compile the resulting file and either replace the .class file in the CMS's jar or put it into a place which is earlier in the classpath.
Use ant to automate this process (well, the compile and build of the JAR; not the decompiling)
When it works, make sure you save everything (original file, changed file, build file) somewhere so you can easily do it again.
While this may sound like a ridiculous or dangerous way to fix the issue, it will work. Especially since your CMS provider doesn't seem to develop his product actively.

Categories

Resources