Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a5d1d05
feature: add builder properties in QueryProjection annotation
Cole-SJ Apr 10, 2025
f31f849
feature: add builder properties in Constructor class
Cole-SJ Apr 10, 2025
be13c36
feature: add public static inner class write methods
Cole-SJ Apr 10, 2025
3bcf85d
feature: add QueryProjection Builder feature on annotation processor
Cole-SJ Apr 10, 2025
bfa028e
feature: add inner builder class and factory method to generated QClass
Cole-SJ Apr 10, 2025
031fe37
test: add QueryProjectionBuilderTestEntity for test
Cole-SJ Apr 10, 2025
60e7b97
feature, test: add QueryProjectionBuilderTestEntity for GenericExporter
Cole-SJ Apr 11, 2025
c8faeba
question: how can i resolve binary incompatible change without this c…
Cole-SJ Apr 11, 2025
da057c4
feature: add duplicate builder name check in TypeElementHandler
Cole-SJ Apr 11, 2025
ae5918b
docs: update javadoc for QueryProjection
Cole-SJ Apr 11, 2025
7a8a09f
feature: enhance builder name validation in TypeElementHandler
Cole-SJ Apr 11, 2025
cc32001
Merge branch 'master' into feature/add-Query-Projection-Builder
Cole-SJ Apr 11, 2025
bbd4fb4
test: add unit tests for QueryProjectionBuilder
Cole-SJ Apr 11, 2025
9d1bea4
Merge remote-tracking branch 'origin/feature/add-Query-Projection-Bui…
Cole-SJ Apr 11, 2025
44afaab
Merge branch 'master' into feature/add-Query-Projection-Builder
Cole-SJ Apr 14, 2025
7f9b0b5
Merge branch 'master' into feature/add-Query-Projection-Builder
Cole-SJ Apr 15, 2025
ef90954
Skip API validation on tooling projects
velo Apr 15, 2025
d6ff231
Merge branch 'master' into feature/add-Query-Projection-Builder
Cole-SJ Apr 16, 2025
1438ef6
modify: remove inner class, methods binary compatibility validation b…
Cole-SJ Apr 17, 2025
ffb7dac
Merge branch 'master' into feature/add-Query-Projection-Builder
Cole-SJ Apr 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,9 @@
<version>0.23.1</version>
<configuration>
<parameter>
<excludes>
<exclude>com.querydsl.core.annotations.QueryProjection</exclude>
</excludes>
<ignoreMissingOldVersion>true</ignoreMissingOldVersion>
<onlyModified>true</onlyModified>
<breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications>
Expand All @@ -465,14 +468,6 @@
<accessModifier>public</accessModifier>
</parameter>
</configuration>
<executions>
<execution>
<goals>
<goal>cmp</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
12 changes: 12 additions & 0 deletions querydsl-libraries/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>cmp</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*
* private String firstName, lastName;
*
* {@code @QueryProjection}
* {@code @QueryProjection(useBuilder = true, builderName = "new")}
* public UserInfo(String firstName, String lastName) {
* this.firstName = firstName;
* this.lastName = lastName;
Expand All @@ -49,8 +49,27 @@
* .select(new QUserInfo(user.firstName, user.lastName))
* .fetch();
* }</pre>
*
* or(with Builder)
*
* <pre>{@code
* QUser user = QUser.user;
* List <UserInfo> result = querydsl.from(user)
* .where(user.valid.eq(true))
* .select(QUserInfo.builderNew()
* .firstName(user.firstName)
* .lastName(user.lastName)
* .build()
* )
* .fetch();
* }</pre>
*/
@Documented
@Target({ElementType.CONSTRUCTOR, ElementType.TYPE})
@Retention(RUNTIME)
public @interface QueryProjection {}
public @interface QueryProjection {

boolean useBuilder() default false;

String builderName() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
Expand Down Expand Up @@ -164,7 +165,7 @@ protected void processAnnotations() {
var altEntityAnn = conf.getAlternativeEntityAnnotation() != null;
var superAnn = conf.getSuperTypeAnnotation() != null;
for (TypeElement element : elements) {
var entityType = elementHandler.handleEntityType(element);
var entityType = elementHandler.handleEntityType(element, processingEnv.getMessager());
registerTypeElement(entityType.getFullName(), element);
if (typeFactory.isSimpleTypeEntity(element, conf.getEntityAnnotation())) {
context.entityTypes.put(entityType.getFullName(), entityType);
Expand All @@ -189,7 +190,7 @@ protected void processAnnotations() {
if (!context.allTypes.containsKey(fullName)) {
var element = processingEnv.getElementUtils().getTypeElement(fullName);
if (element != null) {
elementHandler.handleEntityType(element);
elementHandler.handleEntityType(element, processingEnv.getMessager());
}
}
}
Expand All @@ -206,7 +207,7 @@ protected void processAnnotations() {
addSupertypeFields(entityType, handled);
}

processProjectionTypes(elements);
processProjectionTypes(elements, processingEnv.getMessager());

// extend entity types
typeFactory.extendTypes();
Expand All @@ -231,7 +232,8 @@ private void addExternalParents(EntityType entityType) {
&& !TypeUtils.hasAnnotationOfType(typeElement, conf.getEntityAnnotations())) {
continue;
}
var superEntityType = elementHandler.handleEntityType(typeElement);
var superEntityType =
elementHandler.handleEntityType(typeElement, processingEnv.getMessager());
if (superEntityType.getSuperType() != null) {
superTypes.push(superEntityType.getSuperType().getType());
}
Expand Down Expand Up @@ -306,7 +308,7 @@ protected Set<TypeElement> collectElements() {
// register found elements
for (TypeElement element : embeddedElements) {
if (!elements.contains(element)) {
elementHandler.handleEntityType(element);
elementHandler.handleEntityType(element, processingEnv.getMessager());
}
}
}
Expand Down Expand Up @@ -343,20 +345,20 @@ private void registerTypeElement(String entityName, TypeElement element) {
elements.add(element);
}

private void processProjectionTypes(Set<TypeElement> elements) {
private void processProjectionTypes(Set<TypeElement> elements, Messager messager) {
Set<Element> visited = new HashSet<>();
for (Element element : getElements(QueryProjection.class)) {
if (element.getKind() == ElementKind.CONSTRUCTOR) {
var parent = element.getEnclosingElement();
if (!elements.contains(parent) && !visited.contains(parent)) {
var model = elementHandler.handleProjectionType((TypeElement) parent, true);
var model = elementHandler.handleProjectionType((TypeElement) parent, true, messager);
registerTypeElement(model.getFullName(), (TypeElement) parent);
context.projectionTypes.put(model.getFullName(), model);
visited.add(parent);
}
}
if (element.getKind().isClass() && !visited.contains(element)) {
var model = elementHandler.handleProjectionType((TypeElement) element, false);
var model = elementHandler.handleProjectionType((TypeElement) element, false, messager);
registerTypeElement(model.getFullName(), (TypeElement) element);
context.projectionTypes.put(model.getFullName(), model);
visited.add(element);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.querydsl.codegen.utils.model.TypeCategory;
import com.querydsl.core.annotations.PropertyType;
import com.querydsl.core.annotations.QueryInit;
import com.querydsl.core.annotations.QueryProjection;
import com.querydsl.core.annotations.QueryType;
import com.querydsl.core.util.Annotations;
import com.querydsl.core.util.BeanUtils;
Expand All @@ -34,12 +35,14 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;

/**
* {@code TypeElementHandler} is an APT visitor for entity types
Expand Down Expand Up @@ -67,7 +70,7 @@ public TypeElementHandler(
this.queryTypeFactory = queryTypeFactory;
}

public EntityType handleEntityType(TypeElement element) {
public EntityType handleEntityType(TypeElement element, Messager messager) {
EntityType entityType = typeFactory.getEntityType(element.asType(), true);
List<? extends Element> elements = element.getEnclosedElements();
var config = configuration.getConfig(element, elements);
Expand All @@ -78,7 +81,7 @@ public EntityType handleEntityType(TypeElement element) {

// constructors
if (config.visitConstructors()) {
handleConstructors(entityType, elements, true);
handleConstructors(entityType, elements, true, messager);
}

// fields
Expand Down Expand Up @@ -175,13 +178,14 @@ private Property toProperty(
return new Property(entityType, name, propertyType, inits);
}

public EntityType handleProjectionType(TypeElement e, boolean onlyAnnotatedConstructors) {
public EntityType handleProjectionType(
TypeElement e, boolean onlyAnnotatedConstructors, Messager messager) {
Type c = typeFactory.getType(e.asType(), true);
var entityType =
new EntityType(c.as(TypeCategory.ENTITY), configuration.getVariableNameFunction());
typeMappings.register(entityType, queryTypeFactory.create(entityType));
List<? extends Element> elements = e.getEnclosedElements();
handleConstructors(entityType, elements, onlyAnnotatedConstructors);
handleConstructors(entityType, elements, onlyAnnotatedConstructors, messager);
return entityType;
}

Expand All @@ -198,11 +202,40 @@ private Type getType(VariableElement element) {
}

private void handleConstructors(
EntityType entityType, List<? extends Element> elements, boolean onlyAnnotatedConstructors) {
EntityType entityType,
List<? extends Element> elements,
boolean onlyAnnotatedConstructors,
Messager messager) {
Comment thread
velo marked this conversation as resolved.
var builderNameSet = new HashSet<String>();
for (ExecutableElement constructor : ElementFilter.constructorsIn(elements)) {
if (configuration.isValidConstructor(constructor, onlyAnnotatedConstructors)) {
var parameters = transformParams(constructor.getParameters());
entityType.addConstructor(new Constructor(parameters));
QueryProjection projection = constructor.getAnnotation(QueryProjection.class);
if (projection != null
&& projection.useBuilder()
&& projection.builderName().trim().isEmpty()) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"@QueryProjection with builder=true requires a non-empty builderName",
constructor);
return;
}

Constructor constructorModel = new Constructor(parameters);
constructorModel.setUseBuilder(projection != null && projection.useBuilder());
constructorModel.setBuilderName(projection != null ? projection.builderName() : "");

if (constructorModel.useBuilder()
&& builderNameSet.contains(constructorModel.getBuilderName())) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"Duplicate builderName found: " + constructorModel.getBuilderName(),
constructor);
return;
}

builderNameSet.add(constructorModel.getBuilderName());
entityType.addConstructor(constructorModel);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public void execute() throws IOException {
expected.add("QQueryProjectionTest_EntityWithProjection.java");
expected.add("QEmbeddable3Test_EmbeddableClass.java");
expected.add("QQueryEmbedded4Test_User.java");
expected.add("QQueryProjectionBuilderTestEntity.java");

execute(expected, "GenericExporterTest", "QuerydslAnnotationProcessor");
}
Expand Down Expand Up @@ -89,6 +90,7 @@ public void execute2() throws IOException {
expected.add("QOneToOneTest_Person.java");
expected.add("QGeneric16Test_HidaBez.java");
expected.add("QGeneric16Test_HidaBezGruppe.java");
expected.add("QQueryProjectionBuilderTestEntity.java");

execute(expected, "GenericExporterTest2", "HibernateAnnotationProcessor");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.querydsl.apt.domain;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import com.querydsl.core.types.dsl.Expressions;
import org.junit.Test;

public class QueryProjectionBuilderTest {

@Test
public void builder_buildsQClassCorrectly() {
var property = Expressions.stringPath("x");
QQueryProjectionBuilderTestEntity dto =
QQueryProjectionBuilderTestEntity.builderTest1().setProperty(property).build();

assertNotNull(dto);
assertEquals(QueryProjectionBuilderTestEntity.class, dto.getType());
}

@Test
public void builder_buildsQClassCorrectly2() {
var property = Expressions.stringPath("x");
var intProperty = Expressions.numberPath(Integer.class, "y");
QQueryProjectionBuilderTestEntity dto =
QQueryProjectionBuilderTestEntity.builderTest2()
.setProperty(property)
.setIntProperty(intProperty)
.build();

assertNotNull(dto);
assertEquals(QueryProjectionBuilderTestEntity.class, dto.getType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.querydsl.apt.domain;

import com.querydsl.core.annotations.QueryProjection;

public class QueryProjectionBuilderTestEntity {
private String property;
private int intProperty;
private Test test;

@QueryProjection(useBuilder = true, builderName = "Test1")
public QueryProjectionBuilderTestEntity(String property) {
this.property = property;
}

@QueryProjection(useBuilder = true, builderName = "Test2")
public QueryProjectionBuilderTestEntity(String property, int intProperty) {
this.property = property;
this.intProperty = intProperty;
}

@QueryProjection(useBuilder = true, builderName = "Test3")
public QueryProjectionBuilderTestEntity(String property, int intProperty, Test test) {
this.property = property;
this.intProperty = intProperty;
this.test = test;
}

public static class Test {
private String property;
private int intProperty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public interface CodeWriter extends Appendable {

CodeWriter beginClass(Type type, Type superClass, Type... interfaces) throws IOException;

CodeWriter beginInnerStaticClass(Type type) throws IOException;

CodeWriter beginInnerStaticClass(Type type, Type superClass, Type... interfaces)
throws IOException;

<T> CodeWriter beginConstructor(Collection<T> params, Function<T, Parameter> transformer)
throws IOException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public final class JavaWriter extends AbstractCodeWriter<JavaWriter> {

private static final String PUBLIC_CLASS = "public class ";

private static final String PUBLIC_STATIC_CLASS = "public static class ";

private static final String PUBLIC_FINAL = "public final ";

private static final String PUBLIC_INTERFACE = "public interface ";
Expand Down Expand Up @@ -202,6 +204,33 @@ public JavaWriter beginClass(Type type, Type superClass, Type... interfaces) thr
return this;
}

@Override
public CodeWriter beginInnerStaticClass(Type type) throws IOException {
return beginInnerStaticClass(type, null);
}

@Override
public CodeWriter beginInnerStaticClass(Type type, Type superClass, Type... interfaces)
throws IOException {
beginLine(PUBLIC_STATIC_CLASS, type.getGenericName(false, packages, classes));
if (superClass != null) {
append(EXTENDS).append(superClass.getGenericName(false, packages, classes));
}
if (interfaces.length > 0) {
append(IMPLEMENTS);
for (int i = 0; i < interfaces.length; i++) {
if (i > 0) {
append(Symbols.COMMA);
}
append(interfaces[i].getGenericName(false, packages, classes));
}
}
append(" {").nl().nl();
goIn();
types.push(type);
return this;
}

@Override
public <T> JavaWriter beginConstructor(
Collection<T> parameters, Function<T, Parameter> transformer) throws IOException {
Expand Down
Loading