View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.lade.jpms
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.lade.jpms;
20  
21  import static io.earcam.instrumental.archive.Archive.archive;
22  import static io.earcam.instrumental.archive.jpms.AsJpmsModule.asJpmsModule;
23  import static io.earcam.instrumental.archive.jpms.auto.ArchivePackageModuleMapper.fromArchives;
24  import static io.earcam.instrumental.lade.ClassLoaders.inMemoryClassLoader;
25  import static java.nio.charset.StandardCharsets.UTF_8;
26  import static java.util.stream.Collectors.toSet;
27  import static org.hamcrest.MatcherAssert.assertThat;
28  import static org.hamcrest.Matchers.contains;
29  import static org.hamcrest.Matchers.equalTo;
30  import static org.hamcrest.Matchers.is;
31  
32  import java.io.ByteArrayOutputStream;
33  import java.io.IOException;
34  import java.io.PrintStream;
35  import java.lang.module.Configuration;
36  import java.lang.module.ModuleDescriptor;
37  import java.lang.module.ModuleFinder;
38  import java.lang.module.ModuleReader;
39  import java.lang.module.ModuleReference;
40  import java.lang.reflect.Method;
41  import java.util.NoSuchElementException;
42  import java.util.Set;
43  
44  import org.junit.jupiter.api.Test;
45  
46  import io.earcam.acme.api.AcmeApi;
47  import io.earcam.acme.app.AcmeApp;
48  import io.earcam.acme.imp.AcmeImp;
49  import io.earcam.instrumental.archive.Archive;
50  import io.earcam.instrumental.archive.AsJar;
51  import io.earcam.instrumental.lade.Handler;
52  import io.earcam.instrumental.lade.InMemoryClassLoader;
53  import io.earcam.instrumental.reflect.Methods;
54  import io.earcam.unexceptional.CheckedRunnable;
55  import io.earcam.unexceptional.Exceptional;
56  
57  public class InMemoryModuleFinderTest {
58  
59  	@Test
60  	void findsAllModules()
61  	{
62  		InMemoryClassLoader loader = inMemoryClassLoader()
63  				.jar(archive()
64  						.configured(
65  								asJpmsModule()
66  										.named("a")
67  										.autoRequiring())
68  						.toByteArray())
69  				.jar(archive()
70  						.configured(
71  								AsJar.asJar()
72  										.withManifestHeader("Name", "B"))
73  						.toByteArray())
74  				.jar(archive()
75  						.configured(
76  								asJpmsModule()
77  										.named("c")
78  										.autoRequiring())
79  						.toByteArray());
80  
81  		InMemoryModuleFinder finder = new InMemoryModuleFinder(loader);
82  
83  		Set<String> names = finder.findAll().stream()
84  				.map(ModuleReference::descriptor)
85  				.map(ModuleDescriptor::name)
86  				.collect(toSet());
87  
88  		assertThat(names, contains("a", "c"));
89  	}
90  
91  
92  	@Test
93  	void wiresUpAndRuns() throws Exception
94  	{
95  		// @formatter:off
96  		// EARCAM_SNIPPET_BEGIN: setup-three-jars
97  		
98  		Archive api = archive()
99  			.configured(
100 				asJpmsModule()
101 					.named("api")
102 					.exporting(s -> true)
103 					.autoRequiring()
104 			)
105 			.with(AcmeApi.class)
106 			.toObjectModel();
107 		
108 		Archive app = archive()
109 				.configured(
110 					asJpmsModule()
111 						.named("app")
112 						.autoRequiring()
113 						.autoRequiring(fromArchives(api))
114 						.exporting(s -> true)
115 						.using(AcmeApi.class)
116 						.launching(AcmeApp.class)
117 				)
118 				.toObjectModel();
119 
120 		Archive imp = archive()
121 				.configured(
122 					asJpmsModule()
123 						.named("imp")
124 						.autoRequiring()
125 						.autoRequiring(fromArchives(api))
126 						.providing(AcmeApi.class, AcmeImp.class)
127 				).toObjectModel();
128 
129 		// EARCAM_SNIPPET_END: setup-three-jars
130 
131 		// EARCAM_SNIPPET_BEGIN: create-loader
132 		
133 		Handler.addProtocolHandlerSystemProperty();
134 		
135 		InMemoryClassLoader loader = inMemoryClassLoader()
136 			.jar(api.toByteArray())
137 			.jar(imp.toByteArray())
138 			.jar(app.toByteArray());
139 
140 		// EARCAM_SNIPPET_END: create-loader
141 
142 		// EARCAM_SNIPPET_BEGIN: create-layer
143 		
144 		ModuleFinder finder = new InMemoryModuleFinder(loader);
145 		ModuleLayer parent = ModuleLayer.boot();
146 		
147 		Configuration configuration = parent.configuration().resolveAndBind(
148 				ModuleFinder.of(), 
149 				finder, 
150 				Set.of("app"));
151 		
152 		ModuleLayer layer = parent.defineModulesWithOneLoader(configuration, loader);
153 
154 		// EARCAM_SNIPPET_END: create-layer
155 
156 		// EARCAM_SNIPPET_BEGIN: execute-app
157 
158 		Class<?> mainClass = layer.findLoader("app").loadClass(AcmeApp.class.getCanonicalName());
159 		Method main = Methods.getMethod(mainClass, "main", String[].class)
160 				.orElseThrow(NoSuchMethodError::new);
161 		
162 		String you = "Dynamic & In-Mem";
163 		String stdout = captureStdout(() -> main.invoke(null, new Object[] {new String[] {you}}));
164 		
165 		String expectedResponse = 
166 				"module app: Acme Corp would like to say \"Hello\" to \"" + 
167 				you + 
168 				"\" (Terms and conditions apply)\n";
169 
170 		assertThat(stdout, is(equalTo(expectedResponse)));
171 		// EARCAM_SNIPPET_END: execute-app
172 		// @formatter:on
173 	}
174 
175 
176 	private static String captureStdout(CheckedRunnable<ReflectiveOperationException> runnable)
177 	{
178 		PrintStream old = System.out;
179 		try {
180 			ByteArrayOutputStream baos = new ByteArrayOutputStream();
181 			System.setOut(new PrintStream(baos));
182 
183 			Exceptional.run(runnable);
184 			return new String(baos.toByteArray(), UTF_8);
185 		} finally {
186 			System.setOut(old);
187 		}
188 	}
189 
190 
191 	@Test
192 	void closeDoesNothing() throws IOException
193 	{
194 		InMemoryClassLoader loader = inMemoryClassLoader()
195 				.jar(archive()
196 						.configured(
197 								asJpmsModule()
198 										.named("a")
199 										.autoRequiring())
200 						.toByteArray());
201 
202 		InMemoryModuleFinder finder = new InMemoryModuleFinder(loader);
203 
204 		ModuleReader reader = finder.findAll().stream()
205 				.map(Exceptional.uncheckFunction(ModuleReference::open))
206 				.findFirst()
207 				.orElseThrow(NoSuchElementException::new);
208 
209 		reader.close();
210 
211 		assertThat(reader.find("module-info.class").isPresent(), is(true));
212 	}
213 }