package org.erikaredmark.monkeyshines.editor.importlogic;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import org.erikaredmark.monkeyshines.DeathAnimation;
import org.erikaredmark.monkeyshines.editor.importlogic.WorldTranslationException.TranslationFailure;
/**
*
* Static translation helper methods for read old monkey shines file formats
*
* @author Erika Redmark
*
*/
public final class TranslationUtil {
private TranslationUtil() { }
/**
*
* Reads a 2-byte array interpreted as a signed, little-endian 2 byte value. This is the most common value found
* in old resource fork data.
* <p/>
* Passed array must be of size 2.
*
* @param macShort
* raw bytes representing the short
*
* @return
* integral value of the short
*
* @throws IllegalArgumentException
* if the byte array is not exactly 2 bytes
*
*/
public static int translateMacShort(byte[] macShort) {
if (macShort.length != 2) throw new IllegalArgumentException("Can not interpret " + Arrays.toString(macShort) + " as a short: must be exactly 2 bytes, not " + macShort.length);
ByteBuffer buffer = ByteBuffer.allocate(2);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.put(macShort);
buffer.rewind();
return buffer.getShort();
}
/**
*
* Reads the byte array as an array of shorts. Every two bytes is effectively one short, hence the size of
* the array /2 is the size of the return result.
*
* @param shorts
* raw byte array of shorts
*
* @return
* Java int array of the values interpretted
*
*/
public static int[] translateMacShortArray(byte[] shorts) {
ByteBuffer buffer = ByteBuffer.allocate(shorts.length);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.put(shorts);
buffer.rewind();
int[] returnShorts = new int[shorts.length / 2];
for (int i = 0; i < shorts.length / 2; ++i) {
returnShorts[i] = buffer.getShort();
}
return returnShorts;
}
/**
*
* Reads a 1 byte value as either {@code true} (being 1) or {@code false} (being 0). Other values will cause
* an exception
*
* @param bool
* the byte that represents the boolean
*
* @return
* {@code true} if 1, {@code false} if 0
*
* @throws IllegalArgumentException
* if bool is neither 0 nor 1
*
*/
public static boolean translateMacBoolean(byte bool) {
switch (bool) {
case (byte)0:
return false;
case (byte)1:
return true;
default:
throw new IllegalArgumentException("Boolean values must be either 0 or 1, not " + bool);
}
}
/**
*
* Reads the array of bytes as an array of booleans. Each byte is 1 boolean, so the returned
* array size will match the argument size.
*
* @param bools
* byte array of bools
*
* @return
* java intepreted booleans
*
*/
public static boolean[] translateMacBooleanArray(byte[] bools) {
boolean[] returnBools = new boolean[bools.length];
for (int i = 0; i < bools.length; ++i) {
returnBools[i] = translateMacBoolean(bools[i]);
}
return returnBools;
}
/**
*
* Resolves the integral value of the old-style hazard specification to the port's interpretation
* of a death animation. In the port, death animations include sound hardcoded. In the original this
* was not the case.
* <p/>
* If the type is unknown, {@code DeathAnimation.NORMAL} is returned and the issue is logged.
*
* @param type
* the integral value interpreted from the binary stream
*
* @return
* the proper death animation
*
*/
public static DeathAnimation deathType(int type) {
// TODO This is intended for hazards, which normally burn and electrify. We MAY need to add
// standard and bee deaths to this.
switch (type) {
case 1: return DeathAnimation.BURN;
case 2: return DeathAnimation.ELECTRIC;
case 3: return DeathAnimation.NORMAL;
case 4: return DeathAnimation.BEE;
default: return DeathAnimation.NORMAL;
}
}
/**
*
* Skips the given number of bytes in the input stream, automatically translating the inability to skip
* into an {@code WorldTranslationException} that is intended for the client that passed the stream in the
* first place.
*
* @param is
*
* @param skipBytes
*
* @param ifFail
*
* @param msg
* if the skip fails, the message to display
*
* @throws IOException
* if an unknown error prevents skipping
*
*/
public static void skip(InputStream is, long skipBytes, TranslationFailure ifFail, String msg) throws IOException, WorldTranslationException {
long skipped = is.skip(skipBytes);
if (skipped != skipBytes) throw new WorldTranslationException(ifFail, msg);
}
/**
*
* Reads the given number of bytes in the input stream, automatically translating the inability to read
* into an {@code WorldTranslationException} that is intended for the client that passed the stream in the
* first place.
*
* @param is
*
* @param read
*
* @param ifFail
*
* @param msg
* if the skip fails, the message to display
*
* @throws IOException
* if an unknown error prevents skipping
*
*/
public static void read(InputStream is, byte[] read, TranslationFailure ifFail, String msg) throws IOException, WorldTranslationException {
int bytesRead = is.read(read);
if (bytesRead != read.length) throw new WorldTranslationException(ifFail, msg);
}
/**
* Reads a mac short from the stream. This is effectively reading 2 bytes from the stream and then converting
* the result using {@code translateMacShort}. The translation failure info is provided in case reading the
* stream fails for any reason. The info should include the type of data that was being read.
*/
public static int readMacShort(InputStream is, TranslationFailure ifFail, String msg) throws IOException, WorldTranslationException {
byte[] raw = new byte[2];
read(is, raw, ifFail, msg);
return translateMacShort(raw);
}
/**
* Reads an array of shorts, similiar to {@code readMacShort} but for a consecutive amount of them. See javadocs
* for that method for explanation of other parameters
*
* @param size
* size is in number of shorts, not in number of bytes (shorts take up two bytes)
*
*/
public static int[] readMacShortArray(InputStream is, int size, TranslationFailure ifFail, String msg) throws IOException, WorldTranslationException {
byte[] raw = new byte[size * 2];
read(is, raw, ifFail, msg);
return translateMacShortArray(raw);
}
/**
* Reads a mac boolean from the stream. This effectively reads 1 byte from the stream, and converts the result
* using {@code translateMacBoolean}. The translation failure info is provided in case reading the
* stream fails for any reason. The info should include the type of data that was being read.
*/
public static boolean readMacBoolean(InputStream is, TranslationFailure ifFail, String msg) throws IOException, WorldTranslationException {
// Using array version to re-use existing code.
byte[] raw = new byte[1];
read(is, raw, ifFail, msg);
return translateMacBoolean(raw[0]);
}
/**
* Reads an array of booleans, similiar to {@code readMacBoolean} but for a consecutive amount of them. See javadocs
* for that method for explanation of other parameters
*/
public static boolean[] readMacBooleanArray(InputStream is, int size, TranslationFailure ifFail, String msg) throws IOException, WorldTranslationException {
byte[] raw = new byte[size];
read(is, raw, ifFail, msg);
return translateMacBooleanArray(raw);
}
}