6
6
import org .apache .maven .plugins .annotations .Mojo ;
7
7
import org .apache .maven .plugins .annotations .Parameter ;
8
8
import org .apache .maven .project .MavenProject ;
9
+ import org .burningwave .core .assembler .ComponentSupplier ;
10
+ import org .burningwave .core .classes .ClassCriteria ;
11
+ import org .burningwave .core .classes .SearchConfig ;
9
12
import org .slf4j .Logger ;
10
13
import org .slf4j .LoggerFactory ;
11
14
16
19
import java .net .URLClassLoader ;
17
20
import java .nio .file .Files ;
18
21
import java .nio .file .Path ;
22
+ import java .util .Collections ;
19
23
import java .util .List ;
20
24
import java .util .Set ;
21
25
import java .util .stream .Stream ;
22
26
27
+ import static java .lang .reflect .Modifier .isPrivate ;
28
+ import static java .util .Objects .requireNonNullElseGet ;
23
29
import static java .util .function .Predicate .not ;
30
+ import static java .util .stream .Collectors .joining ;
31
+ import static java .util .stream .Stream .concat ;
24
32
import static org .apache .maven .plugins .annotations .LifecyclePhase .GENERATE_TEST_SOURCES ;
25
33
import static org .apache .maven .plugins .annotations .ResolutionScope .COMPILE ;
26
34
@@ -37,6 +45,12 @@ public class GenerateRecordMatcherMojo extends AbstractMojo {
37
45
@ Parameter
38
46
private Set <String > includes ;
39
47
48
+ /**
49
+ * Specify which packages (includes any sub packages) to scan for records
50
+ */
51
+ @ Parameter (defaultValue = "${project.groupId}" )
52
+ private Set <String > scanPackages ;
53
+
40
54
41
55
/**
42
56
* The directory where the generated Hamcrest matchers will be written to.
@@ -63,35 +77,44 @@ public class GenerateRecordMatcherMojo extends AbstractMojo {
63
77
64
78
@ Override
65
79
public void execute () throws MojoExecutionException , MojoFailureException {
66
- if (includes != null && !includes .isEmpty ()) {
67
- Path outputDirectory = resolveOutputDirectory ();
68
- if (includeGeneratedCodeAsTestSources ) {
69
- mavenProject .addTestCompileSourceRoot (outputDirectory .toString ());
70
- LOG .debug ("{} has been added as a compiler test sources directory" , outputDirectory );
71
- }
72
- try {
73
- Files .createDirectories (outputDirectory );
74
- LOG .info ("Generating Hamcrest matchers in {}" , outputDirectory );
75
- } catch (IOException e ) {
76
- throw new UncheckedIOException (
77
- "Unable to create output directory " + outputDirectory + ", " +
78
- "because " + e .getClass ().getSimpleName () + ": " + e .getMessage (), e );
79
- }
80
- var generator = new RecordMatcherGenerator ();
81
- resolveIncludedRecords ().forEach (recordClass -> {
82
- var recordMatcherCompilationUnit = generator .generateFromRecord (recordClass );
80
+ Path outputDirectory = resolveOutputDirectory ();
81
+ try {
82
+ Files .createDirectories (outputDirectory );
83
+ LOG .info ("Generating Hamcrest matchers in {}" , outputDirectory );
84
+ } catch (IOException e ) {
85
+ throw new UncheckedIOException (
86
+ "Unable to create output directory " + outputDirectory + ", " +
87
+ "because " + e .getClass ().getSimpleName () + ": " + e .getMessage (), e );
88
+ }
89
+ if (includeGeneratedCodeAsTestSources ) {
90
+ mavenProject .addTestCompileSourceRoot (outputDirectory .toString ());
91
+ LOG .debug ("{} has been added as a compiler test sources directory" , outputDirectory );
92
+ }
93
+
94
+ var generator = new RecordMatcherGenerator ();
95
+ var writtenFiles = resolveIncludedRecords ()
96
+ .map (generator ::generateFromRecord )
97
+ .map (compilationUnit -> {
83
98
try {
84
- var writtenFile = recordMatcherCompilationUnit .writeToBaseDirectory (outputDirectory );
85
- LOG .info ("Generated matcher {}" , outputDirectory .relativize (writtenFile ));
99
+ return compilationUnit .writeToBaseDirectory (outputDirectory );
86
100
} catch (IOException e ) {
87
101
throw new UncheckedIOException (
88
- "Unable to write " + recordMatcherCompilationUnit + " to file, " +
102
+ "Unable to write " + compilationUnit + " to file, " +
89
103
"because " + e .getClass ().getSimpleName () + ": " + e .getMessage (), e );
90
104
}
91
- });
105
+ })
106
+ .toList ();
107
+
108
+
109
+ if (writtenFiles .isEmpty ()) {
110
+ LOG .warn ("No matchers was generated!" );
92
111
} else {
93
- LOG .warn ("No records to generate Hamcrest matchers from!" );
112
+ for (var writtenFile : writtenFiles ) {
113
+ LOG .info ("Generated {}" , outputDirectory .relativize (writtenFile ));
114
+ }
115
+ LOG .info ("Total files written: {}" , writtenFiles .size ());
94
116
}
117
+
95
118
}
96
119
97
120
private Path resolveOutputDirectory () {
@@ -101,22 +124,62 @@ private Path resolveOutputDirectory() {
101
124
102
125
private Stream <Class <? extends Record >> resolveIncludedRecords () {
103
126
ClassLoader classLoader = buildProjectClassLoader (mavenProject , this .getClass ().getClassLoader ());
104
- return includes .stream ()
105
- .filter (not (String ::isBlank ))
106
- .map (String ::trim )
107
- .map (recordClassName -> {
108
- try {
109
- return classLoader .loadClass (recordClassName );
110
- } catch (ClassNotFoundException e ) {
111
- throw new IllegalArgumentException (
112
- "Unable to resolve Class from " + recordClassName +
113
- ", because " + e .getClass ().getSimpleName () + ": " + e .getMessage (), e );
114
- }
115
- })
116
- .map (c -> c .asSubclass (Record .class ));
127
+ return concat (
128
+ scanForRecords (classLoader , scanPackages )
129
+ .filter (foundRecord -> {
130
+ var typeParams = foundRecord .getTypeParameters ();
131
+ if (typeParams .length != 0 ) {
132
+ LOG .debug ("Not including {}<{}> because type parameters are not supported" ,
133
+ foundRecord .getName (), Stream .of (typeParams ).map (t -> t .getName ()).collect (joining (", " )));
134
+ return false ;
135
+ }
136
+ return true ;
137
+ }),
138
+ requireNonNullElseGet (includes , Collections ::<String >emptySet ).stream ()
139
+ .filter (not (String ::isBlank ))
140
+ .map (String ::trim )
141
+ .<Class <? extends Record >>map (recordClassName -> load (recordClassName , Record .class , classLoader ))
142
+ .filter (configuredInclusion -> {
143
+ var typeParams = configuredInclusion .getTypeParameters ();
144
+ if (typeParams .length != 0 ) {
145
+ throw new UnsupportedOperationException (
146
+ "Can not include " + configuredInclusion .getName () +
147
+ "<" + Stream .of (typeParams ).map (t -> t .getName ()).collect (joining (", " )) + "> " +
148
+ "because type parameters are not supported" );
149
+ }
150
+ return true ;
151
+ })
152
+ .distinct ());
153
+ }
154
+
155
+ private static Stream <Class <? extends Record >> scanForRecords (ClassLoader classLoader , Set <String > packageNames ) {
156
+ if (packageNames .isEmpty ()) {
157
+ LOG .debug ("No packages configured for scanning" );
158
+ return Stream .empty ();
159
+ }
160
+ LOG .info ("Scanning packages {} for records" , packageNames );
161
+ var components = ComponentSupplier .getInstance ();
162
+ var allRecordsInClassLoader = SearchConfig .forResources (packageNames .stream ().map (p -> p .replace ('.' , '/' )).toList ())
163
+ .by (ClassCriteria .create ().allThoseThatMatch (cls -> Record .class .isAssignableFrom (cls ) && !isPrivate (cls .getModifiers ())))
164
+ .useAsParentClassLoader (classLoader );
165
+
166
+ try (var searchResult = components .getClassHunter ().findBy (allRecordsInClassLoader )) {
167
+ return searchResult .getClasses ().stream ().map (c -> c .asSubclass (Record .class ));
168
+ }
169
+
117
170
}
118
171
119
172
173
+ private static <C > Class <? extends C > load (String className , Class <C > target , ClassLoader classLoader ) {
174
+ try {
175
+ return classLoader .loadClass (className ).asSubclass (target );
176
+ } catch (ClassNotFoundException e ) {
177
+ throw new IllegalArgumentException (
178
+ "Unable to resolve Class from " + className +
179
+ ", because " + e .getClass ().getSimpleName () + ": " + e .getMessage (), e );
180
+ }
181
+ }
182
+
120
183
private static ClassLoader buildProjectClassLoader (MavenProject project , ClassLoader parent ) {
121
184
try {
122
185
@ SuppressWarnings ("unchecked" )
0 commit comments