/* 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; /** * * <p> * Usage: * </p> * * <pre> * BiblePlusPDB bible = new BiblePlusPDB(); * if (!bible.loadVersionInfo()) { * // error * } * </pre> * <p> * At this point we can access some basic information, such as the bible version name and version info. We can also have the list of book names at this point. * </p> * <p> * If we want to access the content of the bible, we need to load the word index. The word index contains the table that maps word number to a word. * </p> * * <pre> * bible.loadWordIndex(); * </pre> * <p> * To know how many books that we have, use: * </p> * * <pre> * int book_count = bible.getBookCount(); * </pre> * <p> * And to get the BookInfo object, use: * </p> * * <pre> * BookInfo b = bible.getBook(index); * </pre> * <p> * The data for the book (the verse content) is not loaded until we call: * </p> * * <pre> * b.openBook(); * </pre> * * * <p> * To get the verse text only, we can call: * </p> * * <pre> * String verse = getVerse(chapter, verse); * </pre> * * If we want to have the chapter title, or verse title, we can use: * * <pre> * StringBuffer[] sb = getCompleteVerse(chapter, verse); * </pre> * <p> * See the documentation of the return value in BookInfo. * </p> * */ public class BiblePlusPDB { private byte[] versionName; private byte[] versionInfo; private byte[] sepChar; private int versionAttr; private int wordIndex; private int totalWordRec; private PDBHeader header; /** * Default encoding used to read strings * Note: Blackberry only supports very limited encoding */ private String encoding = "UTF-8"; private boolean wordIndexLoaded; // initialized with false; private final static int infByteNotShifted = 0x02; /** * Object to access the PDB */ private PDBAccess pdbaccess; /** * Array of BookInfo */ private BookInfo[] booksinfo; private boolean canSeek; /* * Try to go to the bookmark * if booknumber is not found, it will try bookname */ // int[] parseBookmark(Bookmark bm) { // return null; // } private String pathName; private boolean is_greek = false; private boolean is_hebrew = false; private byte word_data[]; private int[] wordLength; private int[] totalWord; private boolean[] compressed; private boolean[] nothing; private int byteacc[]; private final static int BOOK_REC_SIZE = 46; private int fail_reason; /** * No error */ public final static int SUCCESS = 0; /** * The file is not a PDB File */ public final static int ERR_NOT_PDB_FILE = 1; /** * The file is a PDB file, but it is not in Bible+ format */ public final static int ERR_NOT_BIBLE_PLUS_FILE = 2; // public void setPathSeparator(String sep) { // pathSeparator = sep; // } /** * The file is a corrupted */ public final static int ERR_FILE_CORRUPTED = 3; /** * Create a bible object. * * These Greek and Hebrew tables are separated for two * reasons: (1) to reduce file size (2) we can use different * translation, for example using accented or non accented * greek * * @param is * PDBDataStream usually from file but it can be from memory or other source * @param _hebrewtab * hebrew table for translating to unicode * @param _greektab * greek table for translating to unicode */ public BiblePlusPDB(PDBDataStream is, char[] _hebrewtab, char[] _greektab) { Util.setTables(_hebrewtab, _greektab); canSeek = is.canSeek(); pdbaccess = new PDBAccess(is); pathName = is.getPathName(); getFileNamePart(); } /** * Cleanup object. Only used for debugging. */ public void close() throws IOException { word_data = null; wordLength = null; totalWord = null; compressed = null; nothing = null; byteacc = null; if (booksinfo != null) { for (int i = 0; i < booksinfo.length; i++) { if (booksinfo[i] != null) { booksinfo[i].close(); booksinfo[i] = null; } } } if (pdbaccess != null) { pdbaccess.close(); pdbaccess = null; } // System.gc(); } public void setEncoding(String _encoding) { encoding = _encoding; } /** * Get book info at specified index * * @param index * index of book * @return BookInfo index (note: <code>loadVersionInfo</code> and <code>loadWordIndex</code> should be called before ) */ public BookInfo getBook(int index) { return booksinfo[index]; } /** * Get number of books */ public int getBookCount() { return booksinfo.length; } /** * Get currently used encoding * * @return currently used encoding */ public String getEncoding() { return encoding; } /** * In case bible loading failed, get the reason for failure * * @return fail reason (SUCCESS, ERR_NOT_PDB_FILE, ERR_NOT_BIBLE_PLUS_FILE, ERR_FILE_CORRUPTED) */ public int getFailReason() { return fail_reason; } private void getFileNamePart() { /* Blackberry doesn't have 'File.separator' so to be generic we will ask the user of this class to set it for us */ final String pathSeparator = "/"; int pos = pathName.lastIndexOf(pathSeparator.charAt(0)); final String filenamepart; if (pos < 0) { return; } filenamepart = pathName.substring(pos + 1); is_greek = filenamepart.startsWith("z"); is_hebrew = filenamepart.startsWith("q"); } /** * Get PDB Header */ public PDBHeader getHeader() { return header; } PDBAccess getPDBAccess() { return pdbaccess; } int[] getRepeat(int pos, int wordNum) { int repeat; int[] result; // System.out.println("word repeat----"); if (wordNum < 0xFFF0) { boolean compressed = pos == 0 || isCompressed(pos); int len = getWordLength(pos); int wordIndex = getWordIndex(pos, wordNum); if (compressed) { // System.out.println("compressed\n"); repeat = len / 2; if (repeat == 0) { return null; } result = new int[repeat]; int st = wordIndex; // System.out.println("repeat " + repeat + " wi " + wordIndex); for (int i = 0; i < repeat; i++) { result[i] = Util.readShort(word_data, st); // System.out.println("numval = " + result[i]); st += 2; } return result; } else { // System.out.println("Not compressed"); result = new int[1]; result[0] = wordNum; return result; } } result = new int[1]; result[0] = wordNum; return result; } /** * Get word separator. Not private because it is used by BookInfo * * @return word separator */ String getSepChar() { return Util.readStringWithMaybeGreekHebrew(sepChar, 0, sepChar.length, this); } /** * Get the version name of this bible */ public String getVersionName() { return Util.readStringTrimZeroWithMaybeGreekHebrew(versionName, 0, versionName.length, this); } public String getVersionInfo() { return Util.readStringTrimZeroWithMaybeGreekHebrew(versionInfo, 0, versionInfo.length, this); } String getWord(int wordNum) { int pos = getWordPos(wordNum); int index = getWordIndex(pos, wordNum); int len = getWordLength(pos); if (index == -1) { return ""; } return readString(index, len); } int getWordIndex(int pos, int wordNum) { int relNum = wordNum - 1; int decWordIndex = 0; for (int i = 0; i <= pos; i++) { int _totalWord = totalWord[i]; if (relNum < _totalWord) { int decWordLen = wordLength[i]; // System.out.println("wl;"+decWordLen); decWordIndex = byteacc[i] + relNum * decWordLen; break; } else { relNum = (relNum - _totalWord); } } return decWordIndex; } private int getWordLength(int pos) { return wordLength[pos]; } int getWordPos(int wordNum) { int relNum = wordNum - 1; for (int i = 0; i < totalWord.length; i++) { int _totalWord = totalWord[i]; if (relNum < _totalWord) { return i; } else { relNum = (relNum - _totalWord); } } return 0; } /** * Check if the bible is byteshifted * * @return true if the bible is byteshifted */ public boolean isByteShifted() { return (versionAttr & infByteNotShifted) == 0; } /** * Check if the word number at pos is actually represents multiple words */ private boolean isCompressed(int pos) { return compressed[pos]; } /** * Check if this is a Greek bible. Using the same convention * as the PalmBiblePlus, the Greek bible file name must start * with 'z' */ public boolean isGreek() { return is_greek; } /** * Check if this is a Hebrew bible. Using the same convention * as the PalmBiblePlus, the Hebrew bible file name must start * with 'q' */ public boolean isHebrew() { return is_hebrew; } /** * Try to load version information from this bible. If this * fails, then the bible file is not valid, or not enough * memory available. To get the error reason, call <code>getFailReason()</code> * * @return true if loading is OK or false if it failed */ public boolean loadVersionInfo() throws IOException { fail_reason = SUCCESS; header = pdbaccess.getHeader(); if (header == null) { fail_reason = ERR_NOT_PDB_FILE; return false; } if (pdbaccess.isCorrupted()) { fail_reason = ERR_FILE_CORRUPTED; return false; } if (!header.getType().equals("bibl")) { fail_reason = ERR_NOT_BIBLE_PLUS_FILE; return false; } PDBRecord version = pdbaccess.readRecord(0); byte[] data = version.getData(); int idx = 0; versionName = new byte[16]; System.arraycopy(data, idx, versionName, 0, 16); // System.out.println("Version name: ;" + versionName+";"); idx += 16; versionInfo = new byte[128]; System.arraycopy(data, idx, versionInfo, 0, 128); // System.out.println("Info:" + versionInfo); idx += 128; sepChar = new byte[1]; System.arraycopy(data, idx, sepChar, 0, 1); idx++; versionAttr = data[idx] & 0xff; // System.out.println("version attr " + versionAttr); idx++; wordIndex = Util.readShort(data, idx); // System.out.println("Word index: " + wordIndex); idx += 2; totalWordRec = Util.readShort(data, idx); // System.out.println("totalWordRecord: " + totalWordRec); if (wordIndex + totalWordRec >= header.getRecordCount()) { fail_reason = ERR_FILE_CORRUPTED; return false; } idx += 2; final int totalBooks = Util.readShort(data, idx); idx += 2; // System.out.println("totalBooks: " + totalBooks); if (totalBooks < 0) { fail_reason = ERR_FILE_CORRUPTED; return false; } booksinfo = new BookInfo[totalBooks]; for (int i = 0; i < totalBooks; i++) { if (idx + BOOK_REC_SIZE > data.length) { fail_reason = ERR_FILE_CORRUPTED; return false; } booksinfo[i] = BookInfo.createFromData(this, data, idx, i); if (booksinfo[i] == null) { fail_reason = ERR_FILE_CORRUPTED; return false; } // System.out.println(booksinfo[i]); idx += BOOK_REC_SIZE; } pdbaccess.removeFromCache(0); return true; } /** * load word index * */ public void loadWordIndex() throws IOException { if (wordIndexLoaded) { return; } PDBRecord r = pdbaccess.readRecord(wordIndex); int idx = 0; byte[] index_data = r.getData(); int totalIndexes = Util.readShort(index_data, idx); // System.out.println("total indexes: " + totalIndexes); idx += 2; wordLength = new int[totalIndexes]; totalWord = new int[totalIndexes]; compressed = new boolean[totalIndexes]; nothing = new boolean[totalIndexes]; for (int i = 0; i < totalIndexes; i++) { wordLength[i] = Util.readShort(index_data, idx); idx += 2; totalWord[i] = Util.readShort(index_data, idx); idx += 2; compressed[i] = index_data[idx++] != 0; // System.out.println("len " + wordLength[i] + " totalword[" +i + "]=" + totalWord[i] + " compressed = "+compressed[i]); nothing[i] = index_data[idx++] != 0; } // System.out.println("all total " + totalWords); int totalByteAcc = 0; byteacc = new int[totalIndexes + 1]; byteacc[0] = 0; for (int i = 1; i <= totalIndexes; i++) { int _totalWord = totalWord[i - 1]; int _wordLen = wordLength[i - 1]; totalByteAcc += _totalWord * _wordLen; byteacc[i] = totalByteAcc; } PDBRecord[] records = new PDBRecord[totalWordRec]; int total_len = 0; for (int i = 0; i < totalWordRec; i++) { records[i] = pdbaccess.readRecord(wordIndex + i + 1); byte[] d = records[i].getData(); total_len += d.length; } word_data = new byte[total_len]; int l = 0; for (int i = 0; i < totalWordRec; i++) { byte[] d = records[i].getData(); System.arraycopy(d, 0, word_data, l, d.length); l += d.length; pdbaccess.removeFromCache(wordIndex + i + 1); } wordIndexLoaded = true; } private String readString(int index, int len) { if (is_greek) { return Util.readStringGreek(word_data, index, len); } else if (is_hebrew) { return Util.readStringHebrew(word_data, index, len); } else { return Util.readString(word_data, index, len, encoding); } } boolean supportRandomAccess() { return canSeek; } }