Java tutorial
/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.jasm16.emulator; import java.io.File; import java.util.List; import java.util.NoSuchElementException; import org.apache.commons.lang.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import de.codesourcery.jasm16.emulator.IEmulator.EmulationSpeed; import de.codesourcery.jasm16.emulator.ILogger.LogLevel; import de.codesourcery.jasm16.emulator.devices.IDevice; import de.codesourcery.jasm16.emulator.devices.impl.DefaultClock; import de.codesourcery.jasm16.emulator.devices.impl.DefaultFloppyDrive; import de.codesourcery.jasm16.emulator.devices.impl.DefaultKeyboard; import de.codesourcery.jasm16.emulator.devices.impl.DefaultScreen; import de.codesourcery.jasm16.emulator.devices.impl.DefaultVectorDisplay; import de.codesourcery.jasm16.emulator.devices.impl.FileBasedFloppyDisk; /** * Emulation options. * * @author tobias.gierke@code-sourcery.de */ public final class EmulationOptions { private static final EmulationSpeed DEFAULT_EMULATION_SPEED = EmulationSpeed.REAL_SPEED; /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * Adjust the following locations when * adding/removing configuration options: * * - COPY constructor !! * - loadEmulationOptions() * - saveEmulationOptions() * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ private boolean enableDebugOutput = false; private boolean memoryProtectionEnabled = false; private boolean ignoreAccessToUnknownDevices = false; private boolean useLegacyKeyboardBuffer = false; private boolean mapVideoRamUponAddDevice = true; private boolean mapFontRamUponAddDevice = false; private boolean runFloppyAtFullSpeed = false; private EmulationSpeed emulationSpeed = DEFAULT_EMULATION_SPEED; private boolean crashOnStoreWithImmediate = true; private InsertedDisk insertedDisk; private boolean newEmulatorInstanceRequired = false; public static final class InsertedDisk { private final File file; private final boolean writeProtected; public InsertedDisk(File file, boolean writeProtected) { this.file = file; this.writeProtected = writeProtected; } public File getFile() { return file; } public boolean isWriteProtected() { return writeProtected; } } public EmulationOptions() { } public EmulationSpeed getEmulationSpeed() { return emulationSpeed; } /** * Returns whether the emulation should stop when an * instruction like <code>SET 0x1000 ,A</code> is encountered. * * @return <code>true</code> if emulation should stop, <code>false</code> if emulation * should silently continue */ public boolean isCrashOnStoreWithImmediate() { return crashOnStoreWithImmediate; } /** * Sets whether the emulation should when an * instruction like <code>SET 0x1000 ,A</code> is encountered. * * @param doCrashOnImmediate */ public void setCrashOnStoreWithImmediate(boolean doCrashOnImmediate) { this.crashOnStoreWithImmediate = doCrashOnImmediate; } public void setEmulationSpeed(EmulationSpeed emulationSpeed) { if (emulationSpeed == null) { throw new IllegalArgumentException("emulationSpeed must not be null"); } this.emulationSpeed = emulationSpeed; } public void setEnableDebugOutput(boolean enableDebugOutput) { this.enableDebugOutput = enableDebugOutput; } public boolean isEnableDebugOutput() { return enableDebugOutput; } public EmulationOptions(EmulationOptions other) { if (other == null) { throw new IllegalArgumentException("options must not be null"); } this.enableDebugOutput = other.enableDebugOutput; this.memoryProtectionEnabled = other.memoryProtectionEnabled; this.ignoreAccessToUnknownDevices = other.ignoreAccessToUnknownDevices; this.useLegacyKeyboardBuffer = other.useLegacyKeyboardBuffer; this.mapFontRamUponAddDevice = other.mapFontRamUponAddDevice; this.mapVideoRamUponAddDevice = other.mapVideoRamUponAddDevice; this.runFloppyAtFullSpeed = other.runFloppyAtFullSpeed; this.newEmulatorInstanceRequired = other.newEmulatorInstanceRequired; this.insertedDisk = other.insertedDisk; this.emulationSpeed = other.emulationSpeed; this.crashOnStoreWithImmediate = other.crashOnStoreWithImmediate; } public InsertedDisk getInsertedDisk() { return insertedDisk; } public void setInsertedDisk(InsertedDisk disk) { this.insertedDisk = disk; } public void setRunFloppyAtFullSpeed(boolean runFloppyAtFullSpeed) { this.runFloppyAtFullSpeed = runFloppyAtFullSpeed; } public boolean isRunFloppyAtFullSpeed() { return runFloppyAtFullSpeed; } public boolean isMemoryProtectionEnabled() { return memoryProtectionEnabled; } public void setMemoryProtectionEnabled(boolean memoryProtectionEnabled) { this.memoryProtectionEnabled = memoryProtectionEnabled; } public boolean isIgnoreAccessToUnknownDevices() { return ignoreAccessToUnknownDevices; } public void setIgnoreAccessToUnknownDevices(boolean ignoreAccessToUnknownDevices) { this.ignoreAccessToUnknownDevices = ignoreAccessToUnknownDevices; } public boolean isUseLegacyKeyboardBuffer() { return useLegacyKeyboardBuffer; } public void setUseLegacyKeyboardBuffer(boolean useLegacyKeyboardBuffer) { newEmulatorInstanceRequired |= this.useLegacyKeyboardBuffer != useLegacyKeyboardBuffer; this.useLegacyKeyboardBuffer = useLegacyKeyboardBuffer; } public boolean isMapVideoRamUponAddDevice() { return mapVideoRamUponAddDevice; } public void setMapVideoRamUponAddDevice(boolean mapVideoRamUponAddDevice) { newEmulatorInstanceRequired |= this.mapVideoRamUponAddDevice != mapVideoRamUponAddDevice; this.mapVideoRamUponAddDevice = mapVideoRamUponAddDevice; } public boolean isMapFontRamUponAddDevice() { return mapFontRamUponAddDevice; } public void setMapFontRamUponAddDevice(boolean mapFontRamUponAddDevice) { this.newEmulatorInstanceRequired |= this.mapFontRamUponAddDevice != mapFontRamUponAddDevice; this.mapFontRamUponAddDevice = mapFontRamUponAddDevice; } public boolean isNewEmulatorInstanceRequired() { return newEmulatorInstanceRequired; } public void apply(IEmulator emulator) { final ILogger outLogger = new PrintStreamLogger(System.out); if (enableDebugOutput) { outLogger.setLogLevel(LogLevel.DEBUG); } emulator.setOutput(outLogger); emulator.setMemoryProtectionEnabled(memoryProtectionEnabled); emulator.setIgnoreAccessToUnknownDevices(ignoreAccessToUnknownDevices); emulator.setEmulationSpeed(emulationSpeed); emulator.setCrashOnStoreWithImmediate(crashOnStoreWithImmediate); try { final DefaultFloppyDrive drive = getFloppyDrive(emulator); drive.setRunAtMaxSpeed(runFloppyAtFullSpeed); insertDisk(drive); } catch (NoSuchElementException e) { // ok , no floppy attached } } public void saveEmulationOptions(Element element, Document document) { if (isEnableDebugOutput()) { element.setAttribute("debug", "true"); } if (isIgnoreAccessToUnknownDevices()) { element.setAttribute("ignoreAccessToUnknownDevices", "true"); } if (isMapFontRamUponAddDevice()) { element.setAttribute("mapFontRamUponAddDevice", "true"); } if (isMapVideoRamUponAddDevice()) { element.setAttribute("mapVideoRamUponAddDevice", "true"); } if (isMemoryProtectionEnabled()) { element.setAttribute("memoryProtectionEnabled", "true"); } if (isUseLegacyKeyboardBuffer()) { element.setAttribute("useLegacyKeyboardBuffer", "true"); } if (isRunFloppyAtFullSpeed()) { element.setAttribute("runFloppyAtFullSpeed", "true"); } if (isCrashOnStoreWithImmediate()) { element.setAttribute("crashOnStoreWithImmediate", "true"); } element.setAttribute("emulationSpeed", emulationSpeedToString(this.emulationSpeed)); if (getInsertedDisk() != null) { final Element disks = document.createElement("disks"); element.appendChild(disks); final Element disk = document.createElement("disk"); disks.appendChild(disk); disk.setAttribute("writeProtected", getInsertedDisk().isWriteProtected() ? "true" : "false"); disk.setAttribute("file", getInsertedDisk().getFile().getAbsolutePath()); } } public static EmulationOptions loadEmulationOptions(Element element) { final EmulationOptions result = new EmulationOptions(); result.setEnableDebugOutput(isSet(element, "debug")); result.setIgnoreAccessToUnknownDevices(isSet(element, "ignoreAccessToUnknownDevices")); result.setMapFontRamUponAddDevice(isSet(element, "mapFontRamUponAddDevice")); result.setMapVideoRamUponAddDevice(isSet(element, "mapVideoRamUponAddDevice")); result.setMemoryProtectionEnabled(isSet(element, "memoryProtectionEnabled")); result.setUseLegacyKeyboardBuffer(isSet(element, "useLegacyKeyboardBuffer")); result.setRunFloppyAtFullSpeed(isSet(element, "runFloppyAtFullSpeed")); result.setEmulationSpeed(emulationSpeedFromString(element.getAttribute("emulationSpeed"))); result.setCrashOnStoreWithImmediate(isSet(element, "crashOnStoreWithImmediate")); Element disks = getChildElement(element, "disks"); if (disks != null) { Element disk = getChildElement(element, "disk"); if (disk != null) { final boolean writeProtected = isSet(disk, "writeProtected"); final File file = new File(disk.getAttribute("file")); result.setInsertedDisk(new InsertedDisk(file, writeProtected)); } } return result; } private static String emulationSpeedToString(EmulationSpeed speed) { switch (speed) { case MAX_SPEED: return "max"; case REAL_SPEED: return "real"; default: throw new RuntimeException("Unhandled speed: " + speed); } } private static EmulationSpeed emulationSpeedFromString(String s) { if (StringUtils.isBlank(s)) { return DEFAULT_EMULATION_SPEED; } switch (s) { case "max": return EmulationSpeed.MAX_SPEED; case "real": return EmulationSpeed.REAL_SPEED; default: throw new RuntimeException("Unhandled speed: '" + s + "'"); } } private static Element getChildElement(Element parent, String tagName) { final NodeList nodeList = parent.getElementsByTagName(tagName); if (nodeList.getLength() == 1) { return (Element) nodeList.item(0); } if (nodeList.getLength() > 1) { throw new RuntimeException("Parse error, more than one <disks/> node in file?"); } return null; } private static boolean isSet(Element element, String attribute) { final String value = element.getAttribute(attribute); return "true".equals(value); } public Emulator createEmulator() { final Emulator result = new Emulator(); result.addDevice(new DefaultClock()); result.addDevice(new DefaultKeyboard(useLegacyKeyboardBuffer)); result.addDevice(new DefaultScreen(mapVideoRamUponAddDevice, mapFontRamUponAddDevice)); result.addDevice(new DefaultFloppyDrive(runFloppyAtFullSpeed)); result.addDevice(new DefaultVectorDisplay()); apply(result); newEmulatorInstanceRequired = false; return result; } private void insertDisk(DefaultFloppyDrive diskDrive) { final InsertedDisk disk = getInsertedDisk(); if (disk != null) { diskDrive.setDisk(new FileBasedFloppyDisk(disk.getFile(), disk.isWriteProtected())); } else { diskDrive.eject(); } } public DefaultFloppyDrive getFloppyDrive(IEmulator emulator) { List<IDevice> result = emulator.getDevicesByDescriptor(DefaultFloppyDrive.DESC); if (result.isEmpty()) { throw new NoSuchElementException("Internal error, found no floppy drive?"); } if (result.size() > 1) { throw new RuntimeException("Internal error, found more than one floppy drive?"); } return (DefaultFloppyDrive) result.get(0); } public DefaultScreen getScreen(IEmulator emulator) { List<IDevice> result = emulator.getDevicesByDescriptor(DefaultScreen.DESC); if (result.isEmpty()) { throw new NoSuchElementException("Internal error, found no default screen?"); } if (result.size() > 1) { throw new RuntimeException("Internal error, found more than one default screen?"); } return (DefaultScreen) result.get(0); } public DefaultVectorDisplay getVectorDisplay(IEmulator emulator) { List<IDevice> result = emulator.getDevicesByDescriptor(DefaultVectorDisplay.DEVICE_DESCRIPTOR); if (result.isEmpty()) { throw new NoSuchElementException("Internal error, found no default vector display ?"); } if (result.size() > 1) { throw new RuntimeException("Internal error, found more than one vector display ?"); } return (DefaultVectorDisplay) result.get(0); } public DefaultKeyboard getKeyboard(IEmulator emulator) { List<IDevice> result = emulator.getDevicesByDescriptor(DefaultKeyboard.DESC); if (result.isEmpty()) { throw new NoSuchElementException("Internal error, found no default keyboard ?"); } if (result.size() > 1) { throw new RuntimeException("Internal error, found more than one default keyboard ?"); } return (DefaultKeyboard) result.get(0); } }