HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Reading and writing files and resources

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
readingwritingfilesandresources

Problem

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 =

Solution

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.

It sounds like this one class should really be two classes.
Another indicator of that is when you have constructors used like this:

this(charset, file, null, null);
    this(charset, null, clazz, name);


That's really two implementations masquerading as one class.

How about splitting to smaller pieces:

  • IoResource: common interface that file-based and location-based resources can implement



  • AbstractIoResource: skeletal implementation of common logic



  • FileIoResource: file-based implementation, with appropriate constructor



  • ClassPathIoResource: location-based implementation, with appropriate constructor, and write operations throwing UnsupportedOperationException



If you don't want to force clients to know the right class names for the appropriate types,
you can provide factory methods in one common class.
For inspiration, think of Collections and its various methods,
such as Collections.synchronizedList and Collections.unmodifiableList:
the actual implementation types are not visible to clients.
If you go with this approach, then the FileIoResource and ClassPathIoResource implementation can be even private.

Code Snippets

this(charset, file, null, null);
    this(charset, null, clazz, name);

Context

StackExchange Code Review Q#71075, answer score: 3

Revisions (0)

No revisions yet.