I am writing wrapper classes in Java which override methods of an existing implementation, in order to handle an edge case. The full implementation is a bit more complex than needs to be posted here, so I've written a simplified class containing only the parts I'm requesting assistance on.
Problem Summary
I am extending two classes:
One class designed as an "enumeration" class, abstracting a directory on a filesystem which contains symbolic links to other directories. (Real world: "/sys/block".). It has two methods, a scan() method to generate the list of (linked) subdirectories, and a getFirst() to return the first element of the list.
The second class is an "entry" class, abstracting the pointed-to directory enumerated by the first class. It has two methods, a getName() method to return the directory's path as a string, and a getNext() method to iterate to the next element.
Constraints
- Compatibility with JDK 8 or earlier
- Single-threaded use may be assumed
- Constructors may be altered as required.
- Must implement (at least) the two specified classes and the two methods on each.
Focus of review
The scan() method is my struggle here. I think I may have overcomplicated the solution in two ways:
- The nested
try ... catchblocks in thescan()method seem unusual. Am I missing a simpler way to handle this? - (UPDATE: Self-answered this second question, below.) The implemented pattern is obviously a singly linked list that I'm working around by passing around an
ArrayListimplementation. I can imagine theDirEntryclass containing only itsPathand aDirEntry nextobject, but attempts to generate such a list seem even more complex or less performant than the workaround I've created.
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DeviceList {
/**
* Class representing a parent directory which contains symbolic links to other
* directories
*/
static class DirEnumerator {
private Path dirPath;
private List<DirEntry> entryList = Collections.emptyList();
public DirEnumerator(String path) {
dirPath = FileSystems.getDefault().getPath(path);
}
/**
* Scans the directory for entries
*
* @return The number of entries found
*/
public int scan() {
try (Stream<Path> paths = Files.walk(dirPath)) {
List<Path> linkedDirs = paths.filter(Files::isSymbolicLink).map(p -> {
try {
return Files.readSymbolicLink(p);
} catch (IOException e) {
return p;
}
}).collect(Collectors.toList());
this.entryList = new ArrayList<>();
for (int i = 0; i < linkedDirs.size(); i++) {
this.entryList.add(new DirEntry(entryList, linkedDirs.get(i), i));
}
return this.entryList.size();
} catch (IOException e) {
this.entryList = Collections.emptyList();
return 0;
}
}
/**
* Gets the first entry in the scanned list
*
* @return The first entry if it exists; null otherwise
*/
public DirEntry getFirst() {
return entryList.isEmpty() ? null : entryList.get(0);
}
}
/**
* Class representing a directory
*/
static class DirEntry {
private List<DirEntry> entryList;
private Path path;
private int index;
public DirEntry(List<DirEntry> entryList, Path path, int i) {
this.entryList = entryList;
this.path = path;
this.index = i;
}
/**
* Gets the path name of the directory entry
*
* @return a string representing the path
*/
public String getName() {
return this.path.toString();
}
/**
* Gets the next entry in the list
*
* @return the next entry if it exists; null otherwise
*/
public DirEntry getNext() {
int nextIndex = index + 1;
return nextIndex < entryList.size() ? entryList.get(nextIndex) : null;
}
}
public static void main(String[] args) {
// Test on any directory containing symbolic links to other directories
DirEnumerator de = new DirEnumerator("/sys/block");
int n = de.scan();
System.out.println("Found " + n + " directories.");
DirEntry e = de.getFirst();
while (e != null) {
System.out.println("Directory: " + e.getName());
e = e.getNext();
}
}
}
```