Adding DisplayColumn and updating tests and the rest of FileStore to use or support it.

This commit is contained in:
a2geek committed 2026-09-12 13:28:27 -05:00
1 parent 7f29095acd
commit cffedf2a67
6 files changed
+168 -50

No files matched your search

@@ -22,13 +22,14 @@ package com.webcodepro.applecommander.storage;
import com.webcodepro.applecommander.storage.os.prodos.ProdosDirectoryEntry;
import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk;
import org.applecommander.capability.Capability;
import org.applecommander.filestore.DirectoryEntry;
import org.applecommander.filestore.Entry;
import org.applecommander.filestore.Directory;
import org.applecommander.filestore.DisplayColumn;
import org.applecommander.filestore.FileEntry;
import org.applecommander.filestore.FileStore;
import org.applecommander.util.Container;
import java.util.*;
import java.util.function.Function;
/**
* The DiskFileStoreAdapter is a shim that allows FormattedDisk to be mapped into the
@@ -52,8 +53,8 @@ public class DiskFileStoreAdapter implements FileStore {
}
@Override
public DirectoryEntry getRootDirectory() {
return new DiskDirectoryEntryAdapter(this, null, disk);
public DiskDirectoryAdapter getRootDirectory() {
return new DiskDirectoryAdapter(this, null, disk);
}
@Override
@@ -73,46 +74,73 @@ public class DiskFileStoreAdapter implements FileStore {
};
}
public abstract static class DiskEntryAdapter implements Entry {
protected final DiskFileStoreAdapter adapter;
protected final DirectoryEntry parent;
DiskEntryAdapter(DiskFileStoreAdapter adapter, DirectoryEntry parent) {
this.adapter = adapter;
this.parent = parent;
}
public DirectoryEntry getParent() {
return parent;
}
public FileStore getFileStore() {
return adapter;
/**
* Translate the "legacy" FormattedDisk FileColumnHeader to the new DisplayColumn structure.
* Note that we need to track what has been seen as well as where it comes from and the index
* values in order to replicate most of the capability. This does not solve for type since the
* old API pre-formats everything as a String.
*/
@Override
public List<DisplayColumn> getDisplayColumns() {
List<DisplayColumn> columns = new ArrayList<>();
Set<String> alreadySeenKeys = new HashSet<>();
for (int displayMode : List.of(FormattedDisk.FILE_DISPLAY_NATIVE,
FormattedDisk.FILE_DISPLAY_DETAIL)) {
List<FormattedDisk.FileColumnHeader> headers = disk.getFileColumnHeaders(displayMode);
DisplayColumn.Mode mode = DisplayColumn.Mode.DETAIL;
if (displayMode == FormattedDisk.FILE_DISPLAY_NATIVE) {
mode = DisplayColumn.Mode.NATIVE;
};
for (int i = 0; i<headers.size(); i++) {
var header = headers.get(i);
DisplayColumn.Alignment alignment = switch(header.getAlignment()) {
case FormattedDisk.FileColumnHeader.ALIGN_CENTER -> DisplayColumn.Alignment.CENTER;
case FormattedDisk.FileColumnHeader.ALIGN_RIGHT -> DisplayColumn.Alignment.RIGHT;
default -> DisplayColumn.Alignment.LEFT;
};
if (!alreadySeenKeys.contains(header.getKey())) {
alreadySeenKeys.add(header.getKey());
final int headerIndex = i;
Function<FileEntry,String> mappingFn = entry -> {
// TODO investigate to see if generics work across these interfaces
if (entry instanceof DiskFileEntryAdapter fileEntryAdapter) {
return fileEntryAdapter.fileEntry.getFileColumnData(displayMode).get(headerIndex);
}
throw new RuntimeException("Unexpected file entry type: " + entry.getClass().getName());
};
DisplayColumn displayColumn = new DisplayColumn(header.getTitle(),
alignment, mappingFn::apply, "%s", mode);
columns.add(displayColumn);
}
}
}
return columns;
}
/**
* The DiskFileEntryAdapter is a shim that allows a FileEntry to be mapped into the
* new/evolving FileEntry interface(s).
*/
public static class DiskFileEntryAdapter implements FileEntry {
private final DiskFileStoreAdapter adapter;
private final DirectoryEntry parent;
private final DiskDirectoryAdapter parent;
private final com.webcodepro.applecommander.storage.FileEntry fileEntry;
private final DirectoryEntry subdirectory;
private final DiskDirectoryAdapter subdirectory;
public DiskFileEntryAdapter(DiskFileStoreAdapter adapter, DirectoryEntry parent,
public DiskFileEntryAdapter(DiskFileStoreAdapter adapter, DiskDirectoryAdapter parent,
com.webcodepro.applecommander.storage.FileEntry fileEntry) {
this.adapter = adapter;
this.parent = parent;
this.fileEntry = fileEntry;
if (fileEntry instanceof ProdosDirectoryEntry prodosDirectoryEntry) {
this.subdirectory = new DiskDirectoryEntryAdapter(adapter, parent, prodosDirectoryEntry);
this.subdirectory = new DiskDirectoryAdapter(adapter, parent, prodosDirectoryEntry);
}
else {
this.subdirectory = null;
}
}
@Override
public DirectoryEntry getParent() {
public DiskDirectoryAdapter getParent() {
return parent;
}
@Override
@@ -168,18 +196,22 @@ public class DiskFileStoreAdapter implements FileStore {
* The DiskDirectoryEntryAdapter is a shim that allows a FormattedDisk or DirectoryEntry to be mapped into the
* new/evolving DirectoryEntry interface(s).
*/
public static class DiskDirectoryEntryAdapter implements DirectoryEntry {
public static class DiskDirectoryAdapter implements Directory {
private final DiskFileStoreAdapter adapter;
private final DirectoryEntry parent;
private final DiskDirectoryAdapter parent;
private final com.webcodepro.applecommander.storage.DirectoryEntry directoryEntry;
public DiskDirectoryEntryAdapter(DiskFileStoreAdapter adapter, DirectoryEntry parent,
com.webcodepro.applecommander.storage.DirectoryEntry directoryEntry) {
public DiskDirectoryAdapter(DiskFileStoreAdapter adapter, DiskDirectoryAdapter parent,
com.webcodepro.applecommander.storage.DirectoryEntry directoryEntry) {
this.adapter = adapter;
this.parent = parent;
this.directoryEntry = directoryEntry;
}
@Override
public Optional<Directory> getParent() {
return Optional.ofNullable(parent);
}
@Override
public List<FileEntry> getFiles() {
try {
List<FileEntry> entries = new ArrayList<>();
@@ -191,6 +223,10 @@ public class DiskFileStoreAdapter implements FileStore {
throw new RuntimeException(e);
}
}
@Override
public DiskFileStoreAdapter getFileStore() {
return adapter;
}
}
}
@@ -28,7 +28,15 @@ import java.util.stream.Collectors;
* FileStores that don't naturally support a directory do contain a root directory,
* so there will be a directory associated to each FileStore.
*/
public interface DirectoryEntry {
public interface Directory {
/**
* Get the parent directory. If this is the root directory, it returns empty.
*/
Optional<Directory> getParent();
/**
* Returns the FileStore that this Directory belongs to.
*/
FileStore getFileStore();
/**
* Return all entries that are stored in this directory.
*/
@@ -36,10 +44,11 @@ public interface DirectoryEntry {
/**
* Filter out the <code>DirectoryEntry</code> entries in this directory.
*/
default List<DirectoryEntry> getDirectories() {
default List<Directory> getDirectories() {
return getFiles().stream()
.filter(fileEntry -> fileEntry instanceof DirectoryEntry)
.map(fileEntry -> (DirectoryEntry)fileEntry)
.map(fileEntry -> fileEntry.get(Directory.class))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
/**
@@ -50,9 +59,9 @@ public interface DirectoryEntry {
if (name.equalsIgnoreCase(fileEntry.getName())) {
return Optional.of(fileEntry);
}
Optional<DirectoryEntry> directoryEntry = fileEntry.get(DirectoryEntry.class);
if (directoryEntry.isPresent()) {
Optional<FileEntry> finding = directoryEntry.get().findFile(name);
Optional<Directory> directory = fileEntry.get(Directory.class);
if (directory.isPresent()) {
Optional<FileEntry> finding = directory.get().findFile(name);
if (finding.isPresent()) {
return finding;
}
@@ -0,0 +1,60 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2026 by Robert Greene and others
* robgreene at users.sourceforge.net
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.applecommander.filestore;
import java.util.Set;
import java.util.function.Function;
/**
* A DisplayColumn supports formatted output from various FileStores in a
* dynamic fashion. The intent is to provide a flexible display interface
* that does not rely on hard-coded implementations.
*/
public record DisplayColumn(String headerText, Alignment alignment, Function<FileEntry,Object> valueFn, String fmt, Mode... modes) {
/**
* Indicates if this DisplayColumn supports the mode. In this manner, the columns
* can be filtered out. Or it can be ignored to display everything.
*/
public boolean supports(Mode mode) {
return Set.of(modes).contains(mode);
}
/**
* Helper method to convert the column into a formatted string for display.
*/
public String formatAsText(FileEntry fileEntry) {
return String.format(fmt, valueFn.apply(fileEntry));
}
/**
* Indicates the display mode to help filter columns to users' preference.
*/
public enum Mode {
NATIVE,
DETAIL
}
/**
* Indicates how this column should be formatted.
*/
public enum Alignment {
LEFT,
CENTER,
RIGHT
}
}
@@ -29,7 +29,7 @@ public interface FileEntry extends Container {
/**
* The parent <code>DirectoryEntry</code>, if applicable. Can return <code>null</code>.
*/
DirectoryEntry getParent();
Directory getParent();
/**
* Indicates if this <code>Entry</code> is deleted.
*/
@@ -22,6 +22,8 @@ package org.applecommander.filestore;
import org.applecommander.capability.CapabilityProvider;
import org.applecommander.util.Container;
import java.util.List;
/**
* A FileStore is a generic interface for all archive and filesystem objects.
*/
@@ -33,8 +35,10 @@ public interface FileStore extends CapabilityProvider, Container {
*/
String getLabel();
/** All FileStores support a "root" directory that references all files. */
DirectoryEntry getRootDirectory();
Directory getRootDirectory();
/** This is the path separator character. */
// TODO there has to be a better / dynamic way to do this (applies to ProDOS, ShrinkIt only?)
String getPathSeparator();
/** Yields a list of columns to be used in this display mode. */
List<DisplayColumn> getDisplayColumns();
}
@@ -21,7 +21,6 @@ package com.webcodepro.applecommander.storage;
import org.applecommander.capability.Capability;
import org.applecommander.filestore.*;
import org.applecommander.filestore.DirectoryEntry;
import org.applecommander.filestore.FileEntry;
import org.applecommander.source.Source;
import org.applecommander.source.Sources;
@@ -248,20 +247,30 @@ public class DiskFileStoreTest {
return ctx.fileStores;
}
protected void showFiles(DirectoryEntry parent, String indent) {
protected void showFiles(Directory parent, String indent) {
FileStore fileStore = parent.getFileStore();
List<DisplayColumn> displayColumns = fileStore.getDisplayColumns();
if (parent.getParent().isEmpty()) {
// Print columns only at the top
System.out.print(indent);
displayColumns.forEach(column -> {
System.out.print(column.headerText());
System.out.print(" ");
});
System.out.println();
}
for (FileEntry file : parent.getFiles()) {
if (!file.isDeleted()) {
//List<String> data = entry.getFileColumnData(FormattedDisk.FILE_DISPLAY_NATIVE);
//System.out.print(indent);
//for (int d=0; d<data.size(); d++) {
// System.out.print(data.get(d));
// System.out.print(" ");
//}
//System.out.println();
System.out.printf("%s%s %s %d\n", indent, file.getName(), file.getFiletype(), file.getSize());
System.out.print(indent);
displayColumns.forEach(column -> {
System.out.print(column.formatAsText(file));
System.out.print(" ");
});
System.out.println();
Optional<Directory> subdirectory = file.get(Directory.class);
subdirectory.ifPresent(directoryEntry -> showFiles(directoryEntry, indent + " "));
}
Optional<DirectoryEntry> subdirectory = file.get(DirectoryEntry.class);
subdirectory.ifPresent(directoryEntry -> showFiles(directoryEntry, indent + " "));
}
}
@@ -313,9 +322,9 @@ public class DiskFileStoreTest {
}
}
protected void assertCanReadFiles(DirectoryEntry parent) throws DiskException {
protected void assertCanReadFiles(Directory parent) throws DiskException {
for (FileEntry fileEntry : parent.getFiles()) {
Optional<DirectoryEntry> subdirectory = fileEntry.get(DirectoryEntry.class);
Optional<Directory> subdirectory = fileEntry.get(Directory.class);
if (fileEntry.isDeleted()) {
System.out.printf("Skipping deleted file: %s\n", fileEntry.getName());
}