From 9dea51b7a095a907c8f72b7381b27fe9783e665b Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 16 May 2022 18:48:42 +1200 Subject: [PATCH] #2692 - [querybean generation] Compile error, incorrect generation with nested inner class/record @Embeddable and @IdClass --- .../test/java/org/example/domain/MyInner.java | 69 +++++++++++++++++++ .../test/java/org/querytest/MyInnerTest.java | 30 ++++++++ kotlin-querybean-generator/pom.xml | 15 ++-- .../generator/SimpleQueryBeanWriter.java | 49 +++---------- .../io/ebean/querybean/generator/Util.java | 46 +++++++++++++ .../io/ebean/querybean/generator/UtilTest.kt | 20 ++++++ .../kotlin/org/example/domain/MyKotInner.kt | 20 ++++++ .../org/example/otherpackage/InnerTest.kt | 23 +++++++ .../test/resources/META-INF/ebean-version.mf | 1 + .../resources/application-test.properties | 9 +++ .../generator/SimpleQueryBeanWriter.java | 34 +++------ .../io/ebean/querybean/generator/Util.java | 46 +++++++++++++ .../ebean/querybean/generator/UtilTest.java | 22 ++++++ 13 files changed, 316 insertions(+), 68 deletions(-) create mode 100644 ebean-querybean/src/test/java/org/example/domain/MyInner.java create mode 100644 ebean-querybean/src/test/java/org/querytest/MyInnerTest.java create mode 100644 kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/Util.java create mode 100644 kotlin-querybean-generator/src/test/kotlin/io/ebean/querybean/generator/UtilTest.kt create mode 100644 kotlin-querybean-generator/src/test/kotlin/org/example/domain/MyKotInner.kt create mode 100644 kotlin-querybean-generator/src/test/kotlin/org/example/otherpackage/InnerTest.kt create mode 100644 kotlin-querybean-generator/src/test/resources/META-INF/ebean-version.mf create mode 100644 kotlin-querybean-generator/src/test/resources/application-test.properties create mode 100644 querybean-generator/src/main/java/io/ebean/querybean/generator/Util.java create mode 100644 querybean-generator/src/test/java/io/ebean/querybean/generator/UtilTest.java diff --git a/ebean-querybean/src/test/java/org/example/domain/MyInner.java b/ebean-querybean/src/test/java/org/example/domain/MyInner.java new file mode 100644 index 000000000..e37dadaba --- /dev/null +++ b/ebean-querybean/src/test/java/org/example/domain/MyInner.java @@ -0,0 +1,69 @@ +package org.example.domain; + +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import java.util.Objects; + +@Entity +@IdClass(MyInner.ID.class) +public class MyInner { + + @Embeddable + public static class ID { + final long id; + final String one; + + public ID(long id, String one) { + this.id = id; + this.one = one; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ID id1 = (ID) o; + return id == id1.id && one.equals(id1.one); + } + + @Override + public int hashCode() { + return Objects.hash(id, one); + } + } + + @Id + long id; + @Id + String one; + String description; + + public long id() { + return id; + } + + public MyInner id(long id) { + this.id = id; + return this; + } + + public String one() { + return one; + } + + public MyInner one(String beanOne) { + this.one = beanOne; + return this; + } + + public String description() { + return description; + } + + public MyInner description(String description) { + this.description = description; + return this; + } +} diff --git a/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java b/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java new file mode 100644 index 000000000..c08d56d94 --- /dev/null +++ b/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java @@ -0,0 +1,30 @@ +package org.querytest; + +import io.ebean.DB; +import org.example.domain.MyInner; +import org.example.domain.query.QMyInner; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MyInnerTest { + + @Test + void insert_and_find() { + + MyInner myInner = new MyInner() + .id(42).one("one").description("foo"); + + DB.save(myInner); + + MyInner found = DB.find(MyInner.class, new MyInner.ID(42, "one")); + assertThat(found).isNotNull(); + + MyInner found2 = new QMyInner() + .id.eq(42) + .one.eq("one") + .findOne(); + + assertThat(found2).isNotNull(); + } +} diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 70d5ec330..c57f81b61 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -12,7 +12,7 @@ kotlin-querybean-generator - 1.6.0 + 1.6.20 @@ -61,6 +61,13 @@ test + + io.ebean + ebean-platform-h2 + 13.6.2-SNAPSHOT + test + + io.ebean ebean-ddl-generator @@ -106,7 +113,7 @@ io.ebean kotlin-querybean-generator - 12.13.0 + 13.6.2-SNAPSHOT @@ -155,8 +162,8 @@ - 1.8 - 1.8 + 11 + 11 -proc:none 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 ee21711d2..ef3ee6204 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 @@ -63,13 +63,9 @@ class SimpleQueryBeanWriter { ); private final Set importTypes = new TreeSet<>(); - private final List properties = new ArrayList<>(); - private final TypeElement element; - private final ProcessingContext processingContext; - private final boolean isEntity; private final boolean embeddable; private final String dbName; @@ -80,10 +76,9 @@ class SimpleQueryBeanWriter { private String destPackage; private String origDestPackage; - private String shortName; - private String origShortName; - + private final String shortInnerName; + private final String origShortName; private Append writer; SimpleQueryBeanWriter(TypeElement element, ProcessingContext processingContext) { @@ -92,8 +87,12 @@ class SimpleQueryBeanWriter { this.element = element; this.processingContext = processingContext; this.beanFullName = element.getQualifiedName().toString(); - this.destPackage = derivePackage(beanFullName) + ".query"; - this.shortName = deriveShortName(beanFullName); + boolean nested = element.getNestingKind().isNested(); + this.destPackage = Util.packageOf(nested, beanFullName) + ".query"; + String sn = Util.shortName(nested, beanFullName); + this.shortInnerName = Util.shortName(false, sn); + this.shortName = sn.replace(".", ""); + this.origShortName = shortName; this.isEntity = processingContext.isEntity(element); this.embeddable = processingContext.isEmbeddable(element); this.dbName = findDbName(); @@ -129,10 +128,7 @@ class SimpleQueryBeanWriter { *

*/ private void addClassProperties() { - - List fields = processingContext.allFields(element); - - for (VariableElement field : fields) { + for (VariableElement field : processingContext.allFields(element)) { PropertyType type = processingContext.getPropertyType(field); if (type != null) { type.addImports(importTypes); @@ -145,9 +141,7 @@ class SimpleQueryBeanWriter { * Write the type query bean (root bean). */ void writeRootBean() throws IOException { - gatherPropertyDetails(); - if (isEmbeddable()) { processingContext.addEntity(beanFullName, dbName); } else if (isEntity()) { @@ -193,15 +187,12 @@ class SimpleQueryBeanWriter { * Write the type query assoc bean. */ void writeAssocBean() throws IOException { - writingAssocBean = true; origDestPackage = destPackage; destPackage = destPackage + ".assoc"; - origShortName = shortName; shortName = "Assoc" + shortName; prepareAssocBeanImports(); - writer = new Append(createFileWriter()); writePackage(); @@ -279,7 +270,6 @@ class SimpleQueryBeanWriter { * Write all the fields. */ private void writeFields() { - for (PropertyMeta property : properties) { String typeDefn = property.getTypeDefn(shortName, writingAssocBean); lang().fieldDefn(writer, property.getName(), typeDefn); @@ -292,7 +282,6 @@ class SimpleQueryBeanWriter { * Write the class definition. */ private void writeClass() { - if (writingAssocBean) { writer.append("/**").eol(); writer.append(" * Association query bean for %s.", shortName).eol(); @@ -301,7 +290,7 @@ class SimpleQueryBeanWriter { writer.append(" */").eol(); writer.append(Constants.AT_GENERATED).eol(); writer.append(Constants.AT_TYPEQUERYBEAN).eol(); - lang().beginAssocClass(writer, shortName, origShortName); + lang().beginAssocClass(writer, shortName, shortInnerName); } else { writer.append("/**").eol(); @@ -331,7 +320,6 @@ class SimpleQueryBeanWriter { * Write all the imports. */ private void writeImports() { - for (String importType : importTypes) { writer.append("import %s;", importType).eol(); } @@ -343,9 +331,7 @@ class SimpleQueryBeanWriter { } private Writer createFileWriter() throws IOException { - String relPath = destPackage.replace('.', '/'); - File absDir = new File(generatedSourcesDir, relPath); if (!absDir.exists() && !absDir.mkdirs()) { processingContext.logNote("failed to create directories for:" + absDir.getAbsolutePath()); @@ -356,19 +342,4 @@ class SimpleQueryBeanWriter { return new FileWriter(absFile); } - private String derivePackage(String name) { - int pos = name.lastIndexOf('.'); - if (pos == -1) { - return ""; - } - return name.substring(0, pos); - } - - private String deriveShortName(String name) { - int pos = name.lastIndexOf('.'); - if (pos == -1) { - return name; - } - return name.substring(pos + 1); - } } diff --git a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/Util.java b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/Util.java new file mode 100644 index 000000000..022348788 --- /dev/null +++ b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/Util.java @@ -0,0 +1,46 @@ +package io.ebean.querybean.generator; + +class Util { + + static String packageOf(boolean nested, String originName) { + return nested ? nestedPackageOf(originName) : packageOf(originName); + } + + private static String nestedPackageOf(String cls) { + int pos = cls.lastIndexOf('.'); + if (pos < 0) { + return ""; + } + pos = cls.lastIndexOf('.', pos - 1); + return (pos == -1) ? "" : cls.substring(0, pos); + } + + private static String packageOf(String cls) { + int pos = cls.lastIndexOf('.'); + return (pos == -1) ? "" : cls.substring(0, pos); + } + + static String shortName(boolean nested, String fullType) { + return nested ? nestedShortName(fullType): shortName(fullType); + } + + private static String nestedShortName(String fullType) { + int pos = fullType.lastIndexOf('.'); + if (pos < 0) { + return fullType; + } else { + pos = fullType.lastIndexOf('.', pos - 1); + return pos < 0 ? fullType : fullType.substring(pos + 1); + } + } + + private static String shortName(String fullType) { + int p = fullType.lastIndexOf('.'); + if (p == -1) { + return fullType; + } else { + return fullType.substring(p + 1); + } + } + +} diff --git a/kotlin-querybean-generator/src/test/kotlin/io/ebean/querybean/generator/UtilTest.kt b/kotlin-querybean-generator/src/test/kotlin/io/ebean/querybean/generator/UtilTest.kt new file mode 100644 index 000000000..7965a398c --- /dev/null +++ b/kotlin-querybean-generator/src/test/kotlin/io/ebean/querybean/generator/UtilTest.kt @@ -0,0 +1,20 @@ +package io.ebean.querybean.generator + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +internal class UtilTest { + + @Test + fun packageOf() { + Assertions.assertEquals(Util.packageOf(true, "com.example.Foo.Bar"), "com.example") + Assertions.assertEquals(Util.packageOf(false, "com.example.other.foo.Bar"), "com.example.other.foo") + } + + @Test + fun shortName() { + Assertions.assertEquals(Util.shortName(true, "com.example.Foo.Bar"), "Foo.Bar") + Assertions.assertEquals(Util.shortName(false, "com.example.foo.Bar"), "Bar") + Assertions.assertEquals(Util.shortName(false, "Bar"), "Bar") + } +} diff --git a/kotlin-querybean-generator/src/test/kotlin/org/example/domain/MyKotInner.kt b/kotlin-querybean-generator/src/test/kotlin/org/example/domain/MyKotInner.kt new file mode 100644 index 000000000..b96fd987c --- /dev/null +++ b/kotlin-querybean-generator/src/test/kotlin/org/example/domain/MyKotInner.kt @@ -0,0 +1,20 @@ +package org.example.domain + +import javax.persistence.* + +@Entity +@Table(name = "t_inner") +@IdClass(MyKotInner.Mid::class) +class MyKotInner { + + @Id + var one: String = "0" + @Id + var two: String = "0" + + var description: String? = null + + @Embeddable + data class Mid(val one: String, val two: String) + +} diff --git a/kotlin-querybean-generator/src/test/kotlin/org/example/otherpackage/InnerTest.kt b/kotlin-querybean-generator/src/test/kotlin/org/example/otherpackage/InnerTest.kt new file mode 100644 index 000000000..caa3bb0a0 --- /dev/null +++ b/kotlin-querybean-generator/src/test/kotlin/org/example/otherpackage/InnerTest.kt @@ -0,0 +1,23 @@ +package org.example.otherpackage + +import io.ebean.DB +import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat +import org.example.domain.MyKotInner +import org.junit.jupiter.api.Test + +class InnerTest { + + @Test + fun split_normal() { + val my = MyKotInner() + my.one = "a" + my.two = "b" + my.description = "desc" + DB.save(my) + + val myId = MyKotInner.Mid("a", "b") + + val found = DB.find(MyKotInner::class.java, myId) + assertThat(found).isNotNull + } +} diff --git a/kotlin-querybean-generator/src/test/resources/META-INF/ebean-version.mf b/kotlin-querybean-generator/src/test/resources/META-INF/ebean-version.mf new file mode 100644 index 000000000..d95d9c73c --- /dev/null +++ b/kotlin-querybean-generator/src/test/resources/META-INF/ebean-version.mf @@ -0,0 +1 @@ +ebean-version: 141 diff --git a/kotlin-querybean-generator/src/test/resources/application-test.properties b/kotlin-querybean-generator/src/test/resources/application-test.properties new file mode 100644 index 000000000..180ec19fc --- /dev/null +++ b/kotlin-querybean-generator/src/test/resources/application-test.properties @@ -0,0 +1,9 @@ +ebean.ddl.generate=true +ebean.ddl.run=true +#datasource.default=h2 + +datasource.db.username=sa +datasource.db.password= +datasource.db.url=jdbc:h2:mem:testKotlin + + 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 3d55fd1ad..f8e965f04 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 @@ -18,13 +18,9 @@ import java.util.TreeSet; class SimpleQueryBeanWriter { private final Set importTypes = new TreeSet<>(); - private final List properties = new ArrayList<>(); - private final TypeElement element; - private final ProcessingContext processingContext; - private final String dbName; private final String beanFullName; private final boolean isEntity; @@ -33,17 +29,21 @@ class SimpleQueryBeanWriter { private String destPackage; private String origDestPackage; - private String shortName; - private String origShortName; + private final String shortInnerName; + private final String origShortName; private Append writer; SimpleQueryBeanWriter(TypeElement element, ProcessingContext processingContext) { this.element = element; this.processingContext = processingContext; this.beanFullName = element.getQualifiedName().toString(); - this.destPackage = derivePackage(beanFullName) + ".query"; - this.shortName = deriveShortName(beanFullName); + boolean nested = element.getNestingKind().isNested(); + this.destPackage = Util.packageOf(nested, beanFullName) + ".query"; + String sn = Util.shortName(nested, beanFullName); + this.shortInnerName = Util.shortName(false, sn); + this.shortName = sn.replace('.', '$'); + this.origShortName = shortName; this.isEntity = processingContext.isEntity(element); this.embeddable = processingContext.isEmbeddable(element); this.dbName = findDbName(); @@ -125,7 +125,6 @@ class SimpleQueryBeanWriter { writingAssocBean = true; origDestPackage = destPackage; destPackage = destPackage + ".assoc"; - origShortName = shortName; shortName = "Assoc" + shortName; prepareAssocBeanImports(); @@ -318,7 +317,7 @@ class SimpleQueryBeanWriter { writer.append(" */").eol(); writer.append(Constants.AT_GENERATED).eol(); writer.append(Constants.AT_TYPEQUERYBEAN).eol(); - writer.append("public class Q%s extends TQAssocBean<%s,R> {", shortName, origShortName).eol(); + writer.append("public class Q%s extends TQAssocBean<%s,R> {", shortName, shortInnerName).eol(); } else { writer.append("/**").eol(); @@ -388,19 +387,4 @@ class SimpleQueryBeanWriter { return jfo.openWriter(); } - private String derivePackage(String name) { - int pos = name.lastIndexOf('.'); - if (pos == -1) { - return ""; - } - return name.substring(0, pos); - } - - private String deriveShortName(String name) { - int pos = name.lastIndexOf('.'); - if (pos == -1) { - return name; - } - return name.substring(pos + 1); - } } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/Util.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/Util.java new file mode 100644 index 000000000..022348788 --- /dev/null +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/Util.java @@ -0,0 +1,46 @@ +package io.ebean.querybean.generator; + +class Util { + + static String packageOf(boolean nested, String originName) { + return nested ? nestedPackageOf(originName) : packageOf(originName); + } + + private static String nestedPackageOf(String cls) { + int pos = cls.lastIndexOf('.'); + if (pos < 0) { + return ""; + } + pos = cls.lastIndexOf('.', pos - 1); + return (pos == -1) ? "" : cls.substring(0, pos); + } + + private static String packageOf(String cls) { + int pos = cls.lastIndexOf('.'); + return (pos == -1) ? "" : cls.substring(0, pos); + } + + static String shortName(boolean nested, String fullType) { + return nested ? nestedShortName(fullType): shortName(fullType); + } + + private static String nestedShortName(String fullType) { + int pos = fullType.lastIndexOf('.'); + if (pos < 0) { + return fullType; + } else { + pos = fullType.lastIndexOf('.', pos - 1); + return pos < 0 ? fullType : fullType.substring(pos + 1); + } + } + + private static String shortName(String fullType) { + int p = fullType.lastIndexOf('.'); + if (p == -1) { + return fullType; + } else { + return fullType.substring(p + 1); + } + } + +} diff --git a/querybean-generator/src/test/java/io/ebean/querybean/generator/UtilTest.java b/querybean-generator/src/test/java/io/ebean/querybean/generator/UtilTest.java new file mode 100644 index 000000000..cccab2ed8 --- /dev/null +++ b/querybean-generator/src/test/java/io/ebean/querybean/generator/UtilTest.java @@ -0,0 +1,22 @@ +package io.ebean.querybean.generator; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class UtilTest { + + @Test + void packageOf() { + assertEquals(Util.packageOf(true, "com.example.Foo.Bar"), "com.example"); + assertEquals(Util.packageOf(false, "com.example.other.foo.Bar"), "com.example.other.foo"); + } + + @Test + void shortName() { + assertEquals(Util.shortName(true, "com.example.Foo.Bar"), "Foo.Bar"); + assertEquals(Util.shortName(false, "com.example.foo.Bar"), "Bar"); + assertEquals(Util.shortName(false, "Bar"), "Bar"); + } + +}