From 1913e919e4262cd29a5bc06ff9472b12e63cf75e Mon Sep 17 00:00:00 2001 From: Fabien CAYRE Date: Thu, 23 Jan 2025 22:23:36 +0100 Subject: [PATCH] feat: Multiple parent classes support --- .gitignore | 3 + cla/Fabccc | 138 ++++++++++++++++++ integration-tests/pom.xml | 12 +- .../test/inheritance/InheritanceTest.java | 17 ++- .../test/inheritance/model/Publication.java | 13 +- .../test/inheritance/model/Versionable.java | 22 +++ .../test/inheritance/scripts/data.sql | 8 +- .../InternalFieldGeneratorProcessor.java | 82 +++++++---- .../fieldgenerator/test/Publication.java | 13 +- .../fieldgenerator/test/Versionable.java | 20 +++ 10 files changed, 264 insertions(+), 64 deletions(-) create mode 100644 cla/Fabccc create mode 100644 integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/model/Versionable.java create mode 100644 jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.test/src/main/java/com/speedment/jpastreamer/fieldgenerator/test/Versionable.java diff --git a/.gitignore b/.gitignore index ae7047f6..90e12649 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ /docs/package-lock.json .project /javadoc + + +.vscode/ \ No newline at end of file diff --git a/cla/Fabccc b/cla/Fabccc new file mode 100644 index 00000000..e31b0fab --- /dev/null +++ b/cla/Fabccc @@ -0,0 +1,138 @@ +Hash: SHA256 + +*** Individual Contributor License Agreement *** + +Speedment, Inc. + + +In order to clarify the intellectual property license granted with Contributions from any +person or entity to Speedment Projects (each, a “Contributor”), Speedment, Inc. (“Speedment”) +must have a Contributor License Agreement ("CLA" or “Agreement”)) on file that has been +signed by each Contributor, indicating agreement to the license terms below. This license is for +Your protection as a Contributor as well as the protection of Speedment and its users; it does not +change Your rights to use Your own Contributions for any other purpose. If You have not +already done so, copy this file to the directory `/cla`, fill in the data below and rename the file +to your GitHub handle name and include the file in your first pull request. + +Alternately, please complete and send an original, signed Agreement to: + +Speedment, Inc. +470 Ramona Street +Palo Alto, CA 94301 + +OR + +sign, scan and send the Agreement by e-mail to: + +info@speedment.com + + + +Please read this document carefully before signing, and keep a copy for Your records. + + +GitHub Id: Fabccc + +Full name: Fabien CAYRE + +Mailing Address: fabiencayre81@gmail.com + +Country: France + +Telephone: ________________________________ + +E-Mail: fabiencayre81@gmail.com + + +In consideration of the opportunity to participate in the community of Speedment +Contributors, You accept and agree to the following terms and conditions for Your present and +future Contributions submitted to Speedment. Except for the license granted herein to Speedment +and recipients of software distributed by Speedment, You reserve all right, title, and interest +in and to Your Contributions. + +1. Definitions. “You" (or "Your") shall mean the copyright owner or legal entity authorized +by the copyright owner that is making this Agreement with Speedment. For legal entities, +the entity making a Contribution and all other entities that control, are controlled by, or +are under common control with that entity are considered to be a single Contributor. For +the purposes of this definition, "control" means (i) the power, direct or indirect, to cause +the direction or management of such entity, whether by contract or otherwise, or (ii) +ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial +ownership of such entity. "Contribution" shall mean any original work of authorship, +including any modifications or additions to an existing work, that is intentionally +submitted by You to Speedment for inclusion in, or documentation of, any of the projects +owned or managed by Speedment (the "Project"). For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent to +Speedment or its representatives, including but not limited to communication on electronic +mailing lists, source code control systems, and issue tracking systems that are managed +by, or on behalf of, Speedment for the purpose of discussing and improving the Project, +but excluding communication that is conspicuously marked or otherwise designated in +writing by You as "Not a Contribution." + +1. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You +hereby grant to Speedment a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable, sub-licensable, through multiple tiers, copyright license to reproduce, +prepare derivative works of, publicly display, publicly perform, sublicense, and distribute +Your Contributions and such derivative works. + +1. Grant of Patent License. Subject to the terms and conditions of this Agreement, You +hereby grant to Speedment and to recipients of software distributed by Speedment a +perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as +stated in this section), sub-licensable, through multiple tiers, patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Project, where such +license applies only to those patent claims licensable by You that are necessarily +infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with +the Project to which such Contribution(s) was submitted. If any entity institutes patent +litigation against You or any other entity (including a cross-claim or counterclaim in a +lawsuit) alleging that Your Contribution, or the Project to which You have contributed, +constitutes direct or contributory patent infringement, then any patent licenses granted to +that entity under this Agreement for that Contribution or Project shall terminate as of the +date such litigation is filed. + +1. You represent that You are legally entitled to grant the above license. If Your +employer(s) has rights to intellectual property that You create that includes Your +Contributions, You represent that You have received permission to make Contributions +on behalf of that employer, that Your employer has waived such rights for Your +Contributions to Speedment, or that Your employer has executed a separate Corporate +CLA with Speedment. + +1. You represent that each of Your Contributions is Your original creation (see section 7 for +submissions on behalf of others). You represent that Your Contribution submissions +include complete details of any third-party license or other restriction (including, but not +limited to, related patents and trademarks) of which You are personally aware and which +are associated with any part of Your Contributions. + +1. You are not expected to provide support for Your Contributions, except to the extent You +desire to provide support. You may provide support for free, for a fee, or not at all. +UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, YOU +PROVIDE YOUR CONTRIBUTIONS ON AN "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR +CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR +FITNESS FOR A PARTICULAR PURPOSE. + +1. Should You wish to submit work that is not Your original creation, You may submit it to +Speedment separately from any Contribution, identifying the complete details of its source +and of any license or other restriction (including, but not limited to, related patents, +trademarks, and license agreements) of which You are personally aware, and +conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". + +1. You agree to notify Speedment of any facts or circumstances of which You become aware +that would make these representations inaccurate in any respect. + +1. This Agreement will be governed by and shall be construed in accordance with the laws +of the United States and the State of California. ANY DISPUTE REGARDING THE +INTERPRETATION, THE CONCLUSION, THE PERFORMANCE OR THE +TERMINATION OF THIS AGREEMENT WHICH IS NOT RESOLVED AMICABLY +BY THE PARTIES SHALL BE SUBJECT TO THE EXCLUSIVE JURISDICTION OF +THE FEDERAL AND STATE COURTS LOCATED IN SANTA CLARA COUNTY, +CALIFORNIA. The parties hereby irrevocably waive any and all claims and defenses +either might otherwise have in any such action or proceeding in any of such courts based +upon any alleged lack of personal jurisdiction, improper venue, forum non conveniens or +any similar claim or defense. This Agreement sets forth the entire understanding and +agreement between the parties, and supersedes any previous communications, +representations or agreements, whether oral or written, regarding the subject matter +herein. + + + +Please sign: Fabien CAYRE___________________ Date: 23 / 01 / 2025__________________ \ No newline at end of file diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 5701ad82..8be4f6ae 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -12,10 +12,10 @@ See: https://github.com/speedment/jpa-streamer/blob/master/LICENSE --> - + - jpastreamer-parent com.speedment.jpastreamer 3.0.5-SNAPSHOT @@ -26,7 +26,7 @@ Maven http://maven.apache.org/ 2001 - + UTF-8 @@ -74,7 +74,7 @@ - + @@ -85,4 +85,4 @@ - + \ No newline at end of file diff --git a/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/InheritanceTest.java b/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/InheritanceTest.java index 5071285f..80456c12 100644 --- a/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/InheritanceTest.java +++ b/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/InheritanceTest.java @@ -93,5 +93,20 @@ void inheritanceTest3() { assertEquals(expected, actual); } - + @Test + void inheritanceTest4(){ + final List collect = jpaStreamer.stream(BlogPost.class) + .collect(Collectors.toList()); + + final long expected = collect.stream() + .filter(b -> b.getVersion() == 3) + .count(); + + final long actual = jpaStreamer.stream(BlogPost.class) + .filter(BlogPost$.version.equal(3)) + .count(); + + assertEquals(expected, actual); + } + } diff --git a/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/model/Publication.java b/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/model/Publication.java index 1bbd627b..e4a7081c 100644 --- a/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/model/Publication.java +++ b/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/model/Publication.java @@ -17,7 +17,7 @@ import java.time.LocalDateTime; @MappedSuperclass -public abstract class Publication { +public abstract class Publication extends Versionable{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -30,10 +30,6 @@ public abstract class Publication { @Column(name = "title", nullable = false, updatable = false, columnDefinition = "varchar(255)") private String title; - @Version - @Column(name = "version", nullable = false, updatable = false, columnDefinition = "int(6)") - private Integer version; - public Integer getId() { return id; } @@ -58,11 +54,4 @@ public void setTitle(String title) { this.title = title; } - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } } diff --git a/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/model/Versionable.java b/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/model/Versionable.java new file mode 100644 index 00000000..cd9218dc --- /dev/null +++ b/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/model/Versionable.java @@ -0,0 +1,22 @@ +package com.speedment.jpastreamer.integration.test.inheritance.model; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.Version; + +@MappedSuperclass +public abstract class Versionable { + + @Version + @Column(name = "version", nullable = false, updatable = false, columnDefinition = "int(6)") + private Integer version; + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + +} diff --git a/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/scripts/data.sql b/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/scripts/data.sql index ef169501..58bd0a1f 100644 --- a/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/scripts/data.sql +++ b/integration-tests/src/test/java/com/speedment/jpastreamer/integration/test/inheritance/scripts/data.sql @@ -23,7 +23,8 @@ VALUES (1, '2008-7-04', 'Book 1', 2, 213), (9, '2016-7-04', 'Book 9', 2, 523), (10, '2017-7-04', 'Book 10', 2, 432), (11, '2018-7-04', 'Book 11', 2, 322), - (12, '2019-7-04', 'Book 12', 2, 121); + (12, '2019-7-04', 'Book 12', 2, 121), + (13, '2020-7-04', 'Book 13', 3, 148); INSERT INTO blogposts (id, publishing_date, title, version, url) VALUES (1, '2008-7-04', 'Blog Post 1', 2, 'http://speedment.com'), @@ -38,11 +39,14 @@ VALUES (1, '2008-7-04', 'Blog Post 1', 2, 'http://speedment.com'), (10, '2017-7-04', 'Blog Post 10', 2, 'http://speedment.com'), (11, '2018-7-04', 'Blog Post 11', 2, 'http://speedment.com'), (12, '2019-7-04', 'Blog Post 12', 2, 'http://speedment.com'), + (13, '2020-7-04', 'Blog Post 13', 3, 'http://speedment.com'); + INSERT INTO author (id, firstname, lastname, version) (1, 'Author 1', 'Lastname', 2), (2, 'Author 2', 'Blog Post 2', 2), (3, 'Author 3', 'Blog Post 3', 2), (4, 'Author 5', 'Blog Post 4', 2), - (5, 'Author 6', 'Blog Post 5', 2); + (5, 'Author 6', 'Blog Post 5', 2), + (6, 'Author 7', 'Blog Post 6', 3); diff --git a/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.standard/src/main/java/com/speedment/jpastreamer/fieldgenerator/internal/InternalFieldGeneratorProcessor.java b/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.standard/src/main/java/com/speedment/jpastreamer/fieldgenerator/internal/InternalFieldGeneratorProcessor.java index d1aeaaab..3bcfd94e 100644 --- a/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.standard/src/main/java/com/speedment/jpastreamer/fieldgenerator/internal/InternalFieldGeneratorProcessor.java +++ b/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.standard/src/main/java/com/speedment/jpastreamer/fieldgenerator/internal/InternalFieldGeneratorProcessor.java @@ -53,6 +53,7 @@ import java.util.stream.Stream; import static com.speedment.common.codegen.util.Formatting.*; +import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; @@ -135,26 +136,10 @@ public boolean process(Set annotations, RoundEnvironment Writer writer = builderFile.openWriter(); TypeMirror type = ae.asType(); - List typeMirrors = processingEnvironment.getTypeUtils().directSupertypes(type); - - Optional superClass = typeMirrors.stream() - .filter(DeclaredType.class::isInstance) - .map(DeclaredType.class::cast) - .map(DeclaredType::asElement) - .filter(e -> e.getAnnotation(MappedSuperclass.class) != null || e.getAnnotation(Entity.class ) != null) - .map(e -> e.getSimpleName().toString()) - .findFirst(); - - Optional superClassElement = Optional.empty(); - if (superClass.isPresent()) { - // Entity should inherit fields from the superclass, retrieve element - superClassElement = Stream.concat(entities.stream(), superClasses.stream()) - .filter(sc -> sc.getKind() == ElementKind.CLASS) - .filter(sc -> sc.getSimpleName().toString().equals(superClass.get())) - .findFirst(); - } - generateFields(ae, entityName, genEntityName, packageName, superClassElement, writer); + Set superClassHierarchy = walkSuperClass(entities, superClasses, type); + + generateFields(ae, entityName, genEntityName, packageName, superClassHierarchy, writer); writer.close(); } catch (IOException e) { e.printStackTrace(); @@ -168,10 +153,15 @@ void generateFields(final Element annotatedElement, final String entityName, final String genEntityName, final String packageName, - final Optional superClass, + final Set superClassHierarchy, final Writer writer) throws IOException { + + final Collection enclosedElementsHierarchy = Stream.concat( + annotatedElement.getEnclosedElements().stream(), + superClassHierarchy.stream().flatMap(e -> e.getEnclosedElements().stream())) + .collect(toList()); - final Map getters = annotatedElement.getEnclosedElements().stream() + final Map getters = enclosedElementsHierarchy.stream() .filter(ee -> ee.getKind() == ElementKind.METHOD) // Only consider methods with no parameters .filter(ee -> ee.getEnclosedElements().stream().noneMatch(eee -> eee.getKind() == ElementKind.PARAMETER)) @@ -187,24 +177,24 @@ void generateFields(final Element annotatedElement, .map(Formatting::lcfirst) .collect(toSet()); - Stream fields = annotatedElement.getEnclosedElements().stream(); - - if (superClass.isPresent()) { - // Add parent fields - fields = Stream.concat(fields, superClass.get().getEnclosedElements().stream()); - } - - // Retrieve all declared non-final instance fields of the annotated class - Map enclosedFields = fields + Collection fields = enclosedElementsHierarchy.stream() .filter(ee -> ee.getKind().isField() && !ee.getModifiers().contains(Modifier.STATIC) // Ignore static fields && !ee.getModifiers().contains(Modifier.FINAL)) // Ignore final fields + .collect(toMap( + e -> e.getSimpleName().toString(), + Function.identity(), + (k1, k2) -> k1 + )).values(); + + // Retrieve all declared non-final instance fields of the annotated class + Map enclosedFields = fields.stream() .collect( toMap( Function.identity(), ee -> findGetter(ee, getters, isGetters, shortName(entityName), lombokGetterAvailable(annotatedElement, ee))) ); - + final File file = generatedEntity(annotatedElement, enclosedFields, entityName, genEntityName, packageName); writer.write(generator.on(file).orElseThrow(NoSuchElementException::new)); } @@ -486,4 +476,34 @@ private boolean isAccessLevel(String s) { return validAccessLevels.contains(s); } + private Set walkSuperClass( + Set entities, + Set superClasses, + TypeMirror clazz) { + Set result = new HashSet<>(); + List typeMirrors = processingEnvironment.getTypeUtils().directSupertypes(clazz); + + typeMirrors.stream() + .filter(DeclaredType.class::isInstance) + .map(DeclaredType.class::cast) + .map(DeclaredType::asElement) + .filter(e -> e.getAnnotation(MappedSuperclass.class) != null || e.getAnnotation(Entity.class) != null) + .map(e -> e.getSimpleName().toString()) + .findFirst() + .ifPresent(superClassName -> findAndAddSuperClass(entities, superClasses, result, superClassName)); + + return result; + } + + private void findAndAddSuperClass(Set entities, Set superClasses, Set result, String superClassName) { + Stream.concat(entities.stream(), superClasses.stream()) + .filter(sc -> sc.getKind() == ElementKind.CLASS) + .filter(sc -> sc.getSimpleName().toString().equals(superClassName)) + .findFirst() + .ifPresent(superClass -> { + result.add(superClass); + result.addAll(walkSuperClass(entities, superClasses, superClass.asType())); + }); + } + } diff --git a/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.test/src/main/java/com/speedment/jpastreamer/fieldgenerator/test/Publication.java b/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.test/src/main/java/com/speedment/jpastreamer/fieldgenerator/test/Publication.java index 4747e5c3..d43089ad 100644 --- a/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.test/src/main/java/com/speedment/jpastreamer/fieldgenerator/test/Publication.java +++ b/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.test/src/main/java/com/speedment/jpastreamer/fieldgenerator/test/Publication.java @@ -17,7 +17,7 @@ import java.time.LocalDateTime; @MappedSuperclass -public abstract class Publication { +public abstract class Publication extends Versionable{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -29,10 +29,6 @@ public abstract class Publication { @Column(name = "title", nullable = false, updatable = false, columnDefinition = "varchar(255)") private String title; - - @Version - @Column(name = "version", nullable = false, updatable = false, columnDefinition = "int(6)") - private Integer version; public Integer getId() { return id; @@ -58,11 +54,4 @@ public void setTitle(String title) { this.title = title; } - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } } diff --git a/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.test/src/main/java/com/speedment/jpastreamer/fieldgenerator/test/Versionable.java b/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.test/src/main/java/com/speedment/jpastreamer/fieldgenerator/test/Versionable.java new file mode 100644 index 00000000..47caafe4 --- /dev/null +++ b/jpastreamer.fieldgenerator/jpastreamer.fieldgenerator.test/src/main/java/com/speedment/jpastreamer/fieldgenerator/test/Versionable.java @@ -0,0 +1,20 @@ +package com.speedment.jpastreamer.fieldgenerator.test; + +import jakarta.persistence.*; + +@MappedSuperclass +public abstract class Versionable { + + @Version + @Column(name = "version", nullable = false, updatable = false, columnDefinition = "int(6)") + private Integer version; + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + +}