View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.archive
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.archive;
20  
21  import static io.earcam.instrumental.archive.Archive.archive;
22  import static io.earcam.instrumental.archive.AsJar.asJar;
23  import static io.earcam.instrumental.archive.Hamcrest.present;
24  import static io.earcam.instrumental.reflect.Names.typeToResourceName;
25  import static java.lang.Boolean.TRUE;
26  import static java.nio.charset.StandardCharsets.UTF_8;
27  import static java.util.jar.Attributes.Name.MAIN_CLASS;
28  import static java.util.stream.Collectors.toList;
29  import static org.hamcrest.MatcherAssert.assertThat;
30  import static org.hamcrest.Matchers.allOf;
31  import static org.hamcrest.Matchers.contains;
32  import static org.hamcrest.Matchers.containsInAnyOrder;
33  import static org.hamcrest.Matchers.empty;
34  import static org.hamcrest.Matchers.equalTo;
35  import static org.hamcrest.Matchers.hasEntry;
36  import static org.hamcrest.Matchers.hasSize;
37  import static org.hamcrest.Matchers.is;
38  import static org.hamcrest.Matchers.nullValue;
39  import static org.junit.jupiter.api.Assertions.fail;
40  
41  import java.io.InputStream;
42  import java.util.Comparator;
43  import java.util.List;
44  import java.util.ServiceLoader;
45  import java.util.function.Predicate;
46  import java.util.jar.Attributes;
47  import java.util.jar.Attributes.Name;
48  import java.util.jar.Manifest;
49  import java.util.stream.Stream;
50  import java.util.stream.StreamSupport;
51  
52  import org.junit.jupiter.api.Test;
53  
54  import io.earcam.acme.CaseInsensitiveToStringComparator;
55  import io.earcam.acme.DummyMain;
56  import io.earcam.acme.WhitespaceIgnorantToStringComparator;
57  import io.earcam.acme.other.Other;
58  import io.earcam.instrumental.lade.ClassLoaders;
59  import io.earcam.instrumental.lade.InMemoryClassLoader;
60  import io.earcam.utilitarian.io.IoStreams;
61  
62  public class AsJarTest {
63  
64  	@Test
65  	void emptyArchiveHasManifest()
66  	{
67  		Archive archive = archive()
68  				.configured(asJar())
69  				.toObjectModel();
70  
71  		assertThat(archive.contents(), is(empty()));
72  
73  		assertValidJarManifest(archive);
74  	}
75  
76  
77  	private void assertValidJarManifest(Archive archive)
78  	{
79  		assertThat(archive.manifest(), is(present()));
80  
81  		String manifestVersion = archive.manifest().get().getMainAttributes().getValue(Name.MANIFEST_VERSION);
82  		assertThat(manifestVersion, is(equalTo(AbstractAsJarBuilder.V1_0)));
83  	}
84  
85  
86  	@Test
87  	void singleContent()
88  	{
89  		String name = "/path/to/some.file";
90  		byte[] contents = "Some Content".getBytes(UTF_8);
91  
92  		Archive archive = archive()
93  				.configured(asJar())
94  				.with(name, contents)
95  				.toObjectModel();
96  
97  		assertThat(archive.contents(), hasSize(1));
98  		assertThat(archive.contents(), contains(new ArchiveResource(name, contents)));
99  
100 		assertValidJarManifest(archive);
101 	}
102 
103 
104 	@Test
105 	void mainClass()
106 	{
107 		// @formatter:off
108 		// EARCAM_SNIPPET_BEGIN: jar-main
109 		Archive archive = archive()
110 			.configured(asJar()
111 				.launching(DummyMain.class))
112 			.toObjectModel();
113 		// EARCAM_SNIPPET_END: jar-main
114 		// @formatter:on
115 
116 		assertValidJarManifest(archive);
117 
118 		Attributes attributes = archive.manifest().get().getMainAttributes();
119 		assertThat(attributes, hasEntry(MAIN_CLASS, cn(DummyMain.class)));
120 
121 		assertThat(archive.contents(), contains(new ArchiveResource(typeToResourceName(DummyMain.class), new byte[0])));
122 	}
123 
124 
125 	@Test
126 	void invalidMainClassFailsFastWhenNoSuchMethodNameFound()
127 	{
128 		try {
129 			asJar().launching(AsJar.class);
130 			fail();
131 		} catch(IllegalArgumentException e) {}
132 	}
133 
134 
135 	@Test
136 	void invalidMainClassIgnoredWhenValidationDisabled()
137 	{
138 		asJar()
139 				.disableValidation()
140 				.launching(AsJar.class);
141 	}
142 
143 
144 	public static boolean main(String[] ahhargs)
145 	{
146 		return false;
147 	}
148 
149 
150 	@Test
151 	void invalidMainClassFailsFastWhenMainMethodInvalid()
152 	{
153 		try {
154 			asJar().launching(AsJarTest.class);
155 			fail();
156 		} catch(IllegalArgumentException e) {}
157 	}
158 
159 
160 	@Test
161 	void manifestMainAttributesAreMerged()
162 	{
163 
164 		Manifest manifest = new Manifest();
165 		manifest.getMainAttributes().putValue("Existing-Header", "Existing Value");
166 
167 		Archive archive = archive()
168 				.configured(asJar()
169 						.mergingManifest(manifest)
170 						.launching(DummyMain.class))
171 				.toObjectModel();
172 
173 		assertValidJarManifest(archive);
174 
175 		Attributes attributes = archive.manifest().get().getMainAttributes();
176 		assertThat(attributes, allOf(
177 				hasEntry(MAIN_CLASS, cn(DummyMain.class)),
178 				hasEntry(new Name("Existing-Header"), "Existing Value")));
179 	}
180 
181 
182 	@Test
183 	void manifestNamedEntriesAreMerged()
184 	{
185 
186 		Manifest manifestA = new Manifest();
187 		Attributes entryA = new Attributes();
188 		entryA.putValue("Merged", "By A");
189 		manifestA.getEntries().put("named", entryA);
190 
191 		Manifest manifestB = new Manifest();
192 		Attributes entryB = new Attributes();
193 		entryB.putValue("Merged-From", "B");
194 		manifestB.getEntries().put("named", entryB);
195 
196 		Archive archive = archive()
197 				.configured(asJar()
198 						.mergingManifest(manifestA)
199 						.mergingManifest(manifestB)
200 						.withManifestHeader("Main", "Attribute"))
201 				.toObjectModel();
202 
203 		assertValidJarManifest(archive);
204 
205 		Attributes attributes = archive.manifest().get().getAttributes("named");
206 
207 		Attributes expected = new Attributes();
208 		expected.putValue("Merged", "By A");
209 		expected.putValue("Merged-From", "B");
210 		assertThat(attributes, is(equalTo(expected)));
211 
212 		assertThat(archive.manifest().get().getMainAttributes(), hasEntry(new Name("Main"), "Attribute"));
213 	}
214 
215 
216 	@Test
217 	void mainClassFailsFastIfMainMethodNotFound()
218 	{
219 		try {
220 			asJar().launching(AsJarTest.class);
221 			fail();
222 		} catch(Exception e) {}
223 	}
224 
225 
226 	@Test
227 	void singleSpiSingleImplementation()
228 	{
229 		byte[] archive = archive()
230 				.configured(asJar()
231 						.providing(Comparator.class, CaseInsensitiveToStringComparator.class))
232 				.toByteArray();
233 
234 		try(InMemoryClassLoader classLoader = ClassLoaders.inMemoryClassLoader().jar(archive)) {
235 
236 			List<String> implementations = spiServices(Comparator.class, classLoader);
237 
238 			assertThat(implementations, contains(cn(CaseInsensitiveToStringComparator.class)));
239 		}
240 	}
241 
242 
243 	private static String cn(Class<?> type)
244 	{
245 		return type.getCanonicalName();
246 	}
247 
248 
249 	@Test
250 	void singleSpiMultipleImplementations()
251 	{
252 		// @formatter: off
253 		// EARCAM_SNIPPET_BEGIN: jar-spi-classload
254 		byte[] archive = archive().configured(asJar()
255 				.providing(Comparator.class,
256 						CaseInsensitiveToStringComparator.class,
257 						WhitespaceIgnorantToStringComparator.class))
258 				.toByteArray();
259 
260 		ClassLoader loader = ClassLoaders.inMemoryClassLoader().jar(archive);
261 
262 		List<String> imps = spiServices(Comparator.class, loader);
263 
264 		assertThat(imps, containsInAnyOrder(
265 				cn(CaseInsensitiveToStringComparator.class),
266 				cn(WhitespaceIgnorantToStringComparator.class)));
267 		// EARCAM_SNIPPET_END: jar-spi-classload
268 		// @formatter: on
269 	}
270 
271 
272 	@Test
273 	void singleSpiMultipleImplementationsAsInputStream()
274 	{
275 		// @formatter: off
276 		InputStream input = archive().configured(asJar()
277 				.providing(Comparator.class,
278 						CaseInsensitiveToStringComparator.class,
279 						WhitespaceIgnorantToStringComparator.class))
280 				.toInputStream();
281 
282 		byte[] archive = IoStreams.readAllBytes(input);
283 
284 		ClassLoader loader = ClassLoaders.inMemoryClassLoader().jar(archive);
285 
286 		List<String> imps = spiServices(Comparator.class, loader);
287 
288 		assertThat(imps, containsInAnyOrder(
289 				cn(CaseInsensitiveToStringComparator.class),
290 				cn(WhitespaceIgnorantToStringComparator.class)));
291 		// @formatter: on
292 	}
293 
294 
295 	private List<String> spiServices(Class<?> service, ClassLoader classLoader)
296 	{
297 		return streamSpiServices(service, classLoader)
298 				.map(i -> cn(i.getClass()))
299 				.collect(toList());
300 	}
301 
302 
303 	private <T> Stream<T> streamSpiServices(Class<T> service, ClassLoader classLoader)
304 	{
305 		return StreamSupport.stream(ServiceLoader.load(service, classLoader).spliterator(), false);
306 	}
307 
308 
309 	@Test
310 	void providingFailsFastWhenServicesDoesNotImplementInterface()
311 	{
312 		try {
313 			asJar().providing(Comparator.class, Other.class);
314 			fail();
315 		} catch(IllegalArgumentException e) {}
316 	}
317 
318 
319 	@Test
320 	void invalidProvidingIgnoredWhenValidationDisabled()
321 	{
322 		asJar()
323 				.disableValidation()
324 				.providing(Comparator.class, Other.class);
325 	}
326 
327 
328 	@Test
329 	void sealing()
330 	{
331 		Predicate<String> packageMatcher = Predicate.isEqual(pkg(WhitespaceIgnorantToStringComparator.class));
332 
333 		Archive jar = archive()
334 				.configured(asJar()
335 						.sealing(packageMatcher))
336 				.with(WhitespaceIgnorantToStringComparator.class, Other.class)
337 				.toObjectModel();
338 
339 		Manifest manifest = jar.manifest().get();
340 
341 		assertThat(manifest.getAttributes(pkg(Other.class)), is(nullValue()));
342 		assertThat(manifest.getAttributes(pkg(WhitespaceIgnorantToStringComparator.class)), hasEntry(Name.SEALED, TRUE.toString()));
343 	}
344 
345 
346 	private String pkg(Class<?> type)
347 	{
348 		return type.getPackage().getName();
349 	}
350 }