View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.module.osgi
4    * %%
5    * Copyright (C) 2018 earcam
6    * %%
7    * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
8    * 
9    * You <b>must</b> choose to accept, in full - any individual or combination of 
10   * the following licenses:
11   * <ul>
12   * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
13   * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
14   * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
15   * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
16   * </ul>
17   * #L%
18   */
19  package io.earcam.instrumental.module.jpms.parser;
20  
21  import static io.earcam.instrumental.module.jpms.RequireModifier.TRANSITIVE;
22  import static java.nio.charset.Charset.defaultCharset;
23  import static java.nio.charset.StandardCharsets.UTF_8;
24  import static org.hamcrest.MatcherAssert.assertThat;
25  import static org.hamcrest.Matchers.aMapWithSize;
26  import static org.hamcrest.Matchers.allOf;
27  import static org.hamcrest.Matchers.arrayContaining;
28  import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
29  import static org.hamcrest.Matchers.contains;
30  import static org.hamcrest.Matchers.containsInAnyOrder;
31  import static org.hamcrest.Matchers.equalTo;
32  import static org.hamcrest.Matchers.hasEntry;
33  import static org.hamcrest.Matchers.is;
34  
35  import java.io.ByteArrayInputStream;
36  import java.io.IOException;
37  import java.nio.file.Files;
38  import java.nio.file.Paths;
39  
40  import org.junit.jupiter.api.Nested;
41  import org.junit.jupiter.api.Test;
42  
43  import io.earcam.instrumental.module.jpms.Export;
44  import io.earcam.instrumental.module.jpms.ModuleInfo;
45  import io.earcam.instrumental.module.jpms.Require;
46  
47  @SuppressWarnings("squid:S2187") // SonarQube false-positive
48  public class AntlrParserTest {
49  
50  	@Nested
51  	class ModuleDeclarationName {
52  
53  		@Test
54  		void nameOnly()
55  		{
56  			String name = "com.acme.mod.a";
57  			// @formatter:off
58  			String source =
59  					"module " + name + " { \n" +
60  					"}                     \n";
61  			// @formatter:on
62  
63  			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
64  
65  			assertThat(moduleInfo.name(), is(equalTo(name)));
66  		}
67  
68  
69  		@Test
70  		void singleAnnotation()
71  		{
72  			String name = "com.acme.mod.b";
73  			// @formatter:off
74  			String source = 
75  					"@SuppressWarnings(\"some\")  \n" +
76  					"module " + name + " {        \n" +
77  					"}                            \n";
78  			// @formatter:on
79  
80  			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
81  
82  			assertThat(moduleInfo.name(), is(equalTo(name)));
83  		}
84  
85  
86  		@Test
87  		void multipleAnnotations()
88  		{
89  			String name = "com.acme.mod.c";
90  			// @formatter:off
91  			String source = 
92  					"@SuppressWarnings(\"some\")  \n" +
93  					"@Deprecated                  \n" +
94  					"module " + name + " {        \n" +
95  					"}                            \n";
96  			// @formatter:on
97  
98  			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
99  
100 			assertThat(moduleInfo.name(), is(equalTo(name)));
101 		}
102 
103 
104 		@Test
105 		void open()
106 		{
107 			String name = "com.acme.mod.d";
108 			// @formatter:off
109 			String source = 
110 					"open module " + name + " {  \n" +
111 					"}                           \n";
112 			// @formatter:on
113 
114 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
115 
116 			assertThat(moduleInfo.name(), is(equalTo(name)));
117 		}
118 
119 
120 		@Test
121 		void openWithMultipleAnnotations()
122 		{
123 			String name = "com.acme.mod.e";
124 			// @formatter:off
125 			String source = 
126 					"@SuppressWarnings(\"some\")  \n" +
127 					"@Deprecated                  \n" +
128 					"open module " + name + " {   \n" +
129 					"}                            \n";
130 			// @formatter:on
131 
132 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
133 
134 			assertThat(moduleInfo.name(), is(equalTo(name)));
135 		}
136 	}
137 
138 	@Nested
139 	class Required {
140 
141 		@Test
142 		void singleRequire()
143 		{
144 			// @formatter:off
145 			String source =
146 					"module com.acme.mod {   \n" +
147 					"   requires java.sql;   \n" +
148 					"}                       \n";
149 			// @formatter:on
150 
151 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
152 
153 			assertThat(moduleInfo.requires(), contains(new Require("java.sql", 0, null)));
154 		}
155 
156 
157 		@Test
158 		void singleRequireWithTransitiveModifier()
159 		{
160 			// @formatter:off
161 			String source =
162 					"module com.acme.mod.yule {         \n" +
163 					"   requires transitive java.xml;   \n" +
164 					"}                                  \n";
165 			// @formatter:on
166 
167 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
168 
169 			assertThat(moduleInfo.requires(), contains(new Require("java.xml", TRANSITIVE.access(), null)));
170 		}
171 
172 
173 		@Test
174 		void multipleRequires()
175 		{
176 			// @formatter:off
177 			String source =
178 					"module com.acme.mod.yule {         \n" +
179 					"   requires java.sql;              \n" +
180 					"   requires transitive java.xml;   \n" +
181 					"}                                  \n";
182 			// @formatter:on
183 
184 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
185 
186 			assertThat(moduleInfo.requires(), containsInAnyOrder(
187 					new Require("java.sql", 0, null),
188 					new Require("java.xml", TRANSITIVE.access(), null)));
189 		}
190 	}
191 
192 	@Nested
193 	class Exported {
194 
195 		@Test
196 		void singleUnqalifiedExport()
197 		{
198 			// @formatter:off
199 			String source =
200 					"module com.acme.base {     \n" +
201 					"   exports com.acme.api;   \n" +
202 					"}                          \n";
203 			// @formatter:on
204 
205 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
206 
207 			assertThat(moduleInfo.exports(), contains(new Export("com.acme.api")));
208 		}
209 
210 
211 		@Test
212 		void singleQalifiedExport()
213 		{
214 			// @formatter:off
215 			String source =
216 					"module com.acme.base {                        \n" +
217 					"   exports com.acme.guts to com.acme.grunt;   \n" +
218 					"}                                             \n";
219 			// @formatter:on
220 
221 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
222 
223 			assertThat(moduleInfo.exports(), contains(new Export("com.acme.guts", "com.acme.grunt")));
224 		}
225 
226 
227 		@Test
228 		void multipleExports()
229 		{
230 			// @formatter:off
231 			String source =
232 					"module com.acme.base {                        \n" +
233 					"   exports com.acme.api;                      \n" +
234 					"   exports com.acme.guts to com.acme.grunt;   \n" +
235 					"}                                             \n";
236 			// @formatter:on
237 
238 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
239 
240 			assertThat(moduleInfo.exports(), containsInAnyOrder(
241 					new Export("com.acme.api"),
242 					new Export("com.acme.guts", "com.acme.grunt")));
243 		}
244 
245 
246 		@Test
247 		void multipleExportsViaInputStream()
248 		{
249 			// @formatter:off
250 			String source =
251 					"module com.acme.base {                        \n" +
252 					"   exports com.acme.api;                      \n" +
253 					"   exports com.acme.guts to com.acme.grunt;   \n" +
254 					"}                                             \n";
255 			// @formatter:on
256 
257 			ModuleInfo moduleInfo = ModuleInfoParser.parse(new ByteArrayInputStream(source.getBytes(UTF_8)), UTF_8);
258 
259 			assertThat(moduleInfo.exports(), containsInAnyOrder(
260 					new Export("com.acme.api"),
261 					new Export("com.acme.guts", "com.acme.grunt")));
262 		}
263 	}
264 
265 	@Nested
266 	class Opened {
267 
268 		@Test
269 		void singleUnqualifiedOpen()
270 		{
271 			// @formatter:off
272 			String source =
273 					"module com.acme.base {           \n" +
274 					"   opens com.acme.every.thing;   \n" +
275 					"}                                \n";
276 			// @formatter:on
277 
278 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
279 
280 			assertThat(moduleInfo.opens(), contains(new Export("com.acme.every.thing")));
281 		}
282 
283 
284 		@Test
285 		void singleQualifiedOpen()
286 		{
287 			// @formatter:off
288 			String source =
289 					"module com.acme.base {                          \n" +
290 					"   opens com.acme.up to com.acme.friend.sssh;   \n" +
291 					"}                                               \n";
292 			// @formatter:on
293 
294 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
295 
296 			assertThat(moduleInfo.opens(), contains(new Export("com.acme.up", "com.acme.friend.sssh")));
297 		}
298 
299 
300 		@Test
301 		void mutlipleOpens()
302 		{
303 			// @formatter:off
304 			String source =
305 					"module com.acme.base {                          \n" +
306 					"   opens com.acme.every.thing;                  \n" +
307 					"   opens com.acme.up to com.acme.friend.sssh;   \n" +
308 					"}                                               \n";
309 			// @formatter:on
310 
311 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
312 
313 			assertThat(moduleInfo.opens(), contains(
314 					new Export("com.acme.every.thing"),
315 					new Export("com.acme.up", "com.acme.friend.sssh")));
316 		}
317 
318 	}
319 
320 	@Nested
321 	class Used {
322 
323 		@Test
324 		void singleUse()
325 		{
326 			// @formatter:off
327 			String source =
328 					"module com.acme.app {           \n" +
329 					"   uses com.acme.base.Boost;    \n" +
330 					"}                               \n";
331 			// @formatter:on
332 
333 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
334 
335 			assertThat(moduleInfo.uses(), contains("com.acme.base.Boost"));
336 		}
337 
338 
339 		@Test
340 		void multipleUses()
341 		{
342 			// @formatter:off
343 			String source =
344 					"module com.acme.app {           \n" +
345 					"   uses com.acme.base.Boost;    \n" +
346 					"   uses com.acme.spi.Service;   \n" +
347 					"   uses com.acme.spi.EventBus;  \n" +
348 					"}                               \n";
349 			// @formatter:on
350 
351 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
352 
353 			assertThat(moduleInfo.uses(), containsInAnyOrder(
354 					"com.acme.base.Boost",
355 					"com.acme.spi.Service",
356 					"com.acme.spi.EventBus"));
357 		}
358 
359 	}
360 
361 	@Nested
362 	class Provided {
363 
364 		@Test
365 		void singleProvisionOfSingleService()
366 		{
367 			// @formatter:off
368 			String source =
369 					"module com.acme.xyz.service {                 \n" +
370 					"   provides com.acme.spi.Service              \n" +
371 					"         with com.acme.imp.DefaultService;    \n" +
372 					"}                                             \n";
373 			// @formatter:on
374 
375 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
376 
377 			assertThat(moduleInfo.provides(), allOf(
378 					is(aMapWithSize(1)),
379 					hasEntry(equalTo("com.acme.spi.Service"), arrayContaining("com.acme.imp.DefaultService"))));
380 		}
381 
382 
383 		@Test
384 		void multipleProvisionsOfSingleService()
385 		{
386 			// @formatter:off
387 			String source =
388 					"module com.acme.xyz.service {        \n" +
389 					"   provides com.acme.spi.Greet with  \n" +
390 					"         com.acme.imp.Hello,         \n" +
391 					"         com.acme.imp.Hey,           \n" +
392 					"         com.acme.imp.Howdy;         \n" +
393 					"}                                    \n";
394 			// @formatter:on
395 
396 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
397 
398 			assertThat(moduleInfo.provides(), allOf(
399 					is(aMapWithSize(1)),
400 					hasEntry(equalTo("com.acme.spi.Greet"), arrayContainingInAnyOrder(
401 							"com.acme.imp.Hello",
402 							"com.acme.imp.Hey",
403 							"com.acme.imp.Howdy"))));
404 		}
405 
406 
407 		@Test
408 		void multipleProvisionsOfMultipleServices()
409 		{
410 			// @formatter:off
411 			String source =
412 					"module com.acme.xyz.service {        \n" +
413 					"                                     \n" +
414 					"   provides com.acme.spi.Funk with   \n" +
415 					"         com.acme.imp.Funky;         \n" +
416 					"                                     \n" +
417 					"   provides com.acme.spi.Greet with  \n" +
418 					"         com.acme.imp.Hello,         \n" +
419 					"         com.acme.imp.Hey,           \n" +
420 					"         com.acme.imp.Howdy;         \n" +
421 					"}                                    \n";
422 			// @formatter:on
423 
424 			ModuleInfo moduleInfo = ModuleInfoParser.parse(source);
425 
426 			assertThat(moduleInfo.provides(), allOf(
427 					is(aMapWithSize(2)),
428 					hasEntry(equalTo("com.acme.spi.Funk"), arrayContaining("com.acme.imp.Funky")),
429 					hasEntry(equalTo("com.acme.spi.Greet"), arrayContainingInAnyOrder(
430 							"com.acme.imp.Hello",
431 							"com.acme.imp.Hey",
432 							"com.acme.imp.Howdy"))));
433 		}
434 
435 	}
436 
437 	@Nested
438 	class LargeExamples {
439 
440 		// Note; java.base's module-info.java had missing semi-colons on some qualified exports (lines 306-309), these
441 		// were added as otherwise invalid
442 		@Test
443 		void javaBase() throws IOException
444 		{
445 			String source = new String(Files.readAllBytes(Paths.get(".", "src", "test", "resources", "java.base_module-info.source")), defaultCharset());
446 			ModuleInfoParser.parse(source);
447 		}
448 	}
449 }