I'm writing a Java annotation processor that creates package-info for packages in a Java project.
The code to actually generate the source file looks somewhat like this:
JavaFileObject builderFile = filer.createSourceFile(packageName + ".package-info");
try (PrintWriter out = new PrintWriter(builderFile.openWriter()))
{
out.println("... file content ...");
}
Unfortunately, this code crashes when there already is a package-info for the given package, either in the source code or generated by a previous round.
Normally, I would just skip the generation if a package-info already exists, but I couldn't find any way to test for the existence of a package-info.
processingEnv.getElementUtils().getPackageElement(packageName)
Always returns a PackageElement, regardless of whether there is a package-info or not, which seams reasonable, because the package exists with or without the package-info.
There also doesn't seem to be an ElementKind for package-info either and PackageElement.getEnclosedElements() only returns the top level classes and interfaces in that package.
In lack of a better solution, I'm currently catching and ignoring the IOException thrown by Filer.createSourceFile(String) in case the package-info already exists.
Is there a way to check if a certain package already contains a package-info?
Edit:
FWIW, the source file can be found on Github
Related
I want to get the directories of the source files which are getting compiled after annotation processing while doing the annotation processing without relying on directory/build tool conventions.
public class MyProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// Get the directories with source files to be compiled here
// For example, I should be able to find all classes which I obtain via the following in the directories I want:
roundEnv.getElementsAnnotatedWith(MyAnnotation.class)
}
}
I have tried:
A relative path which I got with Paths.get("."). But not only would I still have to rely on directory/build tool conventions to get to the source directories, it also doesn't work when the build is started from anywhere else but the project root.
Checking all properties in System.getProperties() and System.getenv() to see if there is anything useful being set.
Checked javax.lang.model.element.TypeElement, javax.lang.model.type.TypeMirror and the corresponding utility methods to see if I can get an instance's location (Did I miss something?)
Checked various combinations of StandardJavaFileManager's methods, some of which suggest to return what I want, but returning either null/empty collections or throwing exceptions for module related parameters. Example: standardJavaFileManager.list(StandardLocation.SOURCE_PATH, "", Set.of(Kind.SOURCE), true).
// edit
I'm trying to create a functionality where classes can be picked up based on super-types. For that, I created an Annotation where I can specify these super-types. I now need to scan all source files to check for classes that are sub-types of X, for which I am using a library. Said library needs to be pointed to a directory containing the classes to scan, which is why I need the source folder.
I am using the Eclipse JDT to build AST for Java source code, so I can do some code analysis. Currently I would like to obtain the fully qualified name of an annotation. Consider the code below:
import javax.persistence.Entity;
#Entity
public class Class1
If I visit this Compilation Unit, the #Entity is a MarkerAnnotation. And I can do some analysis on it. However I am unable to obtain the Fully qualified name. I would like to obtain "javax.persistence.Entiy". I have tried several ways, but with no success.
public boolean visit(MarkerAnnotation node) {
node.getTypeName(); //returns the simple name
node.getTypeName().getFullyQualifiedName();// I thought this would print javax.persistence.Entiy,
// but it only prints "Entity"
node.resolveTypeBinding().getName(); //Prints "Entity"
node.resolveTypeBinding().getBinaryName(); // Prints "Entity"
node.resolveAnnotationBinding().getName(); //Prints "Entity"
return super.visit(node);
}
I have also tried to cast MarkerAnnotation to Annotation, but I am still unable to get fully qualified name. During debugging sesssions, I had no success either navigating this node
I was able to get the fully qualified name using the imports() method of the CompilationUnit. I did some String manipulations on them, combining with the annotations simple name. However, I feel this is sort of hacky, and I need to look at every import, even ones that are not related to annotations.
What I would like is to obtain the fully qualified name directly from the node, i.e, from the MarkerAnnotation, NormalAnnotation and SingleMemberAnnotation. Is there any way to achieve this? What Am I missing here?
Thanks in advance!
From the javadoc of Annotation.resolveAnnotationBinding():
Note that bindings (which includes resolved annotations) are generally unavailable unless requested when the AST is being built.
So please check how you configure ASTParser, see ASTParser.setResolveBindings(boolean)
In my Android Application I have an annotation processor which generates files using JavaPoet and places them under the package generated.schema.
The files are generating correctly. Whenever I use the generated file like so
GeneratedFile.someGeneratedMethod();
I get the following error:
error: package generated.schema does not exist.
But if I include the fully qualified class name instead of importing like so
generated.schema.GeneratedFile.someGeneratedMethod();
the code compiles and runs without any error.
I don't want to add complete package each time I am using GeneratedFile. I'm not sure what I did wrong, since I'm still learning to work with Annotation Processor.
Files generated by other libraries including Realm, DataBinding are all working correctly as expected.
File Generation :
using JavaPoet I run the following code.
if (roundEnvironment.processingOver()) {
for (TypeElement element : apiList) {
TypeSpec clazz = generateFile(element);
JavaFile.builder(NamespaceCreator.generateClassPackage(element), clazz)
.build()
.writeTo(filer);
}
}
NamespaceCreator.generateClassPackage(element) returns the package name for class i.e generated.schema.
While generating classes I was waiting for the last processing pass. the code generation encapsulated by
if (roundEnvironment.processingOver())
I was getting a warning because of this:
File for type 'generated.schema.GeneratedFile' created in the last round will not be subject to annotation processing.
I was aware of this warning before I posted the question, however I was willing to ignore further annotation processing on my generated files for simplicity of generating all files in one go.
Even though, after removing the last round/pass check from file generation I can correctly (with import) access the generated files without any error; I still don't understand how generating files throughout all rounds affects accessing files during build with import.
For that I will be posting a new question.
Problem
I'm writing a standalone utility program which, given a jar containing a JPA-2 annotated persistence unit, needs to programmatically get a list of all my #Entity classes in a particular persistence unit.
I'd like to decide which of 2 approaches would be the way to go to get this information, and why; or if there is another better way I haven't thought of.
Solution 1
Java program puts jar on the classpath, creates persistence unit from the classes in the jar using JavaSE methodologies. Then it uses the javax.persistence classes to get the JPA Metamodel, pull back list of class tokens from that.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("MY_ PERSISTENCE_UNIT");
Metamodel mm = emf.getMetamodel();
// loop these, using getJavaType() from Type sub-interface to get
// Class tokens for managed classes.
mm.getManagedTypes();
Solution 2
Program scan the directories and files inside the specified jar for persistence.xml files, then finds one with the specified persistence unit name. Then XPath the file to get the list of <class> XML elements and read the fully qualified class names from there. From names, build class tokens.
Constraints/Concerns
I'd like to go with approach 1 if possible.
This utility will NOT run inside a container, but the jar is an EJB project designed to run inside one. How will this be a problem?
The utility will have Open-EJB available on the classpath to get implementations of all the Java EE 6 classes.
Even though the EJB project is built to run on Hibernate, the utility should not be Hibernate-specific.
Are there any stumbling blocks?
In case anyone's interested, Solution 1 worked. Here's essentially what I had to do:
public MySQLSchemaGenerator() throws ClassNotFoundException {
Properties mySQLDialectProps = new Properties();
mySQLDialectProps.setProperty("javax.persistence.transactionType", "RESOURCE_LOCAL");
mySQLDialectProps.setProperty("javax.persistence.jtaDataSource", "");
final EntityManagerFactory emf = Persistence.createEntityManagerFactory("<persistence_unit_name>", mySQLDialectProps);
final Metamodel mm = emf.getMetamodel();
for (final ManagedType<?> managedType : mm.getManagedTypes()) {
managedType.getJavaType(); // this returns the java class of the #Entity object
}
}
The key was to override my transaction type and blank out the jtaDataSource which had been defined in my persistence.xml. Turns out everything else was unnecessary.
If Your jar is well-formed (persistence.xml at the right place - in the META-INF folder), then all looks fine.
It is not necessary to run your utility inside a container, JPA is not a part of JavaEE specs.
I have an application that allows, using an abstract class, people to write their own implementations. I load these implementations as .class-files from a directory. Currently, I have this solution:
File classDir = new File("/users/myproject/classes/");
URL[] url = { classDir.toURI().toURL() };
URLClassLoader urlLoader = new URLClassLoader(url);
String filename;
for (File file : classDir.listFiles()) {
filename = string.getFilenameWithoutExtension(file);
if (filename.equals(".") || filename.equals("..") || filename.startsWith("."))
continue;
AbstractClass instance = (AbstractClass)urlLoader
.loadClass("org.mypackage." + filename)
.getConstructor(ConfigUtil.class, DatabaseUtil.class, StringUtil.class)
.newInstance(config, database, string));
instance.doSomething();
}
As you see - I need to specify the package the classes are located in in order to correctly load them. Omitting the package, I get an
java.lang.NoClassDefFoundError:
MyClass (wrong name: org/mypackage/MyClass)
error.
Now, from a architectural POV, I think it is very ill-designed that classes other people designed have to be compiled to MY package when loading them.
So I ask you: Is there a way I can load classes form the file system without having to specify the package they reside in?
Yes; implement an interface (or use an annotation).
Then use any class-scanning library (there are lots of SO questions about this, like this one) to load the particular class in question. Searching for "Java class scanning" or "Java plugin mechanism" will help.
You might also just want to use the Java Plugin Framework and avoid some effort. Although it's not clear to me that it's maintained any more, I know people are still using it.
You can use the ServiceProvider to load implementations which you don't know.