/* Bible Plus A Bible Reader for Blackberry Copyright (C) 2010 Yohanes Nugroho This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Yohanes Nugroho (yohanes@gmail.com) */ package com.compactbyte.bibleplus.reader; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Represents a book in the bible * * */ public class BookInfo { /** * Factory method for creating BookInfo * * @param bible * the bible object * @param data * array data for this object * @param offset * the position of the data that should be read by this object * @param book_pos * the position of the book in the bible * @return BookInfo object */ static BookInfo createFromData(BiblePlusPDB bible, byte data[], int offset, int book_pos) { BookInfo bi = new BookInfo(bible); int idx = 0; bi.bookPosition = book_pos; bi.bookNum = Util.readShort(data, offset + idx); idx += 2; bi.bookIndex = Util.readShort(data, offset + idx); idx += 2; bi.totalBookRec = Util.readShort(data, offset + idx); if (bi.bookIndex + bi.totalBookRec > bible.getHeader().getRecordCount()) { return null; } idx += 2; bi.simpleName = new byte[8]; System.arraycopy(data, offset + idx, bi.simpleName, 0, 8); idx += 8; bi.complexName = new byte[32]; System.arraycopy(data, offset + idx, bi.complexName, 0, 32); return bi; } /** * The Book number * PDB Files usually have standard book numbering (10 = Genesis, etc) * Note: some PDB files are created with non-standard book numbering */ private int bookNum; /* * index record inside PDB file */ private int bookIndex; private int totalBookRec; /** * The position of the book in the bible file. * For example Matthew can be in position 0 for Bible files with new testament only * But it can be position 39 for Protestant Bible with old and new testament */ private int bookPosition; /** * The sort name of the book, for example GEN for Genesis */ private byte[] simpleName; /** * The complete/long name of the book */ private byte[] complexName; /** * Reference to the bible object */ private BiblePlusPDB bible; /** * total number of chapter in this book */ int totalChapters; /** * flag to indicate whether this book has been opened or not */ boolean bookOpened; // default initialized to false; /*** * These constants are special numbers to indicate "tags" * */ private final int bookTextType = 0xFFFF; private final int chapTextType = 0xFFFE; private final int descTextType = 0xFFFD; private final int versTextType = 0xFFFC; private byte index_data[]; private byte data[]; private int totalVersesAcc[]; private int totalChapterCharsAcc[]; private int totalVerseCharsAcc[]; /** * Temporary StringBuffer * Note: Blackberry doesn't have StringBuilder class */ // <yuku> // <original> // private StringBuffer[] sb; // </original> // </yuku> /** * lookup table for byteshifted PDB */ private int shiftLookup[] = { 0, 3, 2, 1 }; private int verseShiftLookup[] = { 10, 4, 6, 8 }; /** * Construct this BookInfo. This is not public, will only be called by createFromData * * @param bible * object that creates this book */ private BookInfo(BiblePlusPDB bible) { this.bible = bible; // <yuku> // <original> // // /** // * One string buffer for each "tag" // */ // sb = new StringBuffer[4]; // // for (int i = 0; i < 4; i++) { // sb[i] = new StringBuffer(); // } // // </original> // </yuku> } /** * Set references to null. Not really necessary, just for * debugging. If we set everything to null it makes it easy to * detect in case we try to access something that we've thrown * away (there will be null pointer exception). */ void close() { index_data = null; data = null; totalVersesAcc = null; totalChapterCharsAcc = null; totalVerseCharsAcc = null; } /** * Get book numbefr. PDB Files usually have standard book numbering (10 = Genesis, etc) * Note: some PDB files are created with non-standard book numbering. * * @return the book number */ public int getBookNumber() { return bookNum; } /** * Get number of chapters in this book * * @return chapter count */ public int getChapterCount() { return totalChapters; } /** * Get complete verse content for this chapter/verse * * @param chapter * chapter number * @param verse * verse number * @return null on error (book not opened, invalid * chapter/verse) or the content complete content of the verse * (for each "Tags"). Index 0 contains verse (always exist), * index 1 contains pericope title (may not exist, if not * exist it will be empty string), Index 2 contains chapter * title (if exist, this will only appear in verse 1 of a * chapter), Index 3 contains book title (if exists, this will * only appear in chapter 1 verse 1 of a book. * */ public String[] getCompleteVerse(int chapter, int verse) { if (!bookOpened) { return null; } if (chapter < 0 || chapter > getChapterCount()) { return null; } if (verse < 0 || verse > getVerseCount(chapter)) { return null; } if (bible.isByteShifted()) { return getVerseByteShifted(chapter, verse); } // <yuku> // <original> // for (int i = 0; i < 4; i++) { // sb[i].setLength(0); // } // </original> // </yuku> // <yuku> @SuppressWarnings("unchecked") ArrayList<String>[] words = new ArrayList[4]; words[0] = new ArrayList<>(); words[1] = new ArrayList<>(); words[2] = new ArrayList<>(); words[3] = new ArrayList<>(); // </yuku> int sbpos = 0; // verse int verseStart = getVerseStart(chapter, verse); // System.out.println("versestart " + verseStart); int verseLength = getVerseLength(chapter, verse); // System.out.println("Start " + verseStart + " length " + verseLength); int idx = verseStart * 2; for (int i = 0; i < verseLength; i++) { int decWordNum = (data[idx] & 0xff) * 256 + (data[idx + 1] & 0xff); idx += 2; // System.out.println("decWordNum " + decWordNum); int pos = bible.getWordPos(decWordNum); // System.out.println("wordpos " + pos); // System.out.println("wordlength " + bible.getWordLength(pos)); // System.out.println("wordindex " + wordIndex); int[] r = bible.getRepeat(pos, decWordNum); if (r != null) { for (final int t : r) { // if (r[j]<0) break; // System.out.print("rj " + r[j]+" "); if (t == bookTextType || t == chapTextType || t == descTextType || t == versTextType) { sbpos = t - versTextType; // System.out.print("switch sbpos " + sbpos+" "); continue; } String word = bible.getWord(t); // System.out.println("Word " + word); // <yuku> // <original> // addSepChar(sb[sbpos], word); // sb[sbpos].append(word); // </original> words[sbpos].add(word); // </yuku> } } else { String word = bible.getWord(decWordNum); // <yuku> // <original> // addSepChar(sb[sbpos], word); // sb[sbpos].append(word); // </original> words[sbpos].add(word); // </yuku> // System.out.println("r is null" + word); } } // <yuku> String sepChar = bible.getSepChar(); String[] res = new String[4]; res[0] = stringFromWords(words[0], sepChar); res[1] = stringFromWords(words[1], sepChar); res[2] = stringFromWords(words[2], sepChar); res[3] = stringFromWords(words[3], sepChar); // </yuku> // <yuku> // <original> // return sb; // </original> return res; // </yuku> } // <yuku> private String stringFromWords(List<String> words, String sepChar) { if (words == null) return ""; if (words.size() == 0) return ""; StringBuilder sb = new StringBuilder(); // put spaces in all, except: // word + ) // word + ] // word + } // word + . // word + , // word + : // word + ; // word + ? // word + ! // word + - // word + [cjk 0x2e80..0x9fff] // ( + word // [ + word // { + word // - + word // [cjk 0x2e80..0x9fff] + word String prev = null; String cur; for (int i = 0, len = words.size(); i < len; i++) { cur = words.get(i); boolean sep = false; if (prev == null || prev.length() == 0) { // no space } else { char lastPrev = prev.charAt(prev.length() - 1); if (lastPrev == '(' || lastPrev == '[' || lastPrev == '{' || lastPrev == '-' || (lastPrev >= 0x2e80 && lastPrev <= 0x9fff)) { // no space } else if (cur.length() == 0) { // no space too, exceptional case } else { char firstCur = cur.charAt(0); if (")]}.,:;?!-".indexOf(firstCur) >= 0 || (firstCur >= 0x2e80 && firstCur <= 0x9fff)) { // no space } else { sep = true; } } } if (sep) { sb.append(sepChar); } sb.append(cur); prev = cur; } return sb.toString(); } // </yuku> /** * Get raw data for this book * * @return raw data bytes of this book */ byte[] getData() { return data; } /** * Get the complete name of this book * * @return The complete/long name of this book */ public String getFullName() { return Util.readStringTrimZeroWithMaybeGreekHebrew(complexName, 0, complexName.length, bible); } /** * Get the short name of this book * * @return The sort name of this book, for example GEN for Genesis */ public String getShortName() { return Util.readStringTrimZeroWithMaybeGreekHebrew(simpleName, 0, simpleName.length, bible); } /** * Get the verse portion only (no chapter title/book title) * * @param chapter * chapter number * @param verse * verse number * @return verse as string */ public String getVerse(int chapter, int verse) { String[] sb = getCompleteVerse(chapter, verse); if (sb == null) { return null; } return sb[0]; } /** * This is the byteshifted version of getCompleteVerse. It * will be called by getCompleteVerse when the bible file is byte shifted * * @param chapter * chapter number * @param verse * verse number * @return array of StringBuffer for each tag */ String[] getVerseByteShifted(int chapter, int verse) { int verseStart = getVerseStart(chapter, verse); int verseLength = getVerseLength(chapter, verse); // <yuku> // <original> // for (int i = 0; i < 4; i++) { // sb[i].setLength(0); // } // </original> // </yuku> // System.out.println("Start " + verseStart + " length " + verseLength); int decShift; int decValueBuffer[] = new int[3]; int compStart; compStart = verseStart * 7 / 4; decShift = shiftLookup[verseStart * 7 % 4]; int idx = compStart; switch (decShift) { case 1: decValueBuffer[1] = data[idx++]; break; case 2: case 3: decValueBuffer[2] = data[idx++]; break; default: } // <yuku> @SuppressWarnings("unchecked") ArrayList<String>[] words = new ArrayList[4]; words[0] = new ArrayList<>(); words[1] = new ArrayList<>(); words[2] = new ArrayList<>(); words[3] = new ArrayList<>(); // </yuku> int sbpos = 0; ayam: for (int i = 0; i < verseLength; i++) { if (idx >= data.length) {Log.d("idx OOB", getFullName() + " c" + chapter + " v" + verse + " i=" + i + " verseLength=" + verseLength + " data.length=" + data.length + " idx=" + idx); break;} switch (decShift) { case 0: decValueBuffer[0] = data[idx++] & 0xff; if (idx >= data.length) {Log.d("idx OOB", getFullName() + " c" + chapter + " v" + verse + " i=" + i + " verseLength=" + verseLength + " data.length=" + data.length + " idx=" + idx); break ayam;} decValueBuffer[1] = data[idx++] & 0xff; decValueBuffer[2] = 0; break; case 1: decValueBuffer[0] = decValueBuffer[1]; decValueBuffer[1] = data[idx++] & 0xff; if (idx >= data.length) {Log.d("idx OOB", getFullName() + " c" + chapter + " v" + verse + " i=" + i + " verseLength=" + verseLength + " data.length=" + data.length + " idx=" + idx); break ayam;} decValueBuffer[2] = data[idx++] & 0xff; break; case 2: decValueBuffer[0] = decValueBuffer[2]; decValueBuffer[1] = data[idx++] & 0xff; if (idx >= data.length) {Log.d("idx OOB", getFullName() + " c" + chapter + " v" + verse + " i=" + i + " verseLength=" + verseLength + " data.length=" + data.length + " idx=" + idx); break ayam;} decValueBuffer[2] = data[idx++] & 0xff; break; case 3: decValueBuffer[0] = decValueBuffer[2]; decValueBuffer[1] = data[idx++] & 0xff; decValueBuffer[2] = 0; break; default: } int value = decValueBuffer[0] << 16 | decValueBuffer[1] << 8 | decValueBuffer[2]; value = value >> verseShiftLookup[decShift]; value = value & 0x3FFF; decShift++; if (decShift == 4) { decShift = 0; } if (value > 0x3FF0) { value |= 0xC000; } int decWordNum = value; int pos = bible.getWordPos(decWordNum); int[] r = bible.getRepeat(pos, decWordNum); if (r != null) { for (int j = 0; j < r.length; j++) { if (r[j] > 0x3FF0) { r[j] |= 0xC000; } if (r[j] == bookTextType || r[j] == chapTextType || r[j] == descTextType || r[j] == versTextType) { sbpos = r[j] - versTextType; continue; } String word = bible.getWord(r[j]); // System.out.println("Word " + word); // <yuku> // <original> // addSepChar(sb[sbpos], word); // sb[sbpos].append(word); // </original> words[sbpos].add(word); // </yuku> } } else { String word = bible.getWord(decWordNum); // <yuku> // <original> // addSepChar(sb[sbpos], word); // sb[sbpos].append(word); // </original> words[sbpos].add(word); // </yuku> // System.out.println("r is null" + word); } } // <yuku> String sepChar = bible.getSepChar(); String[] res = new String[4]; res[0] = stringFromWords(words[0], sepChar); res[1] = stringFromWords(words[1], sepChar); res[2] = stringFromWords(words[2], sepChar); res[3] = stringFromWords(words[3], sepChar); // </yuku> // <yuku> // <original> // return sb; // </original> return res; // </yuku> } /** * Get the total number of verse in a chapter * * @return total number of verse in a chapter */ public int getVerseCount(int chapter) { int v1 = totalVersesAcc[chapter - 1]; int v2 = chapter == 1 ? 0 : totalVersesAcc[chapter - 2]; return v1 - v2; } /** * Get the total number of "word number" in a verse * A "word number" can represent multiple words * * @param chapter * chapter number * @param verse * verse number * @return the length/size of "word number" */ int getVerseLength(int chapter, int verse) { int verseAcc = (chapter == 1 ? 0 : totalVersesAcc[chapter - 2]) + verse; int verseLength; if (verse > 1) { verseLength = vlen(verseAcc - 1); } else { verseLength = verseAcc == 0 ? 0 : totalVerseCharsAcc[verseAcc - 1]; } return verseLength; } /** * Get the start position of a verse * This is not public, to be called by BiblePlusPDB for fastsearch * * @param chapter * chapter number * @param verse * verse number */ int getVerseStart(int chapter, int verse) { // System.out.println("vstart = " + (chapter==1?0:totalVersesAcc[chapter - 2])); int verseAcc = (chapter == 1 ? 0 : totalVersesAcc[chapter - 2]) + verse; // System.out.println("verseacc = " + verseAcc); int verseStart = chapter == 0 ? 0 : totalChapterCharsAcc[chapter - 1]; // System.out.println("versestart1: " + verseStart); if (verse > 1) { verseStart += verseAcc == 1 ? 0 : totalVerseCharsAcc[verseAcc - 2]; } // System.out.println("versestart2: " + verseStart); return verseStart; } /** * Try to open this book (load verse data for this book) * * @throws IOException * in case of error in reading the book */ public void openBook() throws IOException { if (bookOpened) { return; } if (!bible.supportRandomAccess()) { /* * Blackberry OS before 5.0 can not access randomly. * if we don't have random access * must make sure all previous books have been opened * the good way to do it is to load all previous books in main thread, and make progress bar * this is to prevent error */ for (int i = 0; i < bookPosition; i++) { BookInfo bi = bible.getBook(i); bi.openBook(); } } PDBAccess access = bible.getPDBAccess(); PDBRecord r = access.readRecord(bookIndex); index_data = r.getData(); // Util.dumpBytes(index_data); PDBRecord[] records = new PDBRecord[totalBookRec]; int total_len = 0; for (int i = 0; i < totalBookRec; i++) { records[i] = access.readRecord(bookIndex + i + 1); byte[] d = records[i].getData(); // System.out.println("d = "+ d.length); total_len += d.length; } data = new byte[total_len]; for (int i = 0; i < totalBookRec; i++) { byte[] d = records[i].getData(); /* PDB Record size */ final int RECORD_SIZE = 4096; System.arraycopy(d, 0, data, i * RECORD_SIZE, d.length); access.removeFromCache(bookIndex + i + 1); } totalChapters = Util.readShort(index_data, 0); // System.out.println("total chapters: " + totalChapters); totalVersesAcc = new int[totalChapters]; int offs = 2; for (int i = 0; i < totalChapters; i++) { totalVersesAcc[i] = Util.readShort(index_data, offs); // System.out.println("totalVersesAcc " + totalVersesAcc[i]); offs += 2; } totalChapterCharsAcc = new int[totalChapters]; for (int i = 0; i < totalChapters; i++) { totalChapterCharsAcc[i] = Util.readInt(index_data, offs); offs += 4; } totalVerseCharsAcc = new int[(index_data.length - offs) / 2]; for (int i = 0; offs < index_data.length; i++) { totalVerseCharsAcc[i] = Util.readShort(index_data, offs); offs += 2; } // System.out.println("total length " + total_len + " total chapter " + totalChapters); bookOpened = true; } @Override public String toString() { return getFullName(); } private int vlen(int index) { // System.out.println("vlen = " + index); int v1 = totalVerseCharsAcc[index]; int v2 = index == 0 ? 0 : totalVerseCharsAcc[index - 1]; int diff = v1 - v2; if (diff < 0) { return 0; } return diff; } public static class Log { public static void d(String tag, String msg) { System.out.println(tag + " " + msg); } } }