ExplodedJarInputStream.java

1
/*-
2
 * #%L
3
 * io.earcam.instrumental.io
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.utilitarian.io;
20
21
import static io.earcam.unexceptional.Closing.closeAfterAccepting;
22
23
import java.io.File;
24
import java.io.FileInputStream;
25
import java.io.IOException;
26
import java.io.InputStream;
27
import java.nio.file.Files;
28
import java.nio.file.Path;
29
import java.nio.file.Paths;
30
import java.nio.file.attribute.BasicFileAttributes;
31
import java.nio.file.attribute.FileTime;
32
import java.util.Iterator;
33
import java.util.jar.JarEntry;
34
import java.util.jar.JarInputStream;
35
import java.util.jar.Manifest;
36
37
import javax.annotation.concurrent.NotThreadSafe;
38
39
import io.earcam.unexceptional.Exceptional;
40
41
/**
42
 * BEWARE OF LIMITATIONS; CANNOT BE WRAPPED BY ANOTHER JarInputStream, CANNOT BE READ AS A NORMAL INPUT STREAM
43
 * 
44
 * TODO performance: {@link ExplodedJarEntry#loadContents()} should just be wrapping FileInputStream...
45
 */
46
@SuppressWarnings("squid:MaximumInheritanceDepth") // SonarQube; Not much can be done about this... dirty hack anyhoo
47
@NotThreadSafe
48
public final class ExplodedJarInputStream extends JarInputStream {
49
50
	private static final Path MANIFEST_PATH = Paths.get("META-INF", "MANIFEST.MF");
51
52
	private static class EmptyInputStream extends InputStream {
53
54
		public static final InputStream EMPTY_INPUTSTREAM = new EmptyInputStream();
55
56
57
		@Override
58
		public int read()
59
		{
60 1 1. read : replaced return of integer sized value with (x == 0 ? 1 : 0) → SURVIVED
			return -1;
61
		}
62
	}
63
64
	public class ExplodedJarEntry extends JarEntry {
65
66
		private Path path;
67
		private byte[] contents;
68
		private int position = 0;
69
70
71
		public ExplodedJarEntry(Path path)
72
		{
73
			super(directory.relativize(path).toString());
74
			this.path = path;
75 1 1. : removed call to io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::setMethod → SURVIVED
			setMethod(STORED);
76
		}
77
78
79
		public Path path()
80
		{
81 1 1. path : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::path to ( if (x != null) null else throw new RuntimeException ) → KILLED
			return path;
82
		}
83
84
85
		@Override
86
		public boolean isDirectory()
87
		{
88 1 1. isDirectory : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED
			return path().toFile().isDirectory();
89
		}
90
91
92
		@Override
93
		public FileTime getCreationTime()
94
		{
95 2 1. getCreationTime : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::getCreationTime to ( if (x != null) null else throw new RuntimeException ) → KILLED
2. lambda$getCreationTime$0 : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::lambda$getCreationTime$0 to ( if (x != null) null else throw new RuntimeException ) → KILLED
			return Exceptional.apply(Files::readAttributes, path(), BasicFileAttributes.class).creationTime();
96
		}
97
98
99
		@Override
100
		public FileTime getLastModifiedTime()
101
		{
102 2 1. getLastModifiedTime : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::getLastModifiedTime to ( if (x != null) null else throw new RuntimeException ) → KILLED
2. lambda$getLastModifiedTime$1 : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::lambda$getLastModifiedTime$1 to ( if (x != null) null else throw new RuntimeException ) → KILLED
			return Exceptional.apply(Files::getLastModifiedTime, path());
103
		}
104
105
106
		@Override
107
		public long getTime()
108
		{
109 1 1. getTime : replaced return of long value with value + 1 for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::getTime → KILLED
			return getLastModifiedTime().toMillis();
110
		}
111
112
113
		@Override
114
		public long getSize()
115
		{
116 1 1. getSize : replaced return of long value with value + 1 for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::getSize → KILLED
			return Exceptional.apply(Files::size, path());
117
		}
118
119
120
		private void loadContents()
121
		{
122 1 1. loadContents : negated conditional → KILLED
			if(contents == null) {
123
				contents = Exceptional.apply(Files::readAllBytes, path());
124
			}
125
		}
126
127
128
		/**
129
		 * @deprecated Never intended for public use.
130
		 * Aggressively deprecated, class will become final once dropped.
131
		 * 
132
		 * @return nothing
133
		 * @throws UnsupportedOperationException everytime
134
		 */
135
		@Deprecated
136
		public int read()
137
		{
138
			throw new UnsupportedOperationException("Never intended for public use. Agressively deprecated.");
139
		}
140
141
142
		public int read(byte[] b, int off, int len)
143
		{
144 1 1. read : removed call to io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::loadContents → SURVIVED
			loadContents();
145
			int remaining = available();
146 1 1. read : negated conditional → KILLED
			if(remaining == 0) {
147 1 1. read : replaced return of integer sized value with (x == 0 ? 1 : 0) → TIMED_OUT
				return -1;
148
			}
149
			int length = Math.min(remaining, len);
150 1 1. read : removed call to java/lang/System::arraycopy → KILLED
			System.arraycopy(contents, position, b, off, length);
151 1 1. read : Replaced integer addition with subtraction → KILLED
			position += length;
152 1 1. read : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED
			return length;
153
		}
154
155
156
		int available()
157
		{
158 1 1. available : removed call to io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::loadContents → KILLED
			loadContents();
159 2 1. available : Replaced integer subtraction with addition → KILLED
2. available : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED
			return contents.length - position;
160
		}
161
	}
162
163
	private Iterator<Path> iterator;
164
	private Path directory;
165
	private ExplodedJarEntry current;
166
167
168
	private ExplodedJarInputStream(Path directory, Iterator<Path> iterator) throws IOException
169
	{
170
		super(EmptyInputStream.EMPTY_INPUTSTREAM, false);
171
		this.directory = directory.toRealPath();
172
		this.iterator = iterator;
173
	}
174
175
176
	/**
177
	 * If the {@code path} parameter is a directory then returns an {@link ExplodedJarInputStream},
178
	 * otherwise, the {@code path} parameter is a file so, returns a {@link JarInputStream}
179
	 * 
180
	 * @param path the location on a filesystem
181
	 * @return a jar input stream
182
	 * @throws IOException
183
	 * 
184
	 * @see {@link #explodedJar(Path)}
185
	 */
186
	public static JarInputStream jarInputStreamFrom(Path path) throws IOException
187
	{
188 1 1. jarInputStreamFrom : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::jarInputStreamFrom to ( if (x != null) null else throw new RuntimeException ) → KILLED
		return jarInputStreamFrom(path.toFile());
189
	}
190
191
192
	/**
193
	 * If the {@code path} parameter is a directory then returns an {@link ExplodedJarInputStream},
194
	 * otherwise, the {@code path} parameter is a file so, returns a {@link JarInputStream}
195
	 * 
196
	 * @param path the location on a filesystem
197
	 * @return a jar input stream
198
	 * @throws IOException
199
	 */
200
	public static JarInputStream jarInputStreamFrom(File path) throws IOException
201
	{
202 2 1. jarInputStreamFrom : negated conditional → KILLED
2. jarInputStreamFrom : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::jarInputStreamFrom to ( if (x != null) null else throw new RuntimeException ) → KILLED
		return path.isDirectory() ? explodedJar(path) : new JarInputStream(new FileInputStream(path));
203
	}
204
205
206
	/**
207
	 * Treat the <b>directory</b> as an exploded JAR file
208
	 * 
209
	 * @param directory
210
	 * @return a jar input stream, which can be read using {@link JarEntry} methods
211
	 * @throws IOException
212
	 */
213
	public static ExplodedJarInputStream explodedJar(File directory) throws IOException
214
	{
215 1 1. explodedJar : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::explodedJar to ( if (x != null) null else throw new RuntimeException ) → KILLED
		return explodedJar(directory.toPath());
216
	}
217
218
219
	/**
220
	 * Treat the <b>directory</b> as an exploded JAR file
221
	 * 
222
	 * @param directory
223
	 * @return a jar input stream, which can be read using {@link JarEntry} methods
224
	 * @throws IOException
225
	 */
226
	public static ExplodedJarInputStream explodedJar(Path directory) throws IOException
227
	{
228 1 1. explodedJar : negated conditional → KILLED
		if(!directory.toFile().isDirectory()) {
229
			throw new IOException("'" + directory + "' is not a directory");
230
		}
231
		RecursivePathIterator rpi = new RecursivePathIterator(directory);
232 1 1. explodedJar : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::explodedJar to ( if (x != null) null else throw new RuntimeException ) → KILLED
		return new ExplodedJarInputStream(directory, new Filterator<Path>(rpi, MANIFEST_PATH));
233
	}
234
235
236
	@Override
237
	public JarEntry getNextJarEntry() throws IOException
238
	{
239 1 1. getNextJarEntry : negated conditional → KILLED
		current = iterator.hasNext() ? new ExplodedJarEntry(iterator.next().toRealPath()) : null;
240 1 1. getNextJarEntry : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::getNextJarEntry to ( if (x != null) null else throw new RuntimeException ) → KILLED
		return current;
241
	}
242
243
244
	@Override
245
	public Manifest getManifest()
246
	{
247
		Manifest manifest = null;
248
		Path file = directory.resolve(MANIFEST_PATH);
249 1 1. getManifest : negated conditional → KILLED
		if(file.toFile().exists()) {
250
			manifest = new Manifest();
251 1 1. getManifest : removed call to io/earcam/unexceptional/Closing::closeAfterAccepting → KILLED
			closeAfterAccepting(FileInputStream::new, file.toFile(), manifest::read);
252
		}
253 1 1. getManifest : mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::getManifest to ( if (x != null) null else throw new RuntimeException ) → KILLED
		return manifest;
254
	}
255
256
257
	private void checkCurrent()
258
	{
259 1 1. checkCurrent : negated conditional → KILLED
		if(current == null) {
260
			throw new UnsupportedOperationException(ExplodedJarInputStream.class + " does not work as a regular InputStream");
261
		}
262
	}
263
264
265
	@Override
266
	public int read(byte[] b, int off, int len) throws IOException
267
	{
268 1 1. read : removed call to io/earcam/utilitarian/io/ExplodedJarInputStream::checkCurrent → KILLED
		checkCurrent();
269 1 1. read : replaced return of integer sized value with (x == 0 ? 1 : 0) → TIMED_OUT
		return current.read(b, off, len);
270
	}
271
272
273
	@Override
274
	public int available() throws IOException
275
	{
276 1 1. available : removed call to io/earcam/utilitarian/io/ExplodedJarInputStream::checkCurrent → KILLED
		checkCurrent();
277 1 1. available : replaced return of integer sized value with (x == 0 ? 1 : 0) → SURVIVED
		return current.available();
278
	}
279
}

Mutations

60

1.1
Location : read
Killed by : none
replaced return of integer sized value with (x == 0 ? 1 : 0) → SURVIVED

75

1.1
Location :
Killed by : none
removed call to io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::setMethod → SURVIVED

81

1.1
Location : path
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::path to ( if (x != null) null else throw new RuntimeException ) → KILLED

88

1.1
Location : isDirectory
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

95

1.1
Location : getCreationTime
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::getCreationTime to ( if (x != null) null else throw new RuntimeException ) → KILLED

2.2
Location : lambda$getCreationTime$0
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::lambda$getCreationTime$0 to ( if (x != null) null else throw new RuntimeException ) → KILLED

102

1.1
Location : getLastModifiedTime
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::getLastModifiedTime to ( if (x != null) null else throw new RuntimeException ) → KILLED

2.2
Location : lambda$getLastModifiedTime$1
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::lambda$getLastModifiedTime$1 to ( if (x != null) null else throw new RuntimeException ) → KILLED

109

1.1
Location : getTime
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
replaced return of long value with value + 1 for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::getTime → KILLED

116

1.1
Location : getSize
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
replaced return of long value with value + 1 for io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::getSize → KILLED

122

1.1
Location : loadContents
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
negated conditional → KILLED

144

1.1
Location : read
Killed by : none
removed call to io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::loadContents → SURVIVED

146

1.1
Location : read
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
negated conditional → KILLED

147

1.1
Location : read
Killed by : none
replaced return of integer sized value with (x == 0 ? 1 : 0) → TIMED_OUT

150

1.1
Location : read
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
removed call to java/lang/System::arraycopy → KILLED

151

1.1
Location : read
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
Replaced integer addition with subtraction → KILLED

152

1.1
Location : read
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

158

1.1
Location : available
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.availableCanBeInvokedOnTheEntries()
removed call to io/earcam/utilitarian/io/ExplodedJarInputStream$ExplodedJarEntry::loadContents → KILLED

159

1.1
Location : available
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
Replaced integer subtraction with addition → KILLED

2.2
Location : available
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

188

1.1
Location : jarInputStreamFrom
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.jarInputStreamFromJarFile()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::jarInputStreamFrom to ( if (x != null) null else throw new RuntimeException ) → KILLED

202

1.1
Location : jarInputStreamFrom
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.jarInputStreamFromJarFile()
negated conditional → KILLED

2.2
Location : jarInputStreamFrom
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.jarInputStreamFromJarFile()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::jarInputStreamFrom to ( if (x != null) null else throw new RuntimeException ) → KILLED

215

1.1
Location : explodedJar
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedJarEntrySingleReadMethodIsEffectivelyUseless()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::explodedJar to ( if (x != null) null else throw new RuntimeException ) → KILLED

228

1.1
Location : explodedJar
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedJarThrowsWhenPathIsNotADirectory()
negated conditional → KILLED

232

1.1
Location : explodedJar
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.failsAsNormalInputStreamWithNothingAvailable()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::explodedJar to ( if (x != null) null else throw new RuntimeException ) → KILLED

239

1.1
Location : getNextJarEntry
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
negated conditional → KILLED

240

1.1
Location : getNextJarEntry
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::getNextJarEntry to ( if (x != null) null else throw new RuntimeException ) → KILLED

249

1.1
Location : getManifest
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
negated conditional → KILLED

251

1.1
Location : getManifest
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.disgustingTest()
removed call to io/earcam/unexceptional/Closing::closeAfterAccepting → KILLED

253

1.1
Location : getManifest
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.explodedFromFilesystemWithoutManifest()
mutated return of Object value for io/earcam/utilitarian/io/ExplodedJarInputStream::getManifest to ( if (x != null) null else throw new RuntimeException ) → KILLED

259

1.1
Location : checkCurrent
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.failsAsNormalInputStreamWithNothingAvailable()
negated conditional → KILLED

268

1.1
Location : read
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.failsAsNormalInputStream()
removed call to io/earcam/utilitarian/io/ExplodedJarInputStream::checkCurrent → KILLED

269

1.1
Location : read
Killed by : none
replaced return of integer sized value with (x == 0 ? 1 : 0) → TIMED_OUT

276

1.1
Location : available
Killed by : io.earcam.utilitarian.io.ExplodedJarInputStreamTest.failsAsNormalInputStreamWithNothingAvailable()
removed call to io/earcam/utilitarian/io/ExplodedJarInputStream::checkCurrent → KILLED

277

1.1
Location : available
Killed by : none
replaced return of integer sized value with (x == 0 ? 1 : 0) → SURVIVED

Active mutators

Tests examined


Report generated by PIT 1.4.3