// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.resource.key;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.LinkedHashMap;
import org.infinity.util.io.StreamUtils;
/**
* Abstract base class for specifialized BIFF readers.
*/
public abstract class AbstractBIFFReader //implements AutoCloseable
{
/** Supported BIFF archive types. */
public enum Type {
/** Uncompressed BIFF V1 */
BIFF,
/** File-compressed BIF V1.0 */
BIF,
/** Block-compressed BIFC V1.0 */
BIFC,
}
// A cache for AbstractBIFFReader instances
private static final LinkedHashMap<Path, AbstractBIFFReader> BIFF_CACHE = new LinkedHashMap<>();
// Maps resource locators to BIFF entry structures
private final HashMap<Integer, Entry> mapEntries = new HashMap<>();
protected final Path file;
/**
* Opens the specified BIFF file (of any supported type) and returns it fully initialized and
* ready for read operations as a BIFFReader object.
* @param file Path to the BIFF file.
* @return A BIFFReader object for accessing the BIFF archive.
* @throws IOException On error.
*/
public static synchronized AbstractBIFFReader open(Path file) throws Exception
{
return queryBIFFReader(file);
}
/** Returns a fully initialized TIS header as {@link ByteBuffer} object. */
public static ByteBuffer getTisHeader(int tileCount, int tileSize)
{
ByteBuffer bb = StreamUtils.getByteBuffer(24);
bb.put("TIS V1 ".getBytes());
bb.putInt(tileCount);
bb.putInt(tileSize);
bb.putInt(0x18); // data offset
bb.putInt(0x40); // tile dimension
bb.position(0);
return bb;
}
/** Removes all {@code AbstractBIFFReader} entries from the cache. */
public static void resetCache()
{
BIFF_CACHE.clear();
}
// Fetches a cached AbstractBIFFReader associated of the specified path or creates a new one
private static AbstractBIFFReader queryBIFFReader(Path file) throws Exception
{
AbstractBIFFReader retVal = null;
if (file != null) {
// get and remove an available cached entry
retVal = BIFF_CACHE.get(file);
if (retVal == null) {
Type type = detectBiffType(file);
switch (type) {
case BIFF:
retVal = new BIFFReader(file);
break;
case BIF:
retVal = new BIFReader(file);
break;
case BIFC:
retVal = new BIFCReader(file);
break;
default:
throw new IOException("Unsupported BIFF type");
}
BIFF_CACHE.put(file, retVal);
}
}
return retVal;
}
/** Returns whether the BIFF file uses any kind of compression. */
public boolean isCompressed()
{
return (getType() == Type.BIF || getType() == Type.BIFC);
}
/** Returns the {@link Path} to the BIFF file. */
public Path getFile()
{
return file;
}
/**
* Returns a ResourceInfo array containing size for regular resources
* and tile count and size for TIS resources.
* @param locator The unmodified locator of the desired resource as found in the KEY file.
*/
public int[] getResourceInfo(int locator) throws IOException
{
Entry entry = getEntry(locator);
if (entry != null) {
int[] retVal = null;
if (entry.isTile) {
retVal = new int[]{entry.count, entry.size};
} else {
retVal = new int[]{entry.size};
}
return retVal;
} else {
throw new IOException("Resource not found");
}
}
/** Returns whether the BIFF file is open and ready for read operations. */
// public abstract boolean isOpen();
/** Re-opens the BIFF file if it had been {@code close}d before. Does nothing if the BIFF file is open. */
public abstract void open() throws Exception;
/** Returns the BIFF resource type. */
public abstract Type getType();
/** Returns the number of regular resources in the BIFF file. */
public abstract int getFileCount();
/** Returns the number of tileset (TIS) resources in the BIFF file. */
public abstract int getTilesetCount();
/** Returns the uncompressed size of the BIFF archive in bytes. Returns -1 on error. */
public abstract int getBIFFSize();
/**
* Returns a {@link ByteBuffer} object of the requested (TIS or regular) resource.
* @param locator The unmodified locator of the desired resource as found in the KEY file.
*/
public abstract ByteBuffer getResourceBuffer(int locator) throws IOException;
/**
* Returns an {@link InputStream} object of the requested (TIS or regular) resource.
* @param locator The unmodified locator of the desired resource as found in the KEY file.
*/
public abstract InputStream getResourceAsStream(int locator) throws IOException;
protected AbstractBIFFReader(Path file) throws Exception
{
if (file == null) {
throw new NullPointerException();
}
this.file = file;
}
// Internally used to store BIFF entry information
protected void addEntry(Entry entry)
{
if (entry != null) {
mapEntries.put(Integer.valueOf(entry.locator & 0xfffff), entry);
}
}
// Internally used to retrieve stored BIFF entry information
protected Entry getEntry(int locator)
{
return mapEntries.get(Integer.valueOf(locator & 0xfffff));
}
// Internally used to remove all entries from the map
protected void resetEntries()
{
mapEntries.clear();
}
private static Type detectBiffType(Path file) throws Exception
{
if (file == null) {
throw new NullPointerException();
}
try (InputStream is = StreamUtils.getInputStream(file)) {
String sigver = StreamUtils.readString(is, 8);
if ("BIFFV1 ".equals(sigver)) {
return Type.BIFF;
} else if ("BIF V1.0".equals(sigver)) {
return Type.BIF;
} else if ("BIFCV1.0".equals(sigver)) {
return Type.BIFC;
} else {
throw new IOException("Unsupported BIFF file: " + file);
}
}
}
//-------------------------- INNER CLASSES --------------------------
// File or tileset entry definition
protected static class Entry
{
public int locator; // resource locator
public int offset; // offset to resource data
public int size; // file size or size of each tile
public int count; // number of tiles in the resource (if isTile == true)
public short type; // resource type
public boolean isTile; // indicates whether this is a file or tileset
public Entry(int locator, int offset, int size, short type)
{
this.locator = locator;
this.offset = offset;
this.size = size;
this.count = 0;
this.type = type;
this.isTile = (type == Keyfile.TYPE_TIS);
}
public Entry(int locator, int offset, int count, int size, short type)
{
this.locator = locator;
this.offset = offset;
this.size = size;
this.count = count;
this.type = type;
this.isTile = (type == Keyfile.TYPE_TIS);
}
}
}