Created Tables and TsvParser impl in :core

Created Tables and TsvParser impl in :core
Added lazy loading of table records
This commit is contained in:
Collin Smith 2020-12-17 22:33:32 -08:00
parent 2511b7cfe5
commit c736412b4b
8 changed files with 761 additions and 1 deletions

View File

@ -16,6 +16,10 @@ project.ext.generatedSourceDir = file("$buildDir/generated-src/main/java/")
sourceSets.main.java.srcDirs += generatedSourceDir
idea.module.generatedSourceDirs += generatedSourceDir
project.ext.annotationProcessorGeneratedSourcesDirectory = compileJava.options.annotationProcessorGeneratedSourcesDirectory
sourceSets.main.java.srcDirs += annotationProcessorGeneratedSourcesDirectory
idea.module.generatedSourceDirs += annotationProcessorGeneratedSourcesDirectory
project.ext.vcsGeneratedSourceDir = file('gen/main/java/')
sourceSets.main.java.srcDirs += vcsGeneratedSourceDir
idea.module.generatedSourceDirs += vcsGeneratedSourceDir
@ -46,6 +50,13 @@ dependencies {
implementation "com.squareup:javapoet:1.13.0"
}
// Table
dependencies {
annotationProcessor project(':table:annotation-processor')
implementation project(':table:core')
implementation project(':table:annotations')
}
// Networking
dependencies {
api "com.google.flatbuffers:flatbuffers-java:$flatbuffersVersion"

View File

@ -0,0 +1,16 @@
package com.riiablo.table;
import com.riiablo.logger.LogManager;
import com.riiablo.logger.Logger;
public class Tables {
private static final Logger log = LogManager.getLogger(Tables.class);
private Tables() {}
static <R, T extends Table<R>>
T loadTsv(T table, TsvParser parser) {
parser.primaryKey(table.primaryKey());
return table;
}
}

View File

@ -0,0 +1,351 @@
package com.riiablo.table;
import io.netty.util.AsciiString;
import io.netty.util.CharsetUtil;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.Validate;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.ObjectIntMap;
import com.riiablo.logger.LogManager;
import com.riiablo.logger.Logger;
/**
* Written under the assumptions that the tab-separated value file format is
* consistent as:
*
* C00\tC01\tC02\r\n <--- defines column names
* c10\tc11\tc12\r\n
* EXPANSION\t\t\r\n <--- may or may not be present
* c30\tc31\tc32\r\n
*/
public class TsvParser implements ParserInput {
private static final Logger log = LogManager.getLogger(TsvParser.class);
/** Log warnings if {@link #parseBoolean} parses non-binary radixes */
private static final boolean CHECK_BINARY_RADIX = true;
private static final byte HT = '\t';
private static final byte CR = '\r';
private static final byte LF = '\n';
private static final byte[] TO_UPPER;
static {
TO_UPPER = new byte[1 << Byte.SIZE];
for (int i = 0; i < TO_UPPER.length; i++) {
TO_UPPER[i] = (byte) i;
}
for (int i = 'a'; i <= 'z'; i++) {
TO_UPPER[i] &= ~0x20;
}
}
private static AsciiString toUpper(AsciiString string) {
final byte[] bytes = string.array();
for (int i = string.arrayOffset(), s = i + string.length(); i < s; i++) {
bytes[i] = TO_UPPER[bytes[i]];
}
string.arrayChanged();
return string;
}
private static String toUpper(final AsciiString buffer, final int start, final int end) {
final byte[] bytes = buffer.array();
for (int i = start; i < end; i++) {
bytes[i] = TO_UPPER[bytes[i]];
}
return buffer.toString(start, end);
}
private static final byte[] EXPANSION = "\nEXPANSION".getBytes(CharsetUtil.US_ASCII);
/**
* Consumes the regexp {@code \nEXPANSION\t*\r}
*/
static int skipExpansion(final byte[] bytes, final int offset) {
final byte[] EXPANSION = TsvParser.EXPANSION;
final int length = EXPANSION.length;
if (offset + length >= bytes.length) return offset;
int i = offset;
for (int j = 0; j < length; i++, j++) {
if (TO_UPPER[bytes[i]] != EXPANSION[j]) {
return offset;
}
}
while (bytes[i] == HT) i++;
assert bytes[i] == CR;
return i + 1;
}
public static TsvParser parse(byte[] bytes) {
return new TsvParser(bytes);
}
final byte[] bytes;
final AsciiString buffer;
final int numRecords;
final ObjectIntMap<String> recordIds = new ObjectIntMap<>(389);
final IntArray lineOffsets = new IntArray(256);
final int numFields;
final Array<String> fieldNames = new Array<>(16);
final ObjectIntMap<String> fieldIds = new ObjectIntMap<>(53);
final IntArray tokenOffsets = new IntArray(256 * 16);
int primaryKeyFieldId = -1;
TsvParser(byte[] bytes) {
this.bytes = bytes;
buffer = new AsciiString(bytes, false);
final int firstRecordOffset = preprocessFieldNames();
numFields = parseFieldNames();
numRecords = preprocess(firstRecordOffset);
log.debug("{} records, {} fields, {} tokens",
lineOffsets.size, numFields, tokenOffsets.size);
}
private int preprocessFieldNames() {
lineOffsets.add(0);
tokenOffsets.add(0);
final byte[] bytes = this.bytes;
final int length = bytes.length;
for (int i = 0; i < length; i++) {
switch (bytes[i]) {
case HT:
tokenOffsets.add(i);
tokenOffsets.add(i + 1);
break;
case CR:
tokenOffsets.add(i);
lineOffsets.add(tokenOffsets.size);
break;
case LF:
return i + 1;
}
}
return -1;
}
private int parseFieldNames() {
final int numFields = lineOffsets.get(1);
final int[] tokenOffsets = this.tokenOffsets.items;
for (int i = 0; i < numFields;) {
putFieldName(toUpper(buffer, tokenOffsets[i++], tokenOffsets[i++]));
}
return numFields >> 1;
}
private int preprocess(int offset) {
lineOffsets.clear();
lineOffsets.add(0);
tokenOffsets.clear();
tokenOffsets.add(offset);
final byte[] bytes = this.bytes;
final int length = bytes.length;
for (int i = offset; i < length; i++) {
switch (bytes[i]) {
case HT:
tokenOffsets.add(i);
tokenOffsets.add(i + 1);
break;
case CR:
tokenOffsets.add(i);
lineOffsets.add(tokenOffsets.size);
break;
case LF:
i = skipExpansion(bytes, i);
tokenOffsets.add(i + 1);
break;
}
}
return --lineOffsets.size;
}
private void putFieldName(String fieldName) {
if (!fieldIds.containsKey(fieldName)) {
fieldIds.put(fieldName, fieldNames.size);
}
fieldNames.add(fieldName);
}
public int numFields() {
return numFields;
}
public Iterable<String> fieldNames() {
return fieldNames;
}
public String fieldName(int fieldId) {
return fieldNames.get(fieldId);
}
public int numRecords() {
return numRecords;
}
public Iterable<String> recordNames() {
List<String> recordNames = new ArrayList<>(numRecords());
for (int i = 0, s = numRecords(); i < s; i++) {
recordNames.add(recordName(i));
}
return recordNames;
}
public String recordName(int recordId) {
return primaryKeyFieldId == -1
? "" + recordId
: _recordName(recordId).toString();
}
private AsciiString _recordName(int recordId) {
return token(recordId, primaryKeyFieldId);
}
public void primaryKey(String fieldName) {
Validate.validState(primaryKeyFieldId == -1, "primary key already set");
final int fieldId = primaryKeyFieldId = fieldId(fieldName);
final int[] tokenOffsets = this.tokenOffsets.items;
for (int i = 0, s = numRecords(); i < s; i++) {
final int offset = lineOffset(i, fieldId);
String recordName = toUpper(buffer, tokenOffsets[offset], tokenOffsets[offset + 1]);
recordIds.put(recordName, i);
}
}
private int lineOffset(final int recordId, final int fieldId) {
return lineOffsets.items[recordId] + (fieldId << 1);
}
@Override
public AsciiString token(final int recordId, final int fieldId) {
final int offset = lineOffset(recordId, fieldId);
final int[] tokenOffsets = this.tokenOffsets.items;
return buffer.subSequence(tokenOffsets[offset], tokenOffsets[offset + 1]);
}
public Iterable<String> tokens(final int recordId) {
List<String> tokens = new ArrayList<>(numFields());
for (int i = 0, s = numFields(); i < s; i++) {
tokens.add(TsvTranslators.escapeTsv(token(recordId, i).toString()));
}
return tokens;
}
@Override
public int fieldId(String fieldName) {
return fieldIds.get(fieldName.toUpperCase(), -1);
}
@Override
public int recordId(String recordName) {
return recordIds.get(recordName.toUpperCase(), -1);
}
/**
* @see #parseInt
*/
@Override
public byte parseByte(int recordId, int fieldId) {
return (byte) parseInt(recordId, fieldId);
}
/**
* @see #parseInt
*/
@Override
public short parseShort(int recordId, int fieldId) {
return (short) parseInt(recordId, fieldId);
}
/**
* This is an extremely optimized implementation which does no value or param
* checking.
*
* @return token at {@code (recordId, fieldId)}, otherwise {@code 0}
*/
@Override
public int parseInt(int recordId, int fieldId) {
final int offset = lineOffset(recordId, fieldId);
final int[] tokenOffsets = this.tokenOffsets.items;
final int start = tokenOffsets[offset];
final int end = tokenOffsets[offset + 1];
if (start >= end) return 0;
int i = start;
final byte[] bytes = this.bytes;
boolean negative = bytes[i] == '-';
if (negative) i++;
int result = 0;
final int l = end - start;
for (final int s = i + l; i < s; i++) result = (result * 10) + (bytes[i] & 0xF);
return negative ? -result : result;
}
/**
* This is an extremely optimized implementation which does no value or param
* checking.
*
* @return token at {@code (recordId, fieldId)}, otherwise {@code 0}
*/
@Override
public long parseLong(int recordId, int fieldId) {
final int offset = lineOffset(recordId, fieldId);
final int[] tokenOffsets = this.tokenOffsets.items;
final int start = tokenOffsets[offset];
final int end = tokenOffsets[offset + 1];
if (start >= end) return 0;
int i = start;
final byte[] bytes = this.bytes;
boolean negative = bytes[i] == '-';
if (negative) i++;
long result = 0L;
final int l = end - start;
for (final int s = i + l; i < s; i++) result = (result * 10) + (bytes[i] & 0xF);
return negative ? -result : result;
}
@Override
public boolean parseBoolean(int recordId, int fieldId) {
final int intValue = parseInt(recordId, fieldId);
if (CHECK_BINARY_RADIX && (intValue & 1) != intValue) {
log.warn("boolean exceeds binary radix at {}:{} ({}, {}): {}",
recordId, fieldId,
TsvTranslators.escapeTsv(recordName(recordId)),
TsvTranslators.escapeTsv(fieldName(fieldId)),
token(recordId, fieldId));
}
return intValue != 0;
}
@Override
public float parseFloat(int recordId, int fieldId) {
return Float.parseFloat(parseString(recordId, fieldId));
}
@Override
public double parseDouble(int recordId, int fieldId) {
return Double.parseDouble(parseString(recordId, fieldId));
}
@Override
public String parseString(int recordId, int fieldId) {
final int offset = lineOffset(recordId, fieldId);
final int[] tokenOffsets = this.tokenOffsets.items;
return buffer.toString(tokenOffsets[offset], tokenOffsets[offset + 1]);
}
}

View File

@ -0,0 +1,56 @@
package com.riiablo.table;
import java.io.IOException;
import java.io.Writer;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.translate.CharSequenceTranslator;
public final class TsvTranslators {
private TsvTranslators() {}
public static final CharSequenceTranslator TSV_ESCAPER = new TsvEscaper();
/** Comma character. */
private static final char TSV_DELIMITER = '\t';
/** Quote character. */
private static final char TSV_QUOTE = '"';
/** Quote character converted to string. */
private static final String TSV_QUOTE_STR = String.valueOf(TSV_QUOTE);
/** Escaped quote string. */
private static final String TSV_ESCAPED_QUOTE_STR = TSV_QUOTE_STR + TSV_QUOTE_STR;
/** TSV key characters in an array. */
private static final char[] TSV_SEARCH_CHARS = new char[] {
TSV_DELIMITER, TSV_QUOTE, CharUtils.CR, CharUtils.LF
};
public static String escapeTsv(String input) {
return TSV_ESCAPER.translate(input);
}
public static final class TsvEscaper extends CharSequenceTranslator {
@Override
public int translate(CharSequence input, int index, Writer out) throws IOException {
if (index != 0) {
throw new IllegalArgumentException(TsvEscaper.class.getSimpleName()
+ ".translate(final CharSequence input, final int index, final Writer out) "
+ "can not handle a non-zero index.");
}
translateWhole(input, out);
return Character.codePointCount(input, index, input.length());
}
void translateWhole(final CharSequence input, final Writer out) throws IOException {
final String inputSting = input.toString();
if (StringUtils.containsNone(inputSting, TSV_SEARCH_CHARS)) {
out.write(inputSting);
} else {
// input needs quoting
out.write(TSV_QUOTE);
out.write(StringUtils.replace(inputSting, TSV_QUOTE_STR, TSV_ESCAPED_QUOTE_STR));
out.write(TSV_QUOTE);
}
}
}
}

View File

@ -0,0 +1,222 @@
package com.riiablo.table.schema;
import com.riiablo.table.annotation.Format;
import com.riiablo.table.annotation.PrimaryKey;
import com.riiablo.table.annotation.Schema;
@Schema
@SuppressWarnings("unused")
public class MonStats {
@Override
public String toString() {
return NameStr;
}
@PrimaryKey
public String Id;
public int hcIdx;
public String BaseId;
public String NextInClass;
public int TransLvl;
public String NameStr;
public String MonStatsEx;
public String MonProp;
public String MonType;
public String AI;
public String DescStr;
public String Code;
public boolean enabled;
public boolean rangedtype;
public boolean placespawn;
public String spawn;
public int spawnx;
public int spawny;
public String spawnmode;
public String minion1;
public String minion2;
public boolean SetBoss;
public boolean BossXfer;
public int PartyMin;
public int PartyMax;
public int MinGrp;
public int MaxGrp;
public int sparsePopulate;
public int Velocity;
public int Run;
public int Rarity;
public String MonSound;
public String UMonSound;
public int threat;
public String MissA1;
public String MissA2;
public String MissS1;
public String MissS2;
public String MissS3;
public String MissS4;
public String MissC;
public String MissSQ;
public int Align;
public boolean isSpawn;
public boolean isMelee;
public boolean npc;
public boolean interact;
public boolean inventory;
public boolean inTown;
public boolean lUndead;
public boolean hUndead;
public boolean demon;
public boolean flying;
public boolean opendoors;
public boolean boss;
public boolean primeevil;
public boolean killable;
public boolean switchai;
public boolean noAura;
public boolean nomultishot;
public boolean neverCount;
public boolean petIgnore;
public boolean deathDmg;
public boolean genericSpawn;
public boolean zoo;
public int SendSkills;
public String Skill1;
public String Sk1mode;
public int Sk1lvl;
public String Skill2;
public String Sk2mode;
public int Sk2lvl;
public String Skill3;
public String Sk3mode;
public int Sk3lvl;
public String Skill4;
public String Sk4mode;
public int Sk4lvl;
public String Skill5;
public String Sk5mode;
public int Sk5lvl;
public String Skill6;
public String Sk6mode;
public int Sk6lvl;
public String Skill7;
public String Sk7mode;
public int Sk7lvl;
public String Skill8;
public String Sk8mode;
public int Sk8lvl;
public int DamageRegen;
public String SkillDamage;
public boolean noRatio;
public boolean NoShldBlock;
public int Crit;
public String El1Mode;
public String El1Type;
public String El2Mode;
public String El2Type;
public String El3Mode;
public String El3Type;
public int TCQuestId;
public int TCQuestCP;
public int SplEndDeath;
public boolean SplGetModeChart;
public boolean SplEndGeneric;
public boolean SplClientEnd;
@Format(format = "Level%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] Level;
@Format(format = "aidel%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aidel;
@Format(format = "aidist%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aidist;
@Format(format = "aip1%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aip1;
@Format(format = "aip2%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aip2;
@Format(format = "aip3%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aip3;
@Format(format = "aip4%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aip4;
@Format(format = "aip5%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aip5;
@Format(format = "aip6%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aip6;
@Format(format = "aip7%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aip7;
@Format(format = "aip8%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] aip8;
@Format(format = "Drain%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] Drain;
@Format(format = "coldeffect%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] coldeffect;
@Format(format = "ResDm%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] ResDm;
@Format(format = "ResMa%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] ResMa;
@Format(format = "ResFi%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] ResFi;
@Format(format = "ResLi%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] ResLi;
@Format(format = "ResCo%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] ResCo;
@Format(format = "ResPo%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] ResPo;
@Format(format = "ToBlock%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] ToBlock;
@Format(format = "minHP%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] minHP;
@Format(format = "maxHP%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] maxHP;
@Format(format = "AC%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] AC;
@Format(format = "Exp%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] Exp;
@Format(format = "A1MinD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] A1MinD;
@Format(format = "A1MaxD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] A1MaxD;
@Format(format = "A1TH%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] A1TH;
@Format(format = "A2MinD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] A2MinD;
@Format(format = "A2MaxD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] A2MaxD;
@Format(format = "A2TH%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] A2TH;
@Format(format = "S1MinD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] S1MinD;
@Format(format = "S1MaxD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] S1MaxD;
@Format(format = "S1TH%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] S1TH;
@Format(format = "El1Pct%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El1Pct;
@Format(format = "El1MinD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El1MinD;
@Format(format = "El1MaxD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El1MaxD;
@Format(format = "El1Dur%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El1Dur;
@Format(format = "El2Pct%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El2Pct;
@Format(format = "El2MinD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El2MinD;
@Format(format = "El2MaxD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El2MaxD;
@Format(format = "El2Dur%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El2Dur;
@Format(format = "El3Pct%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El3Pct;
@Format(format = "El3MinD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El3MinD;
@Format(format = "El3MaxD%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El3MaxD;
@Format(format = "El3Dur%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public int[] El3Dur;
@Format(format = "TreasureClass1%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public String[] TreasureClass1;
@Format(format = "TreasureClass2%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public String[] TreasureClass2;
@Format(format = "TreasureClass3%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public String[] TreasureClass3;
@Format(format = "TreasureClass4%s", values = {"", "(N)", "(H)"}, endIndex = 3)
public String[] TreasureClass4;
}

View File

@ -0,0 +1,52 @@
package com.riiablo.table;
import java.util.Arrays;
import org.junit.BeforeClass;
import org.junit.Test;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.riiablo.RiiabloTest;
import com.riiablo.logger.Level;
import com.riiablo.logger.LogManager;
import com.riiablo.table.schema.MonStats;
import com.riiablo.table.table.MonStatsTable;
public class TablesTest extends RiiabloTest {
@BeforeClass
public static void before() {
LogManager.setLevel("com.riiablo.table.Tables", Level.TRACE);
}
@Test
public void monstats() {
FileHandle handle = Gdx.files.internal("test/monstats.txt");
TsvParser parser = TsvParser.parse(handle.readBytes());
MonStatsTable table = Tables.loadTsv(new MonStatsTable(), parser);
MonStats record = table.get(0);
System.out.println(record.Id);
System.out.println(record.hcIdx);
System.out.println(Arrays.toString(record.Level));
}
@Test
public void monstats_random_access() {
FileHandle handle = Gdx.files.internal("test/monstats.txt");
TsvParser parser = TsvParser.parse(handle.readBytes());
MonStatsTable table = Tables.loadTsv(new MonStatsTable(), parser);
MonStats record;
record = table.get(54);
System.out.println(record);
record = table.get(311);
System.out.println(record);
record = table.get(411);
System.out.println(record);
record = table.get(5);
System.out.println(record);
record = table.get(700);
System.out.println(record);
record = table.get(578);
System.out.println(record);
}
}

View File

@ -0,0 +1,47 @@
package com.riiablo.table;
import org.junit.BeforeClass;
import org.junit.Test;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.riiablo.RiiabloTest;
import com.riiablo.logger.Level;
import com.riiablo.logger.LogManager;
public class TsvParserTest extends RiiabloTest {
@BeforeClass
public static void before() {
LogManager.setLevel("com.riiablo.table.TsvParser", Level.TRACE);
}
@Test
public void monstats_field_names() {
FileHandle handle = Gdx.files.internal("test/monstats.txt");
TsvParser parser = TsvParser.parse(handle.readBytes());
System.out.println(parser.fieldNames());
}
@Test
public void monstats_record_indexes() {
FileHandle handle = Gdx.files.internal("test/monstats.txt");
TsvParser parser = TsvParser.parse(handle.readBytes());
System.out.println(parser.recordNames());
}
@Test
public void monstats_record_names() {
FileHandle handle = Gdx.files.internal("test/monstats.txt");
TsvParser parser = TsvParser.parse(handle.readBytes());
parser.primaryKey("ID");
System.out.println(parser.recordNames());
}
@Test
public void monstats_skeleton1_tokens() {
FileHandle handle = Gdx.files.internal("test/monstats.txt");
TsvParser parser = TsvParser.parse(handle.readBytes());
System.out.println(parser.tokens(0));
}
}

View File

@ -79,7 +79,12 @@ public abstract class Table<R> implements Iterable<R> {
}
public R get(int id) {
return records.get(id);
R record = records.get(id);
if (record == null && parser != null) {
records.put(id, record = parser.parseRecord(id, newRecord()));
}
return record;
}
public int index(String id) {