dmolony-DiskBrowser/src/com/bytezone/diskbrowser/disk/AbstractFormattedDisk.java

411 lines
11 KiB
Java
Executable File

package com.bytezone.diskbrowser.disk;
import java.awt.AWTEventMulticaster;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Enumeration;
import java.util.List;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import com.bytezone.diskbrowser.applefile.AbstractFile;
import com.bytezone.diskbrowser.applefile.AppleFileSource;
import com.bytezone.diskbrowser.applefile.BootSector;
import com.bytezone.diskbrowser.gui.DataSource;
public abstract class AbstractFormattedDisk implements FormattedDisk
{
protected Disk disk;
protected FormattedDisk parent; // used by Dual-dos disks
protected ActionListener actionListenerList;
protected JTree catalogTree;
protected Path originalPath;
// protected String originalName;
protected List<SectorType> sectorTypesList = new ArrayList<SectorType> ();
protected List<AppleFileSource> fileEntries = new ArrayList<AppleFileSource> ();
public SectorType[] sectorTypes;
protected BootSector bootSector;
public final SectorType emptySector = new SectorType ("Unused (empty)", Color.white);
public final SectorType usedSector = new SectorType ("Unused (data)", Color.yellow);
// public final SectorType dosSector = new SectorType ("DOS", Color.lightGray);
protected int falsePositives;
protected int falseNegatives;
protected Dimension gridLayout;
protected BitSet freeBlocks;
protected BitSet usedBlocks; // still to be populated - currently using stillAvailable ()
public AbstractFormattedDisk (Disk disk)
{
this.disk = disk;
freeBlocks = new BitSet (disk.getTotalBlocks ());
usedBlocks = new BitSet (disk.getTotalBlocks ());
/*
* All formatted disks will have empty and/or used sectors, so set them
* here, and let the actual subclass add its sector types later. This list
* is used to hold one of each sector type so that the DiskLayoutPanel can
* draw its grid and key correctly. Every additional type that the instance
* creates should be added here too.
*/
sectorTypesList.add (emptySector);
sectorTypesList.add (usedSector);
/*
* Hopefully every used sector will be changed by the subclass to something
* sensible, but deleted files will always leave the sector as used/unknown
* as it contains data.
*/
setSectorTypes ();
setGridLayout ();
/*
* Create the disk name as the root for the catalog tree. Subclasses will
* have to append their catalog entries to this node.
*/
DefaultAppleFileSource afs =
new DefaultAppleFileSource (getName (), disk.toString (), this);
DefaultMutableTreeNode root = new DefaultMutableTreeNode (afs);
DefaultTreeModel treeModel = new DefaultTreeModel (root);
catalogTree = new JTree (treeModel);
treeModel.setAsksAllowsChildren (true); // allows empty nodes to appear as folders
/*
* Add an ActionListener to the disk in case the interleave or blocksize
* changes
*/
disk.addActionListener (new ActionListener ()
{
@Override
public void actionPerformed (ActionEvent e)
{
setSectorTypes ();
}
});
}
private void setSectorTypes ()
{
sectorTypes = new SectorType[disk.getTotalBlocks ()];
for (DiskAddress da : disk)
sectorTypes[da.getBlock ()] = disk.isSectorEmpty (da) ? emptySector : usedSector;
}
private void setGridLayout ()
{
int totalBlocks = disk.getTotalBlocks ();
switch (totalBlocks)
{
case 280:
gridLayout = new Dimension (8, 35);
break;
case 455:
gridLayout = new Dimension (13, 35);
break;
case 560:
gridLayout = new Dimension (16, 35);
break;
case 1600:
gridLayout = new Dimension (16, 100);
break;
default:
int[] sizes = { 32, 20, 16, 8 };
for (int size : sizes)
if ((totalBlocks % size) == 0)
{
gridLayout = new Dimension (size, totalBlocks / size);
break;
}
if (gridLayout == null)
System.out.println ("Unusable total blocks : " + totalBlocks);
}
}
@Override
public Disk getDisk ()
{
return disk;
}
@Override
public FormattedDisk getParent ()
{
return parent;
}
@Override
public void setParent (FormattedDisk disk)
{
parent = disk;
}
@Override
public void setOriginalPath (Path path)
{
this.originalPath = path;
DefaultMutableTreeNode root = (DefaultMutableTreeNode) catalogTree.getModel ().getRoot ();
DefaultAppleFileSource afs =
new DefaultAppleFileSource (getName (), disk.toString (), this);
root.setUserObject (afs);
}
@Override
public String getAbsolutePath ()
{
if (originalPath != null)
return originalPath.toString ();
return disk.getFile ().getAbsolutePath ();
}
@Override
public String getName ()
{
if (originalPath != null)
return originalPath.getFileName ().toString ();
return disk.getFile ().getName ();
}
@Override
public void writeFile (AbstractFile file)
{
System.out.println ("not implemented yet");
}
@Override
public List<AppleFileSource> getCatalogList ()
{
return fileEntries;
}
@Override
public AppleFileSource getFile (String uniqueName)
{
for (AppleFileSource afs : fileEntries)
if (afs.getUniqueName ().equals (uniqueName))
return afs;
return null;
}
/*
* Catalog Tree routines
*/
@Override
public JTree getCatalogTree ()
{
return catalogTree;
}
public DefaultMutableTreeNode getCatalogTreeRoot ()
{
return (DefaultMutableTreeNode) catalogTree.getModel ().getRoot ();
}
public void makeNodeVisible (DefaultMutableTreeNode node)
{
catalogTree.makeVisible (new TreePath (((DefaultTreeModel) catalogTree.getModel ())
.getPathToRoot (node)));
}
protected DefaultMutableTreeNode findNode (DefaultMutableTreeNode node, String name)
{
Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration ();
if (children != null)
{
while (children.hasMoreElements ())
{
DefaultMutableTreeNode childNode = children.nextElement ();
if (childNode.getUserObject ().toString ().indexOf (name) > 0)
return childNode;
}
}
System.out.println ("Node not found : " + name);
return null;
}
/*
* These routines just hand back the information that was created above, and
* added to by the subclass.
*/
@Override
public SectorType getSectorType (int block)
{
return getSectorType (disk.getDiskAddress (block));
}
@Override
public SectorType getSectorType (int track, int sector)
{
return getSectorType (disk.getDiskAddress (track, sector));
}
@Override
public SectorType getSectorType (DiskAddress da)
{
return sectorTypes[da.getBlock ()];
}
@Override
public List<SectorType> getSectorTypeList ()
{
return sectorTypesList;
}
@Override
public void setSectorType (int block, SectorType type)
{
if (block < sectorTypes.length)
sectorTypes[block] = type;
else
System.out.println ("Invalid block number: " + block);
}
// Override this so that the correct sector type can be displayed
@Override
public DataSource getFormattedSector (DiskAddress da)
{
if (da.getBlock () == 0 && bootSector != null)
return bootSector;
SectorType sectorType = sectorTypes[da.getBlock ()];
byte[] buffer = disk.readSector (da);
String address = String.format ("%02X %02X", da.getTrack (), da.getSector ());
if (sectorType == emptySector)
return new DefaultSector ("Empty sector at " + address, disk, buffer);
if (sectorType == usedSector)
return new DefaultSector ("Orphan sector at " + address, disk, buffer);
return new DefaultSector ("Data sector at " + address, disk, buffer);
}
/*
* Override this with something useful
*/
@Override
public AppleFileSource getCatalog ()
{
return new DefaultAppleFileSource (disk.toString (), this);
}
@Override
public String getSectorFilename (DiskAddress da)
{
return "unknown";
}
@Override
public int clearOrphans ()
{
System.out.println ("Not implemented yet");
return 0;
}
@Override
public boolean isSectorFree (DiskAddress da)
{
return freeBlocks.get (da.getBlock ());
}
@Override
public boolean isSectorFree (int blockNo)
{
return freeBlocks.get (blockNo);
}
// representation of the Free Sector Table
@Override
public void setSectorFree (int block, boolean free)
{
if (block < 0 || block >= freeBlocks.size ())
{
System.out.printf ("Block %d not in range : 0-%d%n", block, freeBlocks.size () - 1);
return;
}
// assert block < freeBlocks.size () : String.format ("Set free block # %6d, size %6d",
// block, freeBlocks.size ());
freeBlocks.set (block, free);
}
// Check that the sector hasn't already been flagged as part of the disk structure
@Override
public boolean stillAvailable (DiskAddress da)
{
return sectorTypes[da.getBlock ()] == usedSector
|| sectorTypes[da.getBlock ()] == emptySector;
}
@Override
public boolean stillAvailable (int blockNo)
{
return sectorTypes[blockNo] == usedSector || sectorTypes[blockNo] == emptySector;
}
@Override
public void verify ()
{
System.out.println ("Sectors to clean :");
for (int i = 0, max = disk.getTotalBlocks (); i < max; i++)
{
if (freeBlocks.get (i))
{
if (sectorTypes[i] == usedSector)
System.out.printf ("%04X clean%n", i);
}
else
{
if (sectorTypes[i] == usedSector)
System.out.printf ("%04X *** error ***%n", i);
}
}
}
@Override
public Dimension getGridLayout ()
{
return gridLayout;
}
// VTOC flags sector as free, but it is in use by a file
@Override
public int falsePositiveBlocks ()
{
return falsePositives;
}
// VTOC flags sector as in use, but no file is using it (and not in the DOS tracks)
@Override
public int falseNegativeBlocks ()
{
return falseNegatives;
}
public void addActionListener (ActionListener actionListener)
{
actionListenerList = AWTEventMulticaster.add (actionListenerList, actionListener);
}
public void removeActionListener (ActionListener actionListener)
{
actionListenerList = AWTEventMulticaster.remove (actionListenerList, actionListener);
}
public void notifyListeners (String text)
{
if (actionListenerList != null)
actionListenerList.actionPerformed (new ActionEvent (this, ActionEvent.ACTION_PERFORMED,
text));
}
}