modules/vendor-api-ntt-docomo-doja/src/main/java/cc/squirreljme/runtime/nttdocomo/io/ScratchPadStore.java
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
// Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the GNU General Public License v3+, or later.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------
package cc.squirreljme.runtime.nttdocomo.io;
import cc.squirreljme.jvm.launch.IModeApplication;
import cc.squirreljme.jvm.mle.JarPackageShelf;
import cc.squirreljme.jvm.mle.brackets.JarPackageBracket;
import cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
/**
* Represents storage for a single scratch pad.
*
* @since 2021/12/02
*/
@SquirrelJMEVendorApi
public final class ScratchPadStore
{
/** The static set of stores. */
private static volatile ScratchPadStore[] _STORES;
/** The record store key prefix. */
private static final String _STORE_KEY_PREFIX =
"SquirrelJME-i-Appli-";
/** STO Header size. */
private static final int _STO_HEADER_LEN =
64;
/** STO Header entries. */
private static final int _STO_ENTRIES =
16;
/** The scratch pad being accessed. */
private final int _pad;
/** The record store key. */
private final String _storeKey;
/** Data for this given store. */
private final byte[] _data;
/**
* Initializes the scratch pad store.
*
* @param __pad The pad to load.
* @param __length The length of bytes.
* @throws IOException On read errors.
* @since 2021/12/02
*/
@SquirrelJMEVendorApi
ScratchPadStore(int __pad, int __length)
throws IOException
{
this._pad = __pad;
this._storeKey = ScratchPadStore._STORE_KEY_PREFIX + this._pad;
// Read in the record store data that already exists, if any
byte[] data = new byte[__length];
this._data = data;
try (RecordStore store = this.__rmsStore())
{
// Nothing was actually created ever?
if (store.getNumRecords() <= 0)
{
ScratchPadStore.__seed(__pad, data);
return;
}
// Read in the data
if (__length != store.getRecord(0, data, 0))
Debugging.debugNote("i-appli record size mismatch?");
}
// {@squirreljme.error AH0m Could not read pre-existing data from
// the record store.}
catch (RecordStoreException __e)
{
throw new IOException("AH0m", __e);
}
}
/**
* Flushes the output.
*
* @throws IOException If it could not be flushed.
* @since 2021/12/02
*/
@SquirrelJMEVendorApi
public void flush()
throws IOException
{
byte[] data = this._data;
synchronized (this)
{
// We need to write this somewhere
try (RecordStore store = this.__rmsStore())
{
// Either create or set the only record
if (store.getNumRecords() <= 0)
store.addRecord(data, 0, data.length);
else
store.setRecord(0, data, 0, data.length);
}
// {@squirreljme.error AH0l Could not write scratch pad to the
// record store.}
catch (RecordStoreException __e)
{
throw new IOException("AH0l", __e);
}
}
}
/**
* Opens an input stream to the given scratch pad data.
*
* @param __pos The position to open from.
* @param __length The number of bytes to read.
* @return The stream to the data bytes.
* @throws IndexOutOfBoundsException If the read is outside of bounds.
* @since 2021/12/02
*/
@SquirrelJMEVendorApi
public InputStream inputStream(int __pos, int __length)
throws IndexOutOfBoundsException
{
byte[] data = this._data;
if (__pos < 0 || __length < 0 || (__pos + __length) < 0 ||
(__pos + __length) > data.length)
throw new IndexOutOfBoundsException("IOOB");
return new ByteArrayInputStream(data, __pos, __length);
}
/**
* Opens an output stream for writing to the given scratch pad via a
* single transaction, all or nothing.
*
* @param __pos The position to open from.
* @param __len The number of bytes to write.
* @return The stream for writing the data.
* @throws IndexOutOfBoundsException If the position and/or length are
* not within the scratchpad bounds.
* @throws IOException On data copy errors.
* @since 2021/12/02
*/
@SquirrelJMEVendorApi
public OutputStream outputStream(int __pos, int __len)
throws IndexOutOfBoundsException, IOException
{
byte[] data = this._data;
if (__pos < 0 || __len < 0 || (__pos + __len) < 0 ||
(__pos + __len) > data.length)
throw new IndexOutOfBoundsException("IOOB");
return new ScratchPadOutputTransaction(this, __pos, __len);
}
/**
* Writes to the internal data buffer.
*
* @param __b The buffer.
* @param __o The offset.
* @param __l The number of bytes to write.
* @throws IndexOutOfBoundsException If the offset and/or length are not
* without bounds.
* @throws IOException On write errors.
* @throws NullPointerException On null arguments.
* @since 2021/12/02
*/
@SquirrelJMEVendorApi
public void write(byte[] __b, int __o, int __l)
throws IndexOutOfBoundsException, IOException, NullPointerException
{
if (__b == null)
throw new NullPointerException("NARG");
if (__o < 0 || __l < 0 || (__o + __l) < 0 || (__o + __l) > __b.length)
throw new IndexOutOfBoundsException("IOOB");
// Perform the copy
synchronized (this)
{
System.arraycopy(__b, 0, this._data, __o, __l);
}
}
/**
* Attempts to open the record store.
*
* @return The record store.
* @throws RecordStoreException If it could not be opened or created.
* @since 2021/12/02
*/
private RecordStore __rmsStore()
throws RecordStoreException
{
return RecordStore.openRecordStore(this._storeKey, true,
RecordStore.AUTHMODE_ANY, true, null);
}
/**
* Opens a scratch pad store.
*
* @param __pad The scratch pad to open.
* @param __params The parameters for the scratch pad.
* @return The storage for the scratch pad.
* @throws IOException On read errors.
* @throws NullPointerException On null arguments.
* @since 2021/12/02
*/
static ScratchPadStore __open(int __pad,
ScratchPadParams __params)
throws IOException, NullPointerException
{
if (__params == null)
throw new NullPointerException("NARG");
synchronized (ScratchPadStore.class)
{
// Do we need to initialize the storage base?
ScratchPadStore[] stores = ScratchPadStore._STORES;
if (stores == null)
ScratchPadStore._STORES =
(stores = new ScratchPadStore[__params.count()]);
// Do we need to initialize the already existing store?
ScratchPadStore store = stores[__pad];
if (store == null)
stores[__pad] = (store = new ScratchPadStore(__pad,
__params.getLength(__pad)));
return store;
}
}
/**
* Seeds the scratchpad data.
*
* @param __pad The scratchpad to access.
* @param __data The data to fill in.
* @throws NullPointerException On null arguments.
* @since 2024/01/06
*/
private static void __seed(int __pad, byte[] __data)
throws NullPointerException
{
if (__data == null)
throw new NullPointerException("NARG");
// Is a seed specified?
String libName = System.getProperty(
IModeApplication.SEED_SCRATCHPAD_PREFIX + "." + __pad);
if (libName == null)
libName = System.getProperty(
IModeApplication.SEED_SCRATCHPAD_PREFIX + ".0");
// None was found?
if (libName == null)
{
// Debug
Debugging.debugNote("No seed for SP %d", __pad);
return;
}
// Try to find the library that contains the seed
JarPackageBracket lib = null;
for (JarPackageBracket maybeLib : JarPackageShelf.libraries())
if (libName.equals(JarPackageShelf.libraryPath(maybeLib)))
{
lib = maybeLib;
break;
}
// Not found?
if (lib == null)
{
// Debug
Debugging.debugNote("Did not find seed library %s.",
libName);
return;
}
// Get the size of both
int dataLen = __data.length;
int seedLen = JarPackageShelf.rawSize(lib);
// Invalid?
if (seedLen < 0)
{
// Debug
Debugging.debugNote("Seed %s invalid, raw length was %d.",
libName, seedLen);
return;
}
// There are different formats, one that has a header at the start
// which derives scratchpads accordingly, or direct
if (seedLen >= dataLen + ScratchPadStore._STO_HEADER_LEN)
{
// Do nothing if too far in
if (__pad >= ScratchPadStore._STO_ENTRIES)
return;
// Read in raw header
byte[] rawHeader = new byte[ScratchPadStore._STO_HEADER_LEN];
JarPackageShelf.rawData(lib, 0,
rawHeader, 0, rawHeader.length);
// Setup buffers for position and size
int[] position = new int[ScratchPadStore._STO_ENTRIES];
int[] size = new int[ScratchPadStore._STO_ENTRIES];
// Initial position is always after the header
int at = ScratchPadStore._STO_HEADER_LEN;
// Parse the sizes in the header
try (DataInputStream dos = new DataInputStream(
new ByteArrayInputStream(rawHeader)))
{
for (int i = 0; i < ScratchPadStore._STO_ENTRIES; i++)
{
// Always at the baseline position
position[i] = at;
// Read in size, if it is negative or larger than the
// size of the scratchpad file then it is likely in
// little endian and not big endian
int padSize = dos.readInt();
if (padSize > seedLen || padSize < 0)
padSize = Integer.reverseBytes(padSize);
// Keep it mapped in size
int limit = Math.max(0, dataLen - at);
if (padSize < 0)
padSize = 0;
else if (padSize > limit)
padSize = limit;
// Set current size
size[i] = padSize;
// Move position up
at += padSize;
}
}
catch (IOException __e)
{
__e.printStackTrace();
// Ignore
return;
}
// Debug
seedLen = size[__pad];
Debugging.debugNote("Reading seed %s with dl=%d and sl=%d.",
libName, dataLen, seedLen);
// The limit is the smaller of the two
int limit = Math.min(dataLen, seedLen);
// Read the seed directly into the buffer
JarPackageShelf.rawData(lib, position[__pad],
__data, 0, limit);
}
// Flat seed
else
{
// Debug
Debugging.debugNote("Reading seed %s with dl=%d and sl=%d.",
libName, dataLen, seedLen);
// The limit is the smaller of the two
int limit = Math.min(dataLen, seedLen);
// Read the seed directly into the buffer
JarPackageShelf.rawData(lib, 0,
__data, 0, limit);
}
}
}