Compare commits

...

222 Commits

Author SHA1 Message Date
Jordan Zimmerman
6781824f08 [maven-release-plugin] prepare for next development iteration 2023-07-03 09:57:59 +01:00
Jordan Zimmerman
2c34ffd4ca [maven-release-plugin] prepare release record-builder-37 2023-07-03 09:57:53 +01:00
Paweł Łabaj
c6cf23956f Fixes Randgalt/record-builder/#153 (#154) 2023-07-03 09:46:23 +01:00
Jordan Zimmerman
1b22341b58 [maven-release-plugin] prepare for next development iteration 2023-03-29 17:50:15 +01:00
Jordan Zimmerman
409fb883e4 [maven-release-plugin] prepare release record-builder-36 2023-03-29 17:50:09 +01:00
Jordan Zimmerman
141362845e With interface shouldn't use prefix option (#132) 2023-03-29 10:03:17 +01:00
Stefan Bischof
183ab67c1a handle RecordBuilder.Options on packages (#149)
Thanks for the PR
2023-03-29 08:53:06 +01:00
Jordan Zimmerman
685a31b56b Various clean ups (#150) 2023-03-29 08:07:59 +01:00
Varun Upadhyay
f3303c2386 Fix javadoc builder core (#144)
Thank you for the PR
2023-03-28 08:36:40 +01:00
tison
e12d359696 Upgrade license-maven-plugin version and reformat (#145) 2023-03-12 07:56:15 +00:00
Dmitrii Priporov
4924f7b3ea issue-122: updated add1GetterMethod logic in useImmutableCollections=true case (#138) 2023-02-07 07:45:32 +00:00
Jordan Zimmerman
3e1d7d69d0 Update README.md 2023-01-09 09:17:39 +00:00
Jordan Zimmerman
22f827d81a [maven-release-plugin] prepare for next development iteration 2023-01-09 09:12:02 +00:00
Jordan Zimmerman
91c6090ace [maven-release-plugin] prepare release record-builder-35 2023-01-09 09:11:57 +00:00
David Morris
098a5c8bfd Configurable modifiers on static builders (#135) 2023-01-07 13:28:22 +00:00
David Morris
e2f17d4087 Adding of maven wrapper (#136) 2023-01-07 13:25:29 +00:00
David Morris
2b3e895cf6 initial .gitignore (#137) 2023-01-06 08:10:49 +00:00
Sebastian Hoß
117c789593 use configured build method name instead of hard-coded name (#133)
Co-authored-by: Sebastian Hoß <seb@hoß.de>
2022-12-15 20:45:43 +00:00
Jordan Zimmerman
4ff80fb20c [maven-release-plugin] prepare for next development iteration 2022-08-02 05:05:19 +02:00
Jordan Zimmerman
abfc12bdb0 [maven-release-plugin] prepare release record-builder-34 2022-08-02 05:05:14 +02:00
Jordan Zimmerman
6c0fac0dff [maven-release-plugin] rollback the release of record-builder-34 2022-08-02 05:04:43 +02:00
Jordan Zimmerman
ae527cd8e5 [maven-release-plugin] prepare release record-builder-34 2022-08-02 05:04:20 +02:00
Jordan Zimmerman
73ba62057a [maven-release-plugin] rollback the release of record-builder-34 2022-08-02 05:03:00 +02:00
Jordan Zimmerman
87998aba68 [maven-release-plugin] prepare for next development iteration 2022-08-02 05:02:28 +02:00
Jordan Zimmerman
04a0904d3f [maven-release-plugin] prepare release record-builder-34 2022-08-02 05:02:23 +02:00
Johannes
aa072af8e1 Performance feature: copy collections only when they were changed. Fixes #114. (#118) 2022-06-19 21:17:09 +01:00
Jordan Zimmerman
b435b5d3fd Revert "TYPE_USE annotations were being ignored (#115)"
This reverts commit d3c1bb36f3.

A bug was found - the PR needs more work
2022-06-13 08:37:24 +01:00
Jordan Zimmerman
d3c1bb36f3 TYPE_USE annotations were being ignored (#115)
Java's DAG for annotations processors doesn't contain `TYPE_USE` annotations
on the Element for some reason. However, they are on the type. So, use the
type instead.

Note due to limitations of JavaPoet this doesn't fix `TYPE_USE` annotations on
parameterized types or array components. If we want to address those we will need
changes in JavaPoet which has been dormant for a very long time.

Fixes #113
Relates to #111
2022-06-12 08:56:32 +01:00
Lovro Pandžić
c3719326c9 Use Optional.ofNullable() to check for null values
Fixes #107
2022-05-10 16:06:12 +01:00
Jordan Zimmerman
661d0818c0 _FromWith class was missing @Generated on methods/ctor 2022-04-08 09:15:59 +01:00
Jordan Zimmerman
79bc8396f2 Update README.md 2022-04-08 09:03:30 +01:00
Jordan Zimmerman
b2149622e4 Update README.md 2022-04-08 09:01:07 +01:00
Jordan Zimmerman
0718e37f76 [maven-release-plugin] prepare for next development iteration 2022-04-08 08:35:12 +01:00
Jordan Zimmerman
d112a1b352 [maven-release-plugin] prepare release record-builder-33 2022-04-08 08:35:07 +01:00
Jordan Zimmerman
86093b6bad Make the functional static builder optional (#105)
Closes #100
2022-04-08 08:33:46 +01:00
Jordan Zimmerman
7b6ad4d7ba Generated equals() and the default with() method were missing generic angle brackets causing compiler warnings (#102)
Closes #101
2022-04-08 08:29:55 +01:00
Jordan Zimmerman
cd059f1207 Options/changes so that Jacoco checks don't fail (#104)
- Added new optional Annotation `@RecordBuilderGenerated` - Jacoco ignores
classes with any annotation names "*Generated*" but it needs to be class retained.
For backward compatibility this annotation is not added by default (though it's been
added to `@RecordBuilderFull`). There is a new option to enable it.
- The from with method now uses an internal static class instead of an anonymous
inner class so that the annotation can be on this as well. This new class's name
is configurable in the options.

Thanks to user @madisparn for initial PR and issue report.

Fixes #87
2022-04-07 12:21:50 +01:00
Jordan Zimmerman
642dd01421 Don't include @Valid on base interfaces (#99)
The validation API doesn't accept `@Valid` on base interfaces. Filter
them out.

Fixes #97
2022-03-21 10:06:02 +00:00
Mads Baardsgaard
efd1a6b0d4 Add flag to add concrete setters for optionals (#94)
* Add flag to add concrete setters for optionals

* Add another test with concrete optionals disabled
2022-03-21 09:08:00 +00:00
Mads Baardsgaard
b525eddc76 Make withers and getters optional features (#95)
* Make withers and getters optional features

* Add example record with wither and getter disabled
2022-03-21 08:51:42 +00:00
Jordan Zimmerman
d3828eda74 Fix NPE for uninitialized non-null collection (#98)
Closes #91

Co-authored-by: Clément MATHIEU <clement@unportant.info>
2022-03-20 10:46:39 +00:00
Jordan Zimmerman
fef69af183 [maven-release-plugin] prepare for next development iteration 2022-02-04 11:45:10 +00:00
Jordan Zimmerman
99f9639b82 [maven-release-plugin] prepare release record-builder-32 2022-02-04 11:45:05 +00:00
Mads Baardsgaard
43bc65e258 Add configurable method name prefixes to builders (#86)
Add configurable Bean interface to add prefixed getters to record
2022-02-02 17:44:57 +00:00
Jordan Zimmerman
bae1b771b7 [maven-release-plugin] prepare for next development iteration 2022-01-25 11:06:05 +00:00
Jordan Zimmerman
1dd00b2c65 [maven-release-plugin] prepare release record-builder-31 2022-01-25 11:06:01 +00:00
Jordan Zimmerman
3b34b5dee3 [maven-release-plugin] prepare for next development iteration 2022-01-25 10:59:50 +00:00
Jordan Zimmerman
7248bad2bd [maven-release-plugin] prepare release record-builder-30 2022-01-25 10:59:45 +00:00
Jordan Zimmerman
7e494d8753 Optional functional methods for With
When enabled, some functional methods are added to the `With` nested
class.

E.g.

```java
@RecordBuilder
record MyRecord<T>(String name, T value, int qty) implements MyRecordBuilder.With<T> {}

...

MyRecord<Thing> r = ...

var other = r.map((name, value, qty) -> new Other(...));
```
2022-01-22 09:17:15 +00:00
Madis Pärn
3954499d4b Allow components stream method to work with null fields (#85)
Co-authored-by: Madis Parn <madis.parn@topia.com>
2022-01-22 07:56:39 +00:00
Jordan Zimmerman
13959dee2a [maven-release-plugin] prepare for next development iteration 2021-11-03 08:51:54 +00:00
Jordan Zimmerman
af759c0570 [maven-release-plugin] prepare release record-builder-29 2021-11-03 08:51:47 +00:00
Jordan Zimmerman
9a7d73e78c Support options on includes 2021-11-03 08:44:51 +00:00
Jordan Zimmerman
3b8c3ff9e3 Remove Java 15 support
Java 15 has been EOL for a while now. No need to continue supporting
it.

Closes #78
2021-10-21 19:00:46 +01:00
Jordan Zimmerman
9943667af1 Add support for static from() that returns a Wither
Useful for when you can't add the With interface to your record.
Static method takes a record as an argument and returns a With
instance.
2021-10-19 20:24:10 +01:00
Stefan Kuhn
b0c8f10711 fix: build 2021-10-19 19:42:04 +01:00
Stefan Kuhn
0d3c2f37c1 chore: update test to use annotation processor discovery 2021-10-19 19:42:04 +01:00
Stefan Kuhn
eabcb2f179 doc: update maven usage, so that the default annotation processors discovery process is not disabled 2021-10-19 19:42:04 +01:00
Jordan Zimmerman
5fef81191d [maven-release-plugin] prepare for next development iteration 2021-10-07 10:14:58 +01:00
Jordan Zimmerman
8dbec027e4 [maven-release-plugin] prepare release record-builder-28 2021-10-07 10:14:53 +01:00
Jordan Zimmerman
ef09d68b78 [maven-release-plugin] prepare for next development iteration 2021-10-07 10:11:59 +01:00
Jordan Zimmerman
0a608f3b20 [maven-release-plugin] prepare release record-builder-27-java15 2021-10-07 10:11:54 +01:00
Jordan Zimmerman
3494d01b2b Added link to SingleItemsBuilder gist 2021-10-07 08:35:46 +01:00
Jordan Zimmerman
584bdf4a25 Support single item collection builders
When `addSingleItemCollectionBuilders()` is enabled in options, collection types (`List`, `Set` and `Map`)
are handled specially. The setters for these types now create an internal collection and
items are added to that collection. Additionally, "adder" methods prefixed with
`singleItemBuilderPrefix()` are created to add single items to these collections.

Closes #73
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
9268c295fe Add RecordBuilderFull
- Add `@RecordBuilderFull` which will have most optional features enabled
- Change `@RecordBuilder.Template` to be `RetentionPolicy.CLASS`
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
0d2fe2ffe7 Optionally include all records from a list of packages
Closes #67
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
71e22042e9 Add Java 17 build 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
3c8e305002 Use Map.entry() instead of AbstractMap.SimpleEntry
Closes #68
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
76f93039b0 Support options for RecordInterface
- Copy any @RecordBuilder.Options to the generated Record
- Support RecordInterface templates as well

Closes #64
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
5f82af97ae The null check after instanceof Set was unnecessary in the __collection shim 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
896aab18aa Update customizing.md 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
75010d0add Update README.md 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
26a6141bef [maven-release-plugin] prepare for next development iteration 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
cdd9009fa8 [maven-release-plugin] prepare release record-builder-26 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
0624a6caca [maven-release-plugin] prepare for next development iteration 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
238ef872bf [maven-release-plugin] prepare release record-builder-25-java15 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
ef2a3692d1 Match record visibility
If the builder is in the same package as the record and the record
is package-private, make the builder package-private too.

Closes #52
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
cb9ceb0529 @Override should not be inherited. Also, the inheritComponentAnnotations
option was being ignored.

Closes #57
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
bd8fbeb045 Adds special handling for record components of type java.util.List, java.util.Set, java.util.Map and java.util.Collection. When the record is built, any components of these types are passed through an added shim method that uses the corresponding immutable collection (e.g. List.copyOf(o)) or an empty immutable collection if the component is null.
Closes #56
Closes #58
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
5b10284d4e [maven-release-plugin] prepare for next development iteration 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
49a19ca8f8 [maven-release-plugin] prepare release record-builder-24 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
f9fcd1e540 [maven-release-plugin] prepare for next development iteration 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
eebb2b2bfd [maven-release-plugin] prepare release record-builder-23-java15 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
67f219bd67 Remove downcast in favor of methods
Great suggestion from @Twisol. There's no need for the downcasting
if record component methods are added to the Wither interface.

Closes #27
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
d9f27bc5a6 Validation and null checks are missing for withers. This PR adds them.
Closes #47
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
f7b6f47bbf [maven-release-plugin] prepare for next development iteration 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
7e90e64988 [maven-release-plugin] prepare release record-builder-22 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
cbe3b7cef4 [maven-release-plugin] prepare for next development iteration 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
6c15bdd825 [maven-release-plugin] prepare release record-builder-21 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
53aaf6b7e9 [maven-release-plugin] prepare for next development iteration 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
09ed5905e3 [maven-release-plugin] prepare release record-builder-21-java15 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
41f59e704f Added more unit tests
Closes #36
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
d0c56e6fdf Fix up some minor version/path issues in the POMs 2021-10-07 08:35:17 +01:00
dependabot[bot]
2f3477a1ca Bump hibernate-validator from 6.0.16.Final to 6.0.20.Final
Bumps [hibernate-validator](https://github.com/hibernate/hibernate-validator) from 6.0.16.Final to 6.0.20.Final.
- [Release notes](https://github.com/hibernate/hibernate-validator/releases)
- [Changelog](https://github.com/hibernate/hibernate-validator/blob/6.0.20.Final/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-validator/compare/6.0.16.Final...6.0.20.Final)

---
updated-dependencies:
- dependency-name: org.hibernate.validator:hibernate-validator
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
091e9493b6 Support Java Validation API
Option to pass created records through the Java Validation API if it's
available in the classpath. IMPORTANT: when enabled, the record-builder-validator
module must also be included. record-builder-validator is written totally
via reflection so if no validation framework is included it's a NOP.
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
a0c03e88cc Add support for NotNull-style annotations
When enabled, annotations matching the configured regex for NotNull
annotations cause `Object.requireNonNull()` to be added for annotated
components.
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
d21953c39a Use manifest based automatic module instead of hand-coded version (#43)
This is much saner. It seems silly to maintain an extra file in the
project when this can all be done via Maven config.
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
d9d98e765e Set optional fields to empty by default (#38)
Sets `Optional` fields to `empty()` by default. Adds an option
to control this.

Closes #34
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
5320c8dfa4 Support copying component annotations to builder (#33)
- Enabled via new option `inheritComponentAnnotations` (true by default)
- Canonical constructor parameter annotations are copied to RecordBuilder setters and the static builder
- Record component accessor annotations are copied to RecordBuilder getters
2021-10-07 08:35:17 +01:00
Jordan Zimmerman
5d7bea565f Rework how options are specified (#37)
- Remove `RecordBuilderMetaData`
- Unify how the javac options are handled
- Create `RecordBuilder.Options` to specify options
- Allow creation of custom annotations that bundle options
2021-10-07 08:35:17 +01:00
Thiago Henrique Hüpner
b7c4e22066 Enable syntax-highlighting in the README (#29) 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
19803592a2 Update README.md 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
9e25c6fa47 Update README.md 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
c16e3bb14b Update README.md 2021-10-07 08:35:17 +01:00
Michał Górniewski
57e5d43b07 Add support for Java Platform Module System (#28) 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
feecc29eea [maven-release-plugin] prepare for next development iteration 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
428d3c0378 [maven-release-plugin] prepare release record-builder-1.19 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
61d355b9fc Update README.md 2021-10-07 08:35:17 +01:00
Jordan Zimmerman
2beafc4803 [maven-release-plugin] rollback the release of record-builder-1.19 2021-03-16 09:49:45 -05:00
Jordan Zimmerman
82b3925618 [maven-release-plugin] prepare release record-builder-1.19 2021-03-16 09:49:32 -05:00
Jordan Zimmerman
ba90e6cdca [maven-release-plugin] prepare for next development iteration 2021-02-04 06:35:37 -05:00
Jordan Zimmerman
07e52035ee [maven-release-plugin] prepare release record-builder-1.18 2021-02-04 06:35:29 -05:00
Jordan Zimmerman
07fc606147 Update README.md 2021-02-04 06:33:50 -05:00
Jordan Zimmerman
677813e875 [maven-release-plugin] prepare for next development iteration 2021-02-04 06:31:51 -05:00
Jordan Zimmerman
7d877963fb [maven-release-plugin] prepare release record-builder-1.18-java15 2021-02-04 06:31:51 -05:00
Jordan Zimmerman
96bb6ef9f3 Abandon previous attempt to have Java15 specific modules. I can just manually do it from now on 2021-02-04 06:31:51 -05:00
Jordan Zimmerman
6f3046f507 Update README.md 2021-02-01 13:22:08 -05:00
Jordan Zimmerman
7564643556 [maven-release-plugin] prepare for next development iteration 2021-02-01 13:08:04 -05:00
Jordan Zimmerman
dfd76fc58b [maven-release-plugin] prepare release record-builder-1.17 2021-02-01 13:08:02 -05:00
Jordan Zimmerman
04e9135591 [maven-release-plugin] prepare for next development iteration 2021-02-01 13:06:44 -05:00
Jordan Zimmerman
b512a6e968 [maven-release-plugin] prepare release record-builder-1.17 2021-02-01 13:06:44 -05:00
Jordan Zimmerman
5a1cd35320 [maven-release-plugin] rollback the release of record-builder-1.17 2021-02-01 13:06:44 -05:00
Jordan Zimmerman
a615e3abb6 [maven-release-plugin] prepare release record-builder-1.17 2021-02-01 13:06:44 -05:00
Jordan Zimmerman
3f8bb47cbf Support alternate artifacts built with Java 15 2021-02-01 13:00:42 -05:00
Jordan Zimmerman
5a8e72f0e9 [maven-release-plugin] prepare for next development iteration 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
44ad4531b6 [maven-release-plugin] prepare release record-builder-1.16 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
7e78d32780 Added support for putting @RecordInterface on Java beans 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
0dc4aa7657 Prep for Java 16 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
b6d9a6202f Create FUNDING.yml 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
b21368f32f Have the consumer version of with() use the other with() to get the builder. This will ensure better testing and is more logical 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
501da86afd you're right - we only need provided/compileOnly. I've made the updates to Maven as well. 2021-02-01 12:26:07 -05:00
Marc Philipp
13d867e6e6 Use compileOnly instead of implementation
Since most users only need the annotations.
2021-02-01 12:26:07 -05:00
Jordan Zimmerman
a1206fa57f [maven-release-plugin] prepare for next development iteration 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
b89722ebfe [maven-release-plugin] prepare release record-builder-1.14.ea 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
75163f53ed Update README.md 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
9855e7b504 Switch to Github Actions 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
aa3bdedf28 Update maven.yml 2021-02-01 12:26:07 -05:00
Jordan Zimmerman
6d5e15baa1 Create maven.yml 2021-02-01 12:26:07 -05:00
Mikaël Barbero
9c8e3626ba Stop relying on toString() to detect package name.
Fix for #15
2020-12-11 18:18:32 +00:00
Jordan Zimmerman
24b85e7ad5 [maven-release-plugin] prepare for next development iteration 2020-11-29 08:27:34 -05:00
Jordan Zimmerman
861e2e745a [maven-release-plugin] prepare release record-builder-1.13.ea 2020-11-29 08:27:25 -05:00
Jordan Zimmerman
1fc7c9a4b3 Make sure the downcast variable name doesn't collide with a record component name 2020-11-28 22:39:00 -05:00
Jordan Zimmerman
570514e077 buildPackageName() can't assume that the immediate enclosing element is the package. It may be a nested class, etc. 2020-11-28 22:38:48 -05:00
Jordan Zimmerman
9d8b9e65bc [maven-release-plugin] prepare for next development iteration 2020-11-28 08:44:33 -05:00
Jordan Zimmerman
93d6204b76 [maven-release-plugin] prepare release record-builder-1.12.ea 2020-11-28 08:44:25 -05:00
Jordan Zimmerman
15e5bfccc6 [maven-release-plugin] rollback the release of record-builder-1.12.ea 2020-11-28 08:42:15 -05:00
Jordan Zimmerman
67c54244c5 [maven-release-plugin] prepare release record-builder-1.12.ea 2020-11-28 08:41:59 -05:00
Jordan Zimmerman
870ac4a9d9 [maven-release-plugin] rollback the release of record-builder-1.12.ea 2020-11-28 08:29:46 -05:00
Jordan Zimmerman
9ee8b5912a [maven-release-plugin] prepare for next development iteration 2020-11-28 08:28:09 -05:00
Jordan Zimmerman
6d9bcf27da [maven-release-plugin] prepare release record-builder-1.12.ea 2020-11-28 08:27:49 -05:00
Jordan Zimmerman
44db5fdf17 Change to 1.12.ea-SNAPSHOT - last release must have missed this 2020-11-28 08:25:10 -05:00
Jordan Zimmerman
90a65235a9 Add support for static constructor
Add a static constructor/builder so it can statically imported. Instead of
calling "new Record(...)" you can call just "Record(...)".
2020-11-28 07:35:34 -05:00
Ted Cassirer
7e8ddbd700 Remove unused variable in RecordBuilder 2020-11-25 09:33:08 -05:00
Ted Cassirer
3a534fbea9 Don't add the allArgsConstructor to the RecordBuilder if record has no fields 2020-11-25 08:53:06 -05:00
Jordan Zimmerman
6d7ebe2545 Update README.md 2020-11-09 14:42:30 -05:00
Jordan Zimmerman
f1e47391c8 Update README.md 2020-11-04 11:34:41 -05:00
Jordan Zimmerman
c999d0ba06 Update README.md 2020-11-04 10:47:33 -05:00
Jordan Zimmerman
e0243c8b1c Fixed some typos in previous PR 2020-11-04 10:44:43 -05:00
Jordan Zimmerman
65bbbaea05 Update README.md 2020-11-04 10:38:53 -05:00
Jordan Zimmerman
5ae03a2c66 Update README.md 2020-11-04 10:37:38 -05:00
Jordan Zimmerman
437e314799 Better exception when Wither is set as implementor on non-builder class 2020-11-04 10:36:35 -05:00
Jordan Zimmerman
a870beee21 Update README.md 2020-11-02 23:41:19 -05:00
Jordan Zimmerman
5b879743ef Added simple module-info. I've never done this before, I hope it's right 2020-11-02 23:37:57 -05:00
Jordan Zimmerman
c7bdafb0b9 Add method to do downcasting 2020-11-02 23:37:57 -05:00
Jordan Zimmerman
d67c62ed3b Update README.md 2020-10-05 15:00:28 -05:00
Jordan Zimmerman
39cf2b0353 Update README.md 2020-10-05 15:00:16 -05:00
Jordan Zimmerman
6813b88f8d Update README.md 2020-10-05 14:59:28 -05:00
Jordan Zimmerman
54662d69c7 Support Include versions of the annotation 2020-10-05 14:45:34 -05:00
Jordan Zimmerman
7811ff8823 Update README.md 2020-10-05 11:30:04 -05:00
Jordan Zimmerman
4c5076690d Update README.md 2020-10-05 11:29:32 -05:00
Jordan Zimmerman
39a800f10e Update README.md 2020-10-05 11:29:00 -05:00
Jordan Zimmerman
610081b27e [maven-release-plugin] prepare for next development iteration 2020-10-05 11:22:40 -05:00
Jordan Zimmerman
1eb91d612e [maven-release-plugin] prepare release record-builder-1.10.ea 2020-10-05 11:22:31 -05:00
Jordan Zimmerman
7c84f26972 Generated record from interface should implement the wither 2020-10-05 11:20:43 -05:00
Jordan Zimmerman
82cc4f4cad Update README.md 2020-10-04 14:54:54 -05:00
Jordan Zimmerman
f16e1b1d0e Update README.md 2020-10-04 14:51:40 -05:00
Jordan Zimmerman
c92bf78ec5 [maven-release-plugin] prepare for next development iteration 2020-10-04 14:48:03 -05:00
Jordan Zimmerman
4c4baa015f [maven-release-plugin] prepare release record-builder-1.9.ea 2020-10-04 14:47:55 -05:00
Jordan Zimmerman
81b7b93a5b Added alternate build wither that takes the builder as a consumer argument 2020-10-04 14:43:43 -05:00
Jordan Zimmerman
c39983e342 Some refactoring and simplifying. No need for the method to return the cast "this". It can be done inline, etc. 2020-09-28 08:06:23 -05:00
Jordan Zimmerman
400caa2943 [maven-release-plugin] prepare for next development iteration 2020-09-27 22:23:31 -05:00
Jordan Zimmerman
a2edd7299f [maven-release-plugin] prepare release record-builder-1.8.ea 2020-09-27 22:23:22 -05:00
Jordan Zimmerman
6661c2ae0e Added support for withers and moved to Java 15 2020-09-27 22:18:26 -05:00
Jordan Zimmerman
74c8480b43 Update README.md 2020-08-03 08:48:59 -05:00
Jordan Zimmerman
8dbdb43391 Update README.md 2020-05-30 08:21:30 -05:00
Jordan Zimmerman
44064d656e Update README.md 2020-05-28 21:38:31 -05:00
Jordan Zimmerman
791eb02faf [maven-release-plugin] prepare for next development iteration 2020-05-28 21:36:11 -05:00
Jordan Zimmerman
aa3aefa39c [maven-release-plugin] prepare release record-builder-1.7.ea 2020-05-28 21:36:03 -05:00
Jordan Zimmerman
de6946030f [maven-release-plugin] rollback the release of record-builder-1.7.ea 2020-05-28 21:34:44 -05:00
Jordan Zimmerman
091c663520 [maven-release-plugin] prepare release record-builder-1.7.ea 2020-05-28 21:34:12 -05:00
Jordan Zimmerman
6a45a2cbd9 Added support for RecordInterface to generate records from Interfaces 2020-05-28 21:32:53 -05:00
Jordan Zimmerman
c47e290363 Update README.md 2020-05-28 21:32:53 -05:00
Jordan Zimmerman
d7abf4c60d Update README.md 2020-05-28 21:32:53 -05:00
Jordan Zimmerman
6fbb0d0330 Update README.md 2020-05-28 21:32:53 -05:00
Jordan Zimmerman
e235fc078f Update README.md 2020-05-28 21:32:53 -05:00
randgalt
cf8f277018 [maven-release-plugin] prepare for next development iteration 2020-05-28 21:32:53 -05:00
randgalt
e8e74cce1a [maven-release-plugin] prepare release record-builder-1.6.ea 2020-05-28 21:32:53 -05:00
randgalt
3b425d4dce Updated the README 2020-05-28 21:32:53 -05:00
sipkab
395f0879ac Fix indentation in RecordBuilderProcessor 2020-05-28 21:32:53 -05:00
sipkab
8969a17053 Fix indentation in OptionBasedRecordBuilderMetaData as well 2020-05-28 21:32:53 -05:00
sipkab
5ef0662d99 Fix indentation 2020-05-28 21:32:52 -05:00
sipkab
c6de55d5ad Allow -A option based processor configuration 2020-05-28 21:32:52 -05:00
sipkab
d19a0c2dc5 Mitigate possible runtime Errors when running on older JVMs 2020-05-28 21:32:52 -05:00
randgalt
feb334e6ea updated copyright date in license 2020-05-28 21:32:52 -05:00
randgalt
f812e173e3 [maven-release-plugin] prepare for next development iteration 2020-05-28 21:32:52 -05:00
randgalt
d50303ca09 [maven-release-plugin] prepare release record-builder-1.5.ea 2020-05-28 21:32:52 -05:00
Jordan Zimmerman
13319e643b Update README.md 2020-05-28 21:32:52 -05:00
Jordan Zimmerman
83802125aa Update README.md 2020-05-28 21:32:52 -05:00
randgalt
b1e343f733 Added a component stream method 2020-05-28 21:32:52 -05:00
randgalt
75fce6bb84 [maven-release-plugin] prepare for next development iteration 2020-05-28 21:32:52 -05:00
randgalt
c676436f5e [maven-release-plugin] prepare release record-builder-1.4.ea 2020-05-28 21:32:52 -05:00
Jordan Zimmerman
b861d0f4b1 Update README.md 2020-05-28 21:32:52 -05:00
randgalt
933098b07d Added getter methods, generated annotation and a few other tweaks 2020-05-28 21:32:52 -05:00
Jordan Zimmerman
8560666ec6 Create .travis.yml 2020-05-28 21:32:52 -05:00
randgalt
85070cc106 Added Maven config for now 2019-12-20 15:39:04 -05:00
randgalt
7ac840ae2a [maven-release-plugin] prepare for next development iteration 2019-12-20 13:48:50 -05:00
randgalt
19017e6693 [maven-release-plugin] prepare release record-builder-1.3.ea 2019-12-20 13:48:43 -05:00
randgalt
6d36b86c68 Added hashCode and equals implementations to the builder 2019-12-20 13:45:31 -05:00
randgalt
417ce0e139 [maven-release-plugin] prepare for next development iteration 2019-12-17 18:30:28 -05:00
randgalt
ee2d56999f [maven-release-plugin] prepare release record-builder-1.2.ea 2019-12-17 18:30:21 -05:00
randgalt
f5e5c06ecc added a toString() implementation 2019-12-17 15:06:27 -05:00
randgalt
f346ab6f53 [maven-release-plugin] prepare for next development iteration 2019-12-17 06:59:20 -05:00
103 changed files with 6261 additions and 473 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: Randgalt

24
.github/workflows/maven_java16.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Maven Build - Java 16
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 16
- name: Build with Maven
run: ./mvnw -B package --file pom.xml

24
.github/workflows/maven_java17.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Maven Build - Java 17
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 17
- name: Build with Maven
run: ./mvnw -B package --file pom.xml

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Default ignored files
.idea
**/target/**

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

18
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

319
README.md
View File

@@ -1,10 +1,29 @@
# RecordBuilder - Early Access
[![Maven Build - Java 17](https://github.com/Randgalt/record-builder/actions/workflows/maven_java17.yml/badge.svg)](https://github.com/Randgalt/record-builder/actions/workflows/maven_java17.yml)
[![Maven Central](https://img.shields.io/maven-central/v/io.soabase.record-builder/record-builder.svg?sort=date)](https://search.maven.org/search?q=g:io.soabase.record-builder%20a:record-builder)
# RecordBuilder
## What is RecordBuilder
Java 14 is introducing [Records](https://cr.openjdk.java.net/~briangoetz/amber/datum.html) as a preview feature. Since Java 9, features in Java are being released in stages. While the Java 14 version of records is fantastic, it's currently missing an important feature for data classes: a builder. This project is an annotation processor that creates companion builder classes for Java records.
Java 16 introduces [Records](https://openjdk.java.net/jeps/395). While this version of records is fantastic,
it's currently missing some important features normally found in data classes: a builder
and "with"ers. This project is an annotation processor that creates:
- a companion builder class for Java records
- an interface that adds "with" copy methods
- an annotation that generates a Java record from an Interface template
## Example
_Details:_
- [RecordBuilder Details](#RecordBuilder-Example)
- [Wither Details](#Wither-Example)
- [RecordBuilder Full Definition](#Builder-Class-Definition)
- [Record From Interface Details](#RecordInterface-Example)
- [Generation Via Includes](#generation-via-includes)
- [Usage](#usage)
- [Customizing](customizing.md) (e.g. add immutable collections, etc.)
## RecordBuilder Example
```java
@RecordBuilder
@@ -15,18 +34,63 @@ This will generate a builder class that can be used ala:
```java
// build from components
var n1 = NameAndAgeBuilder.builder().name(aName).age(anAge).build();
NameAndAge n1 = NameAndAgeBuilder.builder().name(aName).age(anAge).build();
// generate a copy with a changed value
var n2 = NameAndAgeBuilder.builder(n1).age(newAge).build(); // name is the same as the name in n1
NameAndAge n2 = NameAndAgeBuilder.builder(n1).age(newAge).build(); // name is the same as the name in n1
// pass to other methods to set components
var builder = new NameAndAgeBuilder();
setName(builder);
setAge(builder);
var n3 = builder.build();
NameAndAge n3 = builder.build();
// use the generated static constructor/builder
import static NameAndAgeBuilder.NameAndAge;
...
var n4 = NameAndAge("hey", 42);
```
## Wither Example
```java
@RecordBuilder
public record NameAndAge(String name, int age) implements NameAndAgeBuilder.With {}
```
In addition to creating a builder, your record is enhanced by "wither" methods ala:
```java
NameAndAge r1 = new NameAndAge("foo", 123);
NameAndAge r2 = r1.withName("bar");
NameAndAge r3 = r2.withAge(456);
// access the builder as well
NameAndAge r4 = r3.with().age(101).name("baz").build();
// alternate method of accessing the builder (note: no need to call "build()")
NameAndAge r5 = r4.with(b -> b.age(200).name("whatever"));
// perform some logic in addition to changing values
NameAndAge r5 = r4.with(b -> {
if (b.age() > 13) {
b.name("Teen " + b.name());
} else {
b.name("whatever"));
}
});
// or, if you cannot add the "With" interface to your record...
NameAndAge r6 = NameAndAgeBuilder.from(r5).with(b -> b.age(200).name("whatever"));
NameAndAge r7 = NameAndAgeBuilder.from(r5).withName("boop");
```
_Hat tip to [Benji Weber](https://benjiweber.co.uk/blog/2020/09/19/fun-with-java-records/) for the Withers idea._
## Builder Class Definition
(Note: you can see a builder class built using `@RecordBuilderFull` here: [FullRecordBuilder.java](https://gist.github.com/Randgalt/8aa487a847ea2acdd76d702f7cf17d6a))
The full builder class is defined as:
```java
@@ -43,6 +107,13 @@ public class NameAndAgeBuilder {
this.age = age;
}
/**
* Static constructor/builder. Can be used instead of new NameAndAge(...)
*/
public static NameAndAge NameAndAge(String name, int age) {
return new NameAndAge(name, age);
}
/**
* Return a new builder with all fields set to default Java values
*/
@@ -57,6 +128,21 @@ public class NameAndAgeBuilder {
return new NameAndAgeBuilder(from.name(), from.age());
}
/**
* Return a "with"er for an existing record instance
*/
public static NameAndAgeBuilder.With from(NameAndAge from) {
return new _FromWith(from);
}
/**
* Return a stream of the record components as map entries keyed with the component name and the value as the component value
*/
public static Stream<Map.Entry<String, Object>> stream(NameAndAge record) {
return Stream.of(new AbstractMap.SimpleImmutableEntry<>("name", record.name()),
new AbstractMap.SimpleImmutableEntry<>("age", record.age()));
}
/**
* Return a new record instance with all fields set to the current values in this builder
*/
@@ -64,8 +150,25 @@ public class NameAndAgeBuilder {
return new NameAndAge(name, age);
}
@Override
public String toString() {
return "NameAndAgeBuilder[name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object o) {
return (this == o) || ((o instanceof NameAndAgeBuilder r)
&& Objects.equals(name, r.name)
&& (age == r.age));
}
/**
* Set a new value for this record component in the builder
* Set a new value for the {@code name} record component in the builder
*/
public NameAndAgeBuilder name(String name) {
this.name = name;
@@ -73,78 +176,184 @@ public class NameAndAgeBuilder {
}
/**
* Set a new value for this record component in the builder
* Return the current value for the {@code name} record component in the builder
*/
public String name() {
return name;
}
/**
* Set a new value for the {@code age} record component in the builder
*/
public NameAndAgeBuilder age(int age) {
this.age = age;
return this;
}
/**
* Return the current value for the {@code age} record component in the builder
*/
public int age() {
return age;
}
/**
* Add withers to {@code NameAndAge}
*/
public interface With {
/**
* Return the current value for the {@code name} record component in the builder
*/
String name();
/**
* Return the current value for the {@code age} record component in the builder
*/
int age();
/**
* Return a new record builder using the current values
*/
default NameAndAgeBuilder with() {
return new NameAndAgeBuilder(name(), age());
}
/**
* Return a new record built from the builder passed to the given consumer
*/
default NameAndAge with(Consumer<NameAndAgeBuilder> consumer) {
NameAndAgeBuilder builder = with();
consumer.accept(builder);
return builder.build();
}
/**
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
*/
default NameAndAge withName(String name) {
return new NameAndAge(name, age());
}
/**
* Return a new instance of {@code NameAndAge} with a new value for {@code age}
*/
default NameAndAge withAge(int age) {
return new NameAndAge(name(), age);
}
}
private static final class _FromWith implements NameAndAgeBuilder.With {
private final NameAndAge from;
private _FromWith(NameAndAge from) {
this.from = from;
}
@Override
public String name() {
return from.name();
}
@Override
public int age() {
return from.age();
}
}
}
```
## RecordInterface Example
```java
@RecordInterface
public interface NameAndAge {
String name();
int age();
}
```
This will generate a record ala:
```java
@RecordBuilder
public record NameAndAgeRecord(String name, int age) implements
NameAndAge, NameAndAgeRecordBuilder.With {}
```
Note that the generated record is annotated with `@RecordBuilder` so a record
builder is generated for the new record as well.
Notes:
- Non static methods in the interface...
- ...cannot have arguments
- ...must return a value
- ...cannot have type parameters
- Methods with default implementations are used in the generation unless they are annotated with `@IgnoreDefaultMethod`
- If you do not want a record builder generated, annotate your interface as `@RecordInterface(addRecordBuilder = false)`
- If your interface is a JavaBean (e.g. `getThing()`, `isThing()`) the "get" and "is" prefixes are
stripped and forwarding methods are added.
## Generation Via Includes
An alternate method of generation is to use the Include variants of the annotations. These variants
act on lists of specified classes. This allows the source classes to be pristine or even come from
libraries where you are not able to annotate the source.
E.g.
```java
import some.library.code.ImportedRecord
import some.library.code.ImportedInterface
@RecordBuilder.Include({
ImportedRecord.class // generates a record builder for ImportedRecord
})
@RecordInterface.Include({
ImportedInterface.class // generates a record interface for ImportedInterface
})
public void Placeholder {
}
```
`@RecordBuilder.Include` also supports a `packages` attribute that includes all records
in the listed packages.
The target package for generation is the same as the package that contains the "Include"
annotation. Use `packagePattern` to change this (see Javadoc for details).
## Usage
### Maven
1\. Add the dependency that contains the `@RecordBuilder` annotation.
Add a dependency that contains the discoverable annotation processor:
```
```xml
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-core</artifactId>
<version>set-version-here</version>
<artifactId>record-builder-processor</artifactId>
<version>${record.builder.version}</version>
<scope>provided</scope>
</dependency>
```
2\. Enable the annotation processing for the Maven Compiler Plugin:
### Gradle
```
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>set-version-here</version>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-processor</artifactId>
<version>set-version-here</version>
</annotationProcessorPath>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>io.soabase.recordbuilder.processor.RecordBuilderProcessor</annotationProcessor>
</annotationProcessors>
Add the following to your build.gradle file:
<!-- "release" and "enable-preview" are required while records are preview features -->
<release>14</release>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
... any other options here ...
</configuration>
</plugin>
```groovy
dependencies {
annotationProcessor 'io.soabase.record-builder:record-builder-processor:$version-goes-here'
compileOnly 'io.soabase.record-builder:record-builder-core:$version-goes-here'
}
```
### IDE
Depending on your IDE you are likely to need to enable Annotation Processing in your IDE settings.
## Enable Preview
## Customizing
Note: records are a preview feature only. You'll need take a number of steps in order to try RecordBuilder:
- Install and make active Java 14 or later
- Make sure your development tool is using Java 14 or later and is configured to enable preview features (for Maven I've documented how to do this here: [https://stackoverflow.com/a/59363152/2048051](https://stackoverflow.com/a/59363152/2048051))
- Bear in mind that this is not yet meant for production and there are numerous bugs in the tools and JDKs.
Note: I've seen some very odd compilation bugs with the current Java 14 and Maven. If you get internal Javac errors I suggest rebuilding with `mvn clean package` and/or `mvn clean install`.
## TODOs
- Document how to integrate with Gradle
- Keep up with changes
- Testing
- Etc.
RecordBuilder can be customized to your needs and you can even create your
own custom RecordBuilder annotations. See [Customizing RecordBuilder](customizing.md)
for details.

95
customizing.md Normal file
View File

@@ -0,0 +1,95 @@
[◀︎ RecordBuilder](README.md) • Customizing RecordBuilder
# RecordBuilderFull
Note: `@RecordBuilderFull` has most optional features enabled. It's an alternate
form of `@RecordBuilder` that uses the [templating mechanism](#create-a-custom-annotation) to
enable optional features.
# Customizing RecordBuilder
RecordBuilder can be customized in a number of ways. The types of customizations will change over time. See
[@RecordBuilder.Options](record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java)
for the current set of customizations and their default values. For example, the `useImmutableCollections` option
adds special handling for record components of type `java.util.List`, `java.util.Set`, `java.util.Map` and `java.util.Collection`. When the record is built, any components of these types are passed through an added shim method that uses the corresponding immutable collection (e.g. `List.copyOf(o)`) or an empty immutable collection if the component is `null`.
You can:
- [Customize an entire build](#customize-an-entire-build) - all uses of `@RecordBuilder` in your project
- [Customize a single record](#customize-a-single-record) annotated with `@RecordBuilder`
- [Create a custom annotation](#create-a-custom-annotation) that specifies your options and use that instead of `@RecordBuilder`
## Customize an entire build
To customize an entire build, use javac's annotation processor options via `-A` on the command line.
The options available are the same as the attributes in [@RecordBuilder.Options](record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java).
i.e. to disable "prefixing enclosing class names", compile with:
```shell
javac -AprefixEnclosingClassNames=false ...
```
_Note: use a separate `-A` for each option._
#### Maven
If you are using Maven, specify the options in the compiler plugin:
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin-version}</version>
<configuration>
<compilerArgs>
<arg>-AprefixEnclosingClassNames=false</arg>
<arg>-AfileComment="something different"</arg>
</compilerArgs>
</configuration>
</plugin>
```
#### Gradle
For Gradle, specify the options:
```groovy
compilerArgs.addAll(['-AprefixEnclosingClassNames=false', '-AfileComment="something different"'])
```
## Customize a single record
To customize a single record, add `@RecordBuilder.Options` in addition to
`@RecordBuilder`.
E.g.
```java
@RecordBuilder.Options(withClassName = "Wither")
@RecordBuilder
public record MyRecord(String s){}
```
## Create a custom annotation
Using `@RecordBuilder.Template` you can create your own RecordBuilder annotation
that uses the set of options you want. E.g. to create a custom annotation that
uses an alternate file comment and an alternate With classname:
```java
@RecordBuilder.Template(options = @RecordBuilder.Options(
fileComment = "MyCo license",
withClassName = "Wither"
))
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface MyCoRecordBuilder {
}
```
Now, you can use `@MyCoRecordBuilder` instead of `@RecordBuilder` and the record
will be built with options as specified.
Note: the template mechanism also supports `@RecordInterface` templates via the `asRecordInterface` attribute.
When it is set a `@RecordInterface` template is created instead.

287
mvnw vendored Executable file
View File

@@ -0,0 +1,287 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.1.1
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
printf '%s' "$(cd "$basedir"; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname $0)")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $wrapperUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
QUIET="--quiet"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
QUIET=""
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath"
fi
[ $? -eq 0 ] || rm -f "$wrapperJarPath"
elif command -v curl > /dev/null; then
QUIET="--silent"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
QUIET=""
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L
fi
[ $? -eq 0 ] || rm -f "$wrapperJarPath"
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=`cygpath --path --windows "$javaSource"`
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

187
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,187 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.1.1
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

148
pom.xml
View File

@@ -1,16 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 The original author or authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the 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">
<modelVersion>4.0.0</modelVersion>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<packaging>pom</packaging>
<version>1.1.ea</version>
<version>38-SNAPSHOT</version>
<modules>
<module>record-builder-core</module>
<module>record-builder-processor</module>
<module>record-builder-test</module>
<module>record-builder-validator</module>
</modules>
<properties>
@@ -18,22 +36,33 @@
<project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jdk-version>14</jdk-version>
<jdk-version>16</jdk-version>
<maven-compiler-plugin-version>3.8.1</maven-compiler-plugin-version>
<maven-source-plugin-version>3.2.0</maven-source-plugin-version>
<maven-install-plugin-version>2.5.2</maven-install-plugin-version>
<maven-deploy-plugin-version>2.8.2</maven-deploy-plugin-version>
<maven-license-plugin-version>1.9.0</maven-license-plugin-version>
<maven-license-plugin-version>4.1</maven-license-plugin-version>
<maven-gpg-plugin-version>1.6</maven-gpg-plugin-version>
<maven-javadoc-plugin-version>3.1.1</maven-javadoc-plugin-version>
<maven-clean-plugin-version>3.1.0</maven-clean-plugin-version>
<maven-shade-plugin-version>3.2.1</maven-shade-plugin-version>
<maven-release-plugin-version>2.5.3</maven-release-plugin-version>
<maven-jar-plugin-version>3.2.0</maven-jar-plugin-version>
<maven-surefire-plugin-version>3.0.0-M5</maven-surefire-plugin-version>
<javapoet-version>1.11.0</javapoet-version>
<jacoco-maven-plugin-version>0.8.7</jacoco-maven-plugin-version>
<formatter-maven-plugin-version>2.22.0</formatter-maven-plugin-version>
<license-file-path>src/etc/header.txt</license-file-path>
<javapoet-version>1.12.1</javapoet-version>
<junit-jupiter-version>5.5.2</junit-jupiter-version>
<assertj-core.version>3.24.2</assertj-core.version>
<asm-version>7.2</asm-version>
<validation-api-version>2.0.1.Final</validation-api-version>
<hibernate-validator-version>6.0.20.Final</hibernate-validator-version>
<javax-el-version>3.0.1-b09</javax-el-version>
</properties>
<name>Record Builder</name>
@@ -70,7 +99,7 @@
<url>https://github.com/randgalt/record-builder</url>
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
<tag>record-builder-1.1.ea</tag>
<tag>record-builder-1.16</tag>
</scm>
<issueManagement>
@@ -105,26 +134,65 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-processor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-validator</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit-jupiter-version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj-core.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api-version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator-version}</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>${javax-el-version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin-version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin-version}</version>
<configuration>
<release>${jdk-version}</release>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
@@ -152,11 +220,11 @@
</plugin>
<plugin>
<groupId>com.mycila.maven-license-plugin</groupId>
<artifactId>maven-license-plugin</artifactId>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>${maven-license-plugin-version}</version>
<configuration>
<header>src/etc/header.txt</header>
<header>${license-file-path}</header>
<excludes>
<exclude>**/*.apt</exclude>
<exclude>**/*.md</exclude>
@@ -179,6 +247,13 @@
<exclude>**/io/soabase/com/google/**</exclude>
<exclude>**/com/company/**</exclude>
<exclude>**/META-INF/services/**</exclude>
<exclude>**/jvm.config</exclude>
<exclude>**/.java-version</exclude>
<exclude>**/.travis.yml</exclude>
<exclude>**/gradlew</exclude>
<exclude>**/.github/**</exclude>
<exclude>**/.mvn/**</exclude>
<exclude>**/mvnw*</exclude>
</excludes>
<strictCheck>true</strictCheck>
</configuration>
@@ -268,6 +343,42 @@
<tagNameFormat>record-builder-@{project.version}</tagNameFormat>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin-version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-plugin-version}</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin-version}</version>
</plugin>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>${formatter-maven-plugin-version}</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>format</goal>
</goals>
<configuration>
<compilerCompliance>17</compilerCompliance>
<compilerSource>17</compilerSource>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
@@ -288,8 +399,8 @@
</plugin>
<plugin>
<groupId>com.mycila.maven-license-plugin</groupId>
<artifactId>maven-license-plugin</artifactId>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
</plugin>
<plugin>
@@ -301,6 +412,16 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
@@ -311,7 +432,6 @@
<plugins>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-plugin-version}</version>
<configuration>
<passphrase>${gpg.passphrase}</passphrase>
<useAgent>true</useAgent>

View File

@@ -1,11 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 The original author or authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the 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>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>1.1.ea</version>
<version>38-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>record-builder-core</artifactId>
<properties>
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>io.soabase.recordbuilder.core</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface IgnoreDefaultMethod {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2016 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,294 @@
package io.soabase.recordbuilder.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.lang.model.element.Modifier;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface RecordBuilder {
@Target({ ElementType.TYPE, ElementType.PACKAGE })
@Retention(RetentionPolicy.SOURCE)
@Inherited
@interface Include {
/**
* @return collection of classes to include
*/
Class<?>[] value() default {};
/**
* Synonym for {@code value()}. When using the other attributes it maybe clearer to use {@code classes()}
* instead of {@code value()}. Note: both attributes are applied (i.e. a union of classes from both attributes).
*
* @return collection of classes
*/
Class<?>[] classes() default {};
/**
* Optional list of package names. All records in the packages will get processed as if they were listed as
* classes to include.
*
* @return collection of package names
*/
String[] packages() default {};
/**
* Pattern used to generate the package for the generated class. The value is the literal package name however
* two replacement values can be used. '@' is replaced with the package of the {@code Include} annotation. '*'
* is replaced with the package of the included class.
*
* @return package pattern
*/
String packagePattern() default "@";
}
@Retention(RetentionPolicy.SOURCE)
@Target({ ElementType.TYPE, ElementType.PACKAGE })
@Inherited
@interface Options {
/**
* The builder class name will be the name of the record (prefixed with any enclosing class) plus this suffix.
* E.g. if the record name is "Foo", the builder will be named "FooBuilder".
*/
String suffix() default "Builder";
/**
* Used by {@code RecordInterface}. The generated record will have the same name as the annotated interface plus
* this suffix. E.g. if the interface name is "Foo", the record will be named "FooRecord".
*/
String interfaceSuffix() default "Record";
/**
* The name to use for the copy builder
*/
String copyMethodName() default "builder";
/**
* The name to use for the builder
*/
String builderMethodName() default "builder";
/**
* The name to use for the build method
*/
String buildMethodName() default "build";
/**
* The name to use for the from-to-wither method
*/
String fromMethodName() default "from";
/**
* The name to use for the method that returns the record components as a stream
*/
String componentsMethodName() default "stream";
/**
* If true, a "With" interface is generated and an associated static factory
*/
boolean enableWither() default true;
/**
* The name to use for the nested With class
*/
String withClassName() default "With";
/**
* The prefix to use for the methods in the With class
*/
String withClassMethodPrefix() default "with";
/**
* Return the comment to place at the top of generated files. Return null or an empty string for no comment.
*/
String fileComment() default "Auto generated by io.soabase.recordbuilder.core.RecordBuilder: https://github.com/Randgalt/record-builder";
/**
* Return the file indent to use
*/
String fileIndent() default " ";
/**
* If the record is declared inside another class, the outer class's name will be prefixed to the builder name
* if this returns true.
*/
boolean prefixEnclosingClassNames() default true;
/**
* If true, any annotations (if applicable) on record components are copied to the builder methods
*
* @return true/false
*/
boolean inheritComponentAnnotations() default true;
/**
* Set the default value of {@code Optional} record components to {@code Optional.empty()}
*/
boolean emptyDefaultForOptional() default true;
/**
* Add non-optional setter methods for optional record components.
*/
boolean addConcreteSettersForOptional() default false;
/**
* Add not-null checks for record components annotated with any annotation named either "NotNull", "NoNull", or
* "NonNull" (see {@link #interpretNotNullsPattern()} for the actual regex matching pattern).
*/
boolean interpretNotNulls() default false;
/**
* If {@link #interpretNotNulls()} is true, this is the regex pattern used to determine if an annotation name
* means "not null"
*/
String interpretNotNullsPattern() default "(?i)((notnull)|(nonnull)|(nonull))";
/**
* <p>
* Pass built records through the Java Validation API if it's available in the classpath.
* </p>
*
* <p>
* IMPORTANT: if this option is enabled you must include the {@code record-builder-validator} dependency in
* addition to {@code record-builder-core}. {@code record-builder-validator} is implemented completely via
* reflection and does not require other dependencies. Alternatively, you can define your own class with the
* package {@code package io.soabase.recordbuilder.validator;} named {@code RecordBuilderValidator} which has a
* public static method: {@code public static <T> T validate(T o)}.
* </p>
*/
boolean useValidationApi() default false;
/**
* Adds special handling for record components of type {@link java.util.List}, {@link java.util.Set},
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components of these
* types are passed through an added shim method that uses the corresponding immutable collection (e.g.
* {@code List.copyOf(o)}) or an empty immutable collection if the component is {@code null}.
*
* @see #useUnmodifiableCollections()
*/
boolean useImmutableCollections() default false;
/**
* Adds special handling for record components of type: {@link java.util.List}, {@link java.util.Set},
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components of these
* types are passed through an added shim method that uses the corresponding unmodifiable collection (e.g.
* {@code Collections.unmodifiableList(o)}) or an empty immutable collection if the component is {@code null}.
*
* <p>
* For backward compatibility, when {@link #useImmutableCollections()} returns {@code true}, this property is
* ignored.
*
* @see #useImmutableCollections()
*
* @since 37
*/
boolean useUnmodifiableCollections() default false;
/**
* When enabled, collection types ({@code List}, {@code Set} and {@code Map}) are handled specially. The setters
* for these types now create an internal collection and items are added to that collection. Additionally,
* "adder" methods prefixed with {@link #singleItemBuilderPrefix()} are created to add single items to these
* collections.
*/
boolean addSingleItemCollectionBuilders() default false;
/**
* The prefix for adder methods when {@link #addSingleItemCollectionBuilders()} is enabled
*/
String singleItemBuilderPrefix() default "add";
/**
* When enabled, adds functional methods to the nested "With" class (such as {@code map()} and
* {@code accept()}).
*/
boolean addFunctionalMethodsToWith() default false;
/**
* If set, all builder setter methods will be prefixed with this string. Camel-casing will still be enforced, so
* if this option is set to "set" a field named "myField" will get a corresponding setter named "setMyField".
*/
String setterPrefix() default "";
/**
* If true, getters will be generated for the Builder class.
*/
boolean enableGetters() default true;
/**
* If set, all builder getter methods will be prefixed with this string. Camel-casing will still be enforced, so
* if this option is set to "get", a field named "myField" will get a corresponding getter named "getMyField".
*/
String getterPrefix() default "";
/**
* If set, all boolean builder getter methods will be prefixed with this string. Camel-casing will still be
* enforced, so if this option is set to "is", a field named "myField" will get a corresponding getter named
* "isMyField".
*/
String booleanPrefix() default "";
/**
* If set, the Builder will contain an internal interface with this name. This interface contains getters for
* all the fields in the Record prefixed with the value supplied in {@link this.getterPrefix} and
* {@link this.booleanPrefix}. This interface can be implemented by the original Record to have proper
* bean-style prefixed getters. Please note that unless either of the aforementioned prefixes are set, this
* option does nothing.
*/
String beanClassName() default "";
/**
* If true, generated classes are annotated with {@code RecordBuilderGenerated} which has a retention policy of
* {@code CLASS}. This ensures that analyzers such as Jacoco will ignore the generated class.
*/
boolean addClassRetainedGenerated() default false;
/**
* The {@link #fromMethodName} method instantiates an internal private class. This is the name of that class.
*/
String fromWithClassName() default "_FromWith";
/**
* If true, a functional-style builder is added so that record instances can be instantiated without
* {@code new}.
*/
boolean addStaticBuilder() default true;
/**
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
* uses an internal class to track changes to lists. This is the name of that class.
*/
String mutableListClassName() default "_MutableList";
/**
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
* uses an internal class to track changes to sets. This is the name of that class.
*/
String mutableSetClassName() default "_MutableSet";
/**
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
* uses an internal class to track changes to maps. This is the name of that class.
*/
String mutableMapClassName() default "_MutableMap";
/**
* Any additional {@link javax.lang.model.element.Modifier} you wish to apply to the builder. For example to
* make the builder public when the record is package protect.
*/
Modifier[] builderClassModifiers() default {};
}
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.ANNOTATION_TYPE)
@Inherited
@interface Template {
RecordBuilder.Options options();
boolean asRecordInterface() default false;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.core;
import java.lang.annotation.*;
/**
* An alternate form of {@code @RecordBuilder} that has most optional features turned on
*/
@RecordBuilder.Template(options = @RecordBuilder.Options(interpretNotNulls = true, useImmutableCollections = true, addSingleItemCollectionBuilders = true, addFunctionalMethodsToWith = true, addClassRetainedGenerated = true))
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface RecordBuilderFull {
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.core;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
/**
* Jacoco ignores classes and methods annotated with `*Generated`
*/
@Target({ PACKAGE, TYPE, METHOD, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, PARAMETER })
@Retention(RetentionPolicy.CLASS)
public @interface RecordBuilderGenerated {
}

View File

@@ -1,98 +0,0 @@
/**
* Copyright 2016 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.core;
public interface RecordBuilderMetaData {
/**
* If you want to use your own meta data instance:
* <ul>
* <li>create a class that implements {@code RecordBuilderMetaData}</li>
* <li>When compiling, make sure that compiled class is in the processor path</li>
* <li>Add a "metaDataClass" compiler option with the class name. E.g. {@code javac ... -AmetaDataClass=foo.bar.MyMetaData}</li>
* </ul>
*/
String JAVAC_OPTION_NAME = "metaDataClass";
/**
* The default meta data instance
*/
RecordBuilderMetaData DEFAULT = new RecordBuilderMetaData() {};
/**
* The builder class name will be the name of the record (prefixed with any enclosing class) plus this suffix. E.g.
* if the record name is "Foo", the builder will be named "FooBuilder".
*
* @return suffix
*/
default String suffix() {
return "Builder";
}
/**
* The name to use for the copy builder
*
* @return copy builder name
*/
default String copyMethodName() {
return builderMethodName();
}
/**
* The name to use for the builder
*
* @return builder name
*/
default String builderMethodName() {
return "builder";
}
/**
* The name to use for the build method
*
* @return build method
*/
default String buildMethodName() {
return "build";
}
/**
* Return the comment to place at the top of generated files. Return null or an empty string for no comment.
*
* @return comment or empty
*/
default String fileComment() {
return "Auto generated by RecordBuilder: https://github.com/Randgalt/record-builder";
}
/**
* Return the file indent to use
*
* @return file index
*/
default String fileIndent() {
return " ";
}
/**
* If the record is declared inside of another class, the outer class's name will
* be prefixed to the builder name if this returns true.
*
* @return true/false
*/
default boolean prefixEnclosingClassNames() {
return true;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.core;
import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface RecordInterface {
boolean addRecordBuilder() default true;
@Target({ ElementType.TYPE, ElementType.PACKAGE })
@Retention(RetentionPolicy.SOURCE)
@Inherited
@interface Include {
/**
* @return collection of classes to include
*/
Class<?>[] value() default {};
/**
* Synonym for {@code value()}. When using the other attributes it maybe clearer to use {@code classes()}
* instead of {@code value()}. Note: both attributes are applied (i.e. a union of classes from both attributes).
*
* @return collection of classes
*/
Class<?>[] classes() default {};
/**
* If true the generated record is annotated with {@code @RecordBuilder}
*
* @return true/false
*/
boolean addRecordBuilder() default true;
/**
* Pattern used to generate the package for the generated class. The value is the literal package name however
* two replacement values can be used. '@' is replaced with the package of the {@code Include} annotation. '*'
* is replaced with the package of the included class.
*
* @return package pattern
*/
String packagePattern() default "@";
}
}

View File

@@ -1,14 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 The original author or authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the 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>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>1.1.ea</version>
<version>38-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>record-builder-processor</artifactId>
<properties>
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
</properties>
<dependencies>
<dependency>
<groupId>com.squareup</groupId>
@@ -24,30 +45,12 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<proc>none</proc>
</configuration>
</plugin>
<!--plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<artifactSet>
<includes>
<include>com.squareup:javapoet</include>
<include>io.soabase.record-builder:record-builder-processor</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>com.squareup.javapoet</pattern>
<shadedPattern>io.soabase.com.squareup.javapoet</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin-->
</plugins>
</build>
</project>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2016 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -0,0 +1,396 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.processor;
import com.squareup.javapoet.*;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.lang.model.element.Modifier;
import java.util.*;
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
class CollectionBuilderUtils {
private final boolean useImmutableCollections;
private final boolean useUnmodifiableCollections;
private final boolean addSingleItemCollectionBuilders;
private final boolean addClassRetainedGenerated;
private final String listShimName;
private final String mapShimName;
private final String setShimName;
private final String collectionShimName;
private final String listMakerMethodName;
private final String mapMakerMethodName;
private final String setMakerMethodName;
private boolean needsListShim;
private boolean needsMapShim;
private boolean needsSetShim;
private boolean needsCollectionShim;
private boolean needsListMutableMaker;
private boolean needsMapMutableMaker;
private boolean needsSetMutableMaker;
private static final Class<?> listType = List.class;
private static final Class<?> mapType = Map.class;
private static final Class<?> setType = Set.class;
private static final Class<?> collectionType = Collection.class;
private static final Class<?> collectionsType = Collections.class;
private static final TypeName listTypeName = TypeName.get(listType);
private static final TypeName mapTypeName = TypeName.get(mapType);
private static final TypeName setTypeName = TypeName.get(setType);
private static final TypeName collectionTypeName = TypeName.get(collectionType);
private static final TypeName collectionsTypeName = TypeName.get(collectionsType);
private static final TypeVariableName tType = TypeVariableName.get("T");
private static final TypeVariableName kType = TypeVariableName.get("K");
private static final TypeVariableName vType = TypeVariableName.get("V");
private static final ParameterizedTypeName parameterizedListType = ParameterizedTypeName
.get(ClassName.get(List.class), tType);
private static final ParameterizedTypeName parameterizedMapType = ParameterizedTypeName
.get(ClassName.get(Map.class), kType, vType);
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName
.get(ClassName.get(Set.class), tType);
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName
.get(ClassName.get(Collection.class), tType);
private static final Class<?> mutableListType = ArrayList.class;
private static final Class<?> mutableMapType = HashMap.class;
private static final Class<?> mutableSetType = HashSet.class;
private static final ClassName mutableListTypeName = ClassName.get(mutableListType);
private static final ClassName mutableMapTypeName = ClassName.get(mutableMapType);
private static final ClassName mutableSetTypeName = ClassName.get(mutableSetType);
private final TypeSpec mutableListSpec;
private final TypeSpec mutableSetSpec;
private final TypeSpec mutableMapSpec;
CollectionBuilderUtils(List<RecordClassType> recordComponents, RecordBuilder.Options metaData) {
useImmutableCollections = metaData.useImmutableCollections();
useUnmodifiableCollections = !useImmutableCollections && metaData.useUnmodifiableCollections();
addSingleItemCollectionBuilders = metaData.addSingleItemCollectionBuilders();
addClassRetainedGenerated = metaData.addClassRetainedGenerated();
listShimName = disambiguateGeneratedMethodName(recordComponents, "__list", 0);
mapShimName = disambiguateGeneratedMethodName(recordComponents, "__map", 0);
setShimName = disambiguateGeneratedMethodName(recordComponents, "__set", 0);
collectionShimName = disambiguateGeneratedMethodName(recordComponents, "__collection", 0);
listMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureListMutable", 0);
setMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureSetMutable", 0);
mapMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureMapMutable", 0);
mutableListSpec = buildMutableCollectionSubType(metaData.mutableListClassName(), mutableListTypeName,
parameterizedListType, tType);
mutableSetSpec = buildMutableCollectionSubType(metaData.mutableSetClassName(), mutableSetTypeName,
parameterizedSetType, tType);
mutableMapSpec = buildMutableCollectionSubType(metaData.mutableMapClassName(), mutableMapTypeName,
parameterizedMapType, kType, vType);
}
enum SingleItemsMetaDataMode {
STANDARD, STANDARD_FOR_SETTER, EXCLUDE_WILDCARD_TYPES
}
record SingleItemsMetaData(Class<?> singleItemCollectionClass, List<TypeName> typeArguments, TypeName wildType) {
}
Optional<SingleItemsMetaData> singleItemsMetaData(RecordClassType component, SingleItemsMetaDataMode mode) {
if (addSingleItemCollectionBuilders
&& (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)) {
Class<?> collectionClass = null;
ClassName wildcardClass = null;
int typeArgumentQty = 0;
if (isList(component)) {
collectionClass = mutableListType;
wildcardClass = ClassName.get(Collection.class);
typeArgumentQty = 1;
} else if (isSet(component)) {
collectionClass = mutableSetType;
wildcardClass = ClassName.get(Collection.class);
typeArgumentQty = 1;
} else if (isMap(component)) {
collectionClass = mutableMapType;
wildcardClass = (ClassName) component.rawTypeName();
typeArgumentQty = 2;
}
var hasWildcardTypeArguments = hasWildcardTypeArguments(parameterizedTypeName, typeArgumentQty);
if (collectionClass != null) {
return switch (mode) {
case STANDARD -> singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass,
typeArgumentQty);
case STANDARD_FOR_SETTER -> {
if (hasWildcardTypeArguments) {
yield Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments,
component.typeName()));
}
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass,
typeArgumentQty);
}
case EXCLUDE_WILDCARD_TYPES -> {
if (hasWildcardTypeArguments) {
yield Optional.empty();
}
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass,
typeArgumentQty);
}
};
}
}
return Optional.empty();
}
boolean isImmutableCollection(RecordClassType component) {
return (useImmutableCollections || useUnmodifiableCollections)
&& (isList(component) || isMap(component) || isSet(component) || isCollection(component));
}
boolean isList(RecordClassType component) {
return component.rawTypeName().equals(listTypeName);
}
boolean isMap(RecordClassType component) {
return component.rawTypeName().equals(mapTypeName);
}
boolean isSet(RecordClassType component) {
return component.rawTypeName().equals(setTypeName);
}
private boolean isCollection(RecordClassType component) {
return component.rawTypeName().equals(collectionTypeName);
}
void addShimCall(CodeBlock.Builder builder, RecordClassType component) {
if (useImmutableCollections || useUnmodifiableCollections) {
if (isList(component)) {
needsListShim = true;
needsListMutableMaker = true;
builder.add("$L($L)", listShimName, component.name());
} else if (isMap(component)) {
needsMapShim = true;
needsMapMutableMaker = true;
builder.add("$L($L)", mapShimName, component.name());
} else if (isSet(component)) {
needsSetShim = true;
needsSetMutableMaker = true;
builder.add("$L($L)", setShimName, component.name());
} else if (isCollection(component)) {
needsCollectionShim = true;
builder.add("$L($L)", collectionShimName, component.name());
} else {
builder.add("$L", component.name());
}
} else {
builder.add("$L", component.name());
}
}
String shimName(RecordClassType component) {
if (isList(component)) {
return listShimName;
} else if (isMap(component)) {
return mapShimName;
} else if (isSet(component)) {
return setShimName;
} else if (isCollection(component)) {
return collectionShimName;
} else {
throw new IllegalArgumentException(component + " is not a supported collection type");
}
}
String mutableMakerName(RecordClassType component) {
if (isList(component)) {
return listMakerMethodName;
} else if (isMap(component)) {
return mapMakerMethodName;
} else if (isSet(component)) {
return setMakerMethodName;
} else {
throw new IllegalArgumentException(component + " is not a supported collection type");
}
}
void addShims(TypeSpec.Builder builder) {
if (!useImmutableCollections && !useUnmodifiableCollections) {
return;
}
if (needsListShim) {
builder.addMethod(
buildShimMethod(listShimName, listTypeName, collectionType, parameterizedListType, tType));
}
if (needsSetShim) {
builder.addMethod(buildShimMethod(setShimName, setTypeName, collectionType, parameterizedSetType, tType));
}
if (needsMapShim) {
builder.addMethod(buildShimMethod(mapShimName, mapTypeName, mapType, parameterizedMapType, kType, vType));
}
if (needsCollectionShim) {
builder.addMethod(buildCollectionsShimMethod());
}
}
void addMutableMakers(TypeSpec.Builder builder) {
if (!useImmutableCollections && !useUnmodifiableCollections) {
return;
}
if (needsListMutableMaker) {
builder.addMethod(
buildMutableMakerMethod(listMakerMethodName, mutableListSpec.name, parameterizedListType, tType));
builder.addType(mutableListSpec);
}
if (needsSetMutableMaker) {
builder.addMethod(
buildMutableMakerMethod(setMakerMethodName, mutableSetSpec.name, parameterizedSetType, tType));
builder.addType(mutableSetSpec);
}
if (needsMapMutableMaker) {
builder.addMethod(buildMutableMakerMethod(mapMakerMethodName, mutableMapSpec.name, parameterizedMapType,
kType, vType));
builder.addType(mutableMapSpec);
}
}
private Optional<SingleItemsMetaData> singleItemsMetaDataWithWildType(ParameterizedTypeName parameterizedTypeName,
Class<?> collectionClass, ClassName wildcardClass, int typeArgumentQty) {
TypeName wildType;
if (typeArgumentQty == 1) {
wildType = ParameterizedTypeName.get(wildcardClass,
WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)));
} else { // if (typeArgumentQty == 2)
wildType = ParameterizedTypeName.get(wildcardClass,
WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)),
WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(1)));
}
return Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, wildType));
}
private boolean hasWildcardTypeArguments(ParameterizedTypeName parameterizedTypeName, int argumentCount) {
for (int i = 0; i < argumentCount; ++i) {
if (parameterizedTypeName.typeArguments.size() > i) {
if (parameterizedTypeName.typeArguments.get(i) instanceof WildcardTypeName) {
return true;
}
}
}
return false;
}
private String disambiguateGeneratedMethodName(List<RecordClassType> recordComponents, String baseName, int index) {
var name = (index == 0) ? baseName : (baseName + index);
if (recordComponents.stream().anyMatch(component -> component.name().equals(name))) {
return disambiguateGeneratedMethodName(recordComponents, baseName, index + 1);
}
return name;
}
private MethodSpec buildShimMethod(String name, TypeName mainType, Class<?> abstractType,
ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
var code = buildShimMethodBody(mainType, parameterizedType);
TypeName[] wildCardTypeArguments = parameterizedType.typeArguments.stream().map(WildcardTypeName::subtypeOf)
.toList().toArray(new TypeName[0]);
var extendedParameterizedType = ParameterizedTypeName.get(ClassName.get(abstractType), wildCardTypeArguments);
return MethodSpec.methodBuilder(name).addAnnotation(generatedRecordBuilderAnnotation)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC).addTypeVariables(Arrays.asList(typeVariables))
.returns(parameterizedType).addParameter(extendedParameterizedType, "o").addStatement(code).build();
}
private CodeBlock buildShimMethodBody(TypeName mainType, ParameterizedTypeName parameterizedType) {
if (!useUnmodifiableCollections) {
return CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
}
if (mainType.equals(listTypeName)) {
return CodeBlock.of("return (o != null) ? $T.<$T>unmodifiableList(($T) o) : $T.<$T>emptyList()",
collectionsTypeName, tType, parameterizedType, collectionsTypeName, tType);
}
if (mainType.equals(setTypeName)) {
return CodeBlock.of("return (o != null) ? $T.<$T>unmodifiableSet(($T) o) : $T.<$T>emptySet()",
collectionsTypeName, tType, parameterizedType, collectionsTypeName, tType);
}
if (mainType.equals(mapTypeName)) {
return CodeBlock.of("return (o != null) ? $T.<$T, $T>unmodifiableMap(($T) o) : $T.<$T, $T>emptyMap()",
collectionsTypeName, kType, vType, parameterizedType, collectionsTypeName, kType, vType);
}
throw new IllegalStateException("Cannot build shim method for" + mainType);
}
private MethodSpec buildMutableMakerMethod(String name, String mutableCollectionType,
ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
var nullCase = CodeBlock.of("if (o == null) return new $L<>()", mutableCollectionType);
var isMutableCase = CodeBlock.of("if (o instanceof $L) return o", mutableCollectionType);
var defaultCase = CodeBlock.of("return new $L<>(o)", mutableCollectionType);
return MethodSpec.methodBuilder(name).addAnnotation(generatedRecordBuilderAnnotation)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC).addTypeVariables(Arrays.asList(typeVariables))
.returns(parameterizedType).addParameter(parameterizedType, "o").addStatement(nullCase)
.addStatement(isMutableCase).addStatement(defaultCase).build();
}
private TypeSpec buildMutableCollectionSubType(String className, ClassName mutableCollectionType,
ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
TypeName[] typeArguments = new TypeName[] {};
typeArguments = Arrays.stream(typeVariables).toList().toArray(typeArguments);
TypeSpec.Builder builder = TypeSpec.classBuilder(className).addAnnotation(generatedRecordBuilderAnnotation)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.superclass(ParameterizedTypeName.get(mutableCollectionType, typeArguments))
.addTypeVariables(Arrays.asList(typeVariables))
.addMethod(MethodSpec.constructorBuilder().addAnnotation(generatedRecordBuilderAnnotation)
.addStatement("super()").build())
.addMethod(MethodSpec.constructorBuilder().addAnnotation(generatedRecordBuilderAnnotation)
.addParameter(parameterizedType, "o").addStatement("super(o)").build());
if (addClassRetainedGenerated) {
builder.addAnnotation(recordBuilderGeneratedAnnotation);
}
return builder.build();
}
private MethodSpec buildCollectionsShimMethod() {
var code = buildCollectionShimMethodBody();
return MethodSpec.methodBuilder(collectionShimName).addAnnotation(generatedRecordBuilderAnnotation)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC).addTypeVariable(tType)
.returns(parameterizedCollectionType).addParameter(parameterizedCollectionType, "o").addCode(code)
.build();
}
private CodeBlock buildCollectionShimMethodBody() {
if (!useUnmodifiableCollections) {
return CodeBlock.builder().add("if (o instanceof Set) {\n").indent()
.addStatement("return $T.copyOf(o)", setTypeName).unindent().addStatement("}")
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listTypeName, listTypeName).build();
}
return CodeBlock.builder().beginControlFlow("if (o instanceof $T)", listType)
.addStatement("return $T.<$T>unmodifiableList(($T) o)", collectionsTypeName, tType,
parameterizedListType)
.endControlFlow().beginControlFlow("if (o instanceof $T)", setType)
.addStatement("return $T.<$T>unmodifiableSet(($T) o)", collectionsTypeName, tType, parameterizedSetType)
.endControlFlow().addStatement("return $T.<$T>emptyList()", collectionsTypeName, tType).build();
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2016 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,14 +19,66 @@ import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeVariableName;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.TypeMirror;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class ElementUtils {
public static Optional<? extends AnnotationMirror> findAnnotationMirror(ProcessingEnvironment processingEnv,
Element element, String annotationClass) {
return processingEnv.getElementUtils().getAllAnnotationMirrors(element).stream()
.filter(e -> e.getAnnotationType().toString().equals(annotationClass)).findFirst();
}
public static Optional<? extends AnnotationValue> getAnnotationValue(
Map<? extends ExecutableElement, ? extends AnnotationValue> values, String name) {
return values.entrySet().stream().filter(e -> e.getKey().getSimpleName().toString().equals(name))
.map(Map.Entry::getValue).findFirst();
}
@SuppressWarnings("unchecked")
public static List<TypeMirror> getAttributeTypeMirrorList(AnnotationValue attribute) {
List<? extends AnnotationValue> values = (attribute != null)
? (List<? extends AnnotationValue>) attribute.getValue() : Collections.emptyList();
return values.stream().map(v -> (TypeMirror) v.getValue()).collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
public static List<String> getAttributeStringList(AnnotationValue attribute) {
List<? extends AnnotationValue> values = (attribute != null)
? (List<? extends AnnotationValue>) attribute.getValue() : Collections.emptyList();
return values.stream().map(v -> (String) v.getValue()).collect(Collectors.toList());
}
public static boolean getBooleanAttribute(AnnotationValue attribute) {
Object value = (attribute != null) ? attribute.getValue() : null;
if (value != null) {
return Boolean.parseBoolean(String.valueOf(value));
}
return false;
}
public static String getStringAttribute(AnnotationValue attribute, String defaultValue) {
Object value = (attribute != null) ? attribute.getValue() : null;
if (value != null) {
return String.valueOf(value);
}
return defaultValue;
}
public static String getPackageName(TypeElement typeElement) {
while (typeElement.getNestingKind().isNested()) {
Element enclosingElement = typeElement.getEnclosingElement();
@@ -36,11 +88,13 @@ public class ElementUtils {
break;
}
}
String name = typeElement.getEnclosingElement().toString();
return !name.equals("unnamed package") ? name : "";
String name = typeElement.getQualifiedName().toString();
int index = name.lastIndexOf(".");
return (index > -1) ? name.substring(0, index) : "";
}
public static ClassType getClassType(String packageName, String simpleName, List<? extends TypeParameterElement> typeParameters) {
public static ClassType getClassType(String packageName, String simpleName,
List<? extends TypeParameterElement> typeParameters) {
return getClassType(ClassName.get(packageName, simpleName), typeParameters);
}
@@ -48,7 +102,8 @@ public class ElementUtils {
return getClassType(ClassName.get(typeElement), typeParameters);
}
public static ClassType getClassType(ClassName builderClassName, List<? extends TypeParameterElement> typeParameters) {
public static ClassType getClassType(ClassName builderClassName,
List<? extends TypeParameterElement> typeParameters) {
if (typeParameters.isEmpty()) {
return new ClassType(builderClassName, builderClassName.simpleName());
}
@@ -56,8 +111,55 @@ public class ElementUtils {
return new ClassType(ParameterizedTypeName.get(builderClassName, typeNames), builderClassName.simpleName());
}
public static ClassType getClassType(RecordComponentElement recordComponent) {
return new ClassType(TypeName.get(recordComponent.asType()), recordComponent.getSimpleName().toString());
public static RecordClassType getRecordClassType(ProcessingEnvironment processingEnv,
RecordComponentElement recordComponent, List<? extends AnnotationMirror> accessorAnnotations,
List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
var typeName = TypeName.get(recordComponent.asType());
var rawTypeName = TypeName.get(processingEnv.getTypeUtils().erasure(recordComponent.asType()));
return new RecordClassType(typeName, rawTypeName, recordComponent.getSimpleName().toString(),
accessorAnnotations, canonicalConstructorAnnotations);
}
public static String getWithMethodName(ClassType component, String prefix) {
var name = component.name();
if (name.length() == 1) {
return prefix + name.toUpperCase();
}
return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
public static String getBuilderName(TypeElement element, RecordBuilder.Options metaData, ClassType classType,
String suffix) {
// generate the class name
var baseName = classType.name() + suffix;
return metaData.prefixEnclosingClassNames() ? (getBuilderNamePrefix(element.getEnclosingElement()) + baseName)
: baseName;
}
public static Optional<? extends Element> findCanonicalConstructor(TypeElement record) {
if (record.getKind() != ElementKind.RECORD) {
return Optional.empty();
}
// based on
// https://github.com/openjdk/jdk/pull/3556/files#diff-a6270f4b50989abe733607c69038b2036306d13f77276af005d023b7fc57f1a2R2368
var componentList = record.getRecordComponents().stream().map(e -> e.asType().toString())
.collect(Collectors.toList());
return record.getEnclosedElements().stream().filter(element -> element.getKind() == ElementKind.CONSTRUCTOR)
.filter(element -> {
var parameters = ((ExecutableElement) element).getParameters();
var parametersList = parameters.stream().map(e -> e.asType().toString())
.collect(Collectors.toList());
return componentList.equals(parametersList);
}).findFirst();
}
private static String getBuilderNamePrefix(Element element) {
// prefix enclosing class names if nested in a class
if (element instanceof TypeElement) {
return getBuilderNamePrefix(element.getEnclosingElement()) + element.getSimpleName().toString();
}
return "";
}
private ElementUtils() {

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
class IncludeHelper {
private final boolean isValid;
private final List<TypeElement> classTypeElements;
private final Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues;
IncludeHelper(ProcessingEnvironment processingEnv, Element element, AnnotationMirror annotationMirror,
boolean packagesSupported) {
annotationValues = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirror);
var value = ElementUtils.getAnnotationValue(annotationValues, "value");
var classes = ElementUtils.getAnnotationValue(annotationValues, "classes");
var packages = ElementUtils.getAnnotationValue(annotationValues, "packages");
var isValid = true;
var classTypeElements = new ArrayList<TypeElement>();
if (value.isPresent() || classes.isPresent() || packages.isPresent()) {
var valueList = value.map(ElementUtils::getAttributeTypeMirrorList).orElseGet(List::of);
var classesList = classes.map(ElementUtils::getAttributeTypeMirrorList).orElseGet(List::of);
var packagesList = packages.map(ElementUtils::getAttributeStringList).orElseGet(List::of);
if (valueList.isEmpty() && classesList.isEmpty() && packagesList.isEmpty()) {
if (packagesSupported) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"At least one of \"value\", \"classes\" or \"packages\" required", element);
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"At least one of \"value\" or \"classes\" required", element);
}
isValid = false;
}
isValid = processList(processingEnv, isValid, element, valueList, classTypeElements);
isValid = processList(processingEnv, isValid, element, classesList, classTypeElements);
packages.ifPresent(annotationValue -> processPackages(processingEnv, classTypeElements, packagesList));
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not read attribute for annotation",
element);
isValid = false;
}
this.isValid = isValid;
this.classTypeElements = List.copyOf(classTypeElements);
}
Map<? extends ExecutableElement, ? extends AnnotationValue> getAnnotationValues() {
return annotationValues;
}
boolean isValid() {
return isValid;
}
List<TypeElement> getClassTypeElements() {
return classTypeElements;
}
private boolean processList(ProcessingEnvironment processingEnv, boolean isValid, Element element,
List<TypeMirror> list, ArrayList<TypeElement> classTypeElements) {
for (var typeMirror : list) {
TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
if (typeElement == null) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Could not get element for: " + typeMirror, element);
isValid = false;
} else {
classTypeElements.add(typeElement);
}
}
return isValid;
}
private void processPackages(ProcessingEnvironment processingEnv, List<TypeElement> classTypeElements,
List<String> packagesList) {
for (var packageName : packagesList) {
var packageElement = processingEnv.getElementUtils().getPackageElement(packageName);
for (var child : packageElement.getEnclosedElements()) {
if (child.getKind() == ElementKind.RECORD) {
classTypeElements.add((TypeElement) child);
}
}
}
}
}

View File

@@ -0,0 +1,956 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.processor;
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.EXCLUDE_WILDCARD_TYPES;
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.STANDARD_FOR_SETTER;
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import com.squareup.javapoet.*;
import io.soabase.recordbuilder.core.RecordBuilder;
class InternalRecordBuilderProcessor {
private final RecordBuilder.Options metaData;
private final ClassType recordClassType;
private final String packageName;
private final ClassType builderClassType;
private final List<TypeVariableName> typeVariables;
private final List<RecordClassType> recordComponents;
private final TypeSpec builderType;
private final TypeSpec.Builder builder;
private final String uniqueVarName;
private final Pattern notNullPattern;
private final CollectionBuilderUtils collectionBuilderUtils;
private static final TypeName overrideType = TypeName.get(Override.class);
private static final TypeName validType = ClassName.get("javax.validation", "Valid");
private static final TypeName validatorTypeName = ClassName.get("io.soabase.recordbuilder.validator",
"RecordBuilderValidator");
private static final TypeVariableName rType = TypeVariableName.get("R");
private final ProcessingEnvironment processingEnv;
InternalRecordBuilderProcessor(ProcessingEnvironment processingEnv, TypeElement record,
RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
this.processingEnv = processingEnv;
var recordActualPackage = ElementUtils.getPackageName(record);
this.metaData = metaData;
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
packageName = packageNameOpt.orElse(recordActualPackage);
builderClassType = ElementUtils.getClassType(packageName,
getBuilderName(record, metaData, recordClassType, metaData.suffix()), record.getTypeParameters());
typeVariables = record.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList());
recordComponents = buildRecordComponents(record);
uniqueVarName = getUniqueVarName();
notNullPattern = Pattern.compile(metaData.interpretNotNullsPattern());
collectionBuilderUtils = new CollectionBuilderUtils(recordComponents, this.metaData);
builder = TypeSpec.classBuilder(builderClassType.name()).addAnnotation(generatedRecordBuilderAnnotation)
.addModifiers(metaData.builderClassModifiers()).addTypeVariables(typeVariables);
if (metaData.addClassRetainedGenerated()) {
builder.addAnnotation(recordBuilderGeneratedAnnotation);
}
addVisibility(recordActualPackage.equals(packageName), record.getModifiers());
if (metaData.enableWither()) {
addWithNestedClass();
}
if (!metaData.beanClassName().isEmpty()) {
addBeanNestedClass();
}
addDefaultConstructor();
if (metaData.addStaticBuilder()) {
addStaticBuilder();
}
if (recordComponents.size() > 0) {
addAllArgsConstructor();
}
addStaticDefaultBuilderMethod();
addStaticCopyBuilderMethod();
if (metaData.enableWither()) {
addStaticFromWithMethod();
}
addStaticComponentsMethod();
addBuildMethod();
addToStringMethod();
addHashCodeMethod();
addEqualsMethod();
recordComponents.forEach(component -> {
add1Field(component);
add1SetterMethod(component);
if (metaData.enableGetters()) {
add1GetterMethod(component);
}
if (metaData.addConcreteSettersForOptional()) {
add1ConcreteOptionalSetterMethod(component);
}
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, EXCLUDE_WILDCARD_TYPES);
collectionMetaData.ifPresent(meta -> add1CollectionBuilders(meta, component));
});
collectionBuilderUtils.addShims(builder);
collectionBuilderUtils.addMutableMakers(builder);
builderType = builder.build();
}
String packageName() {
return packageName;
}
ClassType builderClassType() {
return builderClassType;
}
TypeSpec builderType() {
return builderType;
}
private void addVisibility(boolean builderIsInRecordPackage, Set<Modifier> modifiers) {
if (builderIsInRecordPackage) {
if (modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.PRIVATE)
|| modifiers.contains(Modifier.PROTECTED)) {
builder.addModifiers(Modifier.PUBLIC); // builders are top level classes - can only be public or
// package-private
}
// is package-private
} else {
builder.addModifiers(Modifier.PUBLIC);
}
}
private List<RecordClassType> buildRecordComponents(TypeElement record) {
var accessorAnnotations = record.getRecordComponents().stream().map(e -> e.getAccessor().getAnnotationMirrors())
.collect(Collectors.toList());
var canonicalConstructorAnnotations = ElementUtils.findCanonicalConstructor(record)
.map(constructor -> ((ExecutableElement) constructor).getParameters().stream()
.map(Element::getAnnotationMirrors).collect(Collectors.toList()))
.orElse(List.of());
var recordComponents = record.getRecordComponents();
return IntStream.range(0, recordComponents.size()).mapToObj(index -> {
var thisAccessorAnnotations = (accessorAnnotations.size() > index) ? accessorAnnotations.get(index)
: List.<AnnotationMirror> of();
var thisCanonicalConstructorAnnotations = (canonicalConstructorAnnotations.size() > index)
? canonicalConstructorAnnotations.get(index) : List.<AnnotationMirror> of();
return ElementUtils.getRecordClassType(processingEnv, recordComponents.get(index), thisAccessorAnnotations,
thisCanonicalConstructorAnnotations);
}).collect(Collectors.toList());
}
private void addWithNestedClass() {
/*
* Adds a nested interface that adds withers similar to:
*
* public class MyRecordBuilder { public interface With { // with methods } }
*/
var classBuilder = TypeSpec.interfaceBuilder(metaData.withClassName())
.addAnnotation(generatedRecordBuilderAnnotation)
.addJavadoc("Add withers to {@code $L}\n", recordClassType.name()).addModifiers(Modifier.PUBLIC)
.addTypeVariables(typeVariables);
if (metaData.addClassRetainedGenerated()) {
classBuilder.addAnnotation(recordBuilderGeneratedAnnotation);
}
recordComponents.forEach(component -> addNestedGetterMethod(classBuilder, component, component.name()));
addWithBuilderMethod(classBuilder);
addWithSuppliedBuilderMethod(classBuilder);
IntStream.range(0, recordComponents.size())
.forEach(index -> add1WithMethod(classBuilder, recordComponents.get(index), index));
if (metaData.addFunctionalMethodsToWith()) {
classBuilder.addType(buildFunctionalInterface("Function", true))
.addType(buildFunctionalInterface("Consumer", false))
.addMethod(buildFunctionalHandler("Function", "map", true))
.addMethod(buildFunctionalHandler("Consumer", "accept", false));
}
builder.addType(classBuilder.build());
}
private void addBeanNestedClass() {
/*
* Adds a nested interface that adds getters similar to:
*
* public class MyRecordBuilder { public interface Bean { // getter methods } }
*/
var classBuilder = TypeSpec.interfaceBuilder(metaData.beanClassName())
.addAnnotation(generatedRecordBuilderAnnotation)
.addJavadoc("Add getters to {@code $L}\n", recordClassType.name()).addModifiers(Modifier.PUBLIC)
.addTypeVariables(typeVariables);
recordComponents.forEach(component -> {
if (prefixedName(component, true).equals(component.name())) {
return;
}
addNestedGetterMethod(classBuilder, component, component.name());
add1PrefixedGetterMethod(classBuilder, component);
});
builder.addType(classBuilder.build());
}
private void addWithSuppliedBuilderMethod(TypeSpec.Builder classBuilder) {
/*
* Adds a method that returns a pre-filled copy builder similar to:
*
* default MyRecord with(Consumer<MyRecordBuilder> consumer) { MyRecordBuilder builder = with();
* consumer.accept(builder); return builder.build(); }
*/
var codeBlockBuilder = CodeBlock.builder().add("$T builder = with();\n", builderClassType.typeName())
.add("consumer.accept(builder);\n").add("return builder.$L();\n", metaData.buildMethodName());
var consumerType = ParameterizedTypeName.get(ClassName.get(Consumer.class), builderClassType.typeName());
var parameter = ParameterSpec.builder(consumerType, "consumer").build();
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
.addAnnotation(generatedRecordBuilderAnnotation)
.addJavadoc("Return a new record built from the builder passed to the given consumer")
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT).addParameter(parameter)
.returns(recordClassType.typeName()).addCode(codeBlockBuilder.build()).build();
classBuilder.addMethod(methodSpec);
}
private void addWithBuilderMethod(TypeSpec.Builder classBuilder) {
/*
* Adds a method that returns a pre-filled copy builder similar to:
*
* default MyRecordBuilder with() { return MyRecordBuilder.builder(r); }
*/
var codeBlockBuilder = CodeBlock.builder().add("return new $L$L(", builderClassType.name(),
typeVariables.isEmpty() ? "" : "<>");
addComponentCallsAsArguments(-1, codeBlockBuilder, false);
codeBlockBuilder.add(");");
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
.addAnnotation(generatedRecordBuilderAnnotation)
.addJavadoc("Return a new record builder using the current values")
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT).returns(builderClassType.typeName())
.addCode(codeBlockBuilder.build()).build();
classBuilder.addMethod(methodSpec);
}
private String getUniqueVarName() {
return getUniqueVarName("");
}
private String getUniqueVarName(String prefix) {
var name = prefix + "r";
var alreadyExists = recordComponents.stream().map(ClassType::name).anyMatch(n -> n.equals(name));
return alreadyExists ? getUniqueVarName(prefix + "_") : name;
}
private void add1WithMethod(TypeSpec.Builder classBuilder, RecordClassType component, int index) {
/*
* Adds a with method for the component similar to:
*
* default MyRecord withName(String name) { return new MyRecord(name, r.age()); }
*/
var codeBlockBuilder = CodeBlock.builder();
addNullCheckCodeBlock(codeBlockBuilder, index);
codeBlockBuilder.add("$[return ");
if (metaData.useValidationApi()) {
codeBlockBuilder.add("$T.validate(", validatorTypeName);
}
codeBlockBuilder.add("new $T(", recordClassType.typeName());
addComponentCallsAsArguments(index, codeBlockBuilder, false);
codeBlockBuilder.add(")");
if (metaData.useValidationApi()) {
codeBlockBuilder.add(")");
}
codeBlockBuilder.add(";$]");
var methodName = getWithMethodName(component, metaData.withClassMethodPrefix());
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
addConstructorAnnotations(component, parameterSpecBuilder);
var methodSpec = MethodSpec.methodBuilder(methodName).addAnnotation(generatedRecordBuilderAnnotation)
.addJavadoc("Return a new instance of {@code $L} with a new value for {@code $L}\n",
recordClassType.name(), component.name())
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT).addParameter(parameterSpecBuilder.build())
.addCode(codeBlockBuilder.build()).returns(recordClassType.typeName()).build();
classBuilder.addMethod(methodSpec);
}
private void add1PrefixedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component) {
/*
* Adds a get method for the component similar to:
*
* default MyRecord getName() { return name(); }
*/
var codeBlockBuilder = CodeBlock.builder();
codeBlockBuilder.add("$[return $L()$];", component.name());
var methodName = prefixedName(component, true);
var methodSpec = MethodSpec.methodBuilder(methodName).addAnnotation(generatedRecordBuilderAnnotation)
.addJavadoc("Returns the value of {@code $L}\n", component.name())
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT).addCode(codeBlockBuilder.build())
.returns(component.typeName()).build();
classBuilder.addMethod(methodSpec);
}
private void addComponentCallsAsArguments(int index, CodeBlock.Builder codeBlockBuilder, boolean usePrefixedName) {
IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> {
if (parameterIndex > 0) {
codeBlockBuilder.add(", ");
}
RecordClassType parameterComponent = recordComponents.get(parameterIndex);
if (parameterIndex == index) {
collectionBuilderUtils.addShimCall(codeBlockBuilder, parameterComponent);
} else {
codeBlockBuilder.add("$L()",
usePrefixedName ? prefixedName(parameterComponent, true) : parameterComponent.name());
}
});
}
private void addDefaultConstructor() {
/*
* Adds a default constructor similar to:
*
* private MyRecordBuilder() { }
*/
var constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE)
.addAnnotation(generatedRecordBuilderAnnotation).build();
builder.addMethod(constructor);
}
private void addStaticBuilder() {
/*
* Adds an static builder similar to:
*
* public static MyRecord(int p1, T p2, ...) { return new MyRecord(p1, p2, ...); }
*/
CodeBlock codeBlock = buildCodeBlock();
var builder = MethodSpec.methodBuilder(recordClassType.name())
.addJavadoc("Static constructor/builder. Can be used instead of new $L(...)\n", recordClassType.name())
.addTypeVariables(typeVariables).addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addAnnotation(generatedRecordBuilderAnnotation).returns(recordClassType.typeName()).addCode(codeBlock);
recordComponents.forEach(component -> {
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
addConstructorAnnotations(component, parameterSpecBuilder);
builder.addParameter(parameterSpecBuilder.build());
});
this.builder.addMethod(builder.build());
}
private void addNullCheckCodeBlock(CodeBlock.Builder builder) {
if (metaData.interpretNotNulls()) {
for (int i = 0; i < recordComponents.size(); ++i) {
addNullCheckCodeBlock(builder, i);
}
}
}
private void addNullCheckCodeBlock(CodeBlock.Builder builder, int index) {
if (metaData.interpretNotNulls()) {
var component = recordComponents.get(index);
if (!collectionBuilderUtils.isImmutableCollection(component)) {
if (!component.typeName().isPrimitive() && isNullAnnotated(component)) {
builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(),
component.name() + " is required");
}
}
}
}
private boolean isNullAnnotated(RecordClassType component) {
return component.getCanonicalConstructorAnnotations().stream().anyMatch(annotation -> notNullPattern
.matcher(annotation.getAnnotationType().asElement().getSimpleName().toString()).matches());
}
private void addAllArgsConstructor() {
/*
* Adds an all-args constructor similar to:
*
* private MyRecordBuilder(int p1, T p2, ...) { this.p1 = p1; this.p2 = p2; ... }
*/
var constructorBuilder = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE)
.addAnnotation(generatedRecordBuilderAnnotation);
recordComponents.forEach(component -> {
constructorBuilder.addParameter(component.typeName(), component.name());
constructorBuilder.addStatement("this.$L = $L", component.name(), component.name());
});
builder.addMethod(constructorBuilder.build());
}
private void addToStringMethod() {
/*
* add a toString() method similar to:
*
* @Override public String toString() { return "MyRecord[p1=blah, p2=blah]"; }
*/
var codeBuilder = CodeBlock.builder().add("return \"$L[", builderClassType.name());
IntStream.range(0, recordComponents.size()).forEach(index -> {
if (index > 0) {
codeBuilder.add(", ");
}
String name = recordComponents.get(index).name();
codeBuilder.add("$L=\" + $L + \"", name, name);
});
codeBuilder.add("]\"");
var methodSpec = MethodSpec.methodBuilder("toString").addModifiers(Modifier.PUBLIC)
.addAnnotation(generatedRecordBuilderAnnotation).addAnnotation(Override.class).returns(String.class)
.addStatement(codeBuilder.build()).build();
builder.addMethod(methodSpec);
}
private void addHashCodeMethod() {
/*
* add a hashCode() method similar to:
*
* @Override public int hashCode() { return Objects.hash(p1, p2); }
*/
var codeBuilder = CodeBlock.builder().add("return $T.hash(", Objects.class);
IntStream.range(0, recordComponents.size()).forEach(index -> {
if (index > 0) {
codeBuilder.add(", ");
}
codeBuilder.add("$L", recordComponents.get(index).name());
});
codeBuilder.add(")");
var methodSpec = MethodSpec.methodBuilder("hashCode").addModifiers(Modifier.PUBLIC)
.addAnnotation(generatedRecordBuilderAnnotation).addAnnotation(Override.class).returns(TypeName.INT)
.addStatement(codeBuilder.build()).build();
builder.addMethod(methodSpec);
}
private void addEqualsMethod() {
/*
* add an equals() method similar to:
*
* @Override public boolean equals(Object o) { if (this == o) || ((o instanceof MyRecordBuilder b) &&
* Objects.equals(p1, b.p1) && Objects.equals(p2, b.p2)); }
*/
var codeBuilder = CodeBlock.builder();
codeBuilder.add("return (this == o) || (");
if (typeVariables.isEmpty()) {
codeBuilder.add("(o instanceof $L $L)", builderClassType.name(), uniqueVarName);
} else {
String wildcardList = typeVariables.stream().map(__ -> "?").collect(Collectors.joining(","));
codeBuilder.add("(o instanceof $L<$L> $L)", builderClassType.name(), wildcardList, uniqueVarName);
}
recordComponents.forEach(recordComponent -> {
String name = recordComponent.name();
if (recordComponent.typeName().isPrimitive()) {
codeBuilder.add("\n&& ($L == $L.$L)", name, uniqueVarName, name);
} else {
codeBuilder.add("\n&& $T.equals($L, $L.$L)", Objects.class, name, uniqueVarName, name);
}
});
codeBuilder.add(")");
var methodSpec = MethodSpec.methodBuilder("equals").addParameter(Object.class, "o")
.addModifiers(Modifier.PUBLIC).addAnnotation(generatedRecordBuilderAnnotation)
.addAnnotation(Override.class).returns(TypeName.BOOLEAN).addStatement(codeBuilder.build()).build();
builder.addMethod(methodSpec);
}
private void addBuildMethod() {
/*
* Adds the build method that generates the record similar to:
*
* public MyRecord build() { return new MyRecord(p1, p2, ...); }
*/
CodeBlock codeBlock = buildCodeBlock();
var methodSpec = MethodSpec.methodBuilder(metaData.buildMethodName())
.addJavadoc("Return a new record instance with all fields set to the current values in this builder\n")
.addModifiers(Modifier.PUBLIC).addAnnotation(generatedRecordBuilderAnnotation)
.returns(recordClassType.typeName()).addCode(codeBlock).build();
builder.addMethod(methodSpec);
}
private CodeBlock buildCodeBlock() {
/*
* Builds the code block for allocating the record from its parts
*/
var codeBuilder = CodeBlock.builder();
IntStream.range(0, recordComponents.size()).forEach(index -> {
var recordComponent = recordComponents.get(index);
if (collectionBuilderUtils.isImmutableCollection(recordComponent)) {
codeBuilder.add("$[$L = ", recordComponent.name());
collectionBuilderUtils.addShimCall(codeBuilder, recordComponents.get(index));
codeBuilder.add(";\n$]");
}
});
addNullCheckCodeBlock(codeBuilder);
codeBuilder.add("$[return ");
if (metaData.useValidationApi()) {
codeBuilder.add("$T.validate(", validatorTypeName);
}
codeBuilder.add("new $T(", recordClassType.typeName());
IntStream.range(0, recordComponents.size()).forEach(index -> {
if (index > 0) {
codeBuilder.add(", ");
}
codeBuilder.add("$L", recordComponents.get(index).name());
});
codeBuilder.add(")");
if (metaData.useValidationApi()) {
codeBuilder.add(")");
}
codeBuilder.add(";$]");
return codeBuilder.build();
}
private TypeName buildWithTypeName() {
ClassName rawTypeName = ClassName.get(packageName, builderClassType.name() + "." + metaData.withClassName());
if (typeVariables.isEmpty()) {
return rawTypeName;
}
return ParameterizedTypeName.get(rawTypeName, typeVariables.toArray(new TypeName[] {}));
}
private void addFromWithClass() {
/*
* Adds static private class that implements/proxies the Wither
*
* private static final class _FromWith implements MyRecordBuilder.With { private final MyRecord from;
*
* @Override public String p1() { return from.p1(); }
*
* @Override public String p2() { return from.p2(); } }
*/
var fromWithClassBuilder = TypeSpec.classBuilder(metaData.fromWithClassName())
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.addAnnotation(generatedRecordBuilderAnnotation).addTypeVariables(typeVariables)
.addSuperinterface(buildWithTypeName());
if (metaData.addClassRetainedGenerated()) {
fromWithClassBuilder.addAnnotation(recordBuilderGeneratedAnnotation);
}
fromWithClassBuilder.addField(recordClassType.typeName(), "from", Modifier.PRIVATE, Modifier.FINAL);
MethodSpec constructorSpec = MethodSpec.constructorBuilder().addParameter(recordClassType.typeName(), "from")
.addStatement("this.from = from").addModifiers(Modifier.PRIVATE)
.addAnnotation(generatedRecordBuilderAnnotation).build();
fromWithClassBuilder.addMethod(constructorSpec);
IntStream.range(0, recordComponents.size()).forEach(index -> {
var component = recordComponents.get(index);
MethodSpec methodSpec = MethodSpec.methodBuilder(component.name()).returns(component.typeName())
.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC)
.addStatement("return from.$L()", component.name()).addAnnotation(generatedRecordBuilderAnnotation)
.build();
fromWithClassBuilder.addMethod(methodSpec);
});
this.builder.addType(fromWithClassBuilder.build());
}
private void addStaticFromWithMethod() {
/*
* Adds static method that returns a "with"er view of an existing record.
*
* public static With from(MyRecord from) { return new _FromWith(from); }
*/
addFromWithClass();
var methodSpec = MethodSpec.methodBuilder(metaData.fromMethodName())
.addJavadoc("Return a \"with\"er for an existing record instance\n")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC).addAnnotation(generatedRecordBuilderAnnotation)
.addTypeVariables(typeVariables).addParameter(recordClassType.typeName(), metaData.fromMethodName())
.returns(buildWithTypeName()).addStatement("return new $L$L(from)", metaData.fromWithClassName(),
typeVariables.isEmpty() ? "" : "<>")
.build();
builder.addMethod(methodSpec);
}
private void addStaticCopyBuilderMethod() {
/*
* Adds a copy builder method that pre-fills the builder with existing values similar to:
*
* public static MyRecordBuilder builder(MyRecord from) { return new MyRecordBuilder(from.p1(), from.p2(), ...);
* }
*/
var codeBuilder = CodeBlock.builder().add("return new $T(", builderClassType.typeName());
IntStream.range(0, recordComponents.size()).forEach(index -> {
if (index > 0) {
codeBuilder.add(", ");
}
codeBuilder.add("from.$L()", recordComponents.get(index).name());
});
codeBuilder.add(")");
var methodSpec = MethodSpec.methodBuilder(metaData.copyMethodName())
.addJavadoc(
"Return a new builder with all fields set to the values taken from the given record instance\n")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC).addAnnotation(generatedRecordBuilderAnnotation)
.addTypeVariables(typeVariables).addParameter(recordClassType.typeName(), "from")
.returns(builderClassType.typeName()).addStatement(codeBuilder.build()).build();
builder.addMethod(methodSpec);
}
private void addStaticDefaultBuilderMethod() {
/*
* Adds a the default builder method similar to:
*
* public static MyRecordBuilder builder() { return new MyRecordBuilder(); }
*/
var methodSpec = MethodSpec.methodBuilder(metaData.builderMethodName())
.addJavadoc("Return a new builder with all fields set to default Java values\n")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC).addAnnotation(generatedRecordBuilderAnnotation)
.addTypeVariables(typeVariables).returns(builderClassType.typeName())
.addStatement("return new $T()", builderClassType.typeName()).build();
builder.addMethod(methodSpec);
}
private void addStaticComponentsMethod() {
/*
* Adds a static method that converts a record instance into a stream of its component parts
*
* public static Stream<Map.Entry<String, Object>> stream(MyRecord record) { return Stream.of(new
* AbstractMap.SimpleImmutableEntry<>("p1", record.p1()), new AbstractMap.SimpleImmutableEntry<>("p2",
* record.p2())); }
*/
var codeBuilder = CodeBlock.builder().add("return $T.of(", Stream.class);
IntStream.range(0, recordComponents.size()).forEach(index -> {
if (index > 0) {
codeBuilder.add(",\n ");
}
var name = recordComponents.get(index).name();
codeBuilder.add("new $T<>($S, record.$L())", AbstractMap.SimpleImmutableEntry.class, name, name);
});
codeBuilder.add(")");
var mapEntryTypeVariables = ParameterizedTypeName.get(Map.Entry.class, String.class, Object.class);
var mapEntryType = ParameterizedTypeName.get(ClassName.get(Stream.class), mapEntryTypeVariables);
var methodSpec = MethodSpec.methodBuilder(metaData.componentsMethodName()).addJavadoc(
"Return a stream of the record components as map entries keyed with the component name and the value as the component value\n")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC).addParameter(recordClassType.typeName(), "record")
.addAnnotation(generatedRecordBuilderAnnotation).addTypeVariables(typeVariables).returns(mapEntryType)
.addStatement(codeBuilder.build()).build();
builder.addMethod(methodSpec);
}
private void add1Field(ClassType component) {
/*
* For a single record component, add a field similar to:
*
* private T p;
*/
var fieldSpecBuilder = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE);
if (metaData.emptyDefaultForOptional()) {
Optional<OptionalType> thisOptionalType = OptionalType.fromClassType(component);
if (thisOptionalType.isPresent()) {
var codeBlock = CodeBlock.builder().add("$T.empty()", thisOptionalType.get().typeName()).build();
fieldSpecBuilder.initializer(codeBlock);
}
}
builder.addField(fieldSpecBuilder.build());
}
private void addNestedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component, String methodName) {
/*
* For a single record component, add a getter similar to:
*
* T p();
*/
var methodSpecBuilder = MethodSpec.methodBuilder(methodName)
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n",
component.name())
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC).addAnnotation(generatedRecordBuilderAnnotation)
.returns(component.typeName());
addAccessorAnnotations(component, methodSpecBuilder, this::filterOutValid);
classBuilder.addMethod(methodSpecBuilder.build());
}
private boolean filterOutOverride(AnnotationSpec annotationSpec) {
return !annotationSpec.type.equals(overrideType);
}
private boolean filterOutValid(AnnotationSpec annotationSpec) {
return !annotationSpec.type.equals(validType);
}
private void addConstructorAnnotations(RecordClassType component, ParameterSpec.Builder parameterSpecBuilder) {
if (metaData.inheritComponentAnnotations()) {
component.getCanonicalConstructorAnnotations().stream().map(AnnotationSpec::get)
.filter(this::filterOutOverride).forEach(parameterSpecBuilder::addAnnotation);
}
}
private void addAccessorAnnotations(RecordClassType component, MethodSpec.Builder methodSpecBuilder,
Predicate<AnnotationSpec> additionalFilter) {
if (metaData.inheritComponentAnnotations()) {
component.getAccessorAnnotations().stream().map(AnnotationSpec::get).filter(this::filterOutOverride)
.filter(additionalFilter).forEach(methodSpecBuilder::addAnnotation);
}
}
private String capitalize(String s) {
return (s.length() < 2) ? s.toUpperCase(Locale.ROOT) : (Character.toUpperCase(s.charAt(0)) + s.substring(1));
}
private void add1CollectionBuilders(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
if (collectionBuilderUtils.isList(component) || collectionBuilderUtils.isSet(component)) {
add1ListBuilder(meta, component);
} else if (collectionBuilderUtils.isMap(component)) {
add1MapBuilder(meta, component);
}
}
private void add1MapBuilder(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
/*
* For a single map record component, add a methods similar to:
*
* public T addP(K key, V value) { this.p = __ensureMapMutable(p); this.p.put(key, value); return this; }
*
* public T addP(Stream<? extends Map.Entry<K, V> i) { this.p = __ensureMapMutable(p); i.forEach(this.p::put);
* return this; }
*
* public T addP(Iterable<? extends Map.Entry<K, V> i) { this.p = __ensureMapMutable(p); i.forEach(this.p::put);
* return this; }
*/
for (var i = 0; i < 3; ++i) {
var codeBlockBuilder = CodeBlock.builder();
if (collectionBuilderUtils.isImmutableCollection(component)) {
codeBlockBuilder.addStatement("this.$L = $L($L)", component.name(),
collectionBuilderUtils.mutableMakerName(component), component.name());
} else {
codeBlockBuilder.beginControlFlow("if (this.$L == null)", component.name())
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
.endControlFlow();
}
var methodSpecBuilder = MethodSpec
.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
.addJavadoc("Add to the internally allocated {@code HashMap} for {@code $L}\n", component.name())
.addModifiers(Modifier.PUBLIC).addAnnotation(generatedRecordBuilderAnnotation)
.returns(builderClassType.typeName());
if (i == 0) {
methodSpecBuilder.addParameter(meta.typeArguments().get(0), "key");
methodSpecBuilder.addParameter(meta.typeArguments().get(1), "value");
codeBlockBuilder.addStatement("this.$L.put(key, value)", component.name());
} else {
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
var entryType = ParameterizedTypeName.get(ClassName.get(Map.Entry.class),
WildcardTypeName.subtypeOf(meta.typeArguments().get(0)),
WildcardTypeName.subtypeOf(meta.typeArguments().get(1)));
ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(parameterClass,
WildcardTypeName.subtypeOf(entryType));
methodSpecBuilder.addParameter(parameterizedTypeName, "i");
codeBlockBuilder.addStatement("i.forEach(entry -> this.$L.put(entry.getKey(), entry.getValue()))",
component.name());
}
codeBlockBuilder.addStatement("return this");
methodSpecBuilder.addCode(codeBlockBuilder.build());
builder.addMethod(methodSpecBuilder.build());
}
}
private void add1ListBuilder(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
/*
* For a single list or set record component, add methods similar to:
*
* public T addP(I i) { this.list = __ensureListMutable(list); this.p.add(i); return this; }
*
* public T addP(Stream<? extends I> i) { this.list = __ensureListMutable(list); this.p.addAll(i); return this;
* }
*
* public T addP(Iterable<? extends I> i) { this.list = __ensureListMutable(list); this.p.addAll(i); return
* this; }
*/
for (var i = 0; i < 3; ++i) {
var addClockBlock = CodeBlock.builder();
TypeName parameter;
if (i == 0) {
addClockBlock.addStatement("this.$L.add(i)", component.name());
parameter = meta.typeArguments().get(0);
} else {
addClockBlock.addStatement("i.forEach(this.$L::add)", component.name());
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
parameter = ParameterizedTypeName.get(parameterClass,
WildcardTypeName.subtypeOf(meta.typeArguments().get(0)));
}
var codeBlockBuilder = CodeBlock.builder();
if (collectionBuilderUtils.isImmutableCollection(component)) {
codeBlockBuilder.addStatement("this.$L = $L($L)", component.name(),
collectionBuilderUtils.mutableMakerName(component), component.name());
} else {
codeBlockBuilder.beginControlFlow("if (this.$L == null)", component.name())
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
.endControlFlow();
}
codeBlockBuilder.add(addClockBlock.build()).addStatement("return this");
var methodSpecBuilder = MethodSpec
.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
.addJavadoc("Add to the internally allocated {@code $L} for {@code $L}\n",
meta.singleItemCollectionClass().getSimpleName(), component.name())
.addModifiers(Modifier.PUBLIC).addAnnotation(generatedRecordBuilderAnnotation)
.returns(builderClassType.typeName()).addParameter(parameter, "i")
.addCode(codeBlockBuilder.build());
builder.addMethod(methodSpecBuilder.build());
}
}
private void add1GetterMethod(RecordClassType component) {
/*
* For a single record component, add a getter similar to:
*
* public T p() { return p; }
*/
var methodSpecBuilder = MethodSpec.methodBuilder(prefixedName(component, true))
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n",
component.name())
.addModifiers(Modifier.PUBLIC).addAnnotation(generatedRecordBuilderAnnotation)
.returns(component.typeName()).addCode(checkReturnShim(component));
addAccessorAnnotations(component, methodSpecBuilder, __ -> true);
builder.addMethod(methodSpecBuilder.build());
}
private CodeBlock checkReturnShim(RecordClassType component) {
var codeBuilder = CodeBlock.builder();
if (collectionBuilderUtils.isImmutableCollection(component)) {
codeBuilder.add("return ");
collectionBuilderUtils.addShimCall(codeBuilder, component);
codeBuilder.add(";");
} else {
codeBuilder.addStatement("return $L", component.name());
}
return codeBuilder.build();
}
private void add1SetterMethod(RecordClassType component) {
/*
* For a single record component, add a setter similar to:
*
* public MyRecordBuilder p(T p) { this.p = p; return this; }
*/
var methodSpec = MethodSpec.methodBuilder(prefixedName(component, false)).addModifiers(Modifier.PUBLIC)
.addAnnotation(generatedRecordBuilderAnnotation).returns(builderClassType.typeName());
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD_FOR_SETTER);
var parameterSpecBuilder = collectionMetaData.map(meta -> {
CodeBlock.Builder codeSpec = CodeBlock.builder();
codeSpec.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.shimName(component),
component.name());
methodSpec.addJavadoc(
"Re-create the internally allocated {@code $T} for {@code $L} by copying the argument\n",
component.typeName(), component.name()).addCode(codeSpec.build());
return ParameterSpec.builder(meta.wildType(), component.name());
}).orElseGet(() -> {
methodSpec.addJavadoc("Set a new value for the {@code $L} record component in the builder\n",
component.name()).addStatement("this.$L = $L", component.name(), component.name());
return ParameterSpec.builder(component.typeName(), component.name());
});
addConstructorAnnotations(component, parameterSpecBuilder);
methodSpec.addStatement("return this").addParameter(parameterSpecBuilder.build());
builder.addMethod(methodSpec.build());
}
private void add1ConcreteOptionalSetterMethod(RecordClassType component) {
/*
* For a single optional record component, add a concrete setter similar to:
*
* public MyRecordBuilder p(T p) { this.p = p; return this; }
*/
var optionalType = OptionalType.fromClassType(component);
if (optionalType.isEmpty()) {
return;
}
var type = optionalType.get();
var methodSpec = MethodSpec.methodBuilder(prefixedName(component, false)).addModifiers(Modifier.PUBLIC)
.addAnnotation(generatedRecordBuilderAnnotation).returns(builderClassType.typeName());
var parameterSpecBuilder = ParameterSpec.builder(type.valueType(), component.name());
methodSpec.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
.addStatement(getOptionalStatement(type), component.name(), type.typeName(), component.name());
addConstructorAnnotations(component, parameterSpecBuilder);
methodSpec.addStatement("return this").addParameter(parameterSpecBuilder.build());
builder.addMethod(methodSpec.build());
}
private String getOptionalStatement(OptionalType type) {
if (type.isOptional()) {
return "this.$L = $T.ofNullable($L)";
}
return "this.$L = $T.of($L)";
}
private List<TypeVariableName> typeVariablesWithReturn() {
var variables = new ArrayList<TypeVariableName>();
variables.add(rType);
variables.addAll(typeVariables);
return variables;
}
private MethodSpec buildFunctionalHandler(String className, String methodName, boolean isMap) {
/*
* Build a Functional handler ala:
*
* default <R> R map(Function<R, T> proc) { return proc.apply(p()); }
*/
var localTypeVariables = isMap ? typeVariablesWithReturn() : typeVariables;
var typeName = localTypeVariables.isEmpty() ? ClassName.get("", className)
: ParameterizedTypeName.get(ClassName.get("", className), localTypeVariables.toArray(TypeName[]::new));
var methodBuilder = MethodSpec.methodBuilder(methodName).addAnnotation(generatedRecordBuilderAnnotation)
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT).addParameter(typeName, "proc");
var codeBlockBuilder = CodeBlock.builder();
if (isMap) {
methodBuilder.addJavadoc("Map record components into a new object");
methodBuilder.addTypeVariable(rType);
methodBuilder.returns(rType);
codeBlockBuilder.add("return ");
} else {
methodBuilder.addJavadoc("Perform an operation on record components");
}
codeBlockBuilder.add("proc.apply(");
addComponentCallsAsArguments(-1, codeBlockBuilder, true);
codeBlockBuilder.add(");");
methodBuilder.addCode(codeBlockBuilder.build());
return methodBuilder.build();
}
private TypeSpec buildFunctionalInterface(String className, boolean isMap) {
/*
* Build a Functional interface ala:
*
* @FunctionalInterface interface Function<R, T> { R apply(T a); }
*/
var localTypeVariables = isMap ? typeVariablesWithReturn() : typeVariables;
var methodBuilder = MethodSpec.methodBuilder("apply").addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
recordComponents.forEach(component -> {
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
addConstructorAnnotations(component, parameterSpecBuilder);
methodBuilder.addParameter(parameterSpecBuilder.build());
});
if (isMap) {
methodBuilder.returns(rType);
}
return TypeSpec.interfaceBuilder(className).addAnnotation(generatedRecordBuilderAnnotation)
.addAnnotation(FunctionalInterface.class).addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addTypeVariables(localTypeVariables).addMethod(methodBuilder.build()).build();
}
private String prefixedName(RecordClassType component, boolean isGetter) {
BiFunction<String, String, String> prefixer = (p, s) -> p.isEmpty() ? s
: p + Character.toUpperCase(s.charAt(0)) + s.substring(1);
boolean isBool = component.typeName().toString().toLowerCase(Locale.ROOT).equals("boolean");
if (isGetter) {
if (isBool) {
return prefixer.apply(metaData.booleanPrefix(), component.name());
}
return prefixer.apply(metaData.getterPrefix(), component.name());
}
return prefixer.apply(metaData.setterPrefix(), component.name());
}
}

View File

@@ -0,0 +1,232 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.processor;
import com.squareup.javapoet.*;
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.tools.Diagnostic;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordInterfaceAnnotation;
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
class InternalRecordInterfaceProcessor {
private final ProcessingEnvironment processingEnv;
private final String packageName;
private final TypeSpec recordType;
private final List<Component> recordComponents;
private final TypeElement iface;
private final ClassType recordClassType;
private final List<String> alternateMethods;
private static final String FAKE_METHOD_NAME = "__FAKE__";
private static final Set<String> javaBeanPrefixes = Set.of("get", "is");
private record Component(ExecutableElement element, Optional<String> alternateName) {
}
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder,
RecordBuilder.Options metaData, Optional<String> packageNameOpt, boolean fromTemplate) {
this.processingEnv = processingEnv;
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
recordComponents = getRecordComponents(iface);
this.iface = iface;
ClassType ifaceClassType = ElementUtils.getClassType(iface, iface.getTypeParameters());
recordClassType = ElementUtils.getClassType(packageName,
getBuilderName(iface, metaData, ifaceClassType, metaData.interfaceSuffix()), iface.getTypeParameters());
List<TypeVariableName> typeVariables = iface.getTypeParameters().stream().map(TypeVariableName::get)
.collect(Collectors.toList());
MethodSpec methodSpec = generateArgumentList();
TypeSpec.Builder builder = TypeSpec.classBuilder(recordClassType.name()).addSuperinterface(iface.asType())
.addMethod(methodSpec).addModifiers(Modifier.PUBLIC).addAnnotation(generatedRecordInterfaceAnnotation)
.addTypeVariables(typeVariables);
if (metaData.addClassRetainedGenerated()) {
builder.addAnnotation(recordBuilderGeneratedAnnotation);
}
if (addRecordBuilder) {
ClassType builderClassType = ElementUtils.getClassType(packageName,
getBuilderName(iface, metaData, recordClassType, metaData.suffix()) + "."
+ metaData.withClassName(),
iface.getTypeParameters());
builder.addAnnotation(RecordBuilder.class);
builder.addSuperinterface(builderClassType.typeName());
if (fromTemplate) {
builder.addAnnotation(AnnotationSpec.get(metaData));
} else {
var options = iface.getAnnotation(RecordBuilder.Options.class);
if (options != null) {
builder.addAnnotation(AnnotationSpec.get(options));
}
}
}
alternateMethods = buildAlternateMethods(recordComponents);
recordType = builder.build();
}
boolean isValid() {
return !recordComponents.isEmpty();
}
TypeSpec recordType() {
return recordType;
}
String packageName() {
return packageName;
}
ClassType recordClassType() {
return recordClassType;
}
String toRecord(String classSource) {
// javapoet does yet support records - so a class was created and we can reshape it
// The class will look something like this:
/*
* // Auto generated by io.soabase.recordbuilder.core.RecordBuilder: https://github.com/Randgalt/record-builder
* package io.soabase.recordbuilder.test;
*
* import io.soabase.recordbuilder.core.RecordBuilder; import javax.annotation.processing.Generated;
*
* @Generated("io.soabase.recordbuilder.core.RecordInterface")
*
* @RecordBuilder public class MyRecord implements MyInterface { void __FAKE__(String name, int age) { } }
*/
Pattern pattern = Pattern.compile("(.*)(implements.*)(\\{)(.*" + FAKE_METHOD_NAME + ")(\\(.*\\))(.*)",
Pattern.MULTILINE | Pattern.DOTALL);
Matcher matcher = pattern.matcher(classSource);
if (!matcher.find() || matcher.groupCount() != 6) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Internal error generating record. Group count: " + matcher.groupCount(), iface);
}
String declaration = matcher.group(1).trim().replace("class", "record");
String implementsSection = matcher.group(2).trim();
String argumentList = matcher.group(5).trim();
StringBuilder fixedRecord = new StringBuilder(declaration).append(argumentList).append(' ')
.append(implementsSection).append(" {");
alternateMethods.forEach(method -> fixedRecord.append('\n').append(method));
fixedRecord.append('}');
return fixedRecord.toString();
}
private MethodSpec generateArgumentList() {
MethodSpec.Builder builder = MethodSpec.methodBuilder(FAKE_METHOD_NAME);
recordComponents.forEach(component -> {
String name = component.alternateName.orElseGet(() -> component.element.getSimpleName().toString());
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(component.element.getReturnType()), name)
.build();
builder.addTypeVariables(component.element.getTypeParameters().stream().map(TypeVariableName::get)
.collect(Collectors.toList()));
builder.addParameter(parameterSpec);
});
return builder.build();
}
private List<String> buildAlternateMethods(List<Component> recordComponents) {
return recordComponents.stream().filter(component -> component.alternateName.isPresent()).map(component -> {
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
.addAnnotation(Override.class).addAnnotation(generatedRecordInterfaceAnnotation)
.returns(ClassName.get(component.element.getReturnType())).addModifiers(Modifier.PUBLIC)
.addCode("return $L();", component.alternateName.get()).build();
return method.toString();
}).collect(Collectors.toList());
}
private List<Component> getRecordComponents(TypeElement iface) {
List<Component> components = new ArrayList<>();
try {
getRecordComponents(iface, components, new HashSet<>(), new HashSet<>());
if (components.isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Annotated interface has no component methods", iface);
}
} catch (IllegalInterface e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), iface);
components = Collections.emptyList();
}
return components;
}
private static class IllegalInterface extends RuntimeException {
public IllegalInterface(String message) {
super(message);
}
}
private void getRecordComponents(TypeElement iface, Collection<Component> components, Set<String> visitedSet,
Set<String> usedNames) {
if (!visitedSet.add(iface.getQualifiedName().toString())) {
return;
}
iface.getEnclosedElements().stream()
.filter(element -> (element.getKind() == ElementKind.METHOD)
&& !(element.getModifiers().contains(Modifier.STATIC)))
.map(element -> ((ExecutableElement) element)).filter(element -> {
if (element.isDefault()) {
return element.getAnnotation(IgnoreDefaultMethod.class) == null;
}
return true;
}).peek(element -> {
if (!element.getParameters().isEmpty() || element.getReturnType().getKind() == TypeKind.VOID) {
throw new IllegalInterface(String.format(
"Non-static, non-default methods must take no arguments and must return a value. Bad method: %s.%s()",
iface.getSimpleName(), element.getSimpleName()));
}
if (!element.getTypeParameters().isEmpty()) {
throw new IllegalInterface(
String.format("Interface methods cannot have type parameters. Bad method: %s.%s()",
iface.getSimpleName(), element.getSimpleName()));
}
}).filter(element -> usedNames.add(element.getSimpleName().toString()))
.map(element -> new Component(element, stripBeanPrefix(element.getSimpleName().toString())))
.collect(Collectors.toCollection(() -> components));
iface.getInterfaces().forEach(parentIface -> {
TypeElement parentIfaceElement = (TypeElement) processingEnv.getTypeUtils().asElement(parentIface);
getRecordComponents(parentIfaceElement, components, visitedSet, usedNames);
});
}
private Optional<String> stripBeanPrefix(String name) {
return javaBeanPrefixes.stream().filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
.findFirst().map(prefix -> {
var stripped = name.substring(prefix.length());
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
});
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.processor;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
public record OptionalType(TypeName typeName, TypeName valueType) {
private static final TypeName optionalType = TypeName.get(Optional.class);
private static final TypeName optionalIntType = TypeName.get(OptionalInt.class);
private static final TypeName optionalLongType = TypeName.get(OptionalLong.class);
private static final TypeName optionalDoubleType = TypeName.get(OptionalDouble.class);
private static boolean isOptional(ClassType component) {
if (component.typeName().equals(optionalType)) {
return true;
}
return (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)
&& parameterizedTypeName.rawType.equals(optionalType);
}
static Optional<OptionalType> fromClassType(final ClassType component) {
if (isOptional(component)) {
if (!(component.typeName() instanceof ParameterizedTypeName parameterizedType)) {
return Optional.of(new OptionalType(optionalType, TypeName.get(Object.class)));
}
final TypeName containingType = parameterizedType.typeArguments.isEmpty() ? TypeName.get(Object.class)
: parameterizedType.typeArguments.get(0);
return Optional.of(new OptionalType(optionalType, containingType));
}
if (component.typeName().equals(optionalIntType)) {
return Optional.of(new OptionalType(optionalIntType, TypeName.get(int.class)));
}
if (component.typeName().equals(optionalLongType)) {
return Optional.of(new OptionalType(optionalLongType, TypeName.get(long.class)));
}
if (component.typeName().equals(optionalDoubleType)) {
return Optional.of(new OptionalType(optionalDoubleType, TypeName.get(double.class)));
}
return Optional.empty();
}
public boolean isOptional() {
return typeName.equals(optionalType);
}
}

View File

@@ -1,42 +0,0 @@
/**
* Copyright 2016 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.processor;
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
import java.util.function.Consumer;
class RecordBuilderMetaDataLoader {
private final RecordBuilderMetaData metaData;
RecordBuilderMetaDataLoader(String metaDataClassName, Consumer<String> logger) {
RecordBuilderMetaData localMetaData = null;
if ((metaDataClassName != null) && !metaDataClassName.isEmpty()) {
try {
Class<?> clazz = Class.forName(metaDataClassName);
localMetaData = (RecordBuilderMetaData) clazz.getDeclaredConstructor().newInstance();
logger.accept("Found meta data: " + localMetaData.getClass());
} catch (Exception e) {
logger.accept("Could not load meta data: " + metaDataClassName + " - " + e.getMessage());
}
}
metaData = (localMetaData != null) ? localMetaData : RecordBuilderMetaData.DEFAULT;
}
RecordBuilderMetaData getMetaData() {
return metaData;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.processor;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
class RecordBuilderOptions {
private static final Map<String, Object> defaultValues = buildDefaultValues();
static RecordBuilder.Options build(Map<String, String> options) {
return (RecordBuilder.Options) Proxy.newProxyInstance(RecordBuilderOptions.class.getClassLoader(),
new Class[] { RecordBuilder.Options.class }, (proxy, method, args) -> {
var name = method.getName();
var defaultValue = defaultValues.get(name);
var option = options.get(name);
if (option != null) {
if (defaultValue instanceof String) {
return option;
}
if (defaultValue instanceof Boolean) {
return Boolean.parseBoolean(option);
}
if (defaultValue instanceof Integer) {
return Integer.parseInt(option);
}
if (defaultValue instanceof Long) {
return Long.parseLong(option);
}
if (defaultValue instanceof Double) {
return Double.parseDouble(option);
}
throw new IllegalArgumentException("Unhandled option type: " + defaultValue.getClass());
}
return defaultValue;
});
}
private static Map<String, Object> buildDefaultValues() {
var workMap = new HashMap<String, Object>();
for (Method method : RecordBuilder.Options.class.getDeclaredMethods()) {
workMap.put(method.getName(), method.getDefaultValue());
}
workMap.put("toString", "Generated RecordBuilder.Options");
return Map.copyOf(workMap);
}
private RecordBuilderOptions() {
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2016 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,240 +15,250 @@
*/
package io.soabase.recordbuilder.processor;
import com.squareup.javapoet.*;
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;
import io.soabase.recordbuilder.core.RecordBuilder;
import io.soabase.recordbuilder.core.RecordBuilderGenerated;
import io.soabase.recordbuilder.core.RecordInterface;
import javax.annotation.processing.*;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Generated;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.function.Function;
@SupportedAnnotationTypes("io.soabase.recordbuilder.core.RecordBuilder")
@SupportedSourceVersion(SourceVersion.RELEASE_14)
public class RecordBuilderProcessor extends AbstractProcessor {
private static final String RECORD_BUILDER = RecordBuilder.class.getName();
private static final String RECORD_BUILDER_INCLUDE = RecordBuilder.Include.class.getName().replace('$', '.');
private static final String RECORD_INTERFACE = RecordInterface.class.getName();
private static final String RECORD_INTERFACE_INCLUDE = RecordInterface.Include.class.getName().replace('$', '.');
static final AnnotationSpec generatedRecordBuilderAnnotation = AnnotationSpec.builder(Generated.class)
.addMember("value", "$S", RecordBuilder.class.getName()).build();
static final AnnotationSpec generatedRecordInterfaceAnnotation = AnnotationSpec.builder(Generated.class)
.addMember("value", "$S", RecordInterface.class.getName()).build();
static final AnnotationSpec recordBuilderGeneratedAnnotation = AnnotationSpec.builder(RecordBuilderGenerated.class)
.build();
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation).forEach(this::process));
return true;
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation)
.forEach(element -> process(annotation, element)));
return false;
}
private void process(Element element) {
var messager = processingEnv.getMessager();
if (element.getKind() != ElementKind.RECORD) {
messager.printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", element);
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of("*");
}
@Override
public SourceVersion getSupportedSourceVersion() {
// we don't directly return RELEASE_14 as that may
// not exist in prior releases
// if we're running on an older release, returning latest()
// is fine as we won't encounter any records anyway
return SourceVersion.latest();
}
private void process(TypeElement annotation, Element element) {
String annotationClass = annotation.getQualifiedName().toString();
if (annotationClass.equals(RECORD_BUILDER)) {
var typeElement = (TypeElement) element;
processRecordBuilder(typeElement, getMetaData(typeElement), Optional.empty());
} else if (annotationClass.equals(RECORD_INTERFACE)) {
var typeElement = (TypeElement) element;
processRecordInterface(typeElement, element.getAnnotation(RecordInterface.class).addRecordBuilder(),
getMetaData(typeElement), Optional.empty(), false);
} else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
processIncludes(element, getMetaData(element), annotationClass);
} else {
var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class);
if (recordBuilderTemplate != null) {
if (recordBuilderTemplate.asRecordInterface()) {
processRecordInterface((TypeElement) element, true, recordBuilderTemplate.options(),
Optional.empty(), true);
} else {
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
}
}
}
}
private RecordBuilder.Options getMetaData(Element element) {
var recordSpecificMetaData = element.getAnnotation(RecordBuilder.Options.class);
return (recordSpecificMetaData != null) ? recordSpecificMetaData
: RecordBuilderOptions.build(processingEnv.getOptions());
}
private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass) {
var isRecordBuilderInclude = annotationClass.equals(RECORD_BUILDER_INCLUDE);
var annotationMirrorOpt = ElementUtils.findAnnotationMirror(processingEnv, element, annotationClass);
if (annotationMirrorOpt.isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Could not get annotation mirror for: " + annotationClass, element);
} else {
var includeHelper = new IncludeHelper(processingEnv, element, annotationMirrorOpt.get(),
isRecordBuilderInclude);
if (includeHelper.isValid()) {
var packagePattern = ElementUtils.getStringAttribute(ElementUtils
.getAnnotationValue(includeHelper.getAnnotationValues(), "packagePattern").orElse(null), "*");
for (var typeElement : includeHelper.getClassTypeElements()) {
var packageName = buildPackageName(packagePattern, element, typeElement);
if (packageName != null) {
if (isRecordBuilderInclude) {
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
} else {
var addRecordBuilderOpt = ElementUtils
.getAnnotationValue(includeHelper.getAnnotationValues(), "addRecordBuilder");
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute)
.orElse(true);
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName),
false);
}
}
}
}
}
}
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass) {
PackageElement includedClassPackage = findPackageElement(includedClass, includedClass);
if (includedClassPackage == null) {
return null;
}
String replaced = packagePattern.replace("*", includedClassPackage.getQualifiedName().toString());
if (builderElement instanceof PackageElement) {
return replaced.replace("@", ((PackageElement) builderElement).getQualifiedName().toString());
}
return replaced.replace("@",
((PackageElement) builderElement.getEnclosingElement()).getQualifiedName().toString());
}
private PackageElement findPackageElement(Element actualElement, Element includedClass) {
if (includedClass == null) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Element has not package", actualElement);
return null;
}
if (includedClass.getEnclosingElement() instanceof PackageElement) {
return (PackageElement) includedClass.getEnclosingElement();
}
return findPackageElement(actualElement, includedClass.getEnclosingElement());
}
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData,
Optional<String> packageName, boolean fromTemplate) {
if (!element.getKind().isInterface()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"RecordInterface only valid for interfaces.", element);
return;
}
var metaData = new RecordBuilderMetaDataLoader(processingEnv.getOptions().get(RecordBuilderMetaData.JAVAC_OPTION_NAME), s -> messager.printMessage(Diagnostic.Kind.NOTE, s)).getMetaData();
process((TypeElement) element, metaData);
}
private void process(TypeElement record, RecordBuilderMetaData metaData) {
var recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
var packageName = ElementUtils.getPackageName(record);
var builderClassType = ElementUtils.getClassType(packageName, getBuilderName(record, metaData, recordClassType), record.getTypeParameters());
var typeVariables = record.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList());
var recordComponents = record.getRecordComponents().stream().map(ElementUtils::getClassType).collect(Collectors.toList());
validateMetaData(metaData, element);
var builder = TypeSpec.classBuilder(builderClassType.name()).addModifiers(Modifier.PUBLIC);
builder.addTypeVariables(typeVariables);
addDefaultConstructor(builder);
addAllArgsConstructor(builder, recordComponents);
addStaticDefaultBuilderMethod(builder, builderClassType, typeVariables, metaData);
addStaticCopyMethod(builder, builderClassType, recordClassType, recordComponents, typeVariables, metaData);
addBuildMethod(builder, recordClassType, recordComponents, metaData);
recordComponents.forEach(component -> {
add1Field(builder, component);
add1SetterMethod(builder, component, builderClassType);
});
writeJavaFile(record, packageName, builderClassType, builder, metaData);
}
private String getBuilderName(TypeElement record, RecordBuilderMetaData metaData, ClassType recordClassType) {
// generate the record builder class name
var baseName = recordClassType.name() + metaData.suffix();
return metaData.prefixEnclosingClassNames() ? (getBuilderNamePrefix(record.getEnclosingElement()) + baseName) : baseName;
}
private String getBuilderNamePrefix(Element element) {
// prefix enclosing class names if this record is nested in a class
if (element instanceof TypeElement) {
return getBuilderNamePrefix(element.getEnclosingElement()) + element.getSimpleName().toString();
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData,
packageName, fromTemplate);
if (!internalProcessor.isValid()) {
return;
}
return "";
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(),
internalProcessor.recordType(), metaData, internalProcessor::toRecord);
}
private void addDefaultConstructor(TypeSpec.Builder builder) {
/*
Adds a default constructor similar to:
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData,
Optional<String> packageName) {
// we use string based name comparison for the element kind,
// as the ElementKind.RECORD enum doesn't exist on JRE releases
// older than Java 14, and we don't want to throw unexpected
// NoSuchFieldErrors
if (!"RECORD".equals(record.getKind().name())) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.",
record);
return;
}
private MyRecordBuilder() {
}
*/
var constructorBuilder = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE);
builder.addMethod(constructorBuilder.build());
validateMetaData(metaData, record);
var internalProcessor = new InternalRecordBuilderProcessor(processingEnv, record, metaData, packageName);
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(),
internalProcessor.builderType(), metaData);
}
private void addAllArgsConstructor(TypeSpec.Builder builder, List<ClassType> recordComponents) {
/*
Adds an all-args constructor similar to:
private void validateMetaData(RecordBuilder.Options metaData, Element record) {
var useImmutableCollections = metaData.useImmutableCollections();
var useUnmodifiableCollections = metaData.useUnmodifiableCollections();
private MyRecordBuilder(int p1, T p2, ...) {
this.p1 = p1;
this.p2 = p2;
...
}
*/
var constructorBuilder = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE);
recordComponents.forEach(component -> {
constructorBuilder.addParameter(component.typeName(), component.name());
var codeBuilder = CodeBlock.builder().add("this.$L = $L", component.name(), component.name());
constructorBuilder.addStatement(codeBuilder.build());
});
builder.addMethod(constructorBuilder.build());
if (useImmutableCollections && useUnmodifiableCollections) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING,
"Options.useUnmodifiableCollections property is ignored as Options.useImmutableCollections is set to true",
record);
}
}
private void addBuildMethod(TypeSpec.Builder builder, ClassType recordClassType, List<ClassType> recordComponents, RecordBuilderMetaData metaData) {
/*
Adds the build method that generates the record similar to:
public MyRecord build() {
return new MyRecord(p1, p2, ...);
}
*/
var codeBuilder = CodeBlock.builder().add("return new $T(", recordClassType.typeName());
IntStream.range(0, recordComponents.size()).forEach(index -> {
if (index > 0) {
codeBuilder.add(", ");
}
codeBuilder.add("$L", recordComponents.get(index).name());
});
codeBuilder.add(")");
var methodSpec = MethodSpec.methodBuilder(metaData.buildMethodName())
.addJavadoc("Return a new record instance with all fields set to the current values in this builder\n")
.addModifiers(Modifier.PUBLIC)
.returns(recordClassType.typeName())
.addStatement(codeBuilder.build())
.build();
builder.addMethod(methodSpec);
}
private void addStaticCopyMethod(TypeSpec.Builder builder, ClassType builderClassType, ClassType recordClassType, List<ClassType> recordComponents, List<TypeVariableName> typeVariables, RecordBuilderMetaData metaData) {
/*
Adds a copy builder method that pre-fills the builder with existing values similar to:
public static MyRecordBuilder builder(MyRecord from) {
return new MyRecordBuilder(from.p1(), from.p2(), ...);
}
*/
var codeBuilder = CodeBlock.builder().add("return new $T(", builderClassType.typeName());
IntStream.range(0, recordComponents.size()).forEach(index -> {
if (index > 0) {
codeBuilder.add(", ");
}
codeBuilder.add("from.$L()", recordComponents.get(index).name());
});
codeBuilder.add(")");
var methodSpec = MethodSpec.methodBuilder(metaData.copyMethodName())
.addJavadoc("Return a new builder with all fields set to the values taken from the given record instance\n")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addTypeVariables(typeVariables)
.addParameter(recordClassType.typeName(), "from")
.returns(builderClassType.typeName())
.addStatement(codeBuilder.build())
.build();
builder.addMethod(methodSpec);
}
private void addStaticDefaultBuilderMethod(TypeSpec.Builder builder, ClassType builderClassType, List<TypeVariableName> typeVariables, RecordBuilderMetaData metaData) {
/*
Adds a the default builder method similar to:
public static MyRecordBuilder builder() {
return new MyRecordBuilder();
}
*/
var methodSpec = MethodSpec.methodBuilder(metaData.builderMethodName())
.addJavadoc("Return a new builder with all fields set to default Java values\n")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addTypeVariables(typeVariables)
.returns(builderClassType.typeName())
.addStatement("return new $T()", builderClassType.typeName())
.build();
builder.addMethod(methodSpec);
}
private void add1Field(TypeSpec.Builder builder, ClassType component) {
/*
For a single record component, add a field similar to:
private T p;
*/
var fieldSpec = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE).build();
builder.addField(fieldSpec);
}
private void add1SetterMethod(TypeSpec.Builder builder, ClassType component, ClassType builderClassType) {
/*
For a single record component, add a setter similar to:
public MyRecordBuilder p(T p) {
this.p = p;
return this;
}
*/
var parameterSpec = ParameterSpec.builder(component.typeName(), component.name()).build();
var methodSpec = MethodSpec.methodBuilder(component.name())
.addJavadoc("Set a new value for this record component in the builder\n")
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec)
.returns(builderClassType.typeName())
.addStatement("this.$L = $L", component.name(), component.name())
.addStatement("return this")
.build();
builder.addMethod(methodSpec);
}
private void writeJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec.Builder builder, RecordBuilderMetaData metaData) {
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType,
TypeSpec builderType, RecordBuilder.Options metaData) {
// produces the Java file
var javaFileBuilder = JavaFile.builder(packageName, builder.build())
JavaFile javaFile = javaFileBuilder(packageName, builderType, metaData);
Filer filer = processingEnv.getFiler();
try {
String fullyQualifiedName = packageName.isEmpty() ? builderClassType.name()
: (packageName + "." + builderClassType.name());
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
try (Writer writer = sourceFile.openWriter()) {
javaFile.writeTo(writer);
}
} catch (IOException e) {
handleWriteError(record, e);
}
}
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType,
TypeSpec type, RecordBuilder.Options metaData, Function<String, String> toRecordProc) {
JavaFile javaFile = javaFileBuilder(packageName, type, metaData);
String classSourceCode = javaFile.toString();
int generatedIndex = classSourceCode.indexOf("@Generated");
String recordSourceCode = toRecordProc.apply(classSourceCode);
Filer filer = processingEnv.getFiler();
try {
String fullyQualifiedName = packageName.isEmpty() ? classType.name()
: (packageName + "." + classType.name());
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
try (Writer writer = sourceFile.openWriter()) {
writer.write(recordSourceCode);
}
} catch (IOException e) {
handleWriteError(element, e);
}
}
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData) {
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true)
.indent(metaData.fileIndent());
var comment = metaData.fileComment();
if ((comment != null) && !comment.isEmpty()) {
javaFileBuilder.addFileComment(comment);
}
JavaFile javaFile = javaFileBuilder.build();
Filer filer = processingEnv.getFiler();
try
{
String fullyQualifiedName = packageName.isEmpty() ? builderClassType.name() : (packageName + "." + builderClassType.name());
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
try ( Writer writer = sourceFile.openWriter() )
{
javaFile.writeTo(writer);
}
}
catch ( IOException e )
{
String message = "Could not create source file";
if ( e.getMessage() != null )
{
message = message + ": " + e.getMessage();
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, record);
return javaFileBuilder.build();
}
private void handleWriteError(TypeElement element, IOException e) {
String message = "Could not create source file";
if (e.getMessage() != null) {
message = message + ": " + e.getMessage();
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.processor;
import com.squareup.javapoet.TypeName;
import javax.lang.model.element.AnnotationMirror;
import java.util.List;
public class RecordClassType extends ClassType {
private final TypeName rawTypeName;
private final List<? extends AnnotationMirror> accessorAnnotations;
private final List<? extends AnnotationMirror> canonicalConstructorAnnotations;
public RecordClassType(TypeName typeName, TypeName rawTypeName, String name,
List<? extends AnnotationMirror> accessorAnnotations,
List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
super(typeName, name);
this.rawTypeName = rawTypeName;
this.accessorAnnotations = accessorAnnotations;
this.canonicalConstructorAnnotations = canonicalConstructorAnnotations;
}
public TypeName rawTypeName() {
return rawTypeName;
}
public List<? extends AnnotationMirror> getAccessorAnnotations() {
return accessorAnnotations;
}
public List<? extends AnnotationMirror> getCanonicalConstructorAnnotations() {
return canonicalConstructorAnnotations;
}
}

View File

@@ -1,18 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 The original author or authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the 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>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>1.1.ea</version>
<version>38-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>record-builder-test</artifactId>
<properties>
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
</properties>
<dependencies>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-core</artifactId>
<artifactId>record-builder-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-validator</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@@ -20,26 +64,16 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-processor</artifactId>
<version>${project.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>io.soabase.recordbuilder.processor.RecordBuilderProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
@@ -55,6 +89,48 @@
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>default-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<includes>
<include>io/soabase/recordbuilder/test/jacoco/*</include>
</includes>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>0.60</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
@RecordBuilder
public record Annotated(@NotNull @Null String hey, @Min(10) @Max(100) int i, double d) {
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordInterface;
import java.time.Instant;
@RecordInterface
public interface BeanStyle {
String getName();
Instant getDate();
boolean isSomething();
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import io.soabase.recordbuilder.core.RecordInterface;
@RecordInterface.Include({ Thingy.class })
@RecordBuilder.Include({ Nested.NestedRecord.class })
public class Builder {
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(addSingleItemCollectionBuilders = true, useImmutableCollections = true, mutableListClassName = "PersonalizedMutableList")
public record CollectionCopying<T>(List<String> list, Set<T> set, Map<Instant, T> map, Collection<T> collection,
int count) implements CollectionCopyingBuilder.With<T> {
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import io.soabase.recordbuilder.core.RecordInterface;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RecordInterface
@RecordBuilder.Options(useImmutableCollections = true)
public interface CollectionInterface<T, X extends Point> {
List<T> l();
Set<T> s();
Map<T, X> m();
Collection<X> c();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(useImmutableCollections = true, addFunctionalMethodsToWith = true)
public record CollectionRecord<T, X extends Point>(List<T> l, Set<T> s, Map<T, X> m, Collection<X> c)
implements CollectionRecordBuilder.With<T, X> {
public static void main(String[] args) {
var r = new CollectionRecord<>(List.of("hey"), Set.of("there"), Map.of("one", new Point(10, 20)),
Set.of(new Point(30, 40)));
Instant now = r.map((l1, s1, m1, c1) -> Instant.now());
r.accept((l1, s1, m1, c1) -> {
});
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(useImmutableCollections = true)
public record CollectionRecordConflicts(List<String> __list, Set<String> __set, Map<String, String> __map,
Collection<String> __collection) implements CollectionRecordConflictsBuilder.With {
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import java.util.List;
import java.util.Map;
import io.soabase.recordbuilder.core.RecordBuilder;
import io.soabase.recordbuilder.test.CustomMethodNamesBuilder.Bean;
@RecordBuilder
@RecordBuilder.Options(setterPrefix = "set", getterPrefix = "get", booleanPrefix = "is", beanClassName = "Bean")
public record CustomMethodNames<K, V>(Map<K, V> kvMap, int theValue, List<Integer> theList, boolean theBoolean)
implements Bean, CustomMethodNamesBuilder.With {
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import java.time.Instant;
public interface Customer {
String name();
String address();
Instant activeDate();
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder
public record Empty() implements EmptyBuilder.With {
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.lang.model.type.ErrorType;
import java.util.List;
@RecordBuilder
public record ExceptionDetails(String internalMessage, String endUserMessage, String httpStatus, ErrorType errorType,
List<String> jsonProblems, Throwable cause) {
@Override
public List<String> jsonProblems() {
if (jsonProblems == null) {
return List.of();
}
return jsonProblems;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilderFull;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
@RecordBuilderFull
public record FullRecord(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecord> fullRecords,
@NotNull String justAString) {
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
import io.soabase.recordbuilder.core.RecordInterface;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
@RecordInterface
public interface HasDefaults {
Instant time();
default Instant tomorrow() {
return Instant.now().plusMillis(TimeUnit.DAYS.toMillis(1));
}
@IgnoreDefaultMethod
default void complexMethod(String s1, String s2) {
// do nothing
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.validation.constraints.NotNull;
@RecordBuilder()
@RecordBuilder.Options(inheritComponentAnnotations = false)
public record IgnoreAnnotated(@NotNull String s) {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder.Options(prefixEnclosingClassNames = false)
@RecordBuilder.Include(IncludeWithOption.Hey.class)
public class IncludeWithOption {
public static record Hey(String s) {
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
@MyInterfaceTemplate
public interface InterfaceTemplateTest {
String name();
int age();
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.util.List;
@RecordBuilder
public record MutableCollectionRecord(List<String> l) implements MutableCollectionRecordBuilder.With {
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder.Template(options = @RecordBuilder.Options(fileComment = "This is a test", withClassName = "Com"), asRecordInterface = true)
public @interface MyInterfaceTemplate {
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder.Template(options = @RecordBuilder.Options(fileComment = "This is a test", withClassName = "Com"))
public @interface MyTemplate {
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
public class Nested {
record NestedRecord(int x, int y) {
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordInterface;
import java.math.BigDecimal;
import java.math.BigInteger;
@RecordInterface(addRecordBuilder = false)
public interface NoBuilder {
BigInteger big();
BigDecimal decimal();
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
@RecordBuilder.Options(addStaticBuilder = false)
@RecordBuilder
public record NoStaticBuilder(String foo) {
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
public record Pair<T, U>(T t, U u) {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordInterface;
@RecordInterface
public interface Person {
String name();
int age();
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
public record Point(int x, int y) {
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder
public record RecordWithAnR(int r, String b) {
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import javax.validation.constraints.NotNull;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
@RecordBuilder.Options(emptyDefaultForOptional = true, addConcreteSettersForOptional = true)
@RecordBuilder
public record RecordWithOptional(@NotNull Optional<String> value, Optional raw, OptionalInt i, OptionalLong l,
OptionalDouble d) {
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder.Options(emptyDefaultForOptional = true)
@RecordBuilder
public record RecordWithOptional2(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l,
OptionalDouble d) {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@RecordBuilder
@RecordBuilder.Options(useValidationApi = true)
public record RequestWithValid(@NotNull @Valid Part part) implements RequestWithValidBuilder.With {
public record Part(@NotBlank String name) {
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.validation.constraints.NotNull;
import java.util.List;
@RecordBuilder.Options(interpretNotNulls = true)
@RecordBuilder
public record RequiredRecord(@NotNull String hey, @NotNull int i, @NotNull List<String> l)
implements RequiredRecordBuilder.With {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.validation.constraints.NotNull;
@RecordBuilder.Options(useValidationApi = true)
@RecordBuilder
public record RequiredRecord2(@NotNull String hey, @NotNull int i) implements RequiredRecord2Builder.With {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2016 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,5 +18,5 @@ package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder
public record SimpleGenericRecord<T>(int i, T s) {
public record SimpleGenericRecord<T>(int i, T s) implements SimpleGenericRecordBuilder.With<T> {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2016 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,5 +18,6 @@ package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder
@RecordBuilder.Options(prefixEnclosingClassNames = false)
public record SimpleRecord(int i, String s) {
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(addSingleItemCollectionBuilders = true, singleItemBuilderPrefix = "add1", useImmutableCollections = true, addFunctionalMethodsToWith = true)
public record SingleItems<T>(List<String> strings, Set<List<T>> sets, Map<Instant, T> map, Collection<T> collection)
implements SingleItemsBuilder.With<T> {
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordInterface;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
@RecordInterface
public interface SpecializedPerson<T, U> extends Person {
List<T> features();
Map<Supplier<T>, Function<U, Function<T, U>>> complex();
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder
@RecordBuilder.Options(enableGetters = false, enableWither = false)
public record StrippedFeaturesRecord(int aField) {
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import java.time.Instant;
@MyTemplate
public record TemplateTest(String text, Instant date) implements TemplateTestBuilder.Com {
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
public interface Thingy<T> {
T getIt();
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(useUnmodifiableCollections = true)
record UnmodifiableCollectionsRecord(List<Integer> aList, Set<String> orderedSet, Map<String, Integer> orderedMap,
Collection<String> aCollection) {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2016 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,13 +15,20 @@
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.test.SimpleRecordBuilder;
public class Usage {
public static void main(String[] args) {
var hey = SimpleRecordBuilder.builder().i(10).s("hey").build();
System.out.println(hey);
var hey2 = SimpleRecordBuilder.builder(hey).i(100).build();
System.out.println(hey2);
var person = new PersonRecord("me", 42);
outputPerson(person);
var aged = PersonRecordBuilder.builder(person).age(100).build();
outputPerson(aged);
}
private static void outputPerson(Person p) {
System.out.println(p.toString());
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(addSingleItemCollectionBuilders = true, useImmutableCollections = true)
public record WildcardSingleItems<T>(List<? extends String> strings, Set<? extends List<? extends T>> sets,
Map<? extends Instant, ? extends T> map, Collection<? extends T> collection)
implements WildcardSingleItemsBuilder.With<T> {
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.includes;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder.Include(packages = "io.soabase.recordbuilder.test.includes.pack", classes = JustATest.class)
public class IncludeFactory {
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.includes;
public record JustATest(int i) {
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.includes.pack;
public interface AlsoIgnoreMe {
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.includes.pack;
public class IgnoreMe {
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.includes.pack;
public record PackRecord1(String name) {
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.includes.pack;
public record PackRecord2(String name, int age) {
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.includes.pack;
import java.time.Instant;
public record PackRecord3(Instant time, int age) {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.jacoco;
import io.soabase.recordbuilder.core.RecordBuilderFull;
import io.soabase.recordbuilder.core.RecordBuilderGenerated;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
@RecordBuilderFull
@RecordBuilderGenerated
public record FullRecordForJacoco(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecordForJacoco> fullRecords,
@NotNull String justAString) {
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@RecordBuilder.Include(value = { Point.class, Pair.class }, packagePattern = "*.foo")
@RecordBuilder.Options(fileComment = "MyLicense - Auto generated")
@RecordInterface.Include(value = Customer.class, addRecordBuilder = false, packagePattern = "*.bar")
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import io.soabase.recordbuilder.core.RecordInterface;

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.visibility;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder
record PackagePrivateRecord(int i) {
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.visibility;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.lang.model.element.Modifier;
@RecordBuilder.Options(builderClassModifiers = { Modifier.PUBLIC })
@RecordBuilder
record PackagePrivateRecordWithPublicBuilder(String value) {
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.visibility;
import io.soabase.recordbuilder.core.RecordBuilder;
class Wrapper {
@RecordBuilder
protected record ProtectedRecord(int i) {
}
@RecordBuilder
record PackagePrivateRecord(int i) {
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import java.lang.reflect.AnnotatedElement;
class TestAnnotated {
@Test
void testInheritComponentAnnotationsFalse() throws NoSuchMethodException {
var method = IgnoreAnnotatedBuilder.class.getMethod("s");
Assertions.assertNull(method.getAnnotation(NotNull.class));
}
@Test
void testStaticConstructor() throws NoSuchMethodException {
var method = AnnotatedBuilder.class.getMethod("Annotated", String.class, Integer.TYPE, Double.TYPE);
var parameters = method.getParameters();
Assertions.assertEquals(3, parameters.length);
assertHey(parameters[0]);
assertI(parameters[1]);
assertD(parameters[2]);
}
@Test
void testSetters() throws NoSuchMethodException {
var method = AnnotatedBuilder.class.getMethod("hey", String.class);
var parameters = method.getParameters();
Assertions.assertEquals(1, parameters.length);
assertHey(parameters[0]);
method = AnnotatedBuilder.class.getMethod("i", Integer.TYPE);
parameters = method.getParameters();
Assertions.assertEquals(1, parameters.length);
assertI(parameters[0]);
method = AnnotatedBuilder.class.getMethod("d", Double.TYPE);
parameters = method.getParameters();
Assertions.assertEquals(1, parameters.length);
assertD(parameters[0]);
}
@Test
void testGetters() throws NoSuchMethodException {
var method = AnnotatedBuilder.class.getMethod("hey");
assertHey(method);
method = AnnotatedBuilder.class.getMethod("i");
assertI(method);
method = AnnotatedBuilder.class.getMethod("d");
assertD(method);
}
@Test
void testWitherSetters() throws NoSuchMethodException {
var method = AnnotatedBuilder.With.class.getMethod("withHey", String.class);
var parameters = method.getParameters();
Assertions.assertEquals(1, parameters.length);
assertHey(parameters[0]);
method = AnnotatedBuilder.With.class.getMethod("withI", Integer.TYPE);
parameters = method.getParameters();
Assertions.assertEquals(1, parameters.length);
assertI(parameters[0]);
method = AnnotatedBuilder.With.class.getMethod("withD", Double.TYPE);
parameters = method.getParameters();
Assertions.assertEquals(1, parameters.length);
assertD(parameters[0]);
}
private void assertD(AnnotatedElement d) {
Assertions.assertEquals(0, d.getAnnotations().length);
}
private void assertI(AnnotatedElement i) {
Assertions.assertNotNull(i.getAnnotation(Min.class));
Assertions.assertEquals(i.getAnnotation(Min.class).value(), 10);
Assertions.assertNotNull(i.getAnnotation(Max.class));
Assertions.assertEquals(i.getAnnotation(Max.class).value(), 100);
}
private void assertHey(AnnotatedElement hey) {
Assertions.assertNotNull(hey.getAnnotation(NotNull.class));
Assertions.assertNotNull(hey.getAnnotation(Null.class));
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.*;
import static io.soabase.recordbuilder.test.foo.PointBuilder.Point;
import static org.junit.jupiter.api.Assertions.*;
class TestCollections {
@Test
void testRecordBuilderOptionsCopied() {
try {
assertNotNull(CollectionInterfaceRecordBuilder.class.getDeclaredMethod("__list", Collection.class));
} catch (NoSuchMethodException e) {
Assertions.fail(e);
}
}
@Test
void testCollectionRecordDefaultValues() {
var defaultValues = CollectionRecordBuilder.builder().build();
assertNotNull(defaultValues.l());
assertNotNull(defaultValues.s());
assertNotNull(defaultValues.m());
assertNotNull(defaultValues.c());
}
@Test
void testCollectionRecordImmutable() {
var list = new ArrayList<String>();
list.add("one");
var set = new HashSet<String>();
set.add("one");
var map = new HashMap<String, Point>();
map.put("one", Point(10, 20));
var collectionAsSet = new HashSet<Point>();
collectionAsSet.add(Point(30, 40));
var r = CollectionRecordBuilder.<String, Point> builder().l(list).s(set).m(map).c(collectionAsSet).build();
assertValues(r, list, set, map, collectionAsSet);
assertValueChanges(r, list, set, map, collectionAsSet);
assertImmutable(r);
var collectionAsList = new ArrayList<Point>();
var x = CollectionRecordBuilder.<String, Point> builder().l(list).s(set).m(map).c(collectionAsList).build();
assertTrue(x.c() instanceof List);
}
@Test
void testCollectionRecordImmutableWithers() {
var r = CollectionRecordBuilder.<String, Point> builder().build();
var list = new ArrayList<String>();
list.add("one");
var set = new HashSet<String>();
set.add("one");
var map = new HashMap<String, Point>();
map.put("one", Point(10, 20));
var collectionAsSet = new HashSet<Point>();
collectionAsSet.add(Point(30, 40));
var x = r.withL(list).withS(set).withM(map).withC(collectionAsSet);
assertValues(x, list, set, map, collectionAsSet);
assertValueChanges(x, list, set, map, collectionAsSet);
assertImmutable(x);
}
@Test
public void testMutableCollectionRecord() {
var r = MutableCollectionRecordBuilder.builder().build();
assertNull(r.l());
var list = new ArrayList<String>();
list.add("one");
r = MutableCollectionRecordBuilder.builder().l(list).build();
assertEquals(r.l(), list);
list.add("two");
assertEquals(r.l(), list);
}
private void assertImmutable(CollectionRecord<String, Point> r) {
assertThrows(UnsupportedOperationException.class, () -> r.l().add("hey"));
assertThrows(UnsupportedOperationException.class, () -> r.s().add("hey"));
assertThrows(UnsupportedOperationException.class, () -> r.m().put("hey", Point(1, 2)));
assertThrows(UnsupportedOperationException.class, () -> r.c().add(Point(1, 2)));
}
private void assertValueChanges(CollectionRecord<String, Point> r, ArrayList<String> list, HashSet<String> set,
HashMap<String, Point> map, HashSet<Point> collectionAsSet) {
list.add("two");
set.add("two");
map.put("two", Point(50, 60));
collectionAsSet.add(Point(70, 80));
assertNotEquals(r.l(), list);
assertNotEquals(r.s(), set);
assertNotEquals(r.m(), map);
assertNotEquals(r.c(), collectionAsSet);
}
private void assertValues(CollectionRecord<String, Point> r, ArrayList<String> list, HashSet<String> set,
HashMap<String, Point> map, HashSet<Point> collectionAsSet) {
assertEquals(r.l(), list);
assertEquals(r.s(), set);
assertEquals(r.m(), map);
assertEquals(r.c(), collectionAsSet);
assertTrue(r.c() instanceof Set);
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class TestCollectionsBuilder {
@Test
void testCollectionsBuilderReturnNonNullEmptyCollections() {
CollectionRecordBuilder<Object, Point> builder = CollectionRecordBuilder.builder();
Assertions.assertNotNull(builder.l());
Assertions.assertTrue(builder.l().isEmpty());
Assertions.assertNotNull(builder.c());
Assertions.assertTrue(builder.c().isEmpty());
Assertions.assertNotNull(builder.m());
Assertions.assertTrue(builder.m().isEmpty());
Assertions.assertNotNull(builder.s());
Assertions.assertTrue(builder.s().isEmpty());
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.*;
public class TestImmutableCollections {
@Test
public void testImmutableListNotCopiedWhenNotChanged() {
var item = CollectionCopyingBuilder.<String> builder().addList("a").addList("b").addList("c").build();
Assertions.assertEquals(item.list(), List.of("a", "b", "c"));
var oldList = item.list();
var copy = item.with().count(1).build();
Assertions.assertSame(oldList, copy.list());
var otherCopy = item.with().count(2).build();
Assertions.assertSame(oldList, otherCopy.list());
}
@Test
public void testImmutableSetNotCopiedWhenNotChanged() {
var item = CollectionCopyingBuilder.<String> builder().addSet(Arrays.asList("1", "2", "3")).build();
Assertions.assertEquals(item.set(), Set.of("1", "2", "3"));
var oldSet = item.set();
var copy = item.with().count(1).build();
Assertions.assertSame(oldSet, copy.set());
var otherCopy = item.with().count(2).build();
Assertions.assertSame(oldSet, otherCopy.set());
}
@Test
public void testImmutableCollectionNotCopiedWhenNotChanged() {
var item = CollectionCopyingBuilder.<String> builder().collection(List.of("foo", "bar", "baz")).build();
Assertions.assertEquals(item.collection(), List.of("foo", "bar", "baz"));
var oldCollection = item.collection();
var copy = item.with().count(1).build();
Assertions.assertSame(oldCollection, copy.collection());
var otherCopy = item.with().count(2).build();
Assertions.assertSame(oldCollection, otherCopy.collection());
}
@Test
public void testImmutableMapNotCopiedWhenNotChanged() {
var item = CollectionCopyingBuilder.<String> builder().addMap(Instant.MAX, "future")
.addMap(Instant.MIN, "before").build();
Assertions.assertEquals(item.map(), Map.of(Instant.MAX, "future", Instant.MIN, "before"));
var oldMap = item.map();
var copy = item.with().count(1).build();
Assertions.assertSame(oldMap, copy.map());
var otherCopy = item.with().count(2).build();
Assertions.assertSame(oldMap, otherCopy.map());
}
@Test
void testSourceListNotModified() {
var item = new CollectionCopying<>(new ArrayList<>(), null, null, null, 0);
var modifiedItem = CollectionCopyingBuilder.builder(item).addList("a").build();
Assertions.assertEquals(modifiedItem.list(), List.of("a"));
Assertions.assertTrue(item.list().isEmpty());
}
@Test
void testSourceSetNotModified() {
var item = new CollectionCopying<>(null, new HashSet<>(), null, null, 0);
var modifiedItem = CollectionCopyingBuilder.builder(item).addSet("a").build();
Assertions.assertEquals(modifiedItem.set(), Set.of("a"));
Assertions.assertTrue(item.set().isEmpty());
}
@Test
void testSourceMapNotModified() {
var item = new CollectionCopying<>(null, null, new HashMap<>(), null, 0);
var modifiedItem = CollectionCopyingBuilder.builder(item).addMap(Instant.MIN, "a").build();
Assertions.assertEquals(modifiedItem.map(), Map.of(Instant.MIN, "a"));
Assertions.assertTrue(item.map().isEmpty());
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class TestIncludes {
@Test
void testOptionsOnInclude() {
// assert it's not prefixed with the enclosing class name
IncludeWithOption.Hey hey = io.soabase.recordbuilder.test.HeyBuilder.builder().s("this is s").build();
Assertions.assertEquals("this is s", hey.s());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class TestOptional {
@Test
void testDefaultEmpty() {
var record = RecordWithOptionalBuilder.builder();
Assertions.assertEquals(Optional.empty(), record.value());
Assertions.assertEquals(Optional.empty(), record.raw());
Assertions.assertEquals(OptionalInt.empty(), record.i());
Assertions.assertEquals(OptionalLong.empty(), record.l());
Assertions.assertEquals(OptionalDouble.empty(), record.d());
}
@Test
void testRawSetters() {
var record = RecordWithOptionalBuilder.builder().value("value").raw("rawValue").i(42).l(424242L).d(42.42)
.build();
Assertions.assertEquals(Optional.of("value"), record.value());
Assertions.assertEquals(Optional.of("rawValue"), record.raw());
Assertions.assertEquals(OptionalInt.of(42), record.i());
Assertions.assertEquals(OptionalLong.of(424242L), record.l());
Assertions.assertEquals(OptionalDouble.of(42.42), record.d());
}
@Test
void testOptionalSetters() {
var record = RecordWithOptional2Builder.builder().value(Optional.of("value")).raw(Optional.of("rawValue"))
.i(OptionalInt.of(42)).l(OptionalLong.of(424242L)).d(OptionalDouble.of(42.42)).build();
Assertions.assertEquals(Optional.of("value"), record.value());
Assertions.assertEquals(Optional.of("rawValue"), record.raw());
Assertions.assertEquals(OptionalInt.of(42), record.i());
Assertions.assertEquals(OptionalLong.of(424242L), record.l());
Assertions.assertEquals(OptionalDouble.of(42.42), record.d());
}
@Test
void shouldAcceptNullForOptionalRawSetter() {
// given
String value = null;
// when
var record = RecordWithOptionalBuilder.builder().value(value).build();
// then
Assertions.assertEquals(Optional.empty(), record.value());
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class TestOptionsOnPackage {
@Test
void testOptionsOnInclude() throws IOException {
String text = Files.readString(
Path.of("target/generated-sources/annotations/io/soabase/recordbuilder/test/foo/PairBuilder.java"));
Assertions.assertTrue(text.contains("// MyLicense - Auto generated"));
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class TestRecordBuilderFull {
@Test
void testNonNull() {
var record = FullRecordBuilder.builder().justAString("").build();
Assertions.assertEquals(List.of(), record.numbers());
Assertions.assertEquals(Map.of(), record.fullRecords());
}
@Test
void testImmutable() {
var record = FullRecordBuilder.builder().fullRecords(new HashMap<>()).numbers(new ArrayList<>()).justAString("")
.build();
Assertions.assertThrows(UnsupportedOperationException.class, () -> record.fullRecords().put(1, record));
Assertions.assertThrows(UnsupportedOperationException.class, () -> record.numbers().add(1));
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import static io.soabase.recordbuilder.test.SimpleGenericRecordBuilder.SimpleGenericRecord;
import static io.soabase.recordbuilder.test.SimpleRecordBuilder.SimpleRecord;
public class TestRecordInterface {
@Test
public void testHasDefaults() {
var r1 = new HasDefaultsRecord(Instant.MIN, Instant.MAX);
var r2 = r1.with(b -> b.tomorrow(Instant.MIN));
Assertions.assertEquals(Instant.MIN, r1.time());
Assertions.assertEquals(Instant.MAX, r1.tomorrow());
Assertions.assertEquals(Instant.MIN, r2.time());
Assertions.assertEquals(Instant.MIN, r2.tomorrow());
}
@Test
public void testStaticConstructor() {
var simple = SimpleRecord(10, "hey");
Assertions.assertEquals(simple.i(), 10);
Assertions.assertEquals(simple.s(), "hey");
var now = Instant.now();
var generic = SimpleGenericRecord(101, now);
Assertions.assertEquals(generic.i(), 101);
Assertions.assertEquals(generic.s(), now);
}
@Test
public void testBuilderStreamWithValues() {
var stream = SimpleRecordBuilder.stream(SimpleRecordBuilder.builder().i(19).s("value").build()).toList();
Assertions.assertEquals(stream, List.of(Map.entry("i", 19), Map.entry("s", "value")));
}
@Test
public void testBuilderStreamWithNulls() {
var stream = SimpleRecordBuilder.stream(SimpleRecordBuilder.builder().build()).toList();
Assertions.assertEquals(stream,
List.of(new SimpleImmutableEntry<>("i", 0), new SimpleImmutableEntry<>("s", null)));
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TestSingleItems {
@Test
public void testInternalCollections() {
var now = Instant.now();
var item = SingleItemsBuilder.<String> builder().add1Map(now, "now").add1Map(Instant.MIN, "before")
.add1Sets(Arrays.asList("1", "2")).add1Sets(List.of("3")).add1Strings("a").add1Strings("b")
.add1Strings("c").build();
Assertions.assertEquals(item.map(), Map.of(now, "now", Instant.MIN, "before"));
Assertions.assertEquals(item.sets(), Set.of(List.of("1", "2"), List.of("3")));
Assertions.assertEquals(item.strings(), List.of("a", "b", "c"));
var copy = item.with().add1Strings("new").add1Map(Instant.MAX, "after").add1Sets(List.of("10", "20", "30"))
.build();
Assertions.assertNotEquals(item, copy);
Assertions.assertEquals(copy.map(), Map.of(now, "now", Instant.MIN, "before", Instant.MAX, "after"));
Assertions.assertEquals(copy.sets(), Set.of(List.of("1", "2"), List.of("3"), List.of("10", "20", "30")));
Assertions.assertEquals(copy.strings(), List.of("a", "b", "c", "new"));
var stringsToAdd = Arrays.asList("x", "y", "z");
var listToAdd = Arrays.asList(List.of("aa", "bb"), List.of("cc"));
var mapToAdd = Map.of(now.plusMillis(1), "now+1", now.plusMillis(2), "now+2");
var streamed = SingleItemsBuilder.builder(item).add1Strings(stringsToAdd.stream()).add1Sets(listToAdd.stream())
.add1Map(mapToAdd.entrySet().stream()).build();
Assertions.assertEquals(streamed.map(),
Map.of(now, "now", Instant.MIN, "before", now.plusMillis(1), "now+1", now.plusMillis(2), "now+2"));
Assertions.assertEquals(streamed.sets(),
Set.of(List.of("1", "2"), List.of("3"), List.of("aa", "bb"), List.of("cc")));
Assertions.assertEquals(streamed.strings(), Arrays.asList("a", "b", "c", "x", "y", "z"));
var nulls = SingleItemsBuilder.builder(item).strings(null).sets(null).map(null).build();
Assertions.assertEquals(nulls.map(), Map.of());
Assertions.assertEquals(nulls.sets(), Set.of());
Assertions.assertEquals(nulls.strings(), List.of());
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.Instant;
class TestTemplate {
@Test
void testTemplate() {
var t = TemplateTestBuilder.TemplateTest("one", Instant.MIN);
var w = t.withText("other");
Assertions.assertEquals("one", t.text());
Assertions.assertEquals("other", w.text());
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class TestUnmodifiableCollectionsBuilder {
@Test
void shouldWrapCollectionsWithUnmodifiableView() {
// given
var list = new ArrayList<Integer>();
list.add(2);
list.add(1);
list.add(0);
var orderedSet = new LinkedHashSet<String>();
orderedSet.add("C");
orderedSet.add("B");
orderedSet.add("A");
var orderedMap = new LinkedHashMap<String, Integer>();
orderedMap.put("C", 2);
orderedMap.put("B", 1);
orderedMap.put("A", 0);
var collection = new HashSet<String>();
collection.add("C");
collection.add("B");
collection.add("A");
// when
var record = UnmodifiableCollectionsRecordBuilder.builder().aList(list).orderedSet(orderedSet)
.orderedMap(orderedMap).aCollection(collection).build();
// then
assertAll(() -> assertThrows(UnsupportedOperationException.class, () -> record.aList().add(9)),
() -> assertThat(record.aList()).containsExactly(2, 1, 0),
() -> assertThrows(UnsupportedOperationException.class, () -> record.orderedSet().add("newElement")),
() -> assertThat(record.orderedSet()).containsExactly("C", "B", "A"),
() -> assertThrows(UnsupportedOperationException.class, () -> record.orderedMap().put("newElement", 9)),
() -> assertThat(record.orderedMap()).containsExactly(entry("C", 2), entry("B", 1), entry("A", 0)),
() -> assertThrows(UnsupportedOperationException.class, () -> record.aCollection().add("newElement")),
() -> assertThat(record.aCollection()).containsExactlyInAnyOrder("C", "B", "A"));
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.validation.ValidationException;
import java.util.List;
class TestValidation {
@Test
void testNotNulls() {
Assertions.assertThrows(NullPointerException.class, () -> RequiredRecordBuilder.builder().build());
}
@Test
void testValidation() {
Assertions.assertThrows(ValidationException.class, () -> RequiredRecord2Builder.builder().build());
}
@Test
void testNotNullsWithNewProperty() {
var valid = RequiredRecordBuilder.builder().hey("hey").i(1).l(List.of()).build();
Assertions.assertThrows(NullPointerException.class, () -> valid.withHey(null));
}
@Test
void testValidationWithNewProperty() {
var valid = RequiredRecord2Builder.builder().hey("hey").i(1).build();
Assertions.assertThrows(ValidationException.class, () -> valid.withHey(null));
}
@Test
void testRequestWithValid() {
Assertions.assertDoesNotThrow(
() -> RequestWithValidBuilder.builder().part(new RequestWithValid.Part("jsfjsf")).build());
Assertions.assertThrows(ValidationException.class,
() -> RequestWithValidBuilder.builder().part(new RequestWithValid.Part("")).build());
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
public class TestVariousOptions {
@Test
public void builderGetsCustomSetterAndGetterNames() {
var obj = CustomMethodNamesBuilder.builder().setKvMap(Map.of(1, "one")).setTheValue(1).setTheList(List.of(2))
.setTheBoolean(true);
assertEquals(1, obj.getTheValue());
assertEquals(List.of(2), obj.getTheList());
assertTrue(obj.isTheBoolean());
assertEquals(new CustomMethodNames<>(Map.of(1, "one"), 1, List.of(2), true), obj.build());
}
@Test
public void withBuilderGetsCustomSetterAndGetterNames() {
var obj = CustomMethodNamesBuilder.from(
CustomMethodNamesBuilder.builder().setTheValue(1).setTheList(List.of(2)).setTheBoolean(true).build());
assertEquals(1, obj.theValue());
assertEquals(List.of(2), obj.theList());
assertTrue(obj.theBoolean());
}
@Test
public void recordHasPrefixedGetters() {
var obj = new CustomMethodNames<>(Map.of(1, "one"), 1, List.of(2), true);
assertEquals(1, obj.getTheValue());
assertEquals(List.of(2), obj.getTheList());
assertTrue(obj.isTheBoolean());
}
@Test
public void noStaticBuilder() {
boolean hasStaticBuilder = Stream.of(NoStaticBuilderBuilder.class.getDeclaredMethods())
.anyMatch(method -> method.getName().equals("NoStaticBuilder"));
assertFalse(hasStaticBuilder);
hasStaticBuilder = Stream.of(SimpleRecordBuilder.class.getDeclaredMethods())
.anyMatch(method -> method.getName().equals("SimpleRecord"));
assertTrue(hasStaticBuilder);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
class TestWithers {
@Test
void testFromWithers() {
var r1 = new SimpleGenericRecord<>(10, List.of("1", "2", "3"));
var r2 = SimpleGenericRecordBuilder.from(r1).withS(List.of("4", "5"));
var r3 = SimpleGenericRecordBuilder.from(r1).with(b -> b.i(20).s(List.of("6", "7")));
Assertions.assertEquals(List.of("1", "2", "3"), r1.s());
Assertions.assertEquals(List.of("4", "5"), r2.s());
Assertions.assertEquals(List.of("6", "7"), r3.s());
Assertions.assertEquals(20, r3.i());
}
@Test
void testWithers() {
var r1 = new SimpleGenericRecord<>(10, List.of("1", "2", "3"));
var r2 = r1.withS(List.of("4", "5"));
var r3 = r2.withI(20);
Assertions.assertEquals(10, r1.i());
Assertions.assertEquals(List.of("1", "2", "3"), r1.s());
Assertions.assertEquals(10, r2.i());
Assertions.assertEquals(List.of("4", "5"), r2.s());
Assertions.assertEquals(20, r3.i());
Assertions.assertEquals(List.of("4", "5"), r2.s());
}
@Test
void testWitherBuilder() {
var r1 = new SimpleGenericRecord<>(10, "ten");
var r2 = r1.with().i(20).s("twenty").build();
var r3 = r2.with().s("changed");
Assertions.assertEquals(10, r1.i());
Assertions.assertEquals("ten", r1.s());
Assertions.assertEquals(20, r2.i());
Assertions.assertEquals("twenty", r2.s());
Assertions.assertEquals(20, r3.i());
Assertions.assertEquals("changed", r3.s());
}
@Test
void testWitherBuilderConsumer() {
var r1 = new SimpleGenericRecord<>(10, "ten");
var r2 = r1.with(r -> r.i(15));
var r3 = r1.with(r -> r.s("twenty").i(20));
Assertions.assertEquals(10, r1.i());
Assertions.assertEquals("ten", r1.s());
Assertions.assertEquals(15, r2.i());
Assertions.assertEquals("ten", r2.s());
Assertions.assertEquals(20, r3.i());
Assertions.assertEquals("twenty", r3.s());
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test.visibility;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Modifier;
class TestVisibility {
@Test
void testMatches() {
Assertions.assertFalse(Modifier.isPublic(PackagePrivateRecordBuilder.class.getModifiers()));
Assertions.assertFalse(Modifier.isPrivate(PackagePrivateRecordBuilder.class.getModifiers()));
Assertions.assertFalse(Modifier.isProtected(PackagePrivateRecordBuilder.class.getModifiers()));
Assertions.assertTrue(Modifier.isPublic(WrapperProtectedRecordBuilder.class.getModifiers()));
}
@Test
void testMatchesWithModifers() {
Assertions.assertFalse(Modifier.isPublic(PackagePrivateRecordWithPublicBuilder.class.getModifiers()));
Assertions.assertFalse(Modifier.isPrivate(PackagePrivateRecordWithPublicBuilder.class.getModifiers()));
Assertions.assertFalse(Modifier.isProtected(PackagePrivateRecordWithPublicBuilder.class.getModifiers()));
Assertions.assertTrue(Modifier.isPublic(PackagePrivateRecordWithPublicBuilderBuilder.class.getModifiers()));
}
}

Some files were not shown because too many files have changed in this diff Show More