15
15
*/
16
16
package org .springframework .modulith .core ;
17
17
18
+ import java .lang .annotation .Annotation ;
18
19
import java .util .Objects ;
20
+ import java .util .Optional ;
21
+ import java .util .function .Function ;
19
22
import java .util .stream .Stream ;
20
23
24
+ import org .springframework .modulith .ApplicationModule ;
25
+ import org .springframework .modulith .core .Types .JMoleculesTypes ;
21
26
import org .springframework .util .Assert ;
27
+ import org .springframework .util .StringUtils ;
22
28
23
29
/**
24
30
* The source of an {@link ApplicationModule}. Essentially a {@link JavaPackage} and associated naming strategy for the
32
38
*/
33
39
public class ApplicationModuleSource {
34
40
41
+ private static final ApplicationModuleSourceMetadata ANNOTATION_IDENTIFIER_SOURCE = ApplicationModuleSourceMetadata
42
+ .delegating (
43
+ JMoleculesTypes .getIdentifierSource (),
44
+ ApplicationModuleSourceMetadata .forAnnotation (ApplicationModule .class , ApplicationModule ::id ));
45
+
35
46
private final JavaPackage moduleBasePackage ;
36
- private final String moduleName ;
47
+ private final ApplicationModuleIdentifier identifier ;
37
48
38
49
/**
39
50
* Creates a new {@link ApplicationModuleSource} for the given module base package and module name.
40
51
*
41
52
* @param moduleBasePackage must not be {@literal null}.
42
53
* @param moduleName must not be {@literal null} or empty.
43
54
*/
44
- private ApplicationModuleSource (JavaPackage moduleBasePackage , String moduleName ) {
55
+ private ApplicationModuleSource (JavaPackage moduleBasePackage , ApplicationModuleIdentifier identifier ) {
45
56
46
57
Assert .notNull (moduleBasePackage , "JavaPackage must not be null!" );
47
- Assert .hasText (moduleName , "Module name must not be null or empty!" );
48
58
49
59
this .moduleBasePackage = moduleBasePackage ;
50
- this .moduleName = moduleName ;
60
+ this .identifier = identifier ;
51
61
}
52
62
53
63
/**
54
64
* Returns a {@link Stream} of {@link ApplicationModuleSource}s by applying the given
55
65
* {@link ApplicationModuleDetectionStrategy} to the given base package.
56
66
*
57
- * @param pkg must not be {@literal null}.
67
+ * @param rootPackage must not be {@literal null}.
58
68
* @param strategy must not be {@literal null}.
59
69
* @param fullyQualifiedModuleNames whether to use fully qualified module names.
60
70
* @return will never be {@literal null}.
61
71
*/
62
- public static Stream <ApplicationModuleSource > from (JavaPackage pkg , ApplicationModuleDetectionStrategy strategy ,
63
- boolean fullyQualifiedModuleNames ) {
72
+ public static Stream <ApplicationModuleSource > from (JavaPackage rootPackage ,
73
+ ApplicationModuleDetectionStrategy strategy , boolean fullyQualifiedModuleNames ) {
64
74
65
- Assert .notNull (pkg , "Base package must not be null!" );
75
+ Assert .notNull (rootPackage , "Root package must not be null!" );
66
76
Assert .notNull (strategy , "ApplicationModuleDetectionStrategy must not be null!" );
67
77
68
- return strategy .getModuleBasePackages (pkg )
69
- .flatMap (it -> it .andSubPackagesAnnotatedWith (org .springframework .modulith .ApplicationModule .class ))
70
- .map (it -> new ApplicationModuleSource (it , fullyQualifiedModuleNames ? it .getName () : pkg .getTrailingName (it )));
78
+ return strategy .getModuleBasePackages (rootPackage )
79
+ .flatMap (ANNOTATION_IDENTIFIER_SOURCE ::withNestedPackages )
80
+ .map (it -> {
81
+
82
+ var id = ANNOTATION_IDENTIFIER_SOURCE .lookupIdentifier (it )
83
+ .orElseGet (() -> ApplicationModuleIdentifier .of (
84
+ fullyQualifiedModuleNames ? it .getName () : rootPackage .getTrailingName (it )));
85
+
86
+ return new ApplicationModuleSource (it , id );
87
+ });
71
88
}
72
89
73
90
/**
74
91
* Creates a new {@link ApplicationModuleSource} for the given {@link JavaPackage} and name.
75
92
*
76
93
* @param pkg must not be {@literal null}.
77
- * @param name must not be {@literal null} or empty.
94
+ * @param identifier must not be {@literal null} or empty.
78
95
* @return will never be {@literal null}.
79
96
*/
80
- public static ApplicationModuleSource from (JavaPackage pkg , String name ) {
81
-
82
- Assert .hasText (name , "Name must not be null or empty!" );
83
-
84
- return new ApplicationModuleSource (pkg , name );
97
+ static ApplicationModuleSource from (JavaPackage pkg , String identifier ) {
98
+ return new ApplicationModuleSource (pkg , ApplicationModuleIdentifier .of (identifier ));
85
99
}
86
100
87
101
/**
102
+ * Returns the base package for the module.
103
+ *
88
104
* @return will never be {@literal null}.
89
105
*/
90
106
public JavaPackage getModuleBasePackage () {
91
107
return moduleBasePackage ;
92
108
}
93
109
94
110
/**
95
- * @return will never be {@literal null} or empty.
111
+ * Returns the {@link ApplicationModuleIdentifier} to be used for the module.
112
+ *
113
+ * @return will never be {@literal null}.
96
114
*/
97
- public String getModuleName () {
98
- return moduleName ;
115
+ public ApplicationModuleIdentifier getIdentifier () {
116
+ return identifier ;
99
117
}
100
118
101
119
/*
@@ -113,7 +131,7 @@ public boolean equals(Object obj) {
113
131
return false ;
114
132
}
115
133
116
- return Objects .equals (this .moduleName , that .moduleName )
134
+ return Objects .equals (this .identifier , that .identifier )
117
135
&& Objects .equals (this .moduleBasePackage , that .moduleBasePackage );
118
136
}
119
137
@@ -123,6 +141,105 @@ public boolean equals(Object obj) {
123
141
*/
124
142
@ Override
125
143
public int hashCode () {
126
- return Objects .hash (moduleName , moduleBasePackage );
144
+ return Objects .hash (identifier , moduleBasePackage );
145
+ }
146
+
147
+ /*
148
+ * (non-Javadoc)
149
+ * @see java.lang.Object#toString()
150
+ */
151
+ @ Override
152
+ public String toString () {
153
+ return "ApplicationModuleSource(" + identifier + ", " + moduleBasePackage .getName () + ")" ;
154
+ }
155
+
156
+ /**
157
+ * An intermediate abstraction to detect both the {@link ApplicationModuleIdentifier} and potentially nested module
158
+ * declarations for the {@link JavaPackage}s returned from the first pass of module detection.
159
+ *
160
+ * @author Oliver Drotbohm
161
+ * @see ApplicationModuleDetectionStrategy
162
+ */
163
+ interface ApplicationModuleSourceMetadata {
164
+
165
+ /**
166
+ * Returns an optional {@link ApplicationModuleIdentifier} obtained by the annotation on the given package.
167
+ *
168
+ * @param pkg must not be {@literal null}.
169
+ * @return will never be {@literal null}.
170
+ */
171
+ Optional <ApplicationModuleIdentifier > lookupIdentifier (JavaPackage pkg );
172
+
173
+ /**
174
+ * Return a {@link Stream} of {@link JavaPackage}s that are
175
+ *
176
+ * @param pkg must not be {@literal null}.
177
+ * @return will never be {@literal null}.
178
+ */
179
+ Stream <JavaPackage > withNestedPackages (JavaPackage pkg );
180
+
181
+ /**
182
+ * Creates a new {@link ApplicationModuleSourceFactory} detecting the {@link ApplicationModuleIdentifier} based on a
183
+ * particular annotation's attribute. It also detects nested {@link JavaPackage}s annotated with the given
184
+ * annotation as nested module base packages.
185
+ *
186
+ * @param <T> an annotation type
187
+ * @param annotation must not be {@literal null}.
188
+ * @param extractor must not be {@literal null}.
189
+ * @return will never be {@literal null}.
190
+ */
191
+ static <T extends Annotation > ApplicationModuleSourceMetadata forAnnotation (Class <T > annotation ,
192
+ Function <T , String > extractor ) {
193
+
194
+ Assert .notNull (annotation , "Annotation type must not be null!" );
195
+ Assert .notNull (extractor , "Attribute extractor must not be null!" );
196
+
197
+ return new ApplicationModuleSourceMetadata () {
198
+
199
+ @ Override
200
+ public Optional <ApplicationModuleIdentifier > lookupIdentifier (JavaPackage pkg ) {
201
+
202
+ return pkg .getAnnotation (annotation )
203
+ .map (extractor )
204
+ .filter (StringUtils ::hasText )
205
+ .map (ApplicationModuleIdentifier ::of );
206
+ }
207
+
208
+ @ Override
209
+ public Stream <JavaPackage > withNestedPackages (JavaPackage pkg ) {
210
+ return pkg .getSubPackagesAnnotatedWith (annotation );
211
+ }
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Returns an {@link ApplicationModuleSourceFactory} delegating to the given ones, chosing the first identifier
217
+ * found and assembling nested packages of all delegate {@link ApplicationModuleSourceFactory} instances.
218
+ *
219
+ * @param delegates must not be {@literal null}.
220
+ * @return will never be {@literal null}.
221
+ */
222
+ private static ApplicationModuleSourceMetadata delegating (ApplicationModuleSourceMetadata ... delegates ) {
223
+
224
+ return new ApplicationModuleSourceMetadata () {
225
+
226
+ @ Override
227
+ public Stream <JavaPackage > withNestedPackages (JavaPackage pkg ) {
228
+
229
+ return Stream .concat (Stream .of (pkg ), Stream .of (delegates )
230
+ .filter (Objects ::nonNull )
231
+ .flatMap (it -> it .withNestedPackages (pkg )));
232
+ }
233
+
234
+ @ Override
235
+ public Optional <ApplicationModuleIdentifier > lookupIdentifier (JavaPackage pkg ) {
236
+
237
+ return Stream .of (delegates )
238
+ .filter (Objects ::nonNull )
239
+ .flatMap (it -> it .lookupIdentifier (pkg ).stream ())
240
+ .findFirst ();
241
+ }
242
+ };
243
+ }
127
244
}
128
245
}
0 commit comments