AppleCommander/src/com/webcodepro/applecommander/storage/os/pascal/PascalFormatDisk.java
T. Joseph Carter 292afd24c9 Remove -Xlint:unchecked warnings
Fixed all instances of warnings OpenJDK finds with -Xlint:unchecked.
Merging this will at least result in a dependency on JDK 1.5 or later
that I'm pretty sure of.  But since the only JDKs that are really safe
to use these days are versions 1.8 and 9, that's probably okay.

No promises that this will make modern SWT work or anything, but it's
probably at least a beginnig toward that as well.
2017-11-09 13:03:03 -08:00

676 lines
20 KiB
Java

/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2002 by Robert Greene
* robgreene at users.sourceforge.net
* Copyright (C) 2004 by John B. Matthews
* matthewsj 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 com.webcodepro.applecommander.storage.os.pascal;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import com.webcodepro.applecommander.storage.DirectoryEntry;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.StorageBundle;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import com.webcodepro.applecommander.util.AppleUtil;
import com.webcodepro.applecommander.util.TextBundle;
/**
* Manages a disk that is in the Pascal format.
* <p>
* Date created: Oct 4, 2002 11:56:50 PM
* @author Rob Greene
* @author John B. Matthews [getFiles(), get/putDirectory(), createFile()]
*/
public class PascalFormatDisk extends FormattedDisk {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* The size of the Pascal file entry.
*/
public static final int ENTRY_SIZE = 26;
// filetypes used elsewhere in the code:
private static final String TEXTFILE = "TEXT"; //$NON-NLS-1$
private static final String CODEFILE = "CODE"; //$NON-NLS-1$
private static final String DATAFILE = "DATA"; //$NON-NLS-1$
/**
* The known filetypes for a Pascal disk.
*/
private static final String[] filetypes = {
"xdskfile", //$NON-NLS-1$
CODEFILE,
TEXTFILE,
"INFO", //$NON-NLS-1$
DATAFILE,
"GRAF", //$NON-NLS-1$
"FOTO", //$NON-NLS-1$
"securedir" }; //$NON-NLS-1$
/**
* Use this inner interface for managing the disk usage data.
* This offloads format-specific implementation to the implementing class.
* A BitSet is used to track all blocks, as Pascal disks do not have a
* bitmap stored on the disk. This is safe since we know the number of blocks
* that exist. (BitSet length is of last set bit - unset bits at the end are
* "lost".)
*/
private class PascalDiskUsage implements DiskUsage {
private int location = -1;
private BitSet bitmap = null;
public boolean hasNext() {
return location == -1 || location < getBlocksOnDisk() - 1;
}
public void next() {
if (bitmap == null) {
bitmap = new BitSet(getBlocksOnDisk());
// assume all blocks are unused
for (int block=6; block<getBlocksOnDisk(); block++) {
bitmap.set(block);
}
// process through all files and mark those blocks as used
Iterator files = getFiles().iterator();
while (files.hasNext()) {
PascalFileEntry entry = (PascalFileEntry) files.next();
for (int block=entry.getFirstBlock(); block<entry.getLastBlock(); block++) {
bitmap.clear(block);
}
}
location = 0;
} else {
location++;
}
}
public boolean isFree() {
return bitmap.get(location); // true = free
}
public boolean isUsed() {
return !bitmap.get(location); // false = used
}
}
/**
* Constructor for PascalFormatDisk.
*/
public PascalFormatDisk(String filename, ImageOrder imageOrder) {
super(filename, imageOrder);
}
/**
* Create a PascalFormatDisk.
*/
public static PascalFormatDisk[] create(String filename, String volumeName, ImageOrder imageOrder) {
PascalFormatDisk disk = new PascalFormatDisk(filename, imageOrder);
disk.format();
disk.setDiskName(volumeName);
return new PascalFormatDisk[] { disk };
}
/**
* Identify the operating system format of this disk.
* @see com.webcodepro.applecommander.storage.FormattedDisk#getFormat()
*/
public String getFormat() {
return textBundle.get("PascalFormatDisk.Pascal"); //$NON-NLS-1$
}
/**
* Retrieve a list of files.
* @see com.webcodepro.applecommander.storage.FormattedDisk#getFiles()
*/
public List<FileEntry> getFiles() {
List<FileEntry> list = new ArrayList<>();
byte[] directory = readDirectory();
// process directory blocks:
int entrySize = ENTRY_SIZE;
int count = AppleUtil.getWordValue(directory, 16);
int offset = entrySize;
for (int i=0; i<count; i++) {
byte[] entry = new byte[entrySize];
System.arraycopy(directory, offset, entry, 0, entry.length);
list.add(new PascalFileEntry(entry, this));
offset+= entrySize;
}
return list;
}
/**
* Retrieve the entire directory.
*/
public List<PascalFileEntry> getDirectory() {
List<PascalFileEntry> list = new ArrayList<>();
byte[] directory = readDirectory();
int count = AppleUtil.getWordValue(directory, 16);
int offset = 0;
for (int i = 0; i <= count; i++) {
byte[] entry = new byte[ENTRY_SIZE];
System.arraycopy(directory, offset, entry, 0, entry.length);
list.add(new PascalFileEntry(entry, this));
offset += ENTRY_SIZE;
}
return list;
}
/**
* Write the revised directory.
*/
public void putDirectory(List list) {
byte[] directory = new byte[2048];
int count = list.size();
int offset = 0;
for (int i = 0; i < count; i++) {
byte[] entry = ((PascalFileEntry) list.get(i)).toBytes();
System.arraycopy(entry, 0, directory, offset, entry.length);
offset += ENTRY_SIZE;
}
writeDirectory(directory);
}
/**
* Create a new FileEntry.
*/
public FileEntry createFile() throws DiskFullException {
// find index of largest free space
int count = 0; int index = 0; int max = 0;
int last = 0; int first = 0; int free = 0;
List<PascalFileEntry> dir = getDirectory();
count = dir.size();
for (int i = 1; i < count; i++) {
last = ((PascalFileEntry) dir.get(i - 1)).getLastBlock();
first = ((PascalFileEntry) dir.get(i)).getFirstBlock();
free = first - last;
if (free > max) {
max = free; index = i;
}
}
// check after last entry, too
last = ((PascalFileEntry) dir.get(count - 1)).getLastBlock();
first = getBlocksOnDisk();
free = first - last;
if (free > max) {
max = free; index = count;
}
if (free > 0 && count < 78) {
// update file count in the volume entry
PascalFileEntry volEntry = (PascalFileEntry) dir.get(0);
volEntry.setFileCount(count);
dir.set(0, volEntry);
// add new entry to list
dir.add(index, new PascalFileEntry(new byte[ENTRY_SIZE], this));
PascalFileEntry entry = (PascalFileEntry) dir.get(index);
// fill in plausible values; will rely index, first and last
first = ((PascalFileEntry) dir.get(index - 1)).getLastBlock();
entry.setFirstBlock(first);
entry.setLastBlock(first + max);
entry.setFiletype("data"); //$NON-NLS-1$
entry.setFilename("x"); //$NON-NLS-1$
entry.setBytesUsedInLastBlock(512);
entry.setModificationDate(new Date());
entry.setEntryIndex(index);
dir.set(index, entry);
// write it back to disk
putDirectory(dir);
return entry;
} else {
throw new DiskFullException(textBundle.get("PascalFormatDisk.DiskFull")); //$NON-NLS-1$
}
}
/**
* Identify if additional directories can be created. This
* may indicate that directories are not available to this
* operating system or simply that the disk image is "locked"
* to writing.
*/
public boolean canCreateDirectories() {
return false;
}
/**
* Indicates if this disk image can create a file.
* If not, the reason may be as simple as it has not beem implemented
* to something specific about the disk.
*/
public boolean canCreateFile() {
return true;
}
/**
* Read directory blocks. These are always in blocks 2 - 5 and
* are treated as a 2048 byte array.
*/
public byte[] readDirectory() {
byte[] directory = new byte[4 * BLOCK_SIZE];
for (int i=0; i<4; i++) {
System.arraycopy(readBlock(2+i), 0, directory, i*BLOCK_SIZE, BLOCK_SIZE);
}
return directory;
}
/**
* Write directory blocks.
*/
public void writeDirectory(byte[] directory) {
if (directory == null || directory.length != 2048) {
throw new IllegalArgumentException(textBundle.get("PascalFormatDisk.InvalidPascalDirectory")); //$NON-NLS-1$
}
for (int i=0; i<4; i++) {
byte[] block = new byte[BLOCK_SIZE];
System.arraycopy(directory, i*BLOCK_SIZE, block, 0, BLOCK_SIZE);
writeBlock(2+i, block);
}
}
/**
* Identify if this disk format is capable of having directories.
* @see com.webcodepro.applecommander.storage.FormattedDisk#canHaveDirectories()
*/
public boolean canHaveDirectories() {
return false;
}
/**
* Return the amount of free space in bytes.
* @see com.webcodepro.applecommander.storage.FormattedDisk#getFreeSpace()
*/
public int getFreeSpace() {
return getFreeBlocks() * BLOCK_SIZE;
}
/**
* Return the number of free blocks.
*/
public int getFreeBlocks() {
List files = getFiles();
int blocksFree = getBlocksOnDisk() - 6;
if (files != null) {
for (int i=0; i<files.size(); i++) {
PascalFileEntry entry = (PascalFileEntry) files.get(i);
blocksFree-= entry.getBlocksUsed();
}
}
return blocksFree;
}
/**
* Return the volume entry.
*/
protected byte[] getVolumeEntry() {
byte[] block = readBlock(2);
byte[] entry = new byte[ENTRY_SIZE];
System.arraycopy(block, 0, entry, 0, entry.length);
return entry;
}
/**
* Return the number of blocks on disk.
*/
public int getBlocksOnDisk() {
return AppleUtil.getWordValue(getVolumeEntry(), 14);
}
/**
* Return the number of files on disk.
*/
public int getFilesOnDisk() {
return AppleUtil.getWordValue(getVolumeEntry(), 16);
}
/**
* Return the fisrt block.
*/
public int getFirstBlock() {
return AppleUtil.getWordValue(getVolumeEntry(), 18);
}
/**
* Return the last access date.
*/
public Date getLastAccessDate() {
return AppleUtil.getPascalDate(getVolumeEntry(), 18);
}
/**
* Return the most recent date setting. Huh?
*/
public Date getMostRecentDateSetting() {
return AppleUtil.getPascalDate(getVolumeEntry(), 20);
}
/**
* Return the amount of used space in bytes.
* @see com.webcodepro.applecommander.storage.FormattedDisk#getUsedSpace()
*/
public int getUsedSpace() {
return getUsedBlocks() * BLOCK_SIZE;
}
/**
* Return the number of used blocks.
*/
public int getUsedBlocks() {
List files = getFiles();
int blocksUsed = 6;
if (files != null) {
for (int i=0; i<files.size(); i++) {
PascalFileEntry entry = (PascalFileEntry) files.get(i);
blocksUsed+= entry.getBlocksUsed();
}
}
return blocksUsed;
}
/**
* Return the name of the disk. This is stored on block #2
* offset +6 (string[7]).
* @see com.webcodepro.applecommander.storage.FormattedDisk#getDiskName()
*/
public String getDiskName() {
return AppleUtil.getPascalString(readBlock(2), 6) + ":"; //$NON-NLS-1$
}
/**
* Set the name of the disk. The Pascal parlance is "volume name"
* whereas AppleCommander uses disk name. Max length is 7.
*/
public void setDiskName(String volumeName) {
byte[] directory = readDirectory();
AppleUtil.setPascalString(directory, 6, volumeName.toUpperCase(), 7);
writeDirectory(directory);
}
/**
* Get suggested dimensions for display of bitmap. Since Pascal disks are
* a block device, no suggestion is given.
*/
public int[] getBitmapDimensions() {
return null;
}
/**
* Get the length of the bitmap.
*/
public int getBitmapLength() {
return getBlocksOnDisk();
}
/**
* Get the disk usage iterator.
*/
public DiskUsage getDiskUsage() {
return new PascalDiskUsage();
}
/**
* Get the labels to use in the bitmap.
*/
public String[] getBitmapLabels() {
return new String[] { textBundle.get("Block") }; //$NON-NLS-1$
}
/**
* Get Pascal-specific disk information.
*/
public List<DiskInformation> getDiskInformation() {
List<DiskInformation> list = super.getDiskInformation();
list.add(new DiskInformation(textBundle.get("TotalBlocks"), getBlocksOnDisk())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FreeBlocks"), getFreeBlocks())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("UsedBlocks"), getUsedBlocks())); //$NON-NLS-1$
list.add(new DiskInformation(
textBundle.get("PascalFormatDisk.FilesOnDisk"), getFilesOnDisk())); //$NON-NLS-1$
list.add(new DiskInformation(
textBundle.get("PascalFormatDisk.LastAccessDate"), getLastAccessDate())); //$NON-NLS-1$
list.add(new DiskInformation(
textBundle.get("PascalFormatDisk.MostRecentDateSetting"), getMostRecentDateSetting())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("PascalFormatDisk.FirstBlock"), getFirstBlock())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("PascalFormatDisk.VolumeDate"), getLastAccessDate())); //$NON-NLS-1$
return list;
}
/**
* Get the standard file column header information.
* This default implementation is intended only for standard mode.
*/
public List<FileColumnHeader> getFileColumnHeaders(int displayMode) {
List<FileColumnHeader> list = new ArrayList<>();
switch (displayMode) {
case FILE_DISPLAY_NATIVE:
list.add(new FileColumnHeader(textBundle.get("Modified"), 8, //$NON-NLS-1$
FileColumnHeader.ALIGN_CENTER));
list.add(new FileColumnHeader(textBundle.get("Blocks"), 3, //$NON-NLS-1$
FileColumnHeader.ALIGN_RIGHT));
list.add(new FileColumnHeader(textBundle.get("Filetype"), 8, //$NON-NLS-1$
FileColumnHeader.ALIGN_CENTER));
list.add(new FileColumnHeader(textBundle.get("Name"), 15, //$NON-NLS-1$
FileColumnHeader.ALIGN_LEFT));
break;
case FILE_DISPLAY_DETAIL:
list.add(new FileColumnHeader(textBundle.get("Modified"), 8, //$NON-NLS-1$
FileColumnHeader.ALIGN_CENTER));
list.add(new FileColumnHeader(textBundle.get("Blocks"), 3, //$NON-NLS-1$
FileColumnHeader.ALIGN_RIGHT));
list.add(new FileColumnHeader(
textBundle.get("PascalFormatDisk.BytesInLastBlock"), 3, //$NON-NLS-1$
FileColumnHeader.ALIGN_RIGHT));
list.add(new FileColumnHeader(textBundle.get("SizeInBytes"), 6, //$NON-NLS-1$
FileColumnHeader.ALIGN_RIGHT));
list.add(new FileColumnHeader(textBundle.get("Filetype"), 8, //$NON-NLS-1$
FileColumnHeader.ALIGN_CENTER));
list.add(new FileColumnHeader(textBundle.get("Name"), 15, //$NON-NLS-1$
FileColumnHeader.ALIGN_LEFT));
list.add(new FileColumnHeader(
textBundle.get("PascalFormatDisk.FirstBlock"), 3, //$NON-NLS-1$
FileColumnHeader.ALIGN_RIGHT));
list.add(new FileColumnHeader(
textBundle.get("PascalFormatDisk.LastBlock"), 3, //$NON-NLS-1$
FileColumnHeader.ALIGN_RIGHT));
break;
default: // FILE_DISPLAY_STANDARD
list.addAll(super.getFileColumnHeaders(displayMode));
break;
}
return list;
}
/**
* Indicates if this disk format supports "deleted" files.
*/
public boolean supportsDeletedFiles() {
return false;
}
/**
* Indicates if this disk image can read data from a file.
*/
public boolean canReadFileData() {
return true;
}
/**
* Indicates if this disk image can write data to a file.
*/
public boolean canWriteFileData() {
return true;
}
/**
* Indicates if this disk image can delete a file.
*/
public boolean canDeleteFile() {
return true;
}
/**
* Get the data associated with the specified FileEntry.
*/
public byte[] getFileData(FileEntry fileEntry) {
if ( !(fileEntry instanceof PascalFileEntry)) {
throw new IllegalArgumentException(textBundle.get("PascalFormatDisk.IncorrectFileEntryError")); //$NON-NLS-1$
}
PascalFileEntry pascalEntry = (PascalFileEntry) fileEntry;
int firstBlock = pascalEntry.getFirstBlock();
int lastBlock = pascalEntry.getLastBlock();
byte[] fileData = new byte[pascalEntry.getSize()];
int offset = 0;
for (int block = firstBlock; block < lastBlock; block++) {
byte[] blockData = readBlock(block);
if (block == lastBlock-1) {
System.arraycopy(blockData, 0, fileData, offset,
pascalEntry.getBytesUsedInLastBlock());
} else {
System.arraycopy(blockData, 0, fileData, offset, blockData.length);
}
offset+= blockData.length;
}
return fileData;
}
/**
* Format the disk as an Apple Pascal disk.
* @see com.webcodepro.applecommander.storage.FormattedDisk#format()
*/
public void format() {
getImageOrder().format();
writeBootCode();
// Create volume name
byte[] directory = readDirectory();
AppleUtil.setWordValue(directory, 0, 0); // always 0
AppleUtil.setWordValue(directory, 2, 6); // last directory block
AppleUtil.setWordValue(directory, 4, 0); // entry type (0=vol header)
// volume name should have been set in constructor!
int blocks = getImageOrder().getBlocksOnDevice();
AppleUtil.setWordValue(directory, 14, blocks);
AppleUtil.setWordValue(directory, 16, 0); // no files
AppleUtil.setWordValue(directory, 18, 0); // first block
AppleUtil.setPascalDate(directory, 20, new Date()); // most recent date setting
writeDirectory(directory);
}
/**
* Returns the logical disk number. Returns a 0 to indicate no numbering.
*/
public int getLogicalDiskNumber() {
return 0;
}
/**
* Returns a valid filename for the given filename. This is somewhat
* of a guess, but the Pascal filenames appear to have similar
* restrictions as ProDOS.
*/
public String getSuggestedFilename(String filename) {
StringBuffer newName = new StringBuffer();
if (!Character.isLetter(filename.charAt(0))) {
newName.append('A');
}
int i=0;
while (newName.length() < 15 && i<filename.length()) {
char ch = filename.charAt(i);
if (Character.isLetterOrDigit(ch) || ch == '.') {
newName.append(ch);
}
i++;
}
return newName.toString().toUpperCase().trim();
}
/**
* Returns a valid filetype for the given filename. The most simple
* format will just assume a filetype of binary. This method is
* available for the interface to make an intelligent first guess
* as to the filetype.
*/
public String getSuggestedFiletype(String filename) {
String filetype = DATAFILE;
int pos = filename.lastIndexOf("."); //$NON-NLS-1$
if (pos > 0) {
String what = filename.substring(pos+1);
if ("txt".equalsIgnoreCase(what)) { //$NON-NLS-1$
filetype = TEXTFILE;
} else if ("pas".equalsIgnoreCase(what)) { //$NON-NLS-1$
filetype = CODEFILE;
}
}
return filetype;
}
/**
* Returns a list of possible file types. Since the filetype is
* specific to each operating system, a simple String is used.
*/
public String[] getFiletypes() {
return filetypes;
}
/**
* Indicates if this filetype requires an address component.
* No Pascal filetypes require or support an address.
*/
public boolean needsAddress(String filetype) {
return false;
}
/**
* Indicates if this FormattedDisk supports a disk map.
*/
public boolean supportsDiskMap() {
return true;
}
/**
* Change to a different ImageOrder. Remains in Pascal format but
* the underlying order can chage.
* @see ImageOrder
*/
public void changeImageOrder(ImageOrder imageOrder) {
AppleUtil.changeImageOrderByBlock(getImageOrder(), imageOrder);
setImageOrder(imageOrder);
}
/**
* Writes the raw bytes into the file. This bypasses any special formatting
* of the data (such as prepending the data with a length and/or an address).
* Typically, the FileEntry.setFileData method should be used.
*/
public void setFileData(FileEntry fileEntry, byte[] fileData) throws DiskFullException {
// TODO implement setFileData
}
/**
* Create a new DirectoryEntry.
* @see com.webcodepro.applecommander.storage.DirectoryEntry#createDirectory()
*/
public DirectoryEntry createDirectory() throws DiskFullException {
throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$
}
/**
* Create a new DirectoryEntry.
* @see com.webcodepro.applecommander.storage.DirectoryEntry#createDirectory()
*/
public DirectoryEntry createDirectory(String name) throws DiskFullException {
throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$
}
}