Doing IO in Java is a big pain. It got better with Guava and Java 7 and now about everything is there, but I never know exactly where.
Most of the time I need to slurp a file. Nearly as often I need to write a file, while making sure, I don't end up with some garbage in case of an IO error (atomicity). Leaving .tmp
files around in such a case is intended. Overwriting (and then deleting) a xxx.tmp
when xxx
gets written is a non-issue for me.
Sometimes the location is given like clazz.getResource(name)
. Sometimes, this resource is a file (when working in an IDE with sources and class files; I sometimes generate some data files in the source tree), sometimes it's not. Reading must work in both cases, writing only in the first.
The encoding is UTF-8 by default, but can be changed. Concerning platform default encoding, I don't give a damn, as the the files must be portable by simply binary copying them from one system to another. It's all rather hacky, but it does exactly what I want.
The imports are omitted for brevity and can be found here. Feel free to ignore one-liners containing conditions, as I'm not gonna change this.
I'll probably rewrite it to be Android-compatible, so no try with resources (there'll be a problem with java.nio.file
, but on Linux files moves are atomic, so it's not needed; the challenge will be to get atomicity on both Windows with Java 7+ and Android with Java 6).
@RequiredArgsConstructor(access=AccessLevel.PRIVATE) public class MyIoResource {
public MyIoResource(File file) {
this(UTF8, checkNotNull(file), null, null);
}
public MyIoResource(Class<?> clazz, String name) {
this(UTF8, null, checkNotNull(clazz), checkNotNull(name));
}
public MyIoResource asAnsi() {
return withCharset(ANSI);
}
public byte[] readBytes() throws IOException {
if (file!=null) return Files.readAllBytes(file.toPath());
final URL url = newUrl(clazz, name);
final InputStream in = url.openStream();
try {
final ByteArrayOutputStream result = new ByteArrayOutputStream();
ByteStreams.copy(in, result);
return result.toByteArray();
} finally {
in.close();
}
}
public String readString() throws IOException {
return new String(readBytes(), charset);
}
public void writeAtomic(byte[] data) throws IOException {
checkNotNull(data);
final File file = this.file!=null ? this.file : newFile();
final File tmpFile = new File(file.getParent(), file.getName() + ".tmp");
final StandardCopyOption[] options = {StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING};
Files.write(tmpFile.toPath(), data);
Files.move(tmpFile.toPath(), file.toPath(), options);
}
public void writeAtomic(String data) throws IOException {
writeAtomic(data.getBytes(charset));
}
public void writeAtomic(CharSequence data) throws IOException {
writeAtomic(data.toString());
}
public void writeAtomic(Iterable<String> data) throws IOException {
if (Iterables.isEmpty(data)) {
writeAtomic(new byte[0]);
} else {
writeAtomic(Joiner.on('\n').appendTo(new StringBuilder(), data).append('\n'));
}
}
private File newFile() throws IOException {
final URL dirUrl = newUrl(clazz, ".");
final URL url = new URL(dirUrl, name);
try {
return new File(url.toURI());
} catch (final URISyntaxException e) {
throw new IOException(e); // Don't pollute everything with URISyntaxException.
}
}
private static URL newUrl(Class<?> clazz, String name) throws NoSuchFileException {
final URL url = clazz.getResource(name);
if (url!=null) return url;
throw new NoSuchFileException("No such resource: " + clazz.getName() + " " + name);
}
private static final Charset UTF8 = Charsets.UTF_8;
private static final Charset ANSI = Charsets.ISO_8859_1;
@Wither private final Charset charset;
private final File file;
private final Class<?> clazz;
private final String name;
}