Skip to content

Commit

Permalink
Allow metamodel package and naming customization, fix #290 and fix #280
Browse files Browse the repository at this point in the history
  • Loading branch information
julgus committed Feb 16, 2023
1 parent cb1ff24 commit 957fecc
Show file tree
Hide file tree
Showing 14 changed files with 792 additions and 39 deletions.
3 changes: 2 additions & 1 deletion docs/modules/how-it-works/pages/how-it-works.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ long nrOfFilmsStartingWithA = jpaStreamer.stream(Film.class)
.filter(Film$.title.startsWith("A")) "<1>
.count();
----
#<1> As `title` is of type `StringField` we get access to operators such as `startsWith` that returns a `Predicate<T>` that evaluates to `true` or `false`.
<1> As `title` is of type `StringField` we get access to operators such as `startsWith` that returns a `Predicate<T>` that evaluates to `true` or `false`.

You can read more about the use of fields and predicates xref:predicates:predicates.adoc[here].

NOTE: The metamodel is stored in the projects target-folder and does not need to be checked-in with your source code, nor needs testing.
NOTE: The package where generated entities are placed, as well as the name of the generated entities can be configured. Learn more xref:jpa-streamer:quick-start:quick-start.adoc#_code_generator_settings[here].

== The Stream renderer
Most JPA providers, by default, has the ability to provide the query result as a `Stream`. That makes it possible to e.g. select all rows of a table and then use Stream operators to narrow the results. Although, that process requires every object of the table to be materialized by the JVM, which is not desired as it impedes the performance.
Expand Down
49 changes: 44 additions & 5 deletions docs/modules/quick-start/pages/quick-start.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,51 @@ Film {filmId=5, title='AFRICAN EGG', ...

NOTE: To release any resources potentially held by JPAstreamer, simply close the streamer using the command `jpaStreamer.close()`;

== Next Steps
Now that you have access to JPAstreamer it's time to put the expressiveness of Java Streams to work. If you are not yet familiar with the `Stream` API, you may find our guide on xref:stream-fundamentals:stream_basics.adoc[Stream Fundamentals] helpful.

Otherwise, feel free to check out the xref:fetching-data:stream-examples.adoc[collection of examples] of JPAStreamer usage.

[source, java]
----
jpaStreamer.close();
----

[#_code_generator_settings]
== Code Generator Settings
You can configure where the annotation processor generates entities and how they are named using the link:https://maven.apache.org/plugins/maven-compiler-plugin/[Maven Compiler Plugin]. This section describes how that is done.

=== Package name
By default, all generated classes will be placed under `target/generated-sources/annotations` in a package that matches that of the original entity. Thus, a `com.foo.Film.class` entity will yield a `target/generated-sources/annotations/com/foo/Film$.class`.

You can change the package name for all entities by setting the compiler argument `jpaStreamerPackage`.

NOTE: If your entities have package private fields, they may not be accessible to the generated Entities. Therefore, take caution when deciding to place the generated entities in a separate package.

=== Generated entity name
As shown in examples throughout the user guide, generated entities are by default given the name of the original entity with an extra `$` at the end. Meaning an entity `Foo.class` will yield a `Foo$.class`. You can change the naming convention by setting the compiler argument `jpaStreamerPrefix` and `jpaStreamerSuffix`. The prefix will be added at the start of the original entity name and the suffix will be appended at the end.

NOTE: The default suffix `$` applies if no custom values are provided via the compiler arguments. If just one of `jpaStreamerPrefix` and `jpaStreamerSuffix` is set, the other will automatically default to an empty string. For example, just setting the prefix to `A` will result in entities with the name `AFoo.class`. Take note that the naming pattern must adhere to link:https://www.oracle.com/java/technologies/javase/codeconventions-namingconventions.html[Java's naming conventions].

Here is a complete example configuration of the plugin that sets both the package name, prefix, and suffix.

[source,xml]
----
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<compilerArgs>
<arg>-AjpaStreamerPackage=com.speedment.jpastreamer.test</arg> <1>
<arg>-AjpaStreamerPrefix=A</arg> <2>
<arg>-AjpaStreamerSuffix=B</arg> <3>
</compilerArgs>
</configuration>
</plugin>
----
<1> Package name is set to `com.speedment.jpastreamer.test`
<2> Entity prefix is set to A (start)
<3> Entity suffix is set to B (ending)

In the above example, all generated entities will be placed in `/target/generated-sources/annotations/com.speedment.jpastreamer.test`. An original entity with the name `Film` will yeild a generated entity called `AFilmB`.

== Next Steps
Now that you have access to JPAstreamer it's time to put the expressiveness of Java Streams to work. If you are not yet familiar with the `Stream` API, you may find our guide on xref:stream-fundamentals:stream_basics.adoc[Stream Fundamentals] helpful.

Otherwise, feel free to check out the xref:fetching-data:stream-examples.adoc[collection of examples] of JPAStreamer usage.
2 changes: 1 addition & 1 deletion docs/site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ content:
# only generate from git tags (and not branches)
branches: []
start_path: docs
tags: [0.1*, 1.0*, '!1.0.3', 1.1*]
tags: [0.1*, 1.0*, '!1.0.3', 1.1*, '!1.1.1']
ui:
bundle:
url: ui-bundle.zip
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
*/

@SupportedAnnotationTypes("javax.persistence.Entity")
@SupportedOptions({"jpaStreamerPackage", "jpaStreamerPrefix", "jpaStreamerSuffix"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public final class StandardFieldGeneratorProcessor extends AbstractProcessor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,30 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
.filter(ae -> ae.getKind() == ElementKind.CLASS)
.forEach(ae -> {
try {
String qualifiedGenEntityName = ae.asType().toString() + "$";
final String entityName = ae.asType().toString();
final String shortEntityName = shortName(entityName);

final String prefix = processingEnv.getOptions().getOrDefault("jpaStreamerPrefix", "");
final String suffix = processingEnv.getOptions().getOrDefault("jpaStreamerSuffix", "");

final String genEntityName = (!prefix.equals("") || !suffix.equals("")) ?
prefix + shortEntityName + suffix : shortEntityName + "$";

String annotatedElementPackageName = "";
final PackageElement packageElement = processingEnvironment.getElementUtils().getPackageOf(ae);
if (packageElement.isUnnamed()) {
messager.printMessage(Diagnostic.Kind.WARNING, "Class " + entityName + " has an unnamed package.");
annotatedElementPackageName = "";
} else {
annotatedElementPackageName = packageElement.getQualifiedName().toString();
}

String packageName = processingEnv.getOptions().getOrDefault("jpaStreamerPackage", annotatedElementPackageName);

String qualifiedGenEntityName = packageName + "." + genEntityName;
JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(qualifiedGenEntityName);
Writer writer = builderFile.openWriter();
generateFields(ae, writer);
generateFields(ae, entityName, genEntityName, packageName, writer);
writer.close();
} catch (IOException e) {
e.printStackTrace();
Expand All @@ -111,10 +131,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
return true;
}

void generateFields(final Element annotatedElement, final Writer writer) throws IOException {

final String entityName = shortName(annotatedElement.asType().toString());
final String genEntityName = entityName + "$";
void generateFields(final Element annotatedElement, final String entityName, final String genEntityName, final String packageName, final Writer writer) throws IOException {

final Map<String, Element> getters = annotatedElement.getEnclosedElements().stream()
.filter(ee -> ee.getKind() == ElementKind.METHOD)
Expand All @@ -131,7 +148,7 @@ void generateFields(final Element annotatedElement, final Writer writer) throws
.map(n -> n.substring(2))
.map(Formatting::lcfirst)
.collect(toSet());

// Retrieve all declared non-final instance fields of the annotated class
Map<? extends Element, String> enclosedFields = annotatedElement.getEnclosedElements().stream()
.filter(ee -> ee.getKind().isField()
Expand All @@ -140,25 +157,9 @@ void generateFields(final Element annotatedElement, final Writer writer) throws
.collect(
toMap(
Function.identity(),
ee -> findGetter(ee, getters, isGetters, entityName, lombokGetterAvailable(annotatedElement, ee)))
ee -> findGetter(ee, getters, isGetters, shortName(entityName), lombokGetterAvailable(annotatedElement, ee)))
);

/* if (annotatedElement.getSimpleName().toString().contains("User")) {
messager.printMessage(Diagnostic.Kind.NOTE, " "+ isGetters.size());
messager.printMessage(Diagnostic.Kind.NOTE, " "+ isGetters.iterator().next());
messager.printMessage(Diagnostic.Kind.NOTE, enclosedFields.toString());
throw new UnsupportedEncodingException(isGetters.toString());
}*/

final PackageElement packageElement = processingEnvironment.getElementUtils().getPackageOf(annotatedElement);
String packageName;
if (packageElement.isUnnamed()) {
messager.printMessage(Diagnostic.Kind.WARNING, "Class " + entityName + " has an unnamed package.");
packageName = "";
} else {
packageName = packageElement.getQualifiedName().toString();
}

final File file = generatedEntity(enclosedFields, entityName, genEntityName, packageName);
writer.write(generator.on(file).orElseThrow(NoSuchElementException::new));
}
Expand All @@ -179,7 +180,7 @@ private String findGetter(final Element field,
final String standardJavaName = javaNameFromExternal(fieldName);

final String standardGetterName = getterPrefix + standardJavaName;

if (getters.get(standardGetterName) != null || isGetters.contains(standardGetterName)) {
// We got lucky because the user elected to conform
// to the standard JavaBean notation.
Expand Down Expand Up @@ -210,23 +211,25 @@ private String findGetter(final Element field,

}

private File generatedEntity(Map<? extends Element, String> enclosedFields, String entityName, String genEntityName, String packageName) {
private File generatedEntity(final Map<? extends Element, String> enclosedFields, final String entityName, final String genEntityName, final String packageName) {
final File file = packageName.isEmpty() ?
File.of(genEntityName + ".java") :
File.of(packageName + "/" + genEntityName + ".java");

final String shortEntityName = shortName(entityName);

final Class clazz = Class.of(genEntityName)
.public_()
.final_()
.set(Javadoc.of(
"The generated base for entity {@link " + entityName + "} representing entities of the"
+ " {@code " + lcfirst(entityName) + "}-table in the database." +
"The generated base for entity {@link " + shortEntityName + "} representing entities of the"
+ " {@code " + lcfirst(shortEntityName) + "}-table in the database." +
nl() + "<p> This file has been automatically generated by JPAStreamer."
).author("JPAStreamer"));

enclosedFields
.forEach((field, getter) -> {
addFieldToClass(field, getter, clazz, entityName);
addFieldToClass(field, getter, clazz, shortEntityName);
// Name magic...
if (getter.contains(IllegalJavaBeanException.class.getSimpleName())) {
file.add(Import.of(IllegalJavaBeanException.class));
Expand All @@ -235,6 +238,8 @@ private File generatedEntity(Map<? extends Element, String> enclosedFields, Stri

file.add(clazz);
file.call(new AutoImports(generator.getDependencyMgr())).call(new AlignTabs<>());
file.imports(SimpleType.create(entityName));

return file;
}

Expand Down Expand Up @@ -328,11 +333,11 @@ private Type fieldType(Element field) {
}

private String trimAnnotations(Element field) {
final String fieldType = field.asType().toString();
final String fieldType = field.asType().toString();
final int index = fieldType.lastIndexOf(' ');
return index < 0 ? fieldType : fieldType.substring(index + 1);
return index < 0 ? fieldType : fieldType.substring(index + 1);
}

private Type primitiveFieldType(Type fieldType, Type entityType) {
Type primitiveFieldType;
switch (fieldType.getTypeName()) {
Expand Down
129 changes: 129 additions & 0 deletions fieldgenerator/fieldgenerator-test-package/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
JPAstreamer - Express JPA queries with Java Streams
Copyright (c) 2020-2022, Speedment, Inc. All Rights Reserved.
License: GNU Lesser General Public License (LGPL), version 2.1 or later.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
See: https://github.com/speedment/jpa-streamer/blob/master/LICENSE
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fieldgenerator</artifactId>
<groupId>com.speedment.jpastreamer</groupId>
<version>1.1.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>fieldgenerator-test-package</artifactId>

<properties>
<module-name>jpastreamer.fieldgenerator.test.renaming</module-name>
<maven.javadoc.skip>true</maven.javadoc.skip>
</properties>

<name>FieldGenerator - Test Package Renaming</name>
<description>
Standard field generator tests with package renaming
</description>

<dependencies>

<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
</dependency>

<dependency>
<groupId>com.speedment.jpastreamer</groupId>
<artifactId>fieldgenerator-standard</artifactId>
</dependency>

<dependency>
<groupId>com.speedment.jpastreamer</groupId>
<artifactId>field</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.0.Final</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<compilerArgs>
<arg>-AjpaStreamerPackage=com.speedment.jpastreamer.fieldgenerator.test.renaming.inner</arg>
<arg>-AjpaStreamerSuffix=B</arg>
</compilerArgs>
</configuration>
</plugin>

<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>target/generated-sources/annotations</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

<!--Makes JavaDoc work on deploy -->
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<detectJavaApiLink>false</detectJavaApiLink>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

</project>
Loading

0 comments on commit 957fecc

Please sign in to comment.