package com.wudsn.tools.atariroms;

import com.wudsn.tools.base.repository.Action;
import com.wudsn.tools.base.repository.NLS;

/**
 * Action repository.
 * 
 * @author Peter Dell
 */
public final class Actions extends NLS {

    // Actions: Menu
    public static Action MainMenu_File_Exit;

    public static Action MainMenu_Edit_CompareEntries;
    
    static {
	initializeClass(Actions.class, null);
    }
}
# UI Texts: Menu
MainMenu_File_Exit.label=E&xit
MainMenu_File_Exit.toolTip=Exit Atari ROM Checker

MainMenu_Edit_CompareEntries.label=&Compare Entries...
MainMenu_Edit_CompareEntries.toolTip=Compare the selected entries
package com.wudsn.tools.atariroms;

import com.wudsn.tools.base.repository.DataType;
import com.wudsn.tools.base.repository.NLS;

/**
 * Text repository.
 * 
 * @author Peter Dell
 */
public final class DataTypes extends NLS {

    public static DataType WorkbookEntry_Id = new DataType(Integer.class);
    public static DataType WorkbookEntry_FolderPath = new DataType(String.class);
    public static DataType WorkbookEntry_FileName = new DataType(String.class);
    public static DataType WorkbookEntry_MD5 = new DataType(String.class);
    public static DataType WorkbookEntry_CRC32 = new DataType(String.class);
    public static DataType WorkbookEntry_Message = new DataType(String.class);

    public static DataType ROM_FPP_CheckSum = new DataType(String.class);
    public static DataType ROM_OS1_CheckSum = new DataType(String.class);
    public static DataType ROM_OS2_CheckSum = new DataType(String.class);

    public static DataType Version_Type = new DataType(String.class);
    public static DataType Version_Revision = new DataType(String.class);
    public static DataType Version_Norm = new DataType(String.class);
    public static DataType Version_Year = new DataType(String.class);
    public static DataType Version_Comment = new DataType(String.class);
    public static DataType Version_Parts = new DataType(String.class);

    public static DataType Comparison_EntriesCount = new DataType(Integer.class);
    public static DataType ComparisonEntry_Id = new DataType(Integer.class);
    public static DataType ComparisonEntry_Offset = new DataType(String.class);
    public static DataType ComparisonEntry_Address = new DataType(String.class);
    public static DataType ComparisonEntry_Value = new DataType(String.class);

    static {
	initializeClass(DataTypes.class, null);
    }
}
package com.wudsn.tools.atariroms;

import com.wudsn.tools.base.repository.Message;
import com.wudsn.tools.base.repository.NLS;

/**
 * Message repository.
 * 
 * @author Peter Dell
 */
public final class Messages extends NLS {

    // UI
    public static Message I100;
    public static Message E101;
    public static Message I102;
    public static Message E103;
    public static Message E104;
    public static Message E105;

    static {
	initializeClass(Messages.class, null);
    }
}
# Messages
I100=Welcome to the Atari ROM Checker Version {0} by JAC!. Drop your folders or files on the window.
E101=Exception occurred: {0}
I102={0} files found and analysed.
E103=Select at least {0} entries for comparison.
E104=Select at least {0} entries with equal file size for comparison.
E105=Select at most {0} entries with equal file size for comparison./**
 * Copyright (C) 2014 <a href="http://www.wudsn.com" target="_top">Peter Dell</a>
package com.wudsn.tools.atariroms.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.wudsn.tools.base.gui.AttributeTableModel;

public final class Comparison {

    public static final int MIN = 2;
    public static final int MAX = 5;
	
    private List<WorkbookEntry> workbookEntries;
    private List<ComparisonEntry> entriesList;
    private List<ComparisonEntry> unmodifiableEntriesList;

    private AttributeTableModel entriesModel;

    public Comparison(List<WorkbookEntry> workbookEntries) {
	if (workbookEntries == null) {
	    throw new IllegalArgumentException("Parameter 'workbookEntries' must not be null.");
	}
	this.workbookEntries=workbookEntries;
	entriesList = new ArrayList<ComparisonEntry>();
	unmodifiableEntriesList = Collections.unmodifiableList(entriesList);
    }

    public int getWorkbookEntryCount() {
	return workbookEntries.size();
    }

    public WorkbookEntry getWorkbookEntry(int index) {
	return workbookEntries.get(index);
    }

    public void setEntriesTableModel(AttributeTableModel entriesModel) {
	this.entriesModel = entriesModel;
    }

    public void clear() {
	entriesList.clear();
	if (entriesModel != null) {
	    entriesModel.fireTableDataChanged();
	}
    }

    public ComparisonEntry createEntry(int offset, int address) {
	ComparisonEntry entry = new ComparisonEntry(getWorkbookEntryCount());
	entry.setOffset(offset);
	entry.setAddress(address);
	return entry;
    }

    public void addEntry(ComparisonEntry entry) {
	if (entry == null) {
	    throw new IllegalArgumentException("Parameter 'entry' must not be null.");
	}
	entriesList.add(entry);
	if (entriesModel != null) {
	    entriesModel.fireTableDataChanged();
	}
    }

    public ComparisonEntry getEntry(int row) {
	return entriesList.get(row);
    }

    public int getEntryCount() {
	return entriesList.size();
    }

    public List<ComparisonEntry> getUnmodifiableEntriesList() {
	return unmodifiableEntriesList;
    }

    public long getMaximumOffset() {
	return 0xffff;
    }

    public long getMaximumAddress() {
	return 0xffff;
    }

    @Override
    public String toString() {
	return "workbookEntryCount=" + getWorkbookEntryCount() + "/entries=" + entriesList.toString();
    }
}

package com.wudsn.tools.atariroms.model;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import com.wudsn.tools.atariroms.DataTypes;
import com.wudsn.tools.base.repository.Attribute;

public final class ComparisonEntry {
    public static final class Attributes {
	private Attributes() {
	}

	public static final Attribute ID = new Attribute("id", DataTypes.ComparisonEntry_Id);
	public static final Attribute OFFSET = new Attribute("offset", DataTypes.ComparisonEntry_Offset);
	public static final Attribute ADDRESS = new Attribute("address", DataTypes.ComparisonEntry_Address);
	public static final Attribute VALUE = new Attribute("value", DataTypes.ComparisonEntry_Value);
    }

    private int offset;
    private int address;
    private List<String> values;
    private List<Integer> colors;

    ComparisonEntry(int columnCount) {
	values = new ArrayList<String>(columnCount);
	colors = new ArrayList<Integer>(columnCount);
	for (int i = 0; i < columnCount; i++) {
	    values.add("");
	    colors.add(Integer.valueOf(0));
	}
    }

    void setOffset(int offset) {
	this.offset = offset;
    }

    public int getOffset() {
	return offset;
    }

    void setAddress(int address) {
	this.address = address;
    }

    public int getAddress() {
	return address;
    }

    void setValue(int column, String value, int color) {
	values.set(column, value);
	colors.set(column, Integer.valueOf(color));
    }

    public String getValue(int column) {
	return values.get(column);
    }

    public Color getColor(int column) {
	Integer colorKey = colors.get(column);
	switch (colorKey.intValue()) {
	case 0:
	    return Color.GREEN;
	case 1:
	    return Color.RED;
	case 2:
	    return Color.YELLOW;
	case 3:
	    return Color.ORANGE;
	case 4:
	    return Color.PINK;
	}
	return Color.WHITE;
    }

    @Override
    public String toString() {
	return "offset=" + offset + "/address=" + address + "/values=" + values.toString();
    }

}
package com.wudsn.tools.atariroms.model;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.CRC32;

import com.wudsn.tools.atariroms.DataTypes;
import com.wudsn.tools.base.common.ByteArrayUtility;
import com.wudsn.tools.base.common.HexUtility;
import com.wudsn.tools.base.repository.Attribute;

public final class ROM {
    public static final class Attributes {
	private Attributes() {
	}

	public static final Attribute FPP_CHECK_SUM = new Attribute("fppCheckSum", DataTypes.ROM_FPP_CheckSum);
	public static final Attribute OS1_CHECK_SUM = new Attribute("os1CheckSum", DataTypes.ROM_OS1_CheckSum);
	public static final Attribute OS2_CHECK_SUM = new Attribute("os2CheckSum", DataTypes.ROM_OS2_CheckSum);
    }

    public static final int SIZE = 0x2800;

    public static final int FPP_OFFSET = 0x07fe;
    public static final int OS1_OFFSET1 = 0x0c0f;
    public static final int OS1_OFFSET2 = 0x0c1f;
    public static final int OS2_OFFSET = 0x27f8;

    static Map<String, ROMVersion> crcMap;
    static Map<String, String> md5Map;

    public byte[] content;

    static {
	ROMVersion ATARI_OS_A_NTSC = new ROMVersion("Atari OS", "A", "NTSC", "1979", "Production ROM",
		"CO12499A, CO14599A, CO12399B");
	ROMVersion ATARI_OS_A_PAL = new ROMVersion("Atari OS", "A", "PAL", "1979", "Production ROM",
		"CO15199 , CO15299,  CO12399B");
	ROMVersion ATARI_OS_B_NTSC_LINBUG = new ROMVersion("Atari OS", "B", "NTSC", "1981",
		"Production ROM, LINBUG Version with incorrect checksums", "CO15199,  CO15299,  CO12399B");
	ROMVersion ATARI_OS_B_NTSC = new ROMVersion("Atari OS", "B", "NTSC", "1981", "Production ROM", "Unknown");
	ROMVersion ATARI_OS_B_PAL = new ROMVersion("Atari OS", "B", "PAL", "1981", "Tomasz Krasuski", "Unknown");
	crcMap = new HashMap<String, ROMVersion>();
	crcMap.put("0xc1b3bb02", ATARI_OS_A_NTSC);
	crcMap.put("0x72b3fed4", ATARI_OS_A_PAL);
	crcMap.put("0x0e86d61d", ATARI_OS_B_NTSC_LINBUG);
	crcMap.put("0xf28bc97d", ATARI_OS_B_NTSC);
	crcMap.put("0x0c913dfc", ATARI_OS_B_PAL);

	md5Map = new HashMap<String, String>();

    }

    public ROM(byte[] content) {
	if (content == null) {
	    throw new IllegalArgumentException("Parameter 'content' must not be null.");
	}
	this.content = content;
    }

    public int getSize() {
	return content.length;
    }

    public int getFPP() {
	return getWord(FPP_OFFSET);
    }

    public int getOS1() {
	return getByte(OS1_OFFSET1) + (getByte(OS1_OFFSET2) << 8);
    }

    public int getOS2() {
	return getWord(OS2_OFFSET);
    }

    public String getCRC32() {
	CRC32 crc32 = new CRC32();
	crc32.update(content);
	return "0x" + HexUtility.getLongValueHexString(crc32.getValue(), 8).toLowerCase();
    }

    public String getMD5() {
	MessageDigest md5;
	try {
	    md5 = MessageDigest.getInstance("MD5");
	} catch (NoSuchAlgorithmException ex) {
	    throw new RuntimeException(ex);
	}

	md5.update(content);
	return ByteArrayUtility.toHexString(md5.digest()).toLowerCase();
    }

    private int getWord(int offset) {
	return getByte(offset) + (getByte(offset + 1) << 8);
    }

    public int getByte(int offset) {
	return (content[offset] & 0xff);
    }

    public boolean contentEquals(ROM other) {
	return Arrays.equals(this.content, other.content);
    }

    @Override
    public String toString() {
	String crc32 = getCRC32();
	ROMVersion crcVersion = crcMap.get(crc32);
	String crcVersionString = "Unknown";
	if (crcVersion != null) {
	    crcVersionString = crcVersion.toString();
	}
	return "FPP=" + toHexBytes(getFPP()) + " OS1=" + toHexBytes(getOS1()) + " OS2=" + toHexBytes(getOS2())
		+ " MD5=" + getMD5() + " CRC-32=" + crc32 + " CRC-Version=" + crcVersionString;
    }

    /**
     * Converts a file offset into a ROM address.
     * 
     * @param offset
     *            The file offset or <code>-1</code> if the offset is not valid.
     * @return The ROM address or or <code>-1</code> if the offset is not valid.
     */
    public static int toAddress(int offset) {
	if (offset >= 0 && offset < 0x2800) {
	    return 0xd800 + offset;
	}
	return -1;
    }

    public static String toHexBytes(int value) {
	int low = value & 0xff;
	int high = (value >>> 8) & 0xff;
	return toHexByte(low) + "/" + toHexByte(high);
    }

    private static String toHexByte(int value) {
	value = value & 0xff;
	if (value < 10) {
	    return "$0" + Integer.toHexString(value);
	}
	return "$" + Integer.toHexString(value);
    }

    public ROM createFixture() {
	byte[] newContent;

	newContent = new byte[content.length];
	System.arraycopy(content, 0, newContent, 0, content.length);

	int fpp;
	int os1;
	int os2;

	/* Checksum of FPP ROM. */
	fpp = 0;
	for (int i = 0x0000; i < 0x07fe; i++) {
	    fpp += content[i] & 0xff;
	}
	fpp &= 0xffff;
	newContent[FPP_OFFSET] = (byte) (fpp & 0xff);
	newContent[FPP_OFFSET + 1] = (byte) (fpp >>> 8);

	/* Checksum of first 4K ROM. */
	os1 = 0;
	for (int i = 0x0800; i < 0x0c0f; i++) {
	    os1 += content[i] & 0xff;
	}
	for (int i = 0x0c10; i < 0x0c1f; i++) {
	    os1 += content[i] & 0xff;
	}
	for (int i = 0x0c20; i < 0x1800; i++) {
	    os1 += content[i] & 0xff;
	}
	os1 &= 0xffff;
	newContent[OS1_OFFSET1] = (byte) (os1 & 0xff);
	newContent[OS1_OFFSET2] = (byte) (os1 >>> 8);

	/* Checksum of second 4K ROM. */
	os2 = 0;
	for (int i = 0x1800; i < 0x27f8; i++) {
	    os2 += content[i] & 0xff;
	}
	for (int i = 0x27fa; i < 0x2800; i++) {
	    os2 += content[i] & 0xff;
	}
	os2 &= 0xffff;
	newContent[OS2_OFFSET] = (byte) (os2 & 0xff);
	newContent[OS2_OFFSET + 1] = (byte) (os2 >>> 8);

	return new ROM(newContent);

    }
}

package com.wudsn.tools.atariroms.model;

import java.util.HashMap;
import java.util.Map;

import com.wudsn.tools.atariroms.DataTypes;
import com.wudsn.tools.base.repository.Attribute;

public final class ROMVersion {

    public static final ROMVersion UNKNOWN;
    public static final ROMVersion ATARI_OS_A_NTSC;
    public static final ROMVersion ATARI_OS_A_PAL;
    public static final ROMVersion ATARI_OS_B_NTSC_LINBUG;
    public static final ROMVersion ATARI_OS_B_NTSC;
    public static final ROMVersion ATARI_OS_B_PAL;

    private static Map<String, ROMVersion> crc32Map;

    static {
	UNKNOWN = new ROMVersion("Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown");

	ATARI_OS_A_NTSC = new ROMVersion("Atari OS", "OS-A", "NTSC", "1979", "Production ROM",
		"CO12499A (low-OS, NTSC), CO14599A (high-OS NTSC), CO12399B (FPP)");
	ATARI_OS_A_PAL = new ROMVersion("Atari OS", "OS-A", "PAL", "1979", "Production ROM",
		"CO15199  (low-OS, NTSC), CO15299 (high-OS NTSC), CO12399B (FPP)");
	ATARI_OS_B_NTSC_LINBUG = new ROMVersion("Atari OS", "OS-B", "NTSC", "1981",
		"Production ROM, LINBUG Version with incorrect checksums",
		"C012499B (low-OS, NTSC), CO14599B (high-OS, NTSC), CO12399B (FPP)");
	ATARI_OS_B_NTSC = new ROMVersion("Atari OS", "OS-B", "NTSC", "1981", "Production ROM", "Unknown");
	ATARI_OS_B_PAL = new ROMVersion("Atari OS", "OS-B", "PAL", "1981", "Tomasz Krasuski", "Unknown");

	crc32Map = new HashMap<String, ROMVersion>();
	crc32Map.put("0xc1b3bb02", ATARI_OS_A_NTSC);
	crc32Map.put("0x72b3fed4", ATARI_OS_A_PAL);
	crc32Map.put("0x0e86d61d", ATARI_OS_B_NTSC_LINBUG);
	crc32Map.put("0xf28bc97d", ATARI_OS_B_NTSC);
	crc32Map.put("0x0c913dfc", ATARI_OS_B_PAL);
    }

    public static final class Attributes {
	private Attributes() {
	}

	public static final Attribute TYPE = new Attribute("type", DataTypes.Version_Type);
	public static final Attribute REVISION = new Attribute("revision", DataTypes.Version_Revision);
	public static final Attribute NORM = new Attribute("norm", DataTypes.Version_Norm);
	public static final Attribute YEAR = new Attribute("year", DataTypes.Version_Year);
	public static final Attribute COMMENT = new Attribute("comment", DataTypes.Version_Comment);
	public static final Attribute PARTS = new Attribute("parts", DataTypes.Version_Parts);
    }

    private final String type;
    private final String revision;
    private final String norm;
    private final String year;
    private final String comment;
    private final String parts;

    public ROMVersion(String type, String revision, String norm, String year, String comment, String parts) {
	if (type == null) {
	    throw new IllegalArgumentException("Parameter 'type' must not be null.");
	}
	if (revision == null) {
	    throw new IllegalArgumentException("Parameter 'revision' must not be null.");
	}
	if (norm == null) {
	    throw new IllegalArgumentException("Parameter 'norm' must not be null.");
	}
	if (year == null) {
	    throw new IllegalArgumentException("Parameter 'year' must not be null.");
	}
	if (comment == null) {
	    throw new IllegalArgumentException("Parameter 'comment' must not be null.");
	}
	if (parts == null) {
	    throw new IllegalArgumentException("Parameter 'parts' must not be null.");
	}
	this.type = type;
	this.revision = revision;
	this.norm = norm;
	this.year = year;
	this.comment = comment;
	this.parts = parts;
    }

    public String getType() {
	return type;
    }

    public String getRevision() {
	return revision;
    }

    public String getNorm() {
	return norm;
    }

    public String getYear() {
	return year;
    }

    public String getComment() {
	return comment;
    }

    public String getParts() {
	return parts;
    }

    @Override
    public String toString() {
	return type + "/" + revision + "/" + norm + "/" + year + "/" + comment + "/" + parts;
    }

    public static ROMVersion getByCRC32(String crc32) {
	if (crc32 == null) {
	    throw new IllegalArgumentException("Parameter 'crc32' must not be null.");
	}
	return crc32Map.get(crc32);
    }
}
package com.wudsn.tools.atariroms.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.wudsn.tools.base.gui.AttributeTableModel;

public final class Workbook {

    private List<WorkbookEntry> entriesList;
    private List<WorkbookEntry> unmodifiableEntriesList;

    private int resolvedFilesCount;

    private AttributeTableModel entriesModel;

    public Workbook() {
	entriesList = new ArrayList<WorkbookEntry>();
	unmodifiableEntriesList = Collections.unmodifiableList(entriesList);
    }

    public void setEntriesTableModel(AttributeTableModel entriesModel) {
	this.entriesModel = entriesModel;
    }
    
    public void clear() {
	entriesList.clear();
	resolvedFilesCount = 0;
	if (entriesModel != null) {
	    entriesModel.fireTableDataChanged();
	}
    }

    public void addEntry(WorkbookEntry entry) {
	if (entry == null) {
	    throw new IllegalArgumentException("Parameter 'entry' must not be null.");
	}
	entriesList.add(entry);
	if (entriesModel != null) {
	    entriesModel.fireTableDataChanged();
	}
    }

    public WorkbookEntry getEntry(int row) {
	return entriesList.get(row);
    }

    public int getEntryCount() {
	return entriesList.size();
    }

    public List<WorkbookEntry> getUnmodifiableEntriesList() {
	return unmodifiableEntriesList;
    }

    public void setResolvedFilesCount(int resolvedFilesCount) {
	this.resolvedFilesCount = resolvedFilesCount;
    }

    public int getResolvedFilesCount() {
	return resolvedFilesCount;
    }
}

package com.wudsn.tools.atariroms.model;

import java.io.File;

import com.wudsn.tools.atariroms.DataTypes;
import com.wudsn.tools.base.repository.Attribute;

public final class WorkbookEntry {
    public static final class Attributes {
	private Attributes() {
	}

	public static final Attribute ID = new Attribute("id", DataTypes.WorkbookEntry_Id);
	public static final Attribute FOLDER_PATH = new Attribute("folderPath", DataTypes.WorkbookEntry_FolderPath);
	public static final Attribute FILE_NAME = new Attribute("fileName", DataTypes.WorkbookEntry_FileName);
	public static final Attribute MD5 = new Attribute("md5", DataTypes.WorkbookEntry_MD5);
	public static final Attribute CRC32 = new Attribute("crc32", DataTypes.WorkbookEntry_CRC32);
	public static final Attribute MESSAGE = new Attribute("message", DataTypes.WorkbookEntry_Message);

    }

    private File file;
    private String folderPath;
    private String fileName;
    private String md5;
    private String crc32;
    private ROM rom;
    private ROMVersion romVersion;
    private String message;

    public WorkbookEntry(File file) {
	if (file == null) {
	    throw new IllegalArgumentException("Parameter 'file' must not be null.");
	}
	this.file = file;
	this.folderPath = file.getParentFile().getPath();
	this.fileName = file.getName();
	md5 = "";
	crc32 = "";
	rom = null;
	romVersion = null;
	message = "";
    }

    public File getFile() {
	return file;
    }

    public String getFolderPath() {
	return folderPath;
    }

    public void setFileName(String fileName) {
	if (fileName == null) {
	    throw new IllegalArgumentException("Parameter 'fileName' must not be null.");
	}
 	this.fileName= fileName;
     }
    
    public String getFileName() {
	return fileName;
    }

    public String getMD5() {
	return md5;
    }

    public void setMD5(String md5) {
	if (md5 == null) {
	    throw new IllegalArgumentException("Parameter 'md5' must not be null.");
	}
	this.md5 = md5;
    }

    public String getCRC32() {
	return crc32;
    }

    public void setCRC32(String crc32) {
	if (crc32 == null) {
	    throw new IllegalArgumentException("Parameter 'crc32' must not be null.");
	}
	this.crc32 = crc32;
    }

    public void setROM(ROM rom) {
	if (rom == null) {
	    throw new IllegalArgumentException("Parameter 'rom' must not be null.");
	}
	this.rom = rom;

	setMD5(rom.getMD5());
	setCRC32(rom.getCRC32());
	setROMVersion(ROMVersion.getByCRC32(rom.getCRC32()));
    }

    public ROM getROM() {
	return rom;
    }

    public void setROMVersion(ROMVersion romVersion) {
	if (romVersion == null) {
	    throw new IllegalArgumentException("Parameter 'romVersion' must not be null.");
	}
	this.romVersion = romVersion;
    }

    public ROMVersion getROMVersion() {
	return romVersion;
    }

    public void setMessage(String message) {
	if (message == null) {
	    throw new IllegalArgumentException("Parameter 'message' must not be null.");
	}
	this.message = message;
    }

    public String getMessage() {
	return message;
    }

}

package com.wudsn.tools.atariroms.model;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.wudsn.tools.atariroms.Messages;
import com.wudsn.tools.base.common.CoreException;
import com.wudsn.tools.base.common.FileUtility;
import com.wudsn.tools.base.common.HexUtility;
import com.wudsn.tools.base.common.MessageQueue;
import com.wudsn.tools.base.common.TextUtility;

/**
 * <pre>
 * 
 * FROM: http://atariage.com/forums/topic/201133-os-source-code-all-revisions/#entry2667627
 * Recreated source codes of all known official revisions of the Atari OS by Kr0tki (Tomasz Krasuski).
 * 
 * FROM: http://mixinc.net/atari/books/XL-OS_Full_Searchable.pdf
 * 
 * REVLEVEL DETERMINATION
 * 
 * To allow program products to determine which Atari Home Computer and Operating System Revision level it is operating with,
 * the follOwing tests are recommended,
 * If location $FCD8 = $A2, then product is an A400/ A800 wherein:
 *   If location $FFF8 = $DD and $FFF9 = $57 then OS is NTSC rev A.
 *   If location $FFF8 = $D6 and $FFF9 = $57 then OS is PAL  rev A.
 *   If location $FFF8 = $F3 and $FFF9 = $E6 then OS is NTSC rev B.
 *   If location $FFF8 = $22 and $FFF9 = $58 then OS is PAL  rev B.
 *   Otherwise, it is some future A400/ A800 OS.
 * If location $FCD8 not $A2, then product is a 1200XL or other future home computer product wherein:
 *   If location SFFFl = $Ol. then OS is 1200XL and location $FFF7 will be the internal rev number for the 1200XL OS.
 *   Otherwise, location $FFFl = product code for future Atari Home Computer product and location SFFF7 contains OS rev level for this product.
 * 
 * The check sums are screwed  up with the NTSC rev. B ROM as Atari didn't write the checksums into the ROM before manufacturing.
 * The production ROMs are therefore left with zeroes at $E40F/$E41F, and $FFF8-$FFF9 contains copy of the PIRQ vector, normally stored at $FFFE-$FFFF.
 * The latter is apparently a leftover from debugging - search for "SET UP RAM VECTORS FOR LINBUG VERSION" in the rev. B sources for some interesting piece of code.
 * 
 * 
 * FROM: http://www.ataripreservation.org/websites/freddy.offenga/osroms.txt
 * FROM: http://www.ataripreservation.org/websites/freddy.offenga/osromv36.zip
 * 
 * Rev. TV    Date        CRC-32      Part Nr(s)
 * ~~~~ ~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * A    NTSC  1979        0xc1b3bb02  CO12499A, CO14599A, CO12399B
 * A    PAL   1979        0x72b3fed4  CO15199,  CO15299,  CO12399B
 * B    NTSC  1981        0x0e86d61d  CO12499B, CO14599B, CO12399B
 * B    PAL   (*)         (*)         (*)
 * </pre>
 **/

public final class WorkbookLogic {
    /**
     * Analyze a list of files or folders.
     * 
     * @param workbook
     *            The workbook to contain the result of the analysis.
     * @param filesList
     *            The list of file or folders to be analyzed. May be empty, not
     *            <code>null</code>.
     */
    public void checkFiles(Workbook workbook, List<File> filesList) {
	if (filesList == null) {
	    throw new IllegalArgumentException("Parameter 'filesList' must not be null.");
	}

	List<File> resolvedFilesList = new ArrayList<File>();
	for (File file : filesList) {
	    resolveFiles(resolvedFilesList, file);
	}

	Collections.sort(resolvedFilesList);
	workbook.clear();
	workbook.setResolvedFilesCount(resolvedFilesList.size());

	for (File file : resolvedFilesList) {

	    checkFile(workbook, file);
	}
    }

    /**
     * Resolve a file or folder into the list of files.
     * 
     * @param file
     *            The current file or folder to be resolved, not
     *            <code>null</code>.
     * @param filesList
     *            The modifiable list of files that have been resolved so far,
     *            not <code>null</code>.
     */
    private void resolveFiles(List<File> filesList, File file) {

	if (filesList == null) {
	    throw new IllegalArgumentException("Parameter 'filesList' must not be null.");
	}
	if (filesList.contains(file)) {
	    return;
	}
	if (file == null) {
	    throw new IllegalArgumentException("Parameter 'file' must not be null.");
	}
	if (file.isDirectory()) {
	    File[] innerFiles = file.listFiles();
	    if (innerFiles != null) {
		for (File innerFile : innerFiles) {
		    resolveFiles(filesList, innerFile);
		}
	    }
	} else {
	    filesList.add(file.getAbsoluteFile());
	}
    }

    /**
     * Read current and computes actual checksums of an Atari 400/800 OS ROM
     * image
     * 
     * @param workbook
     *            The workbook, not <code>null</code>.
     * @param file
     *            The file, not <code>null</code>.
     */
    private void checkFile(Workbook workbook, File file) {
	if (workbook == null) {
	    throw new IllegalArgumentException("Parameter 'workbook' must not be null.");
	}
	if (file == null) {
	    throw new IllegalArgumentException("Parameter 'file' must not be null.");
	}

	WorkbookEntry entry = new WorkbookEntry(file);
	workbook.addEntry(entry);
	byte[] content;
	try {
	    content = FileUtility.readBytes(file, ROM.SIZE, true);
	} catch (CoreException ex) {
	    entry.setMessage(ex.getMessage());
	    return;
	}
	if (content.length != ROM.SIZE) {
	    entry.setMessage("Length of content is not equal to " + ROM.SIZE + ".");
	    return;
	}

	ROM rom = new ROM(content);
	entry.setROM(rom);

	ROM fixture = rom.createFixture();
	if (!fixture.contentEquals(rom)) {
	    entry.setMessage("Incorrect checksums");

	    entry = new WorkbookEntry(file);
	    entry.setFileName(entry.getFileName() + " (fixed)");
	    workbook.addEntry(entry);
	    entry.setROM(fixture);
	    entry.setMessage("With corrected checksums");

	}
    }

    public Comparison compareEntries(Workbook workbook, List<WorkbookEntry> selectedEntries, MessageQueue messageQueue) {
	if (workbook == null) {
	    throw new IllegalArgumentException("Parameter 'workbook' must not be null.");
	}
	if (selectedEntries == null) {
	    throw new IllegalArgumentException("Parameter 'selectedEntries' must not be null.");
	}
	if (messageQueue == null) {
	    throw new IllegalArgumentException("Parameter 'messageQueue' must not be null.");
	}

	if (selectedEntries.size() < Comparison.MIN) {
	    // ERROR: Select at least {0} entries for comparison.
	    messageQueue.sendMessage(workbook, null, Messages.E103, TextUtility.formatAsDecimal(Comparison.MIN));
	    return null;
	}

	List<WorkbookEntry> validEntries = new ArrayList<WorkbookEntry>();
	for (int i = 0; i < selectedEntries.size(); i++) {
	    WorkbookEntry entry = selectedEntries.get(i);
	    if (entry.getROM() != null) {
		validEntries.add(entry);
	    }
	}

	if (validEntries.size() < Comparison.MIN) {
	    // ERROR: Select at least {0} entries with equal file size for
	    // comparison.
	    messageQueue.sendMessage(workbook, null, Messages.E104, TextUtility.formatAsDecimal(Comparison.MIN));
	    return null;
	}
	if (validEntries.size() > Comparison.MAX) {
	    // ERROR: Select at most {0} entries with equal file size for
	    // comparison.
	    messageQueue.sendMessage(workbook, null, Messages.E105, TextUtility.formatAsDecimal(Comparison.MAX));
	    return null;
	}
	Comparison result = new Comparison(validEntries);

	Map<Integer, Integer> colorMap = new HashMap<Integer, Integer>();
	for (int offset = 0; offset < ROM.SIZE; offset++) {
	    boolean same = true;
	    int i = 0;
	    int colorIndex = 0;
	    int b0 = validEntries.get(i).getROM().getByte(offset);
	    colorMap.clear();
	    colorMap.put(Integer.valueOf(b0), Integer.valueOf(colorIndex));
	    for (i = 1; i < validEntries.size(); i++) {
		int b1 = validEntries.get(i).getROM().getByte(offset);
		if (b1 != b0) {
		    same = false;
		}
		Integer key = Integer.valueOf(b1);
		Integer colorValue = colorMap.get(key);
		if (colorValue == null) {
		    colorIndex++;
		}
		colorMap.put(key, Integer.valueOf(colorIndex));
	    }
	    if (!same) {

		ComparisonEntry entry = result.createEntry(offset, ROM.toAddress(offset));
		for (i = 0; i < validEntries.size(); i++) {
		    int value = validEntries.get(i).getROM().getByte(offset);
		    entry.setValue(i, "$" + HexUtility.getByteValueHexString(value),
			    colorMap.get(Integer.valueOf(value)).intValue());
		}
		result.addEntry(entry);
	    }
	}
	return result;
    }
}

package com.wudsn.tools.atariroms;

import java.util.Map;
import java.util.TreeMap;

import com.wudsn.tools.base.gui.AttributeTablePreferences;

public final class Preferences implements AttributeTablePreferences {

    private Map<String, String> layoutProperties;

    public Preferences() {
	layoutProperties = new TreeMap<String, String>();
    }

    @Override
    public Map<String, String> getLayoutProperties() {
	return layoutProperties;
    }

}

package com.wudsn.tools.atariroms;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.WindowConstants;

import com.wudsn.tools.atariroms.model.Comparison;
import com.wudsn.tools.atariroms.model.Workbook;
import com.wudsn.tools.atariroms.model.WorkbookEntry;
import com.wudsn.tools.atariroms.model.WorkbookLogic;
import com.wudsn.tools.atariroms.ui.ComparisonDialog;
import com.wudsn.tools.atariroms.ui.MainMenu;
import com.wudsn.tools.atariroms.ui.WorkbookEntriesPanel;
import com.wudsn.tools.base.common.Application;
import com.wudsn.tools.base.common.Log;
import com.wudsn.tools.base.common.MessageQueue;
import com.wudsn.tools.base.common.TextUtility;
import com.wudsn.tools.base.gui.AttributeTable;
import com.wudsn.tools.base.gui.FileDrop;
import com.wudsn.tools.base.gui.HelpDialog;
import com.wudsn.tools.base.gui.FileDrop.Listener;
import com.wudsn.tools.base.gui.StatusBar;
import com.wudsn.tools.base.gui.UIManager;

/**
 * Command line interface.
 * 
 * @author Peter Dell
 */

public final class ROMChecker implements ActionListener, Listener {

    public final class Commands {
	public static final String EXIT = "exit";
	public static final String COMPARE_ENTRIES = "compareEntries";
	public static final String HELP_CONTENTS = "helpContents";
    }

    // Static instance
    static ROMChecker instance;

    // Message queue
    private MessageQueue messageQueue;

    // AttributeTablePreferences
    private Preferences preferences;

    private WorkbookLogic workbookLogic;
    private Workbook workbook;

    private JFrame mainWindowFrame;
    private MainMenu mainMenu;
    private WorkbookEntriesPanel entriesPanel;
    private StatusBar statusBar;

    // Dialogs
    private HelpDialog helpDialog;

    @SuppressWarnings("unused")
    private FileDrop fileDrop;

    public static void main(final String[] args) {
	if (args == null) {
	    throw new IllegalArgumentException("Parameter 'args' must not be null.");
	}

	// Use the event dispatch thread for Swing components
	EventQueue.invokeLater(new Runnable() {

	    @Override
	    public void run() {

		Application.createInstance("http://www.wudsn.com/productions/atari8000/atarirom/atariroms.zip",
			"AtariROMs.jar", "com/wudsn/tools/atariroms/AtariROMs.version");
		instance = new ROMChecker();
		instance.run(args);
	    }
	});
    }

    ROMChecker() {
    }

    void run(String[] args) {
	if (args == null) {
	    throw new IllegalArgumentException("Parameter 'args' must not be null.");
	}

	UIManager.init();

	messageQueue = new MessageQueue();
	preferences = new Preferences();
	workbookLogic = new WorkbookLogic();
	workbook = new Workbook();

	createUI();

	// INFO: Welcome to the Atari ROM Checker Version {0} by JAC!. Drop your
	// folders or files on the window.
	messageQueue.sendMessage(null, null, Messages.I100, Application.getInstance().getLocalVersion());
	dataToUI();

	List<File> filesList = new ArrayList<File>();
	for (String arg : args) {
	    filesList.add(new File(arg));
	}
	if (!filesList.isEmpty()) {
	    performFilesDropped(filesList);
	}
    }

    private void createUI() {
	mainWindowFrame = new JFrame(Texts.MainWindow_Title);
	mainWindowFrame.setSize(800, 600);
	mainWindowFrame.setLocationRelativeTo(null);

	mainWindowFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
	mainWindowFrame.addWindowListener(new WindowAdapter() {
	    @Override
	    public void windowClosing(WindowEvent e) {
		actionPerformed(new ActionEvent(this, 0, Commands.EXIT));
	    }
	});

	mainWindowFrame.setLayout(new BorderLayout());

	mainMenu = new MainMenu(this, preferences);
	mainWindowFrame.add(mainMenu.menuBar, BorderLayout.NORTH);
	entriesPanel = new WorkbookEntriesPanel(preferences, workbook);
	mainWindowFrame.add(entriesPanel, BorderLayout.CENTER);
	statusBar = new StatusBar();
	messageQueue.setMessageQueueRenderer(statusBar);
	mainWindowFrame.add(statusBar.getComponent(), BorderLayout.SOUTH);

	mainWindowFrame.setVisible(true);
	fileDrop = new FileDrop(mainWindowFrame, true, this);
    }

    public void dataFromUI() {

    }

    public void dataToUI() {
	statusBar.displayMessageQueue(messageQueue);
    }

    public void performFilesDropped(List<File> filesList) {
	if (filesList == null) {
	    throw new IllegalArgumentException("Parameter 'filesList' must not be null.");
	}
	dataFromUI();
	messageQueue.clear();
	try {
	    workbookLogic.checkFiles(workbook, filesList);
	    // INFO: {0} files found and analyzed.
	    messageQueue.sendMessage(null, null, Messages.I102,
		    TextUtility.formatAsDecimal(workbook.getResolvedFilesCount()));

	} catch (RuntimeException ex) {
	    // ERROR: Exception occurred: {0}
	    messageQueue.sendMessage(null, null, Messages.E101, ex.getMessage());
	    Log.logError("Exception while checking files.", null, ex);
	}
	dataToUI();
    }

    @Override
    public boolean isDropAllowed() {
	return true;
    }

    @Override
    public void filesDropped(File[] files) {
	if (files == null) {
	    throw new IllegalArgumentException("Parameter 'files' must not be null.");
	}
	performFilesDropped(Arrays.asList(files));
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
	if (actionEvent == null) {
	    throw new IllegalArgumentException("Parameter 'actionEvent' must not be null.");
	}
	dataFromUI();

	String command = actionEvent.getActionCommand();
	messageQueue.clear();
	statusBar.displayMessageQueue(messageQueue);

	if (command.equals(Commands.EXIT)) {
	    performExit();
	} else if (command.equals(Commands.COMPARE_ENTRIES) && mainMenu.compareEntriesMenuItem.isEnabled()) {
	    performCompareEntries();
	} else if (command.equals(Commands.HELP_CONTENTS)) {
	    performHelpDialog();
	}
	dataToUI();
    }

    private void performExit() {
	System.exit(0);
	return;
    }

    private void performHelpDialog() {
	if (helpDialog == null) {
	    helpDialog = new HelpDialog(mainWindowFrame, "help/ROMChecker.html", 640, 320);
	}

	helpDialog.show();
    }

    private void performCompareEntries() {
	AttributeTable entriesTable = entriesPanel.getTable();
	int[] selectedViewRowIndexes = entriesTable.getSelectedRows();
	int[] selectedModelRowIndexes = new int[selectedViewRowIndexes.length];
	for (int i = 0; i < selectedViewRowIndexes.length; i++) {
	    selectedModelRowIndexes[i] = entriesTable.convertRowIndexToModel(selectedViewRowIndexes[i]);
	}
	List<WorkbookEntry> entries = workbook.getUnmodifiableEntriesList();
	List<WorkbookEntry> selectedEntries = new ArrayList<WorkbookEntry>();
	for (int i = 0; i < selectedModelRowIndexes.length; i++) {
	    selectedEntries.add(entries.get(selectedModelRowIndexes[i]));
	}
	Comparison comparison = workbookLogic.compareEntries(workbook, selectedEntries, messageQueue);
	if (comparison != null) {
	    ComparisonDialog dialog = new ComparisonDialog(mainWindowFrame, preferences, comparison);
	    dialog.showModal(messageQueue);
	}

    }
}

package com.wudsn.tools.atariroms;

import com.wudsn.tools.base.repository.NLS;

/**
 * Text repository.
 * 
 * @author Peter Dell
 */
public final class Texts extends NLS {

    // UI Texts: Main Window
    public static String MainWindow_Title;

    // UI Texts: Dialogs
    public static String ComparisonDialog_Title;

    static {
	initializeClass(Texts.class, null);
    }
}
# UI Texts: Main window
MainWindow_Title=Atari ROM Checker by JAC!

# UI Texts: Dialogs
ComparisonDialog_Title=Comparison Result

package com.wudsn.tools.atariroms.ui;

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SpringLayout;

import com.wudsn.tools.atariroms.DataTypes;
import com.wudsn.tools.atariroms.Preferences;
import com.wudsn.tools.atariroms.Texts;
import com.wudsn.tools.atariroms.model.Comparison;
import com.wudsn.tools.base.common.MessageQueue;
import com.wudsn.tools.base.gui.IntegerField;
import com.wudsn.tools.base.gui.ModalDialog;
import com.wudsn.tools.base.gui.SpringUtilities;

@SuppressWarnings("serial")
public final class ComparisonDialog extends ModalDialog {

    private IntegerField entriesCountField;
    private ComparisonEntryPanel entryPanel;

    private Comparison comparison;

    public ComparisonDialog(JFrame parent, Preferences preferences, Comparison comparison) {
	super(parent, Texts.ComparisonDialog_Title);

	if (comparison == null) {
	    throw new IllegalArgumentException("Parameter 'comparison' must not be null.");
	}
	this.comparison = comparison;

	JPanel topPanel = new JPanel(new SpringLayout());
	entriesCountField = SpringUtilities.createIntegerField(topPanel, DataTypes.Comparison_EntriesCount);
	entriesCountField.setEditable(false);
	SpringUtilities.makeCompactGrid(topPanel, 1, 2, // rows, cols
		6, 6, // initX, initY
		6, 6); // xPad, yPad

	entryPanel = new ComparisonEntryPanel(preferences, comparison);
	fieldsPane.setLayout(new BorderLayout());
	fieldsPane.add(topPanel, BorderLayout.NORTH);
	fieldsPane.add(entryPanel, BorderLayout.CENTER);
    }

    public void showModal(MessageQueue messageQueue) {

	if (messageQueue == null) {
	    throw new IllegalArgumentException("Parameter 'messageQueue' must not be null.");
	}


	showModal(entryPanel.getTable());
    }

    @Override
    protected void dataToUi() {
	entriesCountField.setValue(comparison.getEntryCount());
    }
}

package com.wudsn.tools.atariroms.ui;

import java.awt.Component;
import java.awt.Font;

import javax.swing.CellEditor;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableRowSorter;

import com.wudsn.tools.atariroms.Preferences;
import com.wudsn.tools.atariroms.model.Comparison;
import com.wudsn.tools.atariroms.model.ComparisonEntry;
import com.wudsn.tools.atariroms.model.ComparisonEntry.Attributes;
import com.wudsn.tools.atariroms.model.WorkbookEntry;
import com.wudsn.tools.base.common.Log;
import com.wudsn.tools.base.common.TextUtility;
import com.wudsn.tools.base.gui.AttributeTable;
import com.wudsn.tools.base.gui.AttributeTableModel;
import com.wudsn.tools.base.repository.Attribute;
import com.wudsn.tools.base.repository.DataType;

@SuppressWarnings("serial")
public final class ComparisonEntryPanel extends JScrollPane {

    public final static class TableModel extends AttributeTableModel {

	public final class Columns {
	    public final static int ID = 0;
	    public final static int OFFSET = 1;
	    public final static int ADDRESS = 2;
	    public final static int VALUE = 3;

	}

	private Comparison comparison;
	private AttributeTable table;

	public TableModel(final Comparison comparison) {
	    if (comparison == null) {
		throw new IllegalArgumentException("Parameter 'comparison' must not be null.");
	    }

	    this.comparison = comparison;

	    final Font monoSpacedFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
	    DefaultTableCellRenderer cellRenderer = new DefaultTableCellRenderer() {

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
			boolean hasFocus, int row, int column) {
		    Component result = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row,
			    column);
		    int modelRowIndex = table.convertRowIndexToModel(row);
		    int modelColumnIndex = table.convertColumnIndexToModel(column);
		    if (modelColumnIndex >= Columns.VALUE) {
			setBackground(comparison.getEntry(modelRowIndex).getColor(modelColumnIndex-Columns.VALUE));
		    } else {
			setBackground(table.getBackground());
		    }
		    setFont(monoSpacedFont);

		    return result;
		}

		@Override
		protected void setValue(Object value) {
		    super.setValue(value);
		}
	    };

	    addColumn(Attributes.ID, Column.VISIBLE);
	    addColumn(Attributes.OFFSET, Column.VISIBLE, cellRenderer, null);
	    addColumn(Attributes.ADDRESS, Column.VISIBLE, cellRenderer, null);
	    for (int i = 0; i < comparison.getWorkbookEntryCount(); i++) {
		DataType dataType = new DataType(String.class);
		WorkbookEntry workbookEntry = comparison.getWorkbookEntry(i);
		dataType.setTexts(workbookEntry.getFileName(), workbookEntry.getFolderPath());
		Attribute attribute = new Attribute("value" + i, dataType);
		addColumn(attribute, Column.VISIBLE, cellRenderer, null);
	    }
	}

	public void setTable(AttributeTable table) {
	    if (table == null) {
		throw new IllegalArgumentException("Parameter 'table' must not be null.");
	    }
	    this.table = table;
	}

	@Override
	public Object getValueAt(int row, int column) {
	    ComparisonEntry entry = comparison.getEntry(row);

	    switch (column) {
	    case Columns.ID:
		return Integer.valueOf(table.convertRowIndexToView(row) + 1);
	    case Columns.OFFSET:
		return entry.getOffset() >= 0 ? TextUtility.formatAsHexaDecimal(entry.getOffset(),
			comparison.getMaximumOffset()) : "";
	    case Columns.ADDRESS:
		return entry.getAddress() >= 0 ? TextUtility.formatAsHexaDecimal(entry.getAddress(),
			comparison.getMaximumAddress()) : "";
	    default:
		return entry.getValue(column - Columns.VALUE);
	    }

	}

	@Override
	public boolean isCellEditable(int row, int column) {
	    return false;
	}

	@Override
	public int getRowCount() {
	    return comparison.getEntryCount();
	}
    }

    private Comparison comparison;
    private TableModel tableModel;
    private AttributeTable entriesTable;

    public ComparisonEntryPanel(Preferences preferences, final Comparison comparison) {
	if (comparison == null) {
	    throw new IllegalArgumentException("Parameter 'comparison' must not be null.");
	}
	this.comparison = comparison;

	tableModel = new TableModel(comparison);
	comparison.setEntriesTableModel(tableModel);
	entriesTable = new AttributeTable(tableModel, preferences, "comparisonEntriesTable");
	tableModel.setTable(entriesTable);

	// Set the column sorting functionality on
	@SuppressWarnings("unchecked")
	TableRowSorter<AttributeTableModel> sorter = (TableRowSorter<AttributeTableModel>) entriesTable.getRowSorter();
	sorter.setSortable(0, false);

	setViewportView(entriesTable);
    }

    /**
     * Sets the selected comparison entry, if it exists.
     * 
     * @param comparisonEntry
     *            The comparison entry, not <code>null</code>.
     * 
     * @param attribute
     *            The attribute, not <code>null</code>.
     */
    public void setSelectedComparisonEntry(ComparisonEntry comparisonEntry, Attribute attribute) {
	if (comparisonEntry == null) {
	    throw new IllegalArgumentException("Parameter 'comparisonEntry' must not be null.");
	}
	int modelRowIndex = comparison.getUnmodifiableEntriesList().indexOf(comparisonEntry);
	entriesTable.selectCell(modelRowIndex, attribute);
    }

    /**
     * Gets the entries table.
     * 
     * @return The entries table, not <code>null</code>.
     */
    public AttributeTable getTable() {
	return entriesTable;
    }

    /**
     * Update columns and value helps which depend of the comparison root.
     */
    public void dataToUI() {
    }

    /**
     * Stop editing and transfer cell contents to model.
     */
    public void dataFromUI() {
	CellEditor cellEditor = entriesTable.getCellEditor();
	if (cellEditor != null) {
	    try {
		cellEditor.stopCellEditing();
	    } catch (RuntimeException ignore) {
		Log.logError("Inconsistent comparison entries table", null, ignore);
	    }
	}
    }

}
package com.wudsn.tools.atariroms.ui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;

import com.wudsn.tools.atariroms.Actions;
import com.wudsn.tools.atariroms.Preferences;
import com.wudsn.tools.atariroms.ROMChecker.Commands;
import com.wudsn.tools.base.gui.ElementFactory;

/**
 * Main menu component. See
 * http://msdn.microsoft.com/en-us/library/windows/desktop/aa511502.aspx for
 * Microsoft Standards on menus.
 * 
 * @author Peter Dell
 */
public final class MainMenu {
    public final JMenuBar menuBar;

    public final JMenuItem exitMenuItem;

    public final JMenuItem compareEntriesMenuItem;

    public MainMenu(final ActionListener actionListener, final Preferences preferences) {
	if (actionListener == null) {
	    throw new IllegalArgumentException("Parameter 'actionListener' must not be null.");
	}
	if (preferences == null) {
	    throw new IllegalArgumentException("Parameter 'preferences' must not be null.");
	}
	menuBar = new JMenuBar();

	// File menu
	JMenu menu = ElementFactory.createMenu(com.wudsn.tools.base.Actions.MainMenu_File);

	menuBar.add(menu);

	exitMenuItem = ElementFactory.createMenuItem(Actions.MainMenu_File_Exit, Commands.EXIT);
	exitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, ActionEvent.ALT_MASK));
	exitMenuItem.addActionListener(actionListener);
	menu.add(exitMenuItem);

	// Edit menu
	menu = ElementFactory.createMenu(com.wudsn.tools.base.Actions.MainMenu_Edit);
	menuBar.add(menu);

	compareEntriesMenuItem = ElementFactory.createMenuItem(Actions.MainMenu_Edit_CompareEntries,
		Commands.COMPARE_ENTRIES);
	compareEntriesMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, ActionEvent.ALT_MASK));
	compareEntriesMenuItem.addActionListener(actionListener);
	menu.add(compareEntriesMenuItem);
	
	// Edit menu
	// Help menu
	JMenuItem helpMenuItem;
	menu = ElementFactory.createMenu(com.wudsn.tools.base.Actions.MainMenu_Help);
	menuBar.add(menu);

	helpMenuItem = ElementFactory.createMenuItem(com.wudsn.tools.base.Actions.MainMenu_Help_HelpContent,
		Commands.HELP_CONTENTS);
	helpMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
	helpMenuItem.addActionListener(actionListener);
	menu.add(helpMenuItem);

    }

}

package com.wudsn.tools.atariroms.ui;

import java.awt.Font;

import javax.swing.CellEditor;
import javax.swing.JScrollPane;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableRowSorter;

import com.wudsn.tools.atariroms.Preferences;
import com.wudsn.tools.atariroms.model.ROM;
import com.wudsn.tools.atariroms.model.ROMVersion;
import com.wudsn.tools.atariroms.model.Workbook;
import com.wudsn.tools.atariroms.model.WorkbookEntry;
import com.wudsn.tools.atariroms.model.WorkbookEntry.Attributes;
import com.wudsn.tools.base.common.Log;
import com.wudsn.tools.base.gui.AttributeTable;
import com.wudsn.tools.base.gui.AttributeTableModel;
import com.wudsn.tools.base.repository.Attribute;

@SuppressWarnings("serial")
public final class WorkbookEntriesPanel extends JScrollPane {

    public final static class TableModel extends AttributeTableModel {

	public final class Columns {
	    public final static int ID = 0;
	    public final static int FOLDER_PATH = 1;
	    public final static int FILE_NAME = 2;
	    public final static int MD5 = 3;
	    public final static int CRC32 = 4;

	    public final static int FPP_CHECK_SUM = 5;
	    public final static int OS1_CHECK_SUM = 6;
	    public final static int OS2_CHECK_SUM = 7;

	    public final static int TYPE = 8;
	    public final static int REVISION = 9;
	    public final static int NORM = 10;
	    public final static int YEAR = 11;
	    public final static int COMMENT = 12;
	    public final static int PARTS = 13;
	    public final static int MESSAGE = 14;

	}

	private Workbook workbook;
	private AttributeTable table;

	public TableModel(final Workbook workbook) {
	    if (workbook == null) {
		throw new IllegalArgumentException("Parameter 'workbook' must not be null.");
	    }

	    this.workbook = workbook;

	    final Font monoSpacedFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
	    DefaultTableCellRenderer cellRenderer = new DefaultTableCellRenderer() {
		@Override
		protected void setValue(Object value) {
		    setFont(monoSpacedFont);
		    super.setValue(value);
		}
	    };

	    addColumn(Attributes.ID, Column.VISIBLE);
	    addColumn(Attributes.FOLDER_PATH, Column.VISIBLE);
	    addColumn(Attributes.FILE_NAME, Column.VISIBLE);
	    addColumn(Attributes.MD5, Column.VISIBLE, cellRenderer, null);
	    addColumn(Attributes.CRC32, Column.VISIBLE, cellRenderer, null);

	    addColumn(ROM.Attributes.FPP_CHECK_SUM, Column.VISIBLE, cellRenderer, null);
	    addColumn(ROM.Attributes.OS1_CHECK_SUM, Column.VISIBLE, cellRenderer, null);
	    addColumn(ROM.Attributes.OS2_CHECK_SUM, Column.VISIBLE, cellRenderer, null);

	    addColumn(ROMVersion.Attributes.TYPE, Column.VISIBLE);
	    addColumn(ROMVersion.Attributes.REVISION, Column.VISIBLE);
	    addColumn(ROMVersion.Attributes.NORM, Column.VISIBLE);
	    addColumn(ROMVersion.Attributes.YEAR, Column.VISIBLE);
	    addColumn(ROMVersion.Attributes.COMMENT, Column.VISIBLE);
	    addColumn(ROMVersion.Attributes.PARTS, Column.VISIBLE);
	    addColumn(Attributes.MESSAGE, Column.VISIBLE);

	}

	public void setTable(AttributeTable table) {
	    if (table == null) {
		throw new IllegalArgumentException("Parameter 'table' must not be null.");
	    }
	    this.table = table;
	}

	@Override
	public Object getValueAt(int row, int column) {
	    WorkbookEntry entry = workbook.getEntry(row);
	    ROM rom = entry.getROM();
	    ROMVersion romVersion = entry.getROMVersion();
	    if (romVersion == null) {
		romVersion = ROMVersion.UNKNOWN;
	    }
	    switch (column) {
	    case Columns.ID:
		return Integer.valueOf(table.convertRowIndexToView(row) + 1);
	    case Columns.FOLDER_PATH:
		return entry.getFolderPath();
	    case Columns.FILE_NAME:
		return entry.getFileName();
	    case Columns.MD5:
		return entry.getMD5();
	    case Columns.CRC32:
		return entry.getCRC32();

	    case Columns.FPP_CHECK_SUM:
		return rom != null ? ROM.toHexBytes(rom.getFPP()) : "";
	    case Columns.OS1_CHECK_SUM:
		return rom != null ? ROM.toHexBytes(rom.getOS1()) : "";
	    case Columns.OS2_CHECK_SUM:
		return rom != null ? ROM.toHexBytes(rom.getOS2()) : "";

	    case Columns.TYPE:
		return romVersion.getType();
	    case Columns.REVISION:
		return romVersion.getRevision();
	    case Columns.NORM:
		return romVersion.getNorm();
	    case Columns.YEAR:
		return romVersion.getYear();
	    case Columns.COMMENT:
		return romVersion.getComment();
	    case Columns.PARTS:
		return romVersion.getParts();
	    case Columns.MESSAGE:
		return entry.getMessage();

	    default:
		throw new IllegalArgumentException("Invalid column " + column + ".");
	    }

	}

	@Override
	public boolean isCellEditable(int row, int column) {
	    return false;
	}

	@Override
	public int getRowCount() {
	    return workbook.getEntryCount();
	}
    }

    private Workbook workbook;
    private TableModel tableModel;
    private AttributeTable entriesTable;

    public WorkbookEntriesPanel(Preferences preferences, final Workbook workbook) {
	if (workbook == null) {
	    throw new IllegalArgumentException("Parameter 'workbook' must not be null.");
	}
	this.workbook = workbook;

	tableModel = new TableModel(workbook);
	workbook.setEntriesTableModel(tableModel);
	entriesTable = new AttributeTable(tableModel, preferences, "workboolEntriesTable");
	tableModel.setTable(entriesTable);

	entriesTable.setSurrendersFocusOnKeystroke(true);

	// Set the column sorting functionality on
	@SuppressWarnings("unchecked")
	TableRowSorter<AttributeTableModel> sorter = (TableRowSorter<AttributeTableModel>) entriesTable.getRowSorter();
	sorter.setSortable(0, false);

	setViewportView(entriesTable);
    }

    /**
     * Sets the selected workbook entry, if it exists.
     * 
     * @param workbookEntry
     *            The workbook entry, not <code>null</code>.
     * 
     * @param attribute
     *            The attribute, not <code>null</code>.
     */
    public void setSelectedWorkbookEntry(WorkbookEntry workbookEntry, Attribute attribute) {
	if (workbookEntry == null) {
	    throw new IllegalArgumentException("Parameter 'workbookEntry' must not be null.");
	}
	int modelRowIndex = workbook.getUnmodifiableEntriesList().indexOf(workbookEntry);

	entriesTable.selectCell(modelRowIndex, attribute);
    }

    /**
     * Gets the entries table.
     * 
     * @return The entries table, not <code>null</code>.
     */
    public AttributeTable getTable() {
	return entriesTable;
    }

    /**
     * Update columns and value helps which depend of the workbook root.
     */
    public void dataToUI() {
    }

    /**
     * Stop editing and transfer cell contents to model.
     */
    public void dataFromUI() {
	CellEditor cellEditor = entriesTable.getCellEditor();
	if (cellEditor != null) {
	    try {
		cellEditor.stopCellEditing();
	    } catch (RuntimeException ignore) {
		Log.logError("Inconsistent workbook entries table", null, ignore);
	    }
	}
    }

}
