/* * $Id: AbstractObjectTree.java 536 2008-02-19 06:03:27Z weiju $ * * Created on 2006/03/05 * Copyright 2005-2008 by Wei-ju Wu * This file is part of The Z-machine Preservation Project (ZMPP). * * ZMPP 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 3 of the License, or * (at your option) any later version. * * ZMPP 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 ZMPP. If not, see <http://www.gnu.org/licenses/>. */ package org.zmpp.vm; import org.zmpp.base.Memory; /** * This class is the abstract super class of object trees. * * @author Wei-ju Wu * @version 1.0 */ public abstract class AbstractObjectTree implements ObjectTree { private Memory memory; private int address; /** * Constructor. * * @param memory the memory access object * @param address the object table's start address */ public AbstractObjectTree(final Memory memory, final int address) { this.memory = memory; this.address = address; } /** * Returns the memory object. * * @return the memory object */ protected Memory getMemory() { return memory; } /** * Returns this tree's start address. * * @return the address */ protected int getAddress() { return address; } /** * Returns the address of the specified object. * * @param objectNum the object number * @return the object address */ protected abstract int getObjectAddress(int objectNum); /** * {@inheritDoc} */ public void removeObject(final int objectNum) { int oldParent = getParent(objectNum); setParent(objectNum, 0); if (oldParent != 0) { if (getChild(oldParent) == objectNum) { setChild(oldParent, getSibling(objectNum)); } else { // Find the child that comes directly before the removed // node and set the direct sibling of the removed node as // its new sibling int currentChild = getChild(oldParent); int sibling = getSibling(currentChild); // We have to handle the case that in fact that object is a child // of its parent, but not directly (happens for some reasons). // We stop in this case and simply remove the object from its // parent, probably the object tree modification routines should // be reverified while (sibling != 0 && sibling != objectNum) { currentChild = sibling; sibling = getSibling(currentChild); } // sibling might be 0, in that case, the object is not // in the hierarchy if (sibling == objectNum) { setSibling(currentChild, getSibling(objectNum)); } } } setSibling(objectNum, 0); } /** * {@inheritDoc} */ public void insertObject(final int parentNum, final int objectNum) { // we want to ensure, the child has no old parent relationships if (getParent(objectNum) > 0) { removeObject(objectNum); } final int oldChild = getChild(parentNum); setParent(objectNum, parentNum); setChild(parentNum, objectNum); setSibling(objectNum, oldChild); } /** * The size of the property defaults section. * * @return the property defaults section */ protected abstract int getPropertyDefaultsSize(); /** * Returns the start address of the object tree section. * * @return the object tree's start address */ protected int getObjectTreeStart() { return getAddress() + getPropertyDefaultsSize(); } /** * Returns the story file version specific object entry size. * * @return the size of an object entry */ protected abstract int getObjectEntrySize(); // ****************************************************************** // ****** Object methods // **************************** /** * {@inheritDoc} */ public boolean isAttributeSet(int objectNum, int attributeNum) { final short value = memory.readUnsignedByte( getAttributeByteAddress(objectNum, attributeNum)); return (value & (0x80 >> (attributeNum & 7))) > 0; } /** * {@inheritDoc} */ public void setAttribute(int objectNum, int attributeNum) { final int attributeByteAddress = getAttributeByteAddress(objectNum, attributeNum); short value = memory.readUnsignedByte(attributeByteAddress); value |= (0x80 >> (attributeNum & 7)); memory.writeUnsignedByte(attributeByteAddress, value); } /** * {@inheritDoc} */ public void clearAttribute(int objectNum, int attributeNum) { final int attributeByteAddress = getAttributeByteAddress(objectNum, attributeNum); short value = memory.readUnsignedByte(attributeByteAddress); value &= (~(0x80 >> (attributeNum & 7))); memory.writeUnsignedByte(attributeByteAddress, value); } /** * Returns the address of the byte specified object attribute lies in. * * @param objectNum the object number * @param attributeNum the attribute number * @return the address of the attribute byte */ private int getAttributeByteAddress(int objectNum, int attributeNum) { return getObjectAddress(objectNum) + attributeNum / 8; } // ****************************************************************** // ****** Property methods // **************************** /** * {@inheritDoc} */ public int getPropertiesDescriptionAddress(final int objectNum) { return getPropertyTableAddress(objectNum) + 1; } /** * {@inheritDoc} */ public int getPropertyAddress(final int objectNum, final int property) { int propAddr = getPropertyEntriesStart(objectNum); while (true) { int propnum = getPropertyNum(propAddr); if (propnum == 0) { return 0; // not found } if (propnum == property) { return propAddr + getNumPropertySizeBytes(propAddr); } int numPropBytes = getNumPropertySizeBytes(propAddr); propAddr += numPropBytes + getPropertyLength(propAddr + numPropBytes); } } /** * {@inheritDoc} */ public int getNextProperty(final int objectNum, final int property) { if (property == 0) { final int addr = getPropertyEntriesStart(objectNum); return getPropertyNum(addr); } int propDataAddr = getPropertyAddress(objectNum, property); if (propDataAddr == 0) { reportPropertyNotAvailable(objectNum, property); return 0; } else { return getPropertyNum(propDataAddr + getPropertyLength(propDataAddr)); } } private void reportPropertyNotAvailable(int objectNum, int property) { throw new IllegalArgumentException("Property " + property + " of object " + objectNum + " is not available."); } /** * {@inheritDoc} */ public int getProperty(int objectNum, int property) { int propertyDataAddress = getPropertyAddress(objectNum, property); if (propertyDataAddress == 0) { return getPropertyDefault(property); } final int numBytes = getPropertyLength(propertyDataAddress); int value; if (numBytes == 1) { value = memory.readUnsignedByte(propertyDataAddress) & 0xff; } else { final int byte1 = memory.readUnsignedByte(propertyDataAddress); final int byte2 = memory.readUnsignedByte(propertyDataAddress + 1); value = (byte1 << 8 | (byte2 & 0xff)); } return value & 0xffff; } /** * {@inheritDoc} */ public void setProperty(int objectNum, int property, int value) { int propertyDataAddress = getPropertyAddress(objectNum, property); if (propertyDataAddress == 0) { reportPropertyNotAvailable(objectNum, property); } else { int propsize = getPropertyLength(propertyDataAddress); if (propsize == 1) { memory.writeUnsignedByte(propertyDataAddress, (short) (value & 0xff)); } else { memory.writeUnsignedShort(propertyDataAddress, value & 0xffff); } } } /** * Returns the property number at the specified table index. * * @param index the property address * @return the property number */ protected abstract int getPropertyNum(int propertyAddress); /** * Returns the address of an object's property table. * * @param objectNum the object number * @return the table address */ protected abstract int getPropertyTableAddress(int objectNum); /** * Returns the number of property size bytes at the specified address. * * @param propertyAddress the address of the property entry * @return the number of size bytes */ protected abstract int getNumPropertySizeBytes(int propertyAddress); /** * Returns the number of property size bytes at the specified property data * address. * * @param propertyDataAddress the address of the property entry data * @return the number of size bytes */ protected abstract int getNumPropSizeBytesAtData(int propertyDataAddress); /** * Returns the start address of the actual property entries. * * @param objectNum the object number * @return the property entries' start address */ private int getPropertyEntriesStart(int objectNum) { return getPropertyTableAddress(objectNum) + getDescriptionHeaderSize(objectNum); } /** * Returns the size of the description header in bytes that is, the size * byte plus the description string size. This stays the same for all story * file versions. * * @param objectNum the object number * @return the size of the description header */ private int getDescriptionHeaderSize(int objectNum) { final int startAddr = getPropertyTableAddress(objectNum); return memory.readUnsignedByte(startAddr) * 2 + 1; } /** * Returns the property default value at the specified position in the * property defaults table. * * @param propertyNum the default entry's property number * @return the property default value */ private short getPropertyDefault(final int propertyNum) { final int index = propertyNum - 1; return memory.readShort(address + index * 2); } }