/*
* Copyright (C) 2009.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* version 2 as published by the Free Software Foundation.
*
* This program 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.
*/
package uk.me.parabola.imgfmt.app.mdr;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* Super class of all sections.
* @author Steve Ratcliffe
*/
public abstract class MdrSection extends ConfigBase {
private PointerSizes sizes;
private boolean released;
protected int nItems;
private boolean sizeValid;
/**
* Write out the contents of this section.
* @param writer Where to write it.
*/
public abstract void writeSectData(ImgFileWriter writer);
/**
* The size of a record in the section. This is not a constant and
* might vary on various factors, such as the file version, if we are
* preparing for a device, the number of maps etc.
*
* @return The size of a record in this section.
*/
public abstract int getItemSize();
protected PointerSizes getSizes() {
return sizes;
}
public void setSizes(PointerSizes sizes) {
this.sizes = sizes;
}
protected void putMapIndex(ImgFileWriter writer, int mapIndex) {
putN(writer, sizes.getMapSize(), mapIndex);
}
protected void putStringOffset(ImgFileWriter writer, int strOff) {
putN(writer, sizes.getStrOffSize(), strOff);
}
protected void putN(ImgFileWriter writer, int n, int value) {
switch (n) {
case 1:
writer.put((byte) value);
break;
case 2:
writer.putChar((char) value);
break;
case 3:
writer.put3(value);
break;
case 4:
writer.putInt(value);
break;
default: // Don't write anything.
assert false;
break;
}
}
protected static int numberToPointerSize(int n) {
if (n > 0xffffff)
return 4;
else if (n > 0xffff)
return 3;
else if (n > 0xff)
return 2;
else
return 1;
}
/**
* The number of records in this section.
* @return The number of items in the section.
*/
public final int getNumberOfItems() {
assert sizeValid;
if (released)
return nItems;
else
return numberOfItems();
}
/**
* Method to be implemented by subclasses to return the number of items in the section.
* This will only be valid after the section is completely finished etc.
* @return The number of items in the section.
*/
protected abstract int numberOfItems();
/**
* Get the size of an integer that is sufficient to store a record number
* from this section. If the pointer has a flag(s) then this must be
* taken into account too.
* @return A number between 1 and 4 giving the number of bytes required
* to store the largest record number in this section.
*/
public int getSizeForRecord() {
return numberToPointerSize(getNumberOfItems());
}
/**
* Called before the section is written and before the actual size of the section
* is required.
*
* Calling it more than once is ok.
*
* The actual work is implemented in the subclass via the {@link #preWriteImpl()} method.
*/
public final void preWrite() {
if (!sizeValid)
preWriteImpl();
sizeValid = true;
}
/**
* Prepare the final list of items to be written.
* Used to de-duplicate or remove invalid entries from the raw data that was
* saved.
*
* In particular after this call the number of items must not change.
*/
protected void preWriteImpl() {
}
public final void release() {
nItems = numberOfItems();
releaseMemory();
released = true;
}
protected void releaseMemory() {
throw new UnsupportedOperationException();
}
/**
* Provides the pointer sizes required to hold record of offset values
* in the various sections.
*/
static class PointerSizes {
private final MdrSection[] sections;
public PointerSizes(MdrSection[] sections) {
this.sections = sections;
}
public int getMapSize() {
return sections[1].getSizeForRecord();
}
public int getCitySize() {
return sections[5].getSizeForRecord();
}
/**
* Get the number of bytes required to represent a city when there is
* one bit reserved for a flag.
* There is a minimum size of 2.
* @return Number of bytes to represent a city record number and a
* one bit flag.
*/
public int getCitySizeFlagged() {
return Math.max(2, numberToPointerSize(sections[5].getNumberOfItems() << 1));
}
public int getCityFlag() {
return flagForSize(getCitySizeFlagged());
}
public int getStreetSize() {
return sections[7].getSizeForRecord();
}
public int getStreetSizeFlagged() {
return numberToPointerSize(sections[7].getNumberOfItems() << 1);
}
public int getPoiSize() {
return sections[11].getSizeForRecord();
}
public int getZipSize() {
//return Math.max(2, sections[6].getSizeForRecord());
return sections[6].getSizeForRecord();
}
/**
* The number of bytes required to represent a POI (mdr11) record number
* and a flag bit.
*/
public int getPoiSizeFlagged() {
return numberToPointerSize(sections[11].getNumberOfItems() << 1);
}
public int getPoiFlag() {
return flagForSize(getPoiSizeFlagged());
}
/**
* Size of the pointer required to index a byte offset into mdr15 (strings).
* There is a minimum of 3 for this value.
* @return Pointer size required for the string offset value.
*/
public int getStrOffSize() {
return Math.max(3, sections[15].getSizeForRecord());
}
public int getMdr20Size() {
return sections[20].getSizeForRecord();
}
private int flagForSize(int size) {
int flag;
if (size == 1)
flag = 0x80;
else if (size == 2)
flag = 0x8000;
else if (size == 3)
flag = 0x800000;
else if (size == 4)
flag = 0x80000000;
else
flag = 0;
return flag;
}
public int getSize(int sect) {
return sections[sect].getSizeForRecord();
}
}
}