Skip to content

Commit 90f56ed

Browse files
authored
Add Service Validation to module reader (#65)
1 parent f096b23 commit 90f56ed

File tree

1 file changed

+88
-14
lines changed

1 file changed

+88
-14
lines changed

prism-core/src/main/java/io/avaje/prism/internal/ModuleInfoReaderWriter.java

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,19 @@ public static void write(PrintWriter out, String packageName) {
1212
+ packageName
1313
+ ";\n"
1414
+ "\n"
15-
+ "import static java.util.stream.Collectors.joining;\n"
16-
+ "import static java.util.stream.Collectors.toList;\n"
17-
+ "\n"
18-
+ "import java.io.BufferedReader;\n"
19-
+ "import java.io.IOException;\n"
20-
+ "import java.util.ArrayList;\n"
21-
+ "import java.util.Arrays;\n"
22-
+ "import java.util.HashSet;\n"
23-
+ "import java.util.List;\n"
24-
+ "import java.util.Set;\n"
15+
+ "import static java.util.stream.Collectors.*;\n"
16+
+ "\n"
17+
+ "import java.io.*;\n"
18+
+ "import java.net.URI;\n"
19+
+ "import java.util.*;\n"
2520
+ "import java.util.regex.Matcher;\n"
2621
+ "import java.util.regex.Pattern;\n"
2722
+ "\n"
2823
+ "import javax.annotation.processing.Generated;\n"
2924
+ "import javax.lang.model.element.ModuleElement;\n"
3025
+ "import javax.lang.model.element.ModuleElement.DirectiveKind;\n"
3126
+ "import javax.lang.model.element.ModuleElement.RequiresDirective;\n"
27+
+ "import javax.tools.StandardLocation;\n"
3228
+ "import javax.lang.model.element.PackageElement;\n"
3329
+ "\n"
3430
+ "/**\n"
@@ -58,6 +54,7 @@ public static void write(PrintWriter out, String packageName) {
5854
+ " private final List<Exports> exports = new ArrayList<>();\n"
5955
+ " private final List<Opens> opens = new ArrayList<>();\n"
6056
+ " private final List<Provides> provides = new ArrayList<>();\n"
57+
+ " private final ModuleElement moduleElement;\n"
6158
+ "\n"
6259
+ " /**\n"
6360
+ " * Parse a module-info and create a new instance\n"
@@ -77,7 +74,7 @@ public static void write(PrintWriter out, String packageName) {
7774
+ " * @param moduleString a string containing the contents of the module-info.java\n"
7875
+ " */\n"
7976
+ " public ModuleInfoReader(ModuleElement moduleElement, CharSequence moduleString) {\n"
80-
+ "\n"
77+
+ " this.moduleElement = moduleElement;\n"
8178
+ " Matcher importMatcher = IMPORT_PATTERN.matcher(moduleString);\n"
8279
+ " Matcher requiresMatcher = REQUIRES_PATTERN.matcher(moduleString);\n"
8380
+ " Matcher providesMatcher = PROVIDES_PATTERN.matcher(moduleString);\n"
@@ -119,7 +116,7 @@ public static void write(PrintWriter out, String packageName) {
119116
+ " }\n"
120117
+ "\n"
121118
+ " while (providesMatcher.find()) {\n"
122-
+ " String providedInterface = resolveImport(imports,providesMatcher.group(1));\n"
119+
+ " String providedInterface = resolveImport(imports, providesMatcher.group(1));\n"
123120
+ " String implementationClasses = providesMatcher.group(2);\n"
124121
+ "\n"
125122
+ " List<String> implementations =\n"
@@ -139,7 +136,8 @@ public static void write(PrintWriter out, String packageName) {
139136
+ " return imports.stream()\n"
140137
+ " .filter(s -> s.contains(providedInterface))\n"
141138
+ " .findFirst()\n"
142-
+ " .orElse(providedInterface).replaceAll(\"\\\\s\", \"\");\n"
139+
+ " .orElse(providedInterface)\n"
140+
+ " .replaceAll(\"\\\\s\", \"\");\n"
143141
+ " }\n"
144142
+ "\n"
145143
+ " /**\n"
@@ -191,6 +189,82 @@ public static void write(PrintWriter out, String packageName) {
191189
+ " return requires.stream().anyMatch(r -> hasNonStaticModule(name, r.getDependency(), seen));\n"
192190
+ " }\n"
193191
+ "\n"
192+
+ " private String replace$(String k) {\n"
193+
+ " return k.replace('$', '.');\n"
194+
+ " }\n"
195+
+ "\n"
196+
+ " /**\n"
197+
+ " * Checks whether the module-info has the defined provides directive and all their implementations\n"
198+
+ " * Will register an error message compilation\n"
199+
+ " *\n"
200+
+ " * @param providesType the provides directive to check\n"
201+
+ " * @param implementations the implementations to verify the presence of\n"
202+
+ " */\n"
203+
+ " public void validateServices(String providesType, Collection<String> implementations) {\n"
204+
+ " if (!buildPluginAvailable() || !moduleElement.isUnnamed()) {\n"
205+
+ " return;\n"
206+
+ " }\n"
207+
+ " var implSet = new TreeSet<>(implementations);\n"
208+
+ " try (final var file =\n"
209+
+ " APContext.filer()\n"
210+
+ " .getResource(StandardLocation.CLASS_OUTPUT, \"\", \"META-INF/services/\" + providesType)\n"
211+
+ " .toUri()\n"
212+
+ " .toURL()\n"
213+
+ " .openStream();\n"
214+
+ " final var buffer = new BufferedReader(new InputStreamReader(file)); ) {\n"
215+
+ "\n"
216+
+ " String line;\n"
217+
+ " while ((line = buffer.readLine()) != null) {\n"
218+
+ " line.replaceAll(\"\\\\s\", \"\").replace(\",\", \"\\n\").lines().forEach(implSet::add);\n"
219+
+ " }\n"
220+
+ " } catch (Exception e) {\n"
221+
+ " // not a critical error\n"
222+
+ " }\n"
223+
+ " final var missingImpls = implSet.stream().map(this::replace$).collect(toSet());\n"
224+
+ "\n"
225+
+ " provides()\n"
226+
+ " .forEach(\n"
227+
+ " p -> {\n"
228+
+ " final var contract = replace$(providesType);\n"
229+
+ " if (!providesType.equals(contract)) {\n"
230+
+ " return;\n"
231+
+ " }\n"
232+
+ " var impls = p.implementations();\n"
233+
+ " if (missingImpls.size() > impls.size()) {\n"
234+
+ " return;\n"
235+
+ " }\n"
236+
+ " impls.stream().map(this::replace$).forEach(missingImpls::remove);\n"
237+
+ " });\n"
238+
+ "\n"
239+
+ " if (!missingImpls.isEmpty()) {\n"
240+
+ " var message = implementations.stream().collect(joining(\", \"));\n"
241+
+ "\n"
242+
+ " APContext.logError(\"Missing `provides %s with %s;`\", providesType, message);\n"
243+
+ " }\n"
244+
+ " }\n"
245+
+ "\n"
246+
+ " private static boolean buildPluginAvailable() {\n"
247+
+ " return resource(\"target/avaje-plugin-exists.txt\", \"/target/classes\")\n"
248+
+ " || resource(\"build/avaje-plugin-exists.txt\", \"/build/classes/java/main\");\n"
249+
+ " }\n"
250+
+ "\n"
251+
+ " private static boolean resource(String relativeName, String replace) {\n"
252+
+ " try (var inputStream =\n"
253+
+ " new URI(\n"
254+
+ " APContext.filer()\n"
255+
+ " .getResource(StandardLocation.CLASS_OUTPUT, \"\", relativeName)\n"
256+
+ " .toUri()\n"
257+
+ " .toString()\n"
258+
+ " .replace(replace, \"\"))\n"
259+
+ " .toURL()\n"
260+
+ " .openStream()) {\n"
261+
+ "\n"
262+
+ " return inputStream.available() > 0;\n"
263+
+ " } catch (Exception e) {\n"
264+
+ " return false;\n"
265+
+ " }\n"
266+
+ " }\n"
267+
+ "\n"
194268
+ " /** The requires directives associated with this module */\n"
195269
+ " public List<Requires> requires() {\n"
196270
+ " return requires;\n"
@@ -349,6 +423,6 @@ public static void write(PrintWriter out, String packageName) {
349423
+ " return targets;\n"
350424
+ " }\n"
351425
+ " }\n"
352-
+ "}\n");
426+
+ "}");
353427
}
354428
}

0 commit comments

Comments
 (0)