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. |
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 |
|
75 |
1.1 |
|
81 |
1.1 |
|
88 |
1.1 |
|
95 |
1.1 2.2 |
|
102 |
1.1 2.2 |
|
109 |
1.1 |
|
116 |
1.1 |
|
122 |
1.1 |
|
144 |
1.1 |
|
146 |
1.1 |
|
147 |
1.1 |
|
150 |
1.1 |
|
151 |
1.1 |
|
152 |
1.1 |
|
158 |
1.1 |
|
159 |
1.1 2.2 |
|
188 |
1.1 |
|
202 |
1.1 2.2 |
|
215 |
1.1 |
|
228 |
1.1 |
|
232 |
1.1 |
|
239 |
1.1 |
|
240 |
1.1 |
|
249 |
1.1 |
|
251 |
1.1 |
|
253 |
1.1 |
|
259 |
1.1 |
|
268 |
1.1 |
|
269 |
1.1 |
|
276 |
1.1 |
|
277 |
1.1 |