package yuku.alkitab.yes1; import android.util.Log; import yuku.afw.D; import yuku.alkitab.model.Book; import yuku.alkitab.model.FootnoteEntry; import yuku.alkitab.model.PericopeBlock; import yuku.alkitab.model.SingleChapterVerses; import yuku.alkitab.model.XrefEntry; import yuku.alkitab.base.storage.OldVerseTextDecoder; import yuku.alkitab.base.storage.VerseTextDecoder; import yuku.alkitab.util.Ari; import yuku.alkitab.io.BibleReader; import yuku.bintex.BintexReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; public class Yes1Reader implements BibleReader { private static final String TAG = Yes1Reader.class.getSimpleName(); private RandomAccessFile f; private boolean initted = false; private VerseTextDecoder verseTextDecoder; private long text_baseOffset; private long pericopeBlock_baseOffset; private String locale; private String shortName; private String longName; private String description; private int book_count; private int has_pericopes = 0; // default ga ada private int encoding = 1; // 1 = ascii; 2 = utf-8; private Yes1PericopeIndex pericopeIndex_; static class Yes1SingleChapterVerses extends SingleChapterVerses { private final String[] verses; public Yes1SingleChapterVerses(String[] verses) { this.verses = verses; } @Override public String getVerse(int verse_0) { return verses[verse_0]; } @Override public int getVerseCount() { return verses.length; } } public Yes1Reader(String filename) throws IOException { this.f = new RandomAccessFile(filename, "r"); } /** * @return size of section */ private int skipUntilSection(String section) throws Exception { f.seek(8); // setelah header while (true) { String sectionName = readSectionName(f); if (sectionName == null || sectionName.equals("____________")) { // sudah mencapai EOF. Maka kasih tau seksi ini ga ada. Log.d(TAG, "Seksi tidak ditemukan: " + section); return -1; } int ukuran = readSectionSize(f); if (sectionName.equals(section)) { return ukuran; } else { Log.d(TAG, "seksi dilewati: " + sectionName); f.skipBytes(ukuran); } } } private synchronized void init() throws Exception { if (initted) { return; } initted = true; f.seek(0); // cek header { byte[] buf = new byte[8]; f.read(buf); if (!Arrays.equals(buf, new byte[] {(byte) 0x98, 0x58, 0x0d, 0x0a, 0x00, 0x5d, (byte) 0xe0, 0x01})) { throw new RuntimeException("Header ga betul. Ketemunya: " + Arrays.toString(buf)); } } readVersionInfo(); skipUntilSection("teks________"); text_baseOffset = f.getFilePointer(); Log.d(TAG, "text_baseOffset = " + text_baseOffset); } @Override public String getLocale() { return locale; } @Override public String getShortName() { try { init(); return shortName; } catch (Exception e) { Log.e(TAG, "init error", e); return ""; } } @Override public String getLongName() { try { init(); return longName; } catch (Exception e) { Log.e(TAG, "init error", e); return ""; } } public void readVersionInfo() { try { int size = skipUntilSection("infoEdisi___"); byte[] buf = new byte[size]; f.read(buf); BintexReader in = new BintexReader(new ByteArrayInputStream(buf)); String nama = null; label: while (true) { String key = in.readShortString(); switch (key) { case "versi": int versi = in.readInt(); if (versi > 2) throw new RuntimeException("Version number in version info: " + versi + " not supported"); break; case "format": // ini deprecated, sudah diganti jadi "versi". Tapi harus tetap dikenali, kalo ga akan crash. in.readInt(); // buang break; case "nama": nama = in.readShortString(); break; case "shortName": this.shortName = in.readShortString(); break; case "shortTitle": this.shortName = in.readShortString(); break; case "judul": this.longName = in.readShortString(); break; case "keterangan": this.description = in.readLongString(); break; case "nkitab": this.book_count = in.readInt(); break; case "perikopAda": this.has_pericopes = in.readInt(); break; case "encoding": this.encoding = in.readInt(); break; case "locale": this.locale = in.readShortString(); break; case "end": break label; default: throw new RuntimeException("got unknown key in version info: " + key); } } Log.d(TAG, "readVersionInfo selesai, nama=" + nama + " judul=" + longName + " book_count=" + book_count); } catch (Exception e) { Log.e(TAG, "readVersionInfo error", e); } } @Override public Book[] loadBooks() { try { Log.d(TAG, "bacaInfoKitab dipanggil"); init(); Book[] res = new Book[256]; int ukuran = skipUntilSection("infoKitab___"); byte[] buf = new byte[ukuran]; f.read(buf); BintexReader in = new BintexReader(new ByteArrayInputStream(buf)); Log.d(TAG, "akan membaca " + this.book_count + " kitab"); for (int kitabIndex = 0; kitabIndex < this.book_count; kitabIndex++) { Yes1Book k = new Yes1Book(); // kalau true, berarti ini kitab NULL boolean kosong = false; label: for (int keyKe = 0; ; keyKe++) { String key = in.readShortString(); switch (key) { case "versi": int versi = in.readInt(); if (versi > 2) throw new RuntimeException("Versi Kitab (lebih dari 2): " + versi + " tidak dikenal"); break; case "pos": k.bookId = in.readInt(); break; case "nama": k.shortName = in.readShortString(); break; case "judul": k.shortName = in.readShortString(); break; case "npasal": k.chapter_count = in.readInt(); break; case "nayat": k.verse_counts = new int[k.chapter_count]; for (int i = 0; i < k.chapter_count; i++) { k.verse_counts[i] = in.readUint8(); } break; case "ayatLoncat": // ignored in.readInt(); break; case "pdbBookNumber": // ignored in.readInt(); break; case "pasal_offset": k.chapter_offsets = new int[k.chapter_count + 1]; // harus ada +1nya kalo YesPembaca for (int i = 0; i < k.chapter_offsets.length; i++) { k.chapter_offsets[i] = in.readInt(); } break; case "encoding": // ignored, deprecated in.readInt(); break; case "offset": k.offset = in.readInt(); break; case "end": // sudah end sebelum baca apapun? if (keyKe == 0) kosong = true; break label; default: Log.w(TAG, "ada key ga dikenal di kitab " + k + " di infoKitab: " + key); break label; } } if (!kosong) { if (k.bookId < 0 || k.bookId >= res.length) { throw new RuntimeException("ada kitabPos yang sangat besar: " + k.bookId); } res[k.bookId] = k; } } // truncate res supaya ukuran arraynya jangan terlalu besar, sampe non-null terakhir int lenBaru = 0; for (int i = 0; i < res.length; i++) { if (res[i] != null) lenBaru = i + 1; } Book[] resBaru = new Book[lenBaru]; System.arraycopy(res, 0, resBaru, 0, lenBaru); res = resBaru; return res; } catch (Exception e) { Log.e(TAG, "bacaInfoKitab error", e); return null; } } @Override public Yes1SingleChapterVerses loadVerseText(Book book, int pasal_1, boolean janganPisahAyat, boolean hurufKecil) { // init pembacaDecoder if (verseTextDecoder == null) { if (encoding == 1) { verseTextDecoder = new OldVerseTextDecoder.Ascii(); } else if (encoding == 2) { verseTextDecoder = new OldVerseTextDecoder.Utf8(); } else { Log.e(TAG, "Encoding " + encoding + " not recognized!"); verseTextDecoder = new OldVerseTextDecoder.Ascii(); } Log.d(TAG, "encoding " + encoding + " so decoder is " + verseTextDecoder.getClass().getName()); } try { init(); if (pasal_1 > book.chapter_count) { return null; } Yes1Book yesBook = (Yes1Book) book; long seekTo = text_baseOffset; seekTo += yesBook.offset; seekTo += yesBook.chapter_offsets[pasal_1 - 1]; f.seek(seekTo); int length = yesBook.chapter_offsets[pasal_1] - yesBook.chapter_offsets[pasal_1 - 1]; if (D.EBUG) Log.d(TAG, "muatTeks kitab=" + book.shortName + " pasal_1=" + pasal_1 + " offset=" + yesBook.offset + " offset pasal: " + yesBook.chapter_offsets[pasal_1-1]); byte[] ba = new byte[length]; f.read(ba); if (janganPisahAyat) { return new Yes1SingleChapterVerses(new String[] {verseTextDecoder.makeIntoSingleString(ba, hurufKecil)}); } else { return new Yes1SingleChapterVerses(verseTextDecoder.separateIntoVerses(ba, hurufKecil)); } } catch (Exception e) { Log.e(TAG, "muatTeks error", e); return null; } } @SuppressWarnings("deprecation") static String readSectionName(RandomAccessFile f) throws IOException { byte[] buf = new byte[12]; int read = f.read(buf); return read <= 0? null: new String(buf, 0); } static int readSectionSize(RandomAccessFile f) throws IOException { return f.readInt(); } private Yes1PericopeIndex loadPericopeIndex() { if (pericopeIndex_ != null) { return pericopeIndex_; } long wmulai = System.currentTimeMillis(); try { init(); if (has_pericopes == 0) { return null; } int ukuran = skipUntilSection("perikopIndex"); if (ukuran < 0) { Log.d(TAG, "Tidak ada seksi 'perikopIndex'"); return null; } BintexReader in = new BintexReader(new RandomInputStream(f)); pericopeIndex_ = Yes1PericopeIndex.read(in); return pericopeIndex_; } catch (Exception e) { Log.e(TAG, "bacaIndexPerikop error", e); return null; } finally { Log.d(TAG, "Muat index perikop butuh ms: " + (System.currentTimeMillis() - wmulai)); } } @Override public int loadPericope(int kitab, int pasal, int[] xari, PericopeBlock[] xblok, int max) { try { init(); if (D.EBUG) Log.d(TAG, "muatPerikop dipanggil untuk kitab=" + kitab + " pasal_1=" + pasal); Yes1PericopeIndex pericopeIndex = loadPericopeIndex(); if (pericopeIndex == null) { return 0; // ga ada perikop! } int ariMin = Ari.encode(kitab, pasal, 0); int ariMax = Ari.encode(kitab, pasal + 1, 0); int pertama = pericopeIndex.findFirst(ariMin, ariMax); if (pertama == -1) { return 0; } int kini = pertama; int res = 0; if (pericopeBlock_baseOffset != 0) { f.seek(pericopeBlock_baseOffset); } else { skipUntilSection("perikopBlok_"); pericopeBlock_baseOffset = f.getFilePointer(); } BintexReader in = new BintexReader(new RandomInputStream(f)); while (true) { int ari = pericopeIndex.getAri(kini); if (ari >= ariMax) { // habis. Uda ga relevan break; } Yes1PericopeBlock pericopeBlock = pericopeIndex.getBlock(in, kini); kini++; if (res < max) { xari[res] = ari; xblok[res] = pericopeBlock; res++; } else { break; } } return res; } catch (Exception e) { Log.e(TAG, "gagal muatPerikop", e); return 0; } } /** * Mungkin null kalo ga ada. */ @Override public String getDescription() { try { init(); return description; } catch (Exception e) { Log.e(TAG, "init error", e); return null; } } @Override public XrefEntry getXrefEntry(int arif) { // YES1 file cannot contain xref entries. return null; } @Override public FootnoteEntry getFootnoteEntry(final int arif) { // YES1 file cannot contain footnote entries. return null; } }