From c736412b4b06a766ec0d0e2a48dbb387f610b20c Mon Sep 17 00:00:00 2001 From: Collin Smith Date: Thu, 17 Dec 2020 22:33:32 -0800 Subject: [PATCH] Created Tables and TsvParser impl in :core Created Tables and TsvParser impl in :core Added lazy loading of table records --- core/build.gradle | 11 + .../main/java/com/riiablo/table/Tables.java | 16 + .../java/com/riiablo/table/TsvParser.java | 351 ++++++++++++++++++ .../com/riiablo/table/TsvTranslators.java | 56 +++ .../com/riiablo/table/schema/MonStats.java | 222 +++++++++++ .../java/com/riiablo/table/TablesTest.java | 52 +++ .../java/com/riiablo/table/TsvParserTest.java | 47 +++ .../main/java/com/riiablo/table/Table.java | 7 +- 8 files changed, 761 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/riiablo/table/Tables.java create mode 100644 core/src/main/java/com/riiablo/table/TsvParser.java create mode 100644 core/src/main/java/com/riiablo/table/TsvTranslators.java create mode 100644 core/src/main/java/com/riiablo/table/schema/MonStats.java create mode 100644 core/src/test/java/com/riiablo/table/TablesTest.java create mode 100644 core/src/test/java/com/riiablo/table/TsvParserTest.java diff --git a/core/build.gradle b/core/build.gradle index 1aaf7691..4dcfde85 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -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" diff --git a/core/src/main/java/com/riiablo/table/Tables.java b/core/src/main/java/com/riiablo/table/Tables.java new file mode 100644 index 00000000..00993900 --- /dev/null +++ b/core/src/main/java/com/riiablo/table/Tables.java @@ -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 > + T loadTsv(T table, TsvParser parser) { + parser.primaryKey(table.primaryKey()); + return table; + } +} diff --git a/core/src/main/java/com/riiablo/table/TsvParser.java b/core/src/main/java/com/riiablo/table/TsvParser.java new file mode 100644 index 00000000..29bd06e5 --- /dev/null +++ b/core/src/main/java/com/riiablo/table/TsvParser.java @@ -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 recordIds = new ObjectIntMap<>(389); + final IntArray lineOffsets = new IntArray(256); + + final int numFields; + final Array fieldNames = new Array<>(16); + final ObjectIntMap 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 fieldNames() { + return fieldNames; + } + + public String fieldName(int fieldId) { + return fieldNames.get(fieldId); + } + + public int numRecords() { + return numRecords; + } + + public Iterable recordNames() { + List 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 tokens(final int recordId) { + List 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]); + } +} diff --git a/core/src/main/java/com/riiablo/table/TsvTranslators.java b/core/src/main/java/com/riiablo/table/TsvTranslators.java new file mode 100644 index 00000000..9120179f --- /dev/null +++ b/core/src/main/java/com/riiablo/table/TsvTranslators.java @@ -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); + } + } + } +} diff --git a/core/src/main/java/com/riiablo/table/schema/MonStats.java b/core/src/main/java/com/riiablo/table/schema/MonStats.java new file mode 100644 index 00000000..309f29db --- /dev/null +++ b/core/src/main/java/com/riiablo/table/schema/MonStats.java @@ -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; +} diff --git a/core/src/test/java/com/riiablo/table/TablesTest.java b/core/src/test/java/com/riiablo/table/TablesTest.java new file mode 100644 index 00000000..e90764f5 --- /dev/null +++ b/core/src/test/java/com/riiablo/table/TablesTest.java @@ -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); + } +} diff --git a/core/src/test/java/com/riiablo/table/TsvParserTest.java b/core/src/test/java/com/riiablo/table/TsvParserTest.java new file mode 100644 index 00000000..fa946dfc --- /dev/null +++ b/core/src/test/java/com/riiablo/table/TsvParserTest.java @@ -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)); + } +} diff --git a/table/core/src/main/java/com/riiablo/table/Table.java b/table/core/src/main/java/com/riiablo/table/Table.java index f7a5b171..a139529e 100644 --- a/table/core/src/main/java/com/riiablo/table/Table.java +++ b/table/core/src/main/java/com/riiablo/table/Table.java @@ -79,7 +79,12 @@ public abstract class Table implements Iterable { } 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) {