/*
* Copyright (C) 2007 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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.
*
*
* Author: Steve Ratcliffe
* Create date: 23-Sep-2007
*/
package uk.me.parabola.tdbfmt;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.io.EndOfFileException;
import uk.me.parabola.io.StructuredInputStream;
import uk.me.parabola.log.Logger;
/**
* The TDB file. See the package documentation for more details.
*
* @author Steve Ratcliffe
*/
public class TdbFile {
private static final Logger log = Logger.getLogger(TdbFile.class);
public static final int TDB_V407 = 407;
// The version number of the TDB format
private int tdbVersion;
// The blocks that go to make up the file.
private HeaderBlock headerBlock;
private CopyrightBlock copyrightBlock = new CopyrightBlock();
private OverviewMapBlock overviewMapBlock;
private final List<DetailMapBlock> detailBlocks = new ArrayList<>();
private final RBlock rblock = new RBlock();
private final TBlock tblock = new TBlock();
private String overviewDescription;
private int codePage;
public TdbFile() {
}
public TdbFile(int tdbVersion) {
this.tdbVersion = tdbVersion;
}
/**
* Read in a TDB file from the disk.
*
* @param name The file name to load.
* @return A TdbFile instance.
* @throws IOException For problems reading the file.
*/
public static TdbFile read(String name) throws IOException {
TdbFile tdb = new TdbFile();
try (InputStream is = new BufferedInputStream(new FileInputStream(name))) {
tdb.load(is);
}
return tdb;
}
public void setProductInfo(int familyId, int productId,
short productVersion, String seriesName, String familyName, String overviewDescription,
byte enableProfile)
{
headerBlock = new HeaderBlock(tdbVersion);
headerBlock.setFamilyId((short) familyId);
headerBlock.setProductId((short) productId);
headerBlock.setProductVersion(productVersion);
headerBlock.setSeriesName(seriesName);
headerBlock.setFamilyName(familyName);
headerBlock.setEnableProfile(enableProfile);
this.overviewDescription = overviewDescription;
}
public void setCodePage(int codePage) {
this.codePage = codePage;
headerBlock.setCodePage(codePage);
}
/**
* Add a copyright segment to the file.
* @param msg The message to add.
*/
public void addCopyright(String msg) {
if (msg.isEmpty())
return;
CopyrightSegment seg = new CopyrightSegment(CopyrightSegment.CODE_COPYRIGHT_TEXT_STRING, 3, msg);
copyrightBlock.addSegment(seg);
}
/**
* Set the overview information. Basically the overall size of the map
* set.
* @param bounds The bounds for the map.
*/
public void setOverview(Area bounds, String number) {
overviewMapBlock = new OverviewMapBlock();
overviewMapBlock.setArea(bounds);
overviewMapBlock.setMapName(number);
overviewMapBlock.setDescription(overviewDescription);
}
/**
* Add a detail block. This describes and names one of the maps in the
* map set.
* @param detail The detail to add.
*/
public void addDetail(DetailMapBlock detail) {
detailBlocks.add(detail);
}
public void write(String name) throws IOException {
if (headerBlock == null || overviewMapBlock == null)
throw new IOException("Attempting to write file without being fully set up");
try (CheckedOutputStream stream = new CheckedOutputStream(
new BufferedOutputStream(new FileOutputStream(name)),
new CRC32()))
{
headerBlock.writeTo(stream, codePage);
copyrightBlock.writeTo(stream, codePage);
if (tdbVersion >= TDB_V407) {
rblock.writeTo(stream, codePage);
}
overviewMapBlock.writeTo(stream, codePage);
for (DetailMapBlock detail : detailBlocks) {
detail.writeTo(stream, codePage);
}
if (tdbVersion >= TDB_V407) {
tblock.setSum(stream.getChecksum().getValue());
tblock.writeTo(stream, codePage);
}
}
}
/**
* Load from the given file name.
*
* @param ds The stream to read from.
* @throws IOException For problems reading the file.
*/
private void load(InputStream ds) throws IOException {
boolean eof = false;
while (!eof) {
try {
readBlock(ds);
} catch (EndOfFileException ignore) {
eof = true;
}
}
}
/**
* The file is divided into blocks. This reads a single block.
*
* @param is The input stream.
* @throws IOException For problems reading the file.
*/
private void readBlock(InputStream is) throws IOException {
int blockType = is.read();
if (blockType == -1)
throw new EndOfFileException();
int blockLength = readBlockLength(is);
if (blockLength == -1)
throw new EndOfFileException();
byte[] body = new byte[blockLength];
int n = is.read(body);
if (n < 0)
throw new IOException("failed to read block");
StructuredInputStream ds = new StructuredInputStream(new ByteArrayInputStream(body));
switch (blockType) {
case HeaderBlock.BLOCK_ID:
headerBlock = new HeaderBlock(ds);
log.info("header block seen", headerBlock);
break;
case CopyrightBlock.BLOCK_ID:
log.info("copyright block");
copyrightBlock = new CopyrightBlock(ds);
break;
case OverviewMapBlock.BLOCK_ID:
overviewMapBlock = new OverviewMapBlock(ds);
log.info("overview block", overviewMapBlock);
break;
case DetailMapBlock.BLOCK_ID:
DetailMapBlock db = new DetailMapBlock(ds);
log.info("detail block", db);
detailBlocks.add(db);
break;
default:
log.warn("Unknown block in tdb file");
break;
}
}
private int readBlockLength(InputStream is) throws IOException {
int b1 = is.read();
if (b1 < 0)
return -1;
int b2 = is.read();
if (b2 < 0)
return -1;
return ((b2 & 0xff) << 8) | (b1 & 0xff);
}
public int getTdbVersion() {
return headerBlock.getTdbVersion();
}
}