CExoLocSubStringData.java
// <editor-fold defaultstate="collapsed" desc="license">
/*
* Copyright (c) 2014, Karl H. Beckers
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the <ORGANIZATION> nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
**/
// </editor-fold>
package net.jarre_de_the.griffin.types.data;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.Arrays;
import net.jarre_de_the.griffin.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of CEXOLOCSUBSTRING. The GFF CEXOLOCSUBSTRING physically
* consists of a DWORD for the String ID, a DWORD for the string length, and as
* many bytes as stated in length.
* <p>
* @todo Protect against reading a 0 byte string from file.
* @author charly4711
*/
public class CExoLocSubStringData extends AbstractData
implements Cloneable {
private static final Logger LOGGER = LoggerFactory.getLogger(CExoLocSubStringData.class);
private int id = 0;
private byte[] value;
/*
*
* constructors
*
*/
protected CExoLocSubStringData() {
}
/**
* Creates a new instance of {@code CExoLocSubStringData} and sets defined
* values.
* <p>
* @param id String ID to set as the composite of language and gender id.
* @param buf String contents as a byte array.
*/
public CExoLocSubStringData(int id,
byte[] buf) {
setId(id);
setValue(buf);
}
/**
* Creates a new instance of {@code CExoLocSubStringData} and sets defined
* values. Note that the values passed will not be copied, atm.
* <p>
* @param langId The language id.
* @param female True for female, false for other.
* @param buf String contents as a byte array.
* @see #setId(int)
*/
public CExoLocSubStringData(LangId langId,
boolean female,
byte[] buf) {
setId(langId, female);
setValue(buf);
}
/*
*
* read from file
*
*/
/**
* Creates a new instance of {@code CExoLocSubStringData} based on the data
* read from the file passed. Note that the contents are read from whatever
* position the file pointer is at.
* <p>
* @param in An open {@code RandomAccessFile}.
* @return
* @throws java.io.IOException If there are any I/O related problems with
* file access to in.
*/
public static CExoLocSubStringData read(RandomAccessFile in)
throws IOException {
LOGGER.debug("Reading CExoLocSubStringData from file.");
CExoLocSubStringData ret = new CExoLocSubStringData();
// read ID
IntData id = IntData.read(in);
LOGGER.trace("Id is:" + Integer.toHexString(id.getValueAsNumber()));
ret.setId(id.getValueAsNumber());
// read length
IntData len = IntData.read(in);
LOGGER.trace("Need to read " + len.getValueAsNumber() + " bytes");
// we're just hoping we'll never encounter a GFF file where a
// single buffer is > MAXINT
byte[] buf = new byte[len.getValueAsNumber()];
in.readFully(buf);
if (LOGGER.isDebugEnabled()) {
LangId l = LangId.getLangIdById((id.getValueAsNumber() & 0xFFFE) >> 1);
LOGGER.debug("Read: " + Util.getNullTerminatedString(buf, l.charset()));
}
ret.setValue(buf);
return ret;
}
/*
*
* setter
*
*/
/**
* Sets the composite string ID. The ID consist of 1 for female or 0 for
* male or other plus the (language ID * 2) taken from this table:
* <p>
* <table border='1'>
* <tr><td>English</td><td>0</td></tr>
* <tr><td>French</td><td>1</td></tr>
* <tr><td>German</td><td>2</td></tr>
* <tr><td>Italian</td><td>3</td></tr>
* <tr><td>Spanish</td><td>4</td></tr>
* <tr><td>Polish</td><td>5</td></tr>
* <tr><td>Korean</td><td>128</td></tr>
* <tr><td>Chinese Traditional</td><td>129</td></tr>
* <tr><td>Chinese Simplified</td><td>130</td></tr>
* <tr><td>Japanese</td><td>131</td></tr>
* </table>
* <p>
* @param id The string ID to set.
* @see LangId
*/
private void setId(int id) {
this.id = id;
}
/**
* Sets the composite string ID from its individual components.
* <p>
* @param langId The language ID.
* @param female True for female, false for other.
* @see #setId(int)
*/
private void setId(LangId langId,
boolean female) {
int computedId = computeId(langId, female);
LOGGER.debug("Setting id to " + computedId + " from lang " + langId.id()
+ " and female == " + female);
setId(computedId);
}
/**
* Sets the byte array value of this instance. Note that this does not copy
* {@code buf}.
* <p>
* @param buf The byte array containing the string.
*/
private void setValue(byte[] buf) {
if (buf != null) {
value = new byte[buf.length];
System.arraycopy(buf, 0, value, 0, buf.length);
LOGGER.debug("Set CExoLocSubStringData value to: "
+ new String(value, getLangId().charset()));
} else {
buf = null;
}
}
/*
*
* getter
*
*/
/**
* Retrieves this instance's composite string ID.
* <p>
* @return The string ID.
* @see #setId(int)
*/
public int getId() {
return id;
}
/**
* Retrieves this instance's language ID.
* <p>
* @return The language ID.
* @see #setId(int)
*/
public LangId getLangId() {
LOGGER.debug("Id " + id + " translates to lang id "
+ ((id & 0xFFFE) >> 1));
return LangId.getLangIdById((id & 0xFFFE) >> 1);
}
/**
* Checks the gender encoded in this instance's string ID.
* <p>
* @return True for female, false for other.
*/
public boolean isFemale() {
if ((id & 1) > 0) {
return true;
} else {
return false;
}
}
/**
* Gets this instance's actual string content as a byte array. This ignores
* the string ID.
* <p>
* @return The string content byte array.
*/
public byte[] getValueAsByteArray() {
if (null != value) {
byte[] buf = new byte[value.length];
System.arraycopy(value, 0, buf, 0, value.length);
LOGGER.debug("Retrieving byte array "
+ new String(buf, getLangId().charset()));
return buf;
} else {
return null;
}
}
/*
*
* utility
*
*/
/**
* Sets the composite string ID from its individual components.
* <p>
* @param langId The language ID.
* @param female True for female, false for other.
* @return
* @see #setId(int)
*/
public static int computeId(LangId langId,
boolean female) {
int id = 0;
if (female) {
id = 1;
}
id |= (langId.id() << 1);
return id;
}
/**
* Overrides the {@code java.lang.Object} method to ensure we always get
* back an instance of {@code CExoLocSubStringData} rather than just
* {@code Object}.
* <p>
* @return A deep copy of this object.
* @throws java.lang.CloneNotSupportedException
*/
@Override
public CExoLocSubStringData clone() throws CloneNotSupportedException {
LOGGER.debug("Cloning object");
CExoLocSubStringData clone = (CExoLocSubStringData) super.clone();
clone.setId(getLangId(), isFemale());
byte[] dataCopy;
if (null != value) {
dataCopy = new byte[value.length];
System.arraycopy(value, 0, dataCopy, 0, dataCopy.length);
} else {
dataCopy = null;
}
clone.setValue(dataCopy);
return clone;
}
/**
* Overrides the {@code java.lang.Object} method to make test for equality
* possible for this class.
* <p>
* @param compare The Object to compare this object with.
* @return True if the objects have the same value, false if not.
*/
@Override
public boolean equals(Object compare) {
if (compare == this) {
return true;
}
if (!(compare instanceof CExoLocSubStringData)) {
return false;
}
CExoLocSubStringData dw = (CExoLocSubStringData) compare;
boolean valEqual = false;
if (null == value && null == dw.getValueAsByteArray()) {
valEqual = true;
} else if (null == value && dw.getValueAsByteArray().length == 0) {
valEqual = true;
} else if (null == dw.getValueAsByteArray() && value.length == 0) {
valEqual = true;
} else if ((null != getValueAsByteArray()) && Arrays.equals(value, dw.getValueAsByteArray())) {
valEqual = true;
}
return id == dw.getId() && valEqual;
}
/**
* Overrides the {@code java.lang.Object} method to ensure we have a hash
* code consistent with the test for equality.
* <p>
* @return The hash returned is based on the value, but poorly distributed.
*/
@Override
public int hashCode() {
return Arrays.hashCode(value) ^ id;
}
@Override
public String toString() {
if (value != null && value.length > 0) {
return new String(value, getLangId().charset());
}
return EMPTY_VALUE;
}
/**
* Enumeration of valid language IDs for an {@code CExoLocSubString}.
*/
public enum LangId {
EN(0, "windows-1252"),
FR(1, "windows-1252"),
DE(2, "windows-1252"),
IT(3, "windows-1252"),
ES(4, "windows-1252"),
PO(5, "windows-1250"),
KR(128, "x-windows-949"),
ZH_HK(129, "x-windows-950"),
ZH_CN(130, "x-mswin-936"),
JP(131, "windows-31j");
private final int id;
private final Charset charSet;
LangId(int id, String charSetName) {
this.id = id;
this.charSet = Charset.forName(charSetName);
}
public int id() {
return id;
}
public Charset charset() {
return charSet;
}
public static LangId getLangIdById(int id) {
LangId lang = LangId.EN;
for (LangId l : LangId.values()) {
if (l.id() == id) {
lang = l;
}
}
return lang;
}
}
}