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 sectorTypesList = new ArrayList (); protected List fileEntries = new ArrayList (); 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 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 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 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)); } }