Move to a parser combinator

Julien LepillerMon Jun 27 23:06:05+0200 2022

aaa5924

Move to a parser combinator

.idea/gradle.xml

44
  <component name="GradleSettings">
55
    <option name="linkedExternalProjectsSettings">
66
      <GradleProjectSettings>
7-
        <option name="testRunner" value="PLATFORM" />
7+
        <option name="testRunner" value="GRADLE" />
88
        <option name="distributionType" value="DEFAULT_WRAPPED" />
99
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
1010
        <option name="modules">

1414
            <option value="$PROJECT_DIR$/rubytextview" />
1515
          </set>
1616
        </option>
17-
        <option name="resolveModulePerSourceSet" value="false" />
1817
      </GradleProjectSettings>
1918
    </option>
2019
  </component>

.idea/runConfigurations.xml unknown status 2

1-
<?xml version="1.0" encoding="UTF-8"?>
2-
<project version="4">
3-
  <component name="RunConfigurationProducerService">
4-
    <option name="ignoredProducers">
5-
      <set>
6-
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
7-
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
8-
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
9-
      </set>
10-
    </option>
11-
  </component>
12-
</project>
12>
130>
\ No newline at end of file

app/src/main/java/eu/lepiller/nani/dictionary/DictionaryFactory.java

256256
        for(Dictionary d: dictionaries) {
257257
            if (d instanceof ResultDictionary && d.isDownloaded()) {
258258
                available++;
259-
                ArrayList<Result> dr = ((ResultDictionary) d).search(text);
259+
                List<Result> dr = ((ResultDictionary) d).search(text);
260260
                if(dr != null) {
261261
                    for(Result r: dr) {
262262
                        try {

app/src/main/java/eu/lepiller/nani/dictionary/FileDictionary.java

99
import java.io.RandomAccessFile;
1010
import java.util.ArrayList;
1111
import java.util.HashMap;
12+
import java.util.List;
1213
import java.util.Map;
1314
1415
public abstract class FileDictionary extends Dictionary {

3132
        }
3233
    }
3334
34-
    interface TrieValsDecoder<T> {
35-
        T decodeVals(RandomAccessFile file, long pos) throws IOException;
36-
        void skipVals(RandomAccessFile file, long pos) throws IOException;
35+
    static abstract class Parser<T> {
36+
        abstract T parse(RandomAccessFile file) throws IOException;
37+
    }
38+
39+
    static abstract class TrieParser<T> {
40+
        protected Parser<T> valParser;
41+
        TrieParser(Parser<T> parser) {
42+
            valParser = parser;
43+
        }
44+
45+
        final T decodeVals(RandomAccessFile file, long pos) throws IOException {
46+
            seek(file, pos);
47+
            Log.d(TAG, "if it's a list, it has " + file.readShort() + " elements.");
48+
            seek(file, pos);
49+
            return valParser.parse(file);
50+
        }
51+
52+
        void seek(RandomAccessFile file, long pos) throws IOException {
53+
            file.seek(pos);
54+
        }
55+
56+
        abstract void skipVals(RandomAccessFile file, long pos) throws IOException;
3757
    }
3858
3959
    private final String mUrl;

142162
            return 0;
143163
    }
144164
145-
    static String getString(RandomAccessFile file) throws IOException {
146-
        byte b;
147-
        ArrayList<Byte> bs = new ArrayList<>();
148-
        while((b = file.readByte()) != 0) {
149-
            bs.add(b);
150-
        }
151-
        byte[] str = new byte[bs.size()];
152-
        for(int j=0; j<bs.size(); j++) {
153-
            str[j] = bs.get(j);
165+
    static void logHuffman(Huffman h, ArrayList<Boolean> address) {
166+
        if (h instanceof ResultDictionary.HuffmanValue) {
167+
            Log.v(TAG, "HUFF: " + ((ResultDictionary.HuffmanValue) h).character + " -> " + address.toString());
168+
        } else if(h instanceof ResultDictionary.HuffmanTree) {
169+
            ArrayList<Boolean> address_l = new ArrayList<>(address);
170+
            address_l.add(false);
171+
            ArrayList<Boolean> address_r = new ArrayList<>(address);
172+
            address_r.add(true);
173+
            logHuffman(((JMDict.HuffmanTree) h).left, address_l);
174+
            logHuffman(((JMDict.HuffmanTree) h).right, address_r);
154175
        }
155-
        return new String(str, encoding);
156176
    }
157177
158-
    static ArrayList<String> getStringList(RandomAccessFile file) throws IOException {
159-
        ArrayList<String> results = new ArrayList<>();
160-
        int number = file.readShort();
161-
        for(int i=0; i<number; i++) {
162-
            results.add(getString(file));
178+
    static class StringParser extends Parser<String> {
179+
        @Override
180+
        String parse(RandomAccessFile file) throws IOException {
181+
            byte b;
182+
            ArrayList<Byte> bs = new ArrayList<>();
183+
            while((b = file.readByte()) != 0) {
184+
                bs.add(b);
185+
            }
186+
            byte[] str = new byte[bs.size()];
187+
            for(int j=0; j<bs.size(); j++) {
188+
                str[j] = bs.get(j);
189+
            }
190+
            return new String(str, encoding);
163191
        }
164-
        return results;
165192
    }
166193
167-
    static ArrayList<Integer> getIntList(RandomAccessFile file) throws IOException {
168-
        ArrayList<Integer> results = new ArrayList<>();
169-
        int number = file.readShort();
170-
        for(int i=0; i<number; i++) {
171-
            int tag = (int) file.readShort();
172-
            results.add(tag);
194+
    static class ListParser<T> extends Parser<List<T>> {
195+
        Parser<T> parser;
196+
        int lenSize;
197+
198+
        ListParser(Parser<T> parser) {
199+
            this(2, parser);
200+
        }
201+
        ListParser(int lenSize, Parser<T> parser) {
202+
            this.parser = parser;
203+
            this.lenSize = lenSize;
173204
        }
174-
        return results;
175-
    }
176205
177-
    static Huffman loadHuffman(RandomAccessFile file) throws IOException {
178-
        byte b = file.readByte();
179-
        if(b == 1) {
180-
            Huffman left = loadHuffman(file);
181-
            Huffman right = loadHuffman(file);
182-
183-
            return new HuffmanTree(left, right);
184-
        } else if (b == 0) {
185-
            Log.v(TAG, "Skipping byte " + file.readByte());
186-
            return new HuffmanValue("");
187-
        } else {
188-
            ArrayList<Byte> bs = new ArrayList<>();
189-
            bs.add(b);
190-
            while((b = file.readByte()) != 0) {
191-
                bs.add(b);
206+
        @Override
207+
        List<T> parse(RandomAccessFile file) throws IOException {
208+
            List<T> results = new ArrayList<>();
209+
            int number;
210+
            switch(lenSize) {
211+
                case 1:
212+
                    number = file.readByte();
213+
                    break;
214+
                case 2:
215+
                    number = file.readShort();
216+
                    break;
217+
                default:
218+
                    number = file.readInt();
192219
            }
193-
            byte[] array = new byte[bs.size()];
194-
            for(int i=0; i<bs.size(); i++) {
195-
                array[i] = bs.get(i);
220+
            for(int i=0; i<number; i++) {
221+
                Log.d("LISTPARSER", "at position " + file.getFilePointer());
222+
                results.add(parser.parse(file));
196223
            }
197-
            return new HuffmanValue(new String(array, encoding));
224+
            return results;
198225
        }
199226
    }
200227
201-
    static String getHuffmanString(RandomAccessFile file, Huffman huffman) throws IOException {
202-
        StringBuilder b = new StringBuilder();
203-
        ArrayList<Boolean> bits = new ArrayList<>();
204-
        String c = null;
205-
        ResultDictionary.Huffman h = huffman;
206-
        while(c == null || !c.isEmpty()) {
207-
            if(h instanceof ResultDictionary.HuffmanValue) {
208-
                c = ((ResultDictionary.HuffmanValue) h).character;
209-
                //Log.v(TAG, "Huffman read: " + c);
210-
                b.append(c);
211-
                h = huffman;
212-
            } else if(h instanceof ResultDictionary.HuffmanTree) {
213-
                if(bits.isEmpty()) {
214-
                    byte by = file.readByte();
215-
                    //Log.v(TAG, "Read byte for huffman: " + by);
216-
                    for(int i = 7; i>-1; i--) {
217-
                        bits.add((by&(1<<i))!=0);
218-
                    }
219-
                    //Log.v(TAG, "Read byte for huffman: " + bits);
220-
                }
228+
    static class HuffmanParser extends Parser<Huffman> {
229+
230+
        @Override
231+
        Huffman parse(RandomAccessFile file) throws IOException {
232+
            byte b = file.readByte();
233+
            if(b == 1) {
234+
                Huffman left = new HuffmanParser().parse(file);
235+
                Huffman right = new HuffmanParser().parse(file);
221236
222-
                Boolean bo = bits.get(0);
223-
                bits.remove(0);
224-
                h = bo? ((ResultDictionary.HuffmanTree) h).right: ((ResultDictionary.HuffmanTree) h).left;
237+
                return new HuffmanTree(left, right);
238+
            } else if (b == 0) {
239+
                Log.v(TAG, "Skipping byte " + file.readByte());
240+
                return new HuffmanValue("");
241+
            } else {
242+
                ArrayList<Byte> bs = new ArrayList<>();
243+
                bs.add(b);
244+
                while((b = file.readByte()) != 0) {
245+
                    bs.add(b);
246+
                }
247+
                byte[] array = new byte[bs.size()];
248+
                for(int i=0; i<bs.size(); i++) {
249+
                    array[i] = bs.get(i);
250+
                }
251+
                return new HuffmanValue(new String(array, encoding));
225252
            }
226253
        }
227-
228-
        return b.toString();
229254
    }
230255
231-
    static void logHuffman(Huffman h, ArrayList<Boolean> address) {
232-
        if (h instanceof ResultDictionary.HuffmanValue) {
233-
            Log.v(TAG, "HUFF: " + ((ResultDictionary.HuffmanValue) h).character + " -> " + address.toString());
234-
        } else if(h instanceof ResultDictionary.HuffmanTree) {
235-
            ArrayList<Boolean> address_l = new ArrayList<>(address);
236-
            address_l.add(false);
237-
            ArrayList<Boolean> address_r = new ArrayList<>(address);
238-
            address_r.add(true);
239-
            logHuffman(((JMDict.HuffmanTree) h).left, address_l);
240-
            logHuffman(((JMDict.HuffmanTree) h).right, address_r);
256+
    static class HuffmanStringParser extends Parser<String> {
257+
        Huffman huffman;
258+
        HuffmanStringParser(Huffman huffman) {
259+
            this.huffman = huffman;
241260
        }
242-
    }
243261
244-
    static ArrayList<String> getHuffmanStringList(RandomAccessFile file, Huffman huffman) throws IOException {
245-
        ArrayList<String> results = new ArrayList<>();
246-
        int number = file.readShort();
247-
        Log.v(TAG, "huffmanStrings: " + number);
248-
        for(int i=0; i<number; i++) {
249-
            results.add(getHuffmanString(file, huffman));
262+
        @Override
263+
        String parse(RandomAccessFile file) throws IOException {
264+
            StringBuilder b = new StringBuilder();
265+
            ArrayList<Boolean> bits = new ArrayList<>();
266+
            String c = null;
267+
            ResultDictionary.Huffman h = huffman;
268+
            while(c == null || !c.isEmpty()) {
269+
                if(h instanceof ResultDictionary.HuffmanValue) {
270+
                    c = ((ResultDictionary.HuffmanValue) h).character;
271+
                    //Log.v(TAG, "Huffman read: " + c);
272+
                    b.append(c);
273+
                    h = huffman;
274+
                } else if(h instanceof ResultDictionary.HuffmanTree) {
275+
                    if(bits.isEmpty()) {
276+
                        byte by = file.readByte();
277+
                        //Log.v(TAG, "Read byte for huffman: " + by);
278+
                        for(int i = 7; i>-1; i--) {
279+
                            bits.add((by&(1<<i))!=0);
280+
                        }
281+
                        //Log.v(TAG, "Read byte for huffman: " + bits);
282+
                    }
283+
284+
                    Boolean bo = bits.get(0);
285+
                    bits.remove(0);
286+
                    h = bo? ((ResultDictionary.HuffmanTree) h).right: ((ResultDictionary.HuffmanTree) h).left;
287+
                }
288+
            }
289+
290+
            return b.toString();
250291
        }
251-
        return results;
252292
    }
253293
254-
    static<T> T searchTrie(RandomAccessFile file, long triePos, byte[] txt, TrieValsDecoder<T> decoder) throws IOException {
294+
    static<T> T searchTrie(RandomAccessFile file, long triePos, byte[] txt, TrieParser<T> decoder) throws IOException {
255295
        file.seek(triePos);
256296
        if(txt.length == 0) {
257297
            Log.v(TAG, "found trie value, reading values");

app/src/main/java/eu/lepiller/nani/dictionary/KanjiDict.java

3333
        return R.drawable.ic_nani_edrdg;
3434
    }
3535
36-
    KanjiResult getValue(RandomAccessFile file, long pos, String kanji) throws IOException {
37-
        Log.d(TAG, "getValue at " + pos);
38-
        file.seek(pos);
39-
        int stroke = file.readByte();
40-
        Log.d(TAG, "strokes: " + stroke);
41-
42-
        List<String> senses = getHuffmanStringList(file, meaningHuffman);
43-
        List<KanjiResult.Sense> meanings = new ArrayList<>();
44-
        for(String s: senses) {
45-
            meanings.add(new KanjiResult.Sense(this.getLang(), s));
36+
    class ResultParser extends Parser<KanjiResult> {
37+
        String kanji;
38+
        ResultParser(String kanji) {
39+
            this.kanji = kanji;
4640
        }
47-
        List<String> kun = getHuffmanStringList(file, readingHuffman);
48-
        List<String> on = getHuffmanStringList(file, readingHuffman);
49-
        List<String> nanori = getHuffmanStringList(file, readingHuffman);
5041
51-
        return new KanjiResult(kanji, stroke, meanings, kun, on, nanori, null, null);
42+
        @Override
43+
        KanjiResult parse(RandomAccessFile file) throws IOException {
44+
            int stroke = file.readByte();
45+
            Log.d(TAG, "strokes: " + stroke);
46+
47+
            List<String> senses = new ListParser<>(new HuffmanStringParser(meaningHuffman)).parse(file);
48+
            List<KanjiResult.Sense> meanings = new ArrayList<>();
49+
            for(String s: senses) {
50+
                meanings.add(new KanjiResult.Sense(KanjiDict.this.getLang(), s));
51+
            }
52+
            List<String> kun = new ListParser<>(new HuffmanStringParser(readingHuffman)).parse(file);
53+
            List<String> on = new ListParser<>(new HuffmanStringParser(readingHuffman)).parse(file);
54+
            List<String> nanori = new ListParser<>(new HuffmanStringParser(readingHuffman)).parse(file);
55+
56+
            return new KanjiResult(kanji, stroke, meanings, kun, on, nanori, null, null);
57+
        }
5258
    }
5359
5460
    @Override

8288
8389
                byte[] search = kanji.toLowerCase().getBytes();
8490
                file.skipBytes(4); // size
85-
                meaningHuffman = loadHuffman(file);
86-
                readingHuffman = loadHuffman(file);
91+
                meaningHuffman = new HuffmanParser().parse(file);
92+
                readingHuffman = new HuffmanParser().parse(file);
8793
                long kanjiTriePos = file.getFilePointer();
8894
8995
                Log.d(TAG, "trie pos: " + kanjiTriePos);
9096
91-
                return searchTrie(file, kanjiTriePos, search, new TrieValsDecoder<KanjiResult>() {
97+
                return searchTrie(file, kanjiTriePos, search, new TrieParser<KanjiResult>(new ResultParser(kanji)) {
9298
                    @Override
93-
                    public KanjiResult decodeVals(RandomAccessFile file1, long pos) throws IOException {
94-
                        Log.d(TAG, "decoding val");
99+
                    public void skipVals(RandomAccessFile file1, long pos) throws IOException {
95100
                        file1.seek(pos);
96-
                        return getValue(file1, file1.readInt(), kanji);
101+
                        file1.skipBytes(4);
97102
                    }
98103
99104
                    @Override
100-
                    public void skipVals(RandomAccessFile file1, long pos) throws IOException {
101-
                        file1.seek(pos);
102-
                        file1.skipBytes(4);
105+
                    void seek(RandomAccessFile file, long pos) throws IOException {
106+
                        file.seek(pos);
107+
                        file.seek(file.readInt());
103108
                    }
104109
                });
105110
            } catch (FileNotFoundException e) {

app/src/main/java/eu/lepiller/nani/dictionary/KanjiVG.java

6565
6666
                byte[] search = kanji.toLowerCase().getBytes();
6767
                file.skipBytes(4); // size
68-
                commandHuffman = loadHuffman(file);
68+
                commandHuffman = new HuffmanParser().parse(file);
6969
                long kanjiTriePos = file.getFilePointer();
7070
7171
                Log.d(TAG, "trie pos: " + kanjiTriePos);
7272
73-
                return searchTrie(file, kanjiTriePos, search, new TrieValsDecoder<KanjiResult>() {
73+
                return searchTrie(file, kanjiTriePos, search, new TrieParser<KanjiResult>(new ResultParser(kanji)) {
7474
                    @Override
75-
                    public KanjiResult decodeVals(RandomAccessFile file1, long pos) throws IOException {
76-
                        Log.d(TAG, "decoding val");
75+
                    public void skipVals(RandomAccessFile file1, long pos) throws IOException {
7776
                        file1.seek(pos);
78-
                        return getValue(file1, file1.readInt(), kanji);
77+
                        file1.skipBytes(4);
7978
                    }
8079
8180
                    @Override
82-
                    public void skipVals(RandomAccessFile file1, long pos) throws IOException {
83-
                        file1.seek(pos);
84-
                        file1.skipBytes(4);
81+
                    void seek(RandomAccessFile file, long pos) throws IOException {
82+
                        file.seek(pos);
83+
                        file.seek(file.readInt());
8584
                    }
8685
                });
8786
            } catch (FileNotFoundException e) {

241240
        }
242241
    }
243242
244-
    KanjiResult.Stroke getStroke(RandomAccessFile file) throws IOException {
245-
        String command = getHuffmanString(file, commandHuffman);
246-
        String x = getHuffmanString(file, commandHuffman);
247-
        String y = getHuffmanString(file, commandHuffman);
243+
    class StrokeParser extends Parser<KanjiResult.Stroke> {
244+
        @Override
245+
        KanjiResult.Stroke parse(RandomAccessFile file) throws IOException {
246+
            String command = new HuffmanStringParser(commandHuffman).parse(file);
247+
            String x = new HuffmanStringParser(commandHuffman).parse(file);
248+
            String y = new HuffmanStringParser(commandHuffman).parse(file);
248249
249-
        Path path = new Path();
250+
            Path path = new Path();
250251
251-
        try {
252-
            parsePath(path, command);
253-
        } catch (ParseException e) {
254-
            e.printStackTrace();
255-
            path.reset();
256-
        }
252+
            try {
253+
                parsePath(path, command);
254+
            } catch (ParseException e) {
255+
                e.printStackTrace();
256+
                path.reset();
257+
            }
257258
258-
        return new KanjiResult.Stroke(path, 109, Float.parseFloat(x), Float.parseFloat(y));
259+
            return new KanjiResult.Stroke(path, 109, Float.parseFloat(x), Float.parseFloat(y));
260+
        }
259261
    }
260262
261-
    KanjiResult getValue(RandomAccessFile file, long pos, String kanji) throws IOException {
262-
        Log.d(TAG, "getValue at " + pos);
263-
        file.seek(pos);
264-
265-
        List<String> elements = getStringList(file);
266-
        List<KanjiResult.Stroke> strokes = new ArrayList<>();
267-
        int number = file.readShort();
268-
        for(int i=0; i<number; i++) {
269-
            strokes.add(getStroke(file));
263+
    class ResultParser extends Parser<KanjiResult> {
264+
        String kanji;
265+
        ResultParser(String kanji) {
266+
            this.kanji = kanji;
270267
        }
268+
        @Override
269+
        KanjiResult parse(RandomAccessFile file) throws IOException {
270+
            List<String> elements = new ListParser<>(new StringParser()).parse(file);
271+
            List<KanjiResult.Stroke> strokes = new ListParser<>(new StrokeParser()).parse(file);
271272
272-
        return new KanjiResult(kanji, -1, null, null, null, null, elements, strokes);
273+
            return new KanjiResult(kanji, -1, null, null, null, null, elements, strokes);
274+
        }
273275
    }
274276
}

app/src/main/java/eu/lepiller/nani/dictionary/RadicalDict.java

3838
    private void fillStrokeCount(RandomAccessFile file) throws IOException{
3939
        int size = file.readShort();
4040
        for(int i=0; i<size; i++) {
41-
            String kanji = getString(file);
41+
            String kanji = new StringParser().parse(file);
4242
            int stroke = file.readByte();
4343
            strokeCount.put(kanji, stroke);
4444
        }

4747
    private void fillRadicalStrokeCount(RandomAccessFile file) throws IOException {
4848
        int size = file.readShort();
4949
        for(int i=0; i<size; i++) {
50-
            String radical = getString(file);
50+
            String radical = new StringParser().parse(file);
5151
            int stroke = file.readByte();
5252
5353
            radicalStrokeCount.put(radical, stroke);

5757
    private void fillRadicals(RandomAccessFile file) throws IOException {
5858
        int size = file.readShort();
5959
        for(int i=0; i<size; i++) {
60-
            String radical = getString(file);
61-
            String kanji = getString(file);
60+
            String radical = new StringParser().parse(file);
61+
            String kanji = new StringParser().parse(file);
6262
            List<String> lst = new ArrayList<>();
6363
            for(String s: kanji.split("")) {
6464
                if(s.compareTo("") != 0) {

app/src/main/java/eu/lepiller/nani/dictionary/ResultDictionary.java

99
import java.util.ArrayList;
1010
import java.util.Arrays;
1111
import java.util.Collections;
12+
import java.util.List;
1213
1314
import eu.lepiller.nani.result.Result;
1415

2930
        meaningHuffman = null;
3031
    }
3132
32-
    private Result getValue(RandomAccessFile file, long pos) throws IOException {
33-
        file.seek(pos);
34-
        Log.v(TAG, "Getting value at " + pos);
35-
        ArrayList<String> kanjis = getHuffmanStringList(file, kanjiHuffman);
36-
37-
        Log.v(TAG, "Getting readings");
38-
        ArrayList<Result.Reading> readings = new ArrayList<>();
39-
        int reading_number = file.readShort();
40-
        Log.v(TAG, reading_number + " readings.");
41-
        for(int i=0; i<reading_number; i++) {
42-
            ArrayList<String> reading_kanjis = getStringList(file);
33+
    class ReadingParser extends Parser<Result.Reading> {
34+
        @Override
35+
        Result.Reading parse(RandomAccessFile file) throws IOException {
36+
            List<String> reading_kanjis = new ListParser<>(new StringParser()).parse(file);
4337
            Log.v(TAG, "kanjis: " + reading_kanjis);
44-
            ArrayList<String> reading_infos = getStringList(file);
38+
            List<String> reading_infos = new ListParser<>(new StringParser()).parse(file);
4539
            Log.v(TAG, "infos: " + reading_kanjis);
46-
            ArrayList<String> reading_readings = getHuffmanStringList(file, readingHuffman);
47-
            Result.Reading r = new Result.Reading(reading_kanjis, reading_infos, reading_readings);
48-
            readings.add(r);
40+
            List<String> reading_readings = new ListParser<>(new HuffmanStringParser(readingHuffman)).parse(file);
41+
            return new Result.Reading(reading_kanjis, reading_infos, reading_readings);
4942
        }
43+
    }
5044
51-
        ArrayList<Result.Sense> senses = new ArrayList<>();
52-
        int meaning_number = file.readShort();
53-
        Log.v(TAG, meaning_number + " meanings.");
54-
        for(int i=0; i<meaning_number; i++) {
55-
            ArrayList<String> sense_references = getStringList(file);
56-
            ArrayList<String> sense_limits = getStringList(file);
57-
            ArrayList<Result.Source> sense_sources = new ArrayList<>();
58-
            int source_number = file.readShort();
59-
            for(int j=0; j<source_number; j++) {
60-
                ArrayList<String> source_content = getStringList(file);
61-
                boolean source_wasei = file.read() != 0;
62-
                String source_language = getString(file);
63-
                sense_sources.add(new Result.Source(source_content, source_wasei, source_language));
64-
            }
65-
            ArrayList<String> sense_infos = getHuffmanStringList(file, meaningHuffman);
66-
            ArrayList<String> sense_glosses = getHuffmanStringList(file, meaningHuffman);
67-
            String sense_language = getString(file);
68-
            senses.add(new Result.Sense(sense_references, sense_limits, sense_infos, sense_sources,
69-
                    sense_glosses, sense_language));
45+
    static class SourceParser extends Parser<Result.Source> {
46+
        @Override
47+
        Result.Source parse(RandomAccessFile file) throws IOException {
48+
            List<String> source_content = new ListParser<>(new StringParser()).parse(file);
49+
            boolean source_wasei = file.read() != 0;
50+
            String source_language = new StringParser().parse(file);
51+
            return new Result.Source(source_content, source_wasei, source_language);
7052
        }
53+
    }
7154
72-
        int score = file.readChar();
73-
        return new Result(kanjis, readings, senses, score);
55+
    class SenseParser extends Parser<Result.Sense> {
56+
        @Override
57+
        Result.Sense parse(RandomAccessFile file) throws IOException {
58+
            List<String> sense_references = new ListParser<>(new StringParser()).parse(file);
59+
            List<String> sense_limits = new ListParser<>(new StringParser()).parse(file);
60+
            List<Result.Source> sense_sources = new ListParser<>(new SourceParser()).parse(file);
61+
            List<String> sense_infos = new ListParser<>(new HuffmanStringParser(meaningHuffman)).parse(file);
62+
            List<String> sense_glosses = new ListParser<>(new HuffmanStringParser(meaningHuffman)).parse(file);
63+
            String sense_language = new StringParser().parse(file);
64+
            return new Result.Sense(sense_references, sense_limits, sense_infos, sense_sources,
65+
                    sense_glosses, sense_language);
66+
        }
7467
    }
7568
76-
    private ArrayList<Integer> getValues(RandomAccessFile file, long triePos) throws IOException {
77-
        file.seek(triePos);
78-
        Log.v(TAG, "Getting values");
79-
        int valuesLength = file.readShort();
80-
        ArrayList<Integer> results = new ArrayList<>();
81-
        ArrayList<Integer> exactResults = new ArrayList<>();
69+
    class ResultParser extends Parser<Result> {
70+
        @Override
71+
        Result parse(RandomAccessFile file) throws IOException {
72+
            List<String> kanjis = new ListParser<>(new HuffmanStringParser(kanjiHuffman)).parse(file);
8273
83-
        Log.v(TAG, "Number of values: " + valuesLength);
84-
        for(int i=0; i<valuesLength; i++) {
85-
            exactResults.add(file.readInt());
86-
        }
74+
            Log.v(TAG, "Getting readings");
75+
            List<Result.Reading> readings = new ListParser<>(new ReadingParser()).parse(file);
8776
88-
        int transitionLength = file.readByte();
89-
        Log.v(TAG, "Number of transitions: " + transitionLength);
90-
        int[] others = new int[transitionLength];
91-
        for(int i=0; i<transitionLength; i++) {
92-
            file.skipBytes(1);
93-
            others[i] = file.readInt();
94-
        }
77+
            List<Result.Sense> senses = new ListParser<>(new SenseParser()).parse(file);
9578
96-
        for(int i=0; i<transitionLength; i++) {
97-
            results.addAll(getValues(file, others[i]));
79+
            int score = file.readChar();
80+
            return new Result(kanjis, readings, senses, score);
9881
        }
82+
    }
9983
100-
        Collections.sort(results);
101-
        Collections.sort(exactResults);
84+
    static class ValuesParser extends Parser<List<Integer>> {
85+
        @Override
86+
        List<Integer> parse(RandomAccessFile file) throws IOException {
87+
            ArrayList<Integer> results = new ArrayList<>();
10288
103-
        Log.v(TAG, "exact result size: " + exactResults.size() + ", result size: " + results.size());
104-
        Log.v(TAG, "exact: " + Arrays.toString(exactResults.toArray()) + ", others: " + Arrays.toString(results.toArray()));
105-
        exactResults.addAll(results);
106-
        return exactResults;
107-
    }
89+
            Log.v(TAG, "Getting values");
90+
            List<Integer> exactResults = new ListParser<>(new Parser<Integer>() {
91+
                @Override
92+
                Integer parse(RandomAccessFile file) throws IOException {
93+
                    return file.readInt();
94+
                }
95+
            }).parse(file);
10896
109-
    private ArrayList<Integer> searchTrie(RandomAccessFile file, long triePos, byte[] txt) throws IOException {
110-
        return searchTrie(file, triePos, txt, new TrieValsDecoder<ArrayList<Integer>>() {
111-
            @Override
112-
            public ArrayList<Integer> decodeVals(RandomAccessFile file, long pos) throws IOException {
113-
                return getValues(file, pos);
97+
            List<Integer> others = new ListParser<>(1, new Parser<Integer>() {
98+
                @Override
99+
                Integer parse(RandomAccessFile file) throws IOException {
100+
                    file.skipBytes(1);
101+
                    return file.readInt();
102+
                }
103+
            }).parse(file);
104+
105+
            for(Integer pos: others) {
106+
                file.seek(pos);
107+
                results.addAll(new ValuesParser().parse(file));
114108
            }
115109
110+
            Collections.sort(results);
111+
            Collections.sort(exactResults);
112+
113+
            Log.v(TAG, "exact result size: " + exactResults.size() + ", result size: " + results.size());
114+
            Log.v(TAG, "exact: " + Arrays.toString(exactResults.toArray()) + ", others: " + Arrays.toString(results.toArray()));
115+
            exactResults.addAll(results);
116+
            return exactResults;
117+
        }
118+
    }
119+
120+
    private List<Integer> searchTrie(RandomAccessFile file, long triePos, byte[] txt) throws IOException {
121+
        return searchTrie(file, triePos, txt, new TrieParser<List<Integer>>(new ValuesParser()) {
116122
            @Override
117123
            public void skipVals(RandomAccessFile file, long pos) throws IOException {
118124
                file.seek(pos);

123129
        });
124130
    }
125131
126-
    ArrayList<Result> search(String text) throws IncompatibleFormatException {
132+
    List<Result> search(String text) throws IncompatibleFormatException {
127133
        if (isDownloaded()) {
128134
            try {
129135
                RandomAccessFile file = new RandomAccessFile(getFile(), "r");

160166
                Log.v(TAG, "reading: " + readingTriePos);
161167
                Log.v(TAG, "meaning: " + meaningTriePos);
162168
163-
                kanjiHuffman = loadHuffman(file);
164-
                readingHuffman = loadHuffman(file);
165-
                meaningHuffman = loadHuffman(file);
169+
                kanjiHuffman = new HuffmanParser().parse(file);
170+
                readingHuffman = new HuffmanParser().parse(file);
171+
                meaningHuffman = new HuffmanParser().parse(file);
166172
167173
                logHuffman(readingHuffman, new ArrayList<>());
168174
169175
                // Search in Japanese, by kanji and reading
170176
                Log.d(TAG, "search: by kanji and reading");
171-
                ArrayList<Integer> results = searchTrie(file, kanjiTriePos, search);
172-
                ArrayList<Integer> readingResults = searchTrie(file, readingTriePos, search);
177+
                List<Integer> results = searchTrie(file, kanjiTriePos, search);
178+
                List<Integer> readingResults = searchTrie(file, readingTriePos, search);
173179
                if(results != null && readingResults != null)
174180
                    results.addAll(readingResults);
175181
                else if (results == null)

185191
186192
                Log.d(TAG, results.size() + " result(s)");
187193
188-
                ArrayList<Result> r = new ArrayList<>();
189-
                ArrayList<Integer> uniqResults = new ArrayList<>();
194+
                List<Result> r = new ArrayList<>();
195+
                List<Integer> uniqResults = new ArrayList<>();
190196
                for(Integer i: results) {
191197
                    if(!uniqResults.contains(i))
192198
                        uniqResults.add(i);

195201
                Log.v(TAG, Arrays.toString(uniqResults.toArray()));
196202
197203
                int num = 0;
198-
                for(int i: uniqResults) {
204+
                for(int pos: uniqResults) {
199205
                    if(num > 10)
200206
                        break;
201207
                    num++;
202-
                    r.add(getValue(file, i));
208+
                    file.seek(pos);
209+
                    r.add(new ResultParser().parse(file));
203210
                }
204211
                return r;
205212
            } catch (FileNotFoundException e) {

app/src/main/java/eu/lepiller/nani/dictionary/WadokuPitchDictionary.java

2929
3030
    private String findPitch(String kanji, String reading, RandomAccessFile file) throws IOException {
3131
        String concat = kanji + reading;
32-
        return searchTrie(file, triePos, concat.getBytes(), new TrieValsDecoder<String>() {
33-
            @Override
34-
            public String decodeVals(RandomAccessFile file, long pos) throws IOException {
35-
                file.seek(pos);
36-
                Log.d(TAG, "decoding at " + pos);
37-
                return getHuffmanString(file, huffman);
38-
            }
39-
32+
        return searchTrie(file, triePos, concat.getBytes(), new TrieParser<String>(new HuffmanStringParser(huffman)) {
4033
            @Override
4134
            public void skipVals(RandomAccessFile file, long pos) throws IOException {
4235
                file.seek(pos);
43-
                getHuffmanString(file, huffman);
36+
                new HuffmanStringParser(huffman).parse(file);
4437
            }
4538
        });
4639
    }

6255
                // number of entries
6356
                file.readInt();
6457
65-
                huffman = loadHuffman(file);
58+
                huffman = new HuffmanParser().parse(file);
6659
                logHuffman(huffman, new ArrayList<>());
6760
6861
                triePos = file.getFilePointer();

app/src/main/java/eu/lepiller/nani/result/Result.java

1414
1515
public class Result {
1616
    public static class Source {
17-
        private final ArrayList<String> content;
17+
        private final List<String> content;
1818
        private final boolean wasei;
1919
        private final String language;
2020
21-
        public Source(ArrayList<String> content, boolean wasei, String language) {
21+
        public Source(List<String> content, boolean wasei, String language) {
2222
            this.content = content;
2323
            this.wasei = wasei;
2424
            this.language = language;

2626
    }
2727
2828
    public static class Sense {
29-
        private final ArrayList<String> references, limits, infos, glosses;
29+
        private final List<String> references, limits, infos, glosses;
3030
        private final String language;
31-
        private final ArrayList<Source> sources;
31+
        private final List<Source> sources;
3232
33-
        public Sense(ArrayList<String> references, ArrayList<String> limits, ArrayList<String> infos,
34-
                     ArrayList<Source> sources, ArrayList<String> glosses,
33+
        public Sense(List<String> references, List<String> limits, List<String> infos,
34+
                     List<Source> sources, List<String> glosses,
3535
                     String language) {
3636
            this.references = references;
3737
            this.limits = limits;

4141
            this.language = language;
4242
        }
4343
44-
        public ArrayList<String> getGlosses() {
44+
        public List<String> getGlosses() {
4545
            return glosses;
4646
        }
4747

5555
    }
5656
5757
    public static class Reading {
58-
        private final ArrayList<String> kanjis, infos, readings, pitches;
58+
        private final List<String> kanjis, infos, readings, pitches;
5959
60-
        public Reading(ArrayList<String> kanjis, ArrayList<String> infos, ArrayList<String> readings) {
60+
        public Reading(List<String> kanjis, List<String> infos, List<String> readings) {
6161
            this.kanjis = kanjis;
6262
            this.infos = infos;
6363
            this.readings = readings;

7777
        }
7878
    }
7979
80-
    private final ArrayList<String> kanjis;
81-
    private final ArrayList<Reading> readings;
82-
    private final ArrayList<Sense> senses;
80+
    private final List<String> kanjis;
81+
    private final List<Reading> readings;
82+
    private final List<Sense> senses;
8383
    private final int score;
8484
85-
    public Result(ArrayList<String> kanjis, ArrayList<Reading> readings, ArrayList<Sense> senses, int score) {
85+
    public Result(List<String> kanjis, List<Reading> readings, List<Sense> senses, int score) {
8686
        this.kanjis = kanjis;
8787
        this.readings = readings;
8888
        this.senses = senses;

9696
        return k;
9797
    }
9898
99-
    public ArrayList<String> getAlternatives() {
99+
    public List<String> getAlternatives() {
100100
        return kanjis;
101101
    }
102102
103-
    public ArrayList<Sense> getSenses() {
103+
    public List<Sense> getSenses() {
104104
        return senses;
105105
    }
106106
107-
    public ArrayList<Reading> getReadings() {
107+
    public List<Reading> getReadings() {
108108
        return readings;
109109
    }
110110
111111
    public String getReading() {
112112
        String reading = "";
113113
        if(readings.size() > 0) {
114-
            ArrayList<String> rs = readings.get(0).readings;
114+
            List<String> rs = readings.get(0).readings;
115115
            if(rs.size() > 0)
116116
                reading = rs.get(0);
117117
        }

126126
127127
    public String getPitch() {
128128
        if(readings.size() > 0) {
129-
            ArrayList<String> pitches = readings.get(0).pitches;
129+
            List<String> pitches = readings.get(0).pitches;
130130
            if(pitches.size() > 0)
131131
                return pitches.get(0);
132132
        }

app/src/main/java/eu/lepiller/nani/views/KanjiStrokeView.java

9494
        int color;
9595
        int i = 0;
9696
        int m = strokes.size();
97-
        paint.setTextSize((float)size/24);
98-
        paint.setStyle(Paint.Style.STROKE);
97+
        paint.setTextSize((float)size/12);
9998
        for(KanjiResult.Stroke s: strokes) {
10099
            color = getColor(i, m, scheme, defaultColor);
101100
            paint.setColor(color);
102101
103102
            // Draw number
104103
            paint.setStrokeWidth(2);
105-
            canvas.drawText(String.valueOf(i),s.getNumX() / 109 * size, s.getNumY() / 109 * size, paint);
104+
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
105+
            canvas.drawText(String.valueOf(i+1),s.getNumX() / 109 * size, s.getNumY() / 109 * size, paint);
106106
107107
            // Draw stroke
108108
            path.set(s.getPath());

111111
            path.transform(matrix);
112112
113113
            paint.setStrokeWidth((float)size/32);
114+
            paint.setStyle(Paint.Style.STROKE);
114115
            canvas.drawPath(path, paint);
115116
116117
            i++;

app/src/main/res/values/dimens.xml

11
<resources>
22
    <dimen name="fab_margin">16dp</dimen>
33
    <dimen name="title_size">32sp</dimen>
4-
    <dimen name="huge_kanji_size">64sp</dimen>
4+
    <dimen name="huge_kanji_size">128sp</dimen>
55
    <dimen name="subtitle_size">24sp</dimen>
66
    <dimen name="normal_size">18sp</dimen>
77
    <dimen name="text_margin">16dp</dimen>