diff --git a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java index 784e2bdb7..4d03ad6db 100644 --- a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java +++ b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java @@ -31,4 +31,7 @@ interface Constants { String MODULEINFO = "io.ebean.config.ModuleInfo"; String METAINF_MANIFEST = "META-INF/ebean-generated-info.mf"; String METAINF_SERVICES_MODULELOADER = "META-INF/services/io.ebean.config.EntityClassRegister"; + + String AVAJE_LANG_NULLABLE = "io.avaje.lang.Nullable"; + String JAVA_COLLECTION = "java.util.Collection"; } diff --git a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java index 036885b59..f960ad194 100644 --- a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java +++ b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java @@ -334,7 +334,7 @@ class ProcessingContext implements Constants { } } - private Element asElement(TypeMirror argType) { + Element asElement(TypeMirror argType) { if (argType.getKind() == TypeKind.WILDCARD) { argType = ((WildcardType) argType).getExtendsBound(); } diff --git a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java index ef3ee6204..5ccb617d9 100644 --- a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java +++ b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java @@ -3,6 +3,7 @@ package io.ebean.querybean.generator; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -65,6 +66,9 @@ class SimpleQueryBeanWriter { private final Set importTypes = new TreeSet<>(); private final List properties = new ArrayList<>(); private final TypeElement element; + private final TypeElement implementsInterface; + private String implementsInterfaceFullName; + private String implementsInterfaceShortName; private final ProcessingContext processingContext; private final boolean isEntity; private final boolean embeddable; @@ -96,6 +100,18 @@ class SimpleQueryBeanWriter { this.isEntity = processingContext.isEntity(element); this.embeddable = processingContext.isEmbeddable(element); this.dbName = findDbName(); + this.implementsInterface = initInterface(element); + } + + private TypeElement initInterface(TypeElement element) { + for (TypeMirror anInterface : element.getInterfaces()) { + TypeElement e = (TypeElement)processingContext.asElement(anInterface); + String name = e.getQualifiedName().toString(); + if (!name.startsWith("java") && !name.startsWith("io.ebean")) { + return e; + } + } + return null; } private String findDbName() { @@ -115,6 +131,11 @@ class SimpleQueryBeanWriter { importTypes.add(Constants.FETCHGROUP); importTypes.add(Constants.QUERY); importTypes.add(Constants.TRANSACTION); + if (implementsInterface != null) { + implementsInterfaceFullName = implementsInterface.getQualifiedName().toString(); + boolean nested = implementsInterface.getNestingKind().isNested(); + implementsInterfaceShortName = Util.shortName(nested, implementsInterfaceFullName); + } if (dbName != null) { importTypes.add(Constants.DB); } @@ -219,6 +240,11 @@ class SimpleQueryBeanWriter { if (isEntity()) { importTypes.add(Constants.TQPROPERTY); importTypes.add(origDestPackage + ".Q" + origShortName); + //if (implementsInterface != null) { + // importTypes.add(Constants.AVAJE_LANG_NULLABLE); + // importTypes.add(Constants.JAVA_COLLECTION); + // importTypes.add(implementsInterfaceFullName); + //} } // remove imports for the same package @@ -255,9 +281,33 @@ class SimpleQueryBeanWriter { private void writeAssocBeanFetch() { if (isEntity()) { lang().fetch(writer, origShortName); + //if (implementsInterface != null) { + // writeAssocBeanExpression(false, "eq", "Is equal to by ID property."); + // writeAssocBeanExpression(true, "eqIfPresent", "Is equal to by ID property if the value is not null, if null no expression is added."); + // writeAssocBeanExpression(false, "in", "IN the given values.", implementsInterfaceShortName + "...", "in"); + // writeAssocBeanExpression(false, "inBy", "IN the given interface values.", "Collection<" + implementsInterfaceShortName + ">", "in"); + // writeAssocBeanExpression(true, "inOrEmptyBy", "IN the given interface values if the collection is not empty. No expression is added if the collection is empty..", "Collection<" + implementsInterfaceShortName + ">", "inOrEmpty"); + //} } } + private void writeAssocBeanExpression(boolean nullable,String expression, String comment) { + writeAssocBeanExpression(nullable, expression, comment, implementsInterfaceShortName, expression); + } + + private void writeAssocBeanExpression(boolean nullable, String expression, String comment, String param, String actualExpression) { + final String nullableAnnotation = nullable ? "@Nullable " : ""; + String values = expression.startsWith("in") ? "values" : "value"; + writer.append(" /**").eol(); + writer.append(" * ").append(comment).eol(); + writer.append(" */").eol(); + writer.append(" fun %s(%s%s %s): R {", expression, nullableAnnotation, param, values).eol(); + writer.append(" expr().%s(_name, %s);", actualExpression, values).eol(); + writer.append(" return _root;").eol(); + writer.append(" }").eol(); + writer.eol(); + } + /** * Write constructor for 'assoc' type query bean. */ diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java index 187d7ca02..8263cd160 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java @@ -33,4 +33,6 @@ interface Constants { String METAINF_MANIFEST = "META-INF/ebean-generated-info.mf"; String METAINF_SERVICES_MODULELOADER = "META-INF/services/io.ebean.config.EntityClassRegister"; + String AVAJE_LANG_NULLABLE = "io.avaje.lang.Nullable"; + String JAVA_COLLECTION = "java.util.Collection"; } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java index 7002ac954..2fbca0fb1 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java @@ -541,4 +541,8 @@ class ProcessingContext implements Constants { } return null; } + + Element asElement(TypeMirror mirror) { + return typeUtils.asElement(mirror); + } } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java index 75400b935..537d216b3 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java @@ -3,6 +3,7 @@ package io.ebean.querybean.generator; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.Writer; @@ -20,6 +21,9 @@ class SimpleQueryBeanWriter { private final Set importTypes = new TreeSet<>(); private final List properties = new ArrayList<>(); private final TypeElement element; + private final TypeElement implementsInterface; + private String implementsInterfaceFullName; + private String implementsInterfaceShortName; private final ProcessingContext processingContext; private final String dbName; private final String beanFullName; @@ -47,6 +51,18 @@ class SimpleQueryBeanWriter { this.isEntity = processingContext.isEntity(element); this.embeddable = processingContext.isEmbeddable(element); this.dbName = findDbName(); + this.implementsInterface = initInterface(element); + } + + private TypeElement initInterface(TypeElement element) { + for (TypeMirror anInterface : element.getInterfaces()) { + TypeElement e = (TypeElement)processingContext.asElement(anInterface); + String name = e.getQualifiedName().toString(); + if (!name.startsWith("java") && !name.startsWith("io.ebean")) { + return e; + } + } + return null; } private String findDbName() { @@ -70,7 +86,11 @@ class SimpleQueryBeanWriter { importTypes.add(Constants.FETCHGROUP); importTypes.add(Constants.QUERY); importTypes.add(Constants.TRANSACTION); - + if (implementsInterface != null) { + implementsInterfaceFullName = implementsInterface.getQualifiedName().toString(); + boolean nested = implementsInterface.getNestingKind().isNested(); + implementsInterfaceShortName = Util.shortName(nested, implementsInterfaceFullName); + } if (dbName != null) { importTypes.add(Constants.DB); } @@ -154,6 +174,11 @@ class SimpleQueryBeanWriter { if (isEntity()) { importTypes.add(Constants.TQPROPERTY); importTypes.add(origDestPackage + ".Q" + origShortName); + if (implementsInterface != null) { + importTypes.add(Constants.AVAJE_LANG_NULLABLE); + importTypes.add(Constants.JAVA_COLLECTION); + importTypes.add(implementsInterfaceFullName); + } } // remove imports for the same package @@ -267,9 +292,34 @@ class SimpleQueryBeanWriter { writeAssocBeanFetch("Query", "Eagerly fetch this association using a 'query join' loading the specified properties."); writeAssocBeanFetch("Cache", "Eagerly fetch this association using L2 cache."); writeAssocBeanFetch("Lazy", "Use lazy loading for this association loading the specified properties."); + if (implementsInterface != null) { + writeAssocBeanExpression(false, "eq", "Is equal to by ID property."); + writeAssocBeanExpression(true, "eqIfPresent", "Is equal to by ID property if the value is not null, if null no expression is added."); + writeAssocBeanExpression(false, "in", "IN the given values.", implementsInterfaceShortName + "...", "in"); + writeAssocBeanExpression(false, "inBy", "IN the given interface values.", "Collection", "in"); + writeAssocBeanExpression(true, "inOrEmptyBy", "IN the given interface values if the collection is not empty. No expression is added if the collection is empty..", "Collection", "inOrEmpty"); + } } } + private void writeAssocBeanExpression(boolean nullable,String expression, String comment) { + writeAssocBeanExpression(nullable, expression, comment, implementsInterfaceShortName, expression); + } + + private void writeAssocBeanExpression(boolean nullable, String expression, String comment, String param, String actualExpression) { + final String nullableAnnotation = nullable ? "@Nullable " : ""; + String values = expression.startsWith("in") ? "values" : "value"; + String castVarargs = expression.equals("in") ? "(Object[])" : ""; + writer.append(" /**").eol(); + writer.append(" * ").append(comment).eol(); + writer.append(" */").eol(); + writer.append(" public final R %s(%s%s %s) {", expression, nullableAnnotation, param, values).eol(); + writer.append(" expr().%s(_name, %s%s);", actualExpression, castVarargs, values).eol(); + writer.append(" return _root;").eol(); + writer.append(" }").eol(); + writer.eol(); + } + private void writeAssocBeanFetch(String fetchType, String comment) { writer.append(" /**").eol(); writer.append(" * ").append(comment).eol(); diff --git a/tests/test-kotlin/pom.xml b/tests/test-kotlin/pom.xml index 53845b2da..60d0fe7be 100644 --- a/tests/test-kotlin/pom.xml +++ b/tests/test-kotlin/pom.xml @@ -21,6 +21,12 @@ + + io.ebean + ebean + 13.13.2 + + io.avaje junit @@ -81,6 +87,24 @@ test-compile + + + + + + + + + + + + + + + + + + diff --git a/tests/test-kotlin/src/test/kotlin/org/example/api/ICustomer.kt b/tests/test-kotlin/src/test/kotlin/org/example/api/ICustomer.kt new file mode 100644 index 000000000..dc75b3143 --- /dev/null +++ b/tests/test-kotlin/src/test/kotlin/org/example/api/ICustomer.kt @@ -0,0 +1,5 @@ +package org.example.api + +interface ICustomer { + +} diff --git a/tests/test-kotlin/src/test/kotlin/org/example/order/Customer.kt b/tests/test-kotlin/src/test/kotlin/org/example/order/Customer.kt index 72c2795af..514739a39 100644 --- a/tests/test-kotlin/src/test/kotlin/org/example/order/Customer.kt +++ b/tests/test-kotlin/src/test/kotlin/org/example/order/Customer.kt @@ -3,11 +3,12 @@ package org.example.order import javax.persistence.Entity import javax.persistence.Id import javax.persistence.Version +import org.example.api.ICustomer @Entity class Customer( name: String, -) { +) : ICustomer { @Id var id: Int = 0 val name: String = name