1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package io.earcam.utilitarian.io;
20
21 import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
22 import static org.hamcrest.MatcherAssert.assertThat;
23 import static org.hamcrest.Matchers.*;
24 import static org.junit.jupiter.api.Assertions.*;
25
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.nio.file.attribute.BasicFileAttributes;
34 import java.util.UUID;
35 import java.util.jar.Attributes;
36 import java.util.jar.JarEntry;
37 import java.util.jar.JarInputStream;
38 import java.util.jar.Manifest;
39
40 import org.hamcrest.Matchers;
41 import org.junit.jupiter.api.Nested;
42 import org.junit.jupiter.api.Test;
43
44 import io.earcam.unexceptional.Exceptional;
45 import io.earcam.utilitarian.io.ExplodedJarInputStream.ExplodedJarEntry;
46
47 public class ExplodedJarInputStreamTest {
48
49 private static final Path TEST_DIR = Paths.get(".", "target", "test", ExplodedJarInputStreamTest.class.getSimpleName(), UUID.randomUUID().toString());
50
51
52
53 @Test
54 public void disgustingTest() throws Exception
55 {
56 Path jarDir = TEST_DIR.resolve(Paths.get("explodesToFilesystem"));
57
58 final Class<?> archivedType = ExplodedJarInputStreamTest.class;
59
60 Path archivedPath = writeArchiveClass(jarDir, archivedType);
61
62 archiveManifest(jarDir);
63
64 boolean manifestChecked = false;
65 boolean archivedClassChecked = false;
66
67 try(JarInputStream input = ExplodedJarInputStream.jarInputStreamFrom(jarDir)) {
68
69 Manifest manifest = input.getManifest();
70 assertManifest(manifest);
71
72 JarEntry entry;
73 while((entry = input.getNextJarEntry()) != null) {
74
75 if(entry.isDirectory()) {
76 assertThat(entry.getName(), anyOf(
77 startsWith("io"),
78 is(equalTo("META-INF"))));
79 } else {
80
81 if("META-INF/MANIFEST.MF".equals(entry.getName())) {
82 assertArchivedManifest(input);
83 manifestChecked = true;
84 } else if(!entry.getName().contains("$")) {
85
86 assertArchivedClass(archivedType, archivedPath, input, entry);
87 archivedClassChecked = true;
88 }
89 }
90 }
91 assertThat(manifestChecked, is(true));
92 assertThat(archivedClassChecked, is(true));
93 }
94 }
95
96
97 private void assertArchivedManifest(JarInputStream input) throws IOException
98 {
99 Manifest manifest = new Manifest(input);
100 assertManifest(manifest);
101 }
102
103
104 private void assertManifest(Manifest manifest)
105 {
106 assertThat(manifest.getMainAttributes().getValue("SomeKey"), is(equalTo("SomeValue")));
107 }
108
109
110 private void assertArchivedClass(final Class<?> archivedType, Path archivedPath, JarInputStream input, JarEntry entry) throws IOException
111 {
112 byte[] bytecode = IoStreams.readAllBytes(input);
113
114 assertThat(entry.getSize(), is((long) bytecode.length));
115 assertThat(entry.getSize(), is(archivedPath.toFile().length()));
116
117 assertThat(entry.getCreationTime(),
118 is(equalTo(Exceptional.apply(Files::readAttributes, archivedPath, BasicFileAttributes.class).creationTime())));
119 assertThat(entry.getLastModifiedTime(), is(equalTo(Files.getLastModifiedTime(archivedPath, NOFOLLOW_LINKS))));
120 assertThat(entry.getTime(), is(equalTo(entry.getLastModifiedTime().toMillis())));
121
122 new ClassLoader(null) {
123 {
124 Class<?> type = defineClass(archivedType.getCanonicalName(), bytecode, 0, bytecode.length);
125
126 assertThat(type.getCanonicalName(), is(equalTo(archivedType.getCanonicalName())));
127
128 assertThat(type, is(not(equalTo(archivedType))));
129 }
130 };
131 }
132
133
134 private void archiveManifest(Path jarDir) throws IOException, FileNotFoundException
135 {
136 Path mf = jarDir.resolve(Paths.get("META-INF", "MANIFEST.MF"));
137 mf.getParent().toFile().mkdirs();
138
139 Manifest archivedManifest = new Manifest();
140 archivedManifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
141 archivedManifest.getMainAttributes().putValue("SomeKey", "SomeValue");
142 archivedManifest.write(new FileOutputStream(mf.toFile()));
143 }
144
145
146 private Path writeArchiveClass(Path jarDir, final Class<?> archivedType) throws IOException, FileNotFoundException
147 {
148 String archivedFile = archivedType.getCanonicalName().replace('.', '/') + ".class";
149 Path archivedPath = jarDir.resolve(archivedFile);
150 archivedPath.getParent().toFile().mkdirs();
151 try(InputStream in = archivedType.getClassLoader().getResourceAsStream(archivedFile)) {
152 try(FileOutputStream out = new FileOutputStream(archivedPath.toFile())) {
153 IoStreams.transfer(in, out);
154 }
155 }
156 return archivedPath;
157 }
158
159
160 @Test
161 public void explodedFromFilesystemWithoutManifest() throws Exception
162 {
163 Path jarDir = TEST_DIR.resolve(Paths.get("explodedFromFilesystemWithoutManifest"));
164
165 final Class<?> archivedType = ExplodedJarInputStreamTest.class;
166
167 Path archivedPath = writeArchiveClass(jarDir, archivedType);
168
169 boolean manifestChecked = false;
170 boolean archivedClassChecked = false;
171
172
173 try(JarInputStream input = ExplodedJarInputStream.jarInputStreamFrom(jarDir)) {
174
175 Manifest manifest = input.getManifest();
176
177
178 assertThat(manifest, is(nullValue()));
179
180 JarEntry entry;
181 while((entry = input.getNextJarEntry()) != null) {
182
183 if(entry.isDirectory()) {
184 assertThat(entry.getName(), anyOf(
185 startsWith("io"),
186 is(equalTo("META-INF"))));
187 } else {
188
189 if("META-INF/MANIFEST.MF".equals(entry.getName())) {
190 manifestChecked = true;
191 } else if(!entry.getName().contains("$")) {
192
193 assertArchivedClass(archivedType, archivedPath, input, entry);
194 archivedClassChecked = true;
195 }
196 }
197 }
198 assertThat(manifestChecked, is(false));
199 assertThat(archivedClassChecked, is(true));
200
201 }
202
203 }
204
205
206 @Test
207 public void explodedJarThrowsWhenPathIsNotADirectory()
208 {
209 try {
210 ExplodedJarInputStream.explodedJar(Paths.get(".", "pom.xml"));
211 fail();
212 } catch(IOException e) {
213
214 }
215 }
216
217
218 @Test
219 public void jarInputStreamFromJarFile() throws IOException
220 {
221 String resource = Matchers.class.getClassLoader().getResource(Matchers.class.getCanonicalName().replace('.', '/') + ".class").toString();
222 resource = resource.replaceFirst("jar:file:", "").replaceAll("!.*", "");
223 Path jarFile = Paths.get(resource);
224
225 try(JarInputStream input = ExplodedJarInputStream.jarInputStreamFrom(jarFile)) {
226
227 Manifest manifest = input.getManifest();
228 assertThat(manifest.getMainAttributes().getValue("Implementation-Vendor"), is(equalTo("hamcrest.org")));
229 }
230 }
231
232 @Nested
233 public class CurrentUndesirableBehaviour {
234
235 @Test
236 public void failsAsNormalInputStream() throws IOException
237 {
238 Path outputDir = Paths.get("target", "test-classes");
239 JarInputStream explodedJar = ExplodedJarInputStream.explodedJar(outputDir);
240
241 try {
242 IoStreams.readAllBytes(explodedJar);
243 fail();
244 } catch(UnsupportedOperationException uoe) {}
245 }
246
247
248 @Test
249 public void failsAsNormalInputStreamWithNothingAvailable() throws IOException
250 {
251 Path outputDir = Paths.get("target", "test-classes");
252 JarInputStream explodedJar = ExplodedJarInputStream.explodedJar(outputDir);
253
254 try {
255 explodedJar.available();
256 fail();
257 } catch(UnsupportedOperationException uoe) {}
258 }
259
260
261 @Test
262 public void availableCanBeInvokedOnTheEntries() throws IOException
263 {
264 Path outputDir = Paths.get("target", "test-classes");
265 JarInputStream explodedJar = ExplodedJarInputStream.explodedJar(outputDir);
266
267 JarEntry nextJarEntry;
268 do {
269 nextJarEntry = explodedJar.getNextJarEntry();
270 } while(nextJarEntry.isDirectory());
271
272 assertThat(explodedJar.available(), is(greaterThanOrEqualTo(0)));
273 }
274
275
276 @Deprecated
277 @Test
278 public void explodedJarEntrySingleReadMethodIsEffectivelyUseless() throws IOException
279 {
280 ExplodedJarInputStream in = (ExplodedJarInputStream) ExplodedJarInputStream.jarInputStreamFrom(Paths.get(".").toAbsolutePath());
281 ExplodedJarEntry explodedJarEntry = in.new ExplodedJarEntry(Paths.get(".", "target").toAbsolutePath());
282 try {
283 explodedJarEntry.read();
284 fail();
285 } catch(UnsupportedOperationException e) {}
286 }
287 }
288 }