Annotation Processing get element subclass/subtypes - java

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.

Related

How to remove this use of dynamic class loading or replace this class loading?

othersMap.put("maskedPan", Class.forName("Some Class"));
Remove this use of dynamic class loading.
Rule
Changelog
Classes should not be loaded dynamically
Dynamically loaded classes could contain malicious code executed by a static class initializer. I.E. you wouldn't even have to instantiate or explicitly invoke methods on such classes to be vulnerable to an attack.
This rule raises an issue for each use of dynamic class loading.
Noncompliant Code Example
String className = System.getProperty("messageClassName");
Class clazz = Class.forName(className); // Noncompliant
See
Let's first state the obvious: a SonarQube rule is not meant to be taken as The One And Only Truth In The Universe. It is merely a way to bring your attention to a potentially sensitive piece of code, and up to you to take the appropriate action. If people in your organization force you to abide by SonarQube's rules, then they don't understand the purpose of the tool.
In this case, the rule is telling you that you are at a risk of arbitrary code execution, due to the class name being loaded through a system property, without any safety check whatsoever. And I can only agree with what the rule says.
Now, it is up to you to decide what to do with this information:
If you believe that your build and deployment system is robust enough that no malicious code can be side-loaded through this channel, you can just mark this issue as won't fix, optionally provide a comment about why you consider this as not an issue and move on
if instead you assume that an attacker could drop a .class or .jar file somewhere in your application's class path and use this as a side-loading channel for arbitrary code execution, you should at the very least validate that the provided class name is one you expect, and reject any unexpected one
One option would be something like that:
Class<?> cls;
switch (System.getProperty("messageClassName")){
case "com.example.Message1":
cls = com.example.Message1.class;
break;
...
}
Well you could try to outsmart the Sonar rule, e.g. by using reflection to call the Class.forName() method, but I'm feeling you would be solving the wrong problem there:
Class.class.getDeclaredMethod("forName", String.class).invoke(null, className);
The right way to do it is to either convince the people who run Sonar in your org that what you do is necessary and they need to make an exception to the rule for you. Or if you can't convince them, stop doing it.

Force a class to implement one of two interfaces that implements another interface

I am not really sure about Java Annotations, but I think they can solve my problem.
I have an java interface "Target". This is an empty interface, so I can give that implementation into an "TargetHolder", which is simply a list of Targets.
Now I only have 2 Types of Targets. Type "Alpha" and type "Beta".
Type "Alpha" has no functionality in common with Type "Beta".
Easiest way would be to just extend "Beta" and "Alpha" from "Target". But with this solution it is possible for a programmer to create a class that extends "Target" only, which must not be possible.
Can I solve that with annotations?
How?
In theory you might be able to implement the checks (at compile time) using an annotation processor. The problem is that javac will only run an annotation processor on a source file if it finds the right kind annotation in the source.
"After scanning the source files and classes on the command line to determine what annotations are present, the compiler queries the processors to determine what annotations they process. When a match is found, the processor will be invoked."
(Javac manual)
But it seems like you want an annotation on an interface to constrain all classes that implement that interface. That means checking all such classes ... but I can't see how you could trigger the running of an annotation processor on a class that has no relevant annotations.
That leaves you with a couple of options:
Implement the checking as (say) a PMD rule.
Write a tool to find the relevant interfaces at runtime, retrieve their annotations, then trawl for all classes that implement the annotated interfaces.
My advice would be to put this into the "too hard" basket. It is probably going to take more time to implement this than you would save in picking up related coding errors earlier. (I'm thinking that the case that you are trying to avoid will be picked up when someone tries to use class. So, you (or your client) should find your (their) incorrect class in testing ...)
How about?
Create a package just for this work. Let us call it target.
Put Target.java in package target - package private.
Put Alpha.java in package target - public
Put Beta.java in package target - public
Compile, jar, and seal package target.
Using Tool like JArchitect allows to enforce design rules.
In your case you can use the following cqlinq query:
warnif count > 0 from t in Types where t.Implement ("Target")
&& (!t.Implement("Alpha")|| !t.Implement("Beta"))
select t

Multiple classes in the same eclipse window

I'm reading Thinking in Java and it's frustrating to declare each class in a separate window in Eclipse, as the examples often contain 6-7 very simple classes.
I can just make a new class file, make one class public in this class file and the others with default access, but I don't know what should be the class' name I created. For example, I do the following:
New -> Class -> and then I must choose a class name, let's say it's Dog.
Now, in this file, I have this:
public class Dog {
}
class Cat {
}
But since I have two classes, it's a little weird to have this class file (I don't know if it's the right word here?) to be named Dog in Eclipse (The name in the src folder).
Is there a better way to declare multiple classes in the same window(?) in Eclipse?
A java file can have at most only one public class into it. And the name of that file should be same as of that public class.
I would say the frustration are not genuine because:
This is the how Java is designed and makes all sense to define each
class in a separate file. (Unless you want to write your own compiler)
You may want to use some shortcuts e.g.
Cntrl + Shift + R` to search a class
Alt + Shift + R to rename
You can update Eclipse to use shortcut for switching within classes.
What you're doing isn't going to compile. Each top level java class must be declared in a file with the same name. It will give you an error "Cat must be declared in its own file" or something like that. If you really want to, you can put the Cat class inside of the Dog class, which is called an inner class. However since they aren't related classes you shouldn't do that. Just declare each one in its own file.
Keep each class in it's own position. If your class is small and data can be exposed you can consider using nested (inner) class.
By the way, in Eclipse you can show multiple class at same time. Just drag you file title to some place.
To actually answer your question, rather than leave a bunch of comments stating why you shouldn't (which you seem to understand already), no. There isn't really a better way to do what you want. I don't know if it will compile or not (I seem to recall seeing that in the past in Java 5), but KyleM seems to think not so we'll go with that.
Short answer: no, there is not a better way to declare multiple classes in the same file.
(I don't want to suggest inner classes because that is kind of complicated for someone just starting java, as your post suggests).
Don't mix Eclipse window with files, you can understand a .java file as a container for a java class. It's the standard way and it would help you to have a more clear project when it becomes bigger.
You can have more information about this here
If you want 2 classes in the screen you can split the eclipse editor window by dragging the opened tab file and drop it on the tabs zone.
Unfortunately you do have to do this the long way, as everyone else has suggested / insisted. If the problem is a matter of clicking around through tabs, though, eclipse does allow you to drag tabs into new windows on the screen, which lets you view potentially all of them at once.
You also end up with an "overview" of the classes in the file explorer on the left of the screen, if that's more along the lines of what you're looking for.
Good luck (:

Custom compile-time class loading in Eclipse?

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.

Seeing all classes that extent current viewed class in javadocs?

At the risk of sounding incredibly stupid and receiving a rather patronising answer, how do I view all other classes that extend the current 'viewed class' in javadocS?
i.e.
Object a {}
Object b extends a {}
Viewing a, is there a way to see 'all classes that have extended this class A'... therefore showing 'Class B'.
EDIT:
Thanks for that. There is an API I am reading at the minute where I was certain there had to be subclasses... turns out it doesn't! Interesting.
Javadoc should automatically generate the "Direct known subclasses" section, which in this case would list class B.
You shouldn't need to do anything to make this show up, so if you're not, there's a possibility it's a bug in the javadoc generator (however, unless you've presented a simplified version here, I'd be surprised if this trivial case triggers it).
Outside of your IDE javadocs in browser have a header "Direct Known Subclasses:" like the one in the javadoc for ArrayList. In IntelliJ IDEA all implementations/extensions of currently viewed interface/class can be seen if you press + B.

Categories

Resources