1
20 package org.crosswire.jsword.book.sword;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.RandomAccessFile;
25 import java.net.URI;
26
27 import org.crosswire.common.activate.Activatable;
28 import org.crosswire.common.activate.Activator;
29 import org.crosswire.common.activate.Lock;
30 import org.crosswire.common.util.FileUtil;
31 import org.crosswire.common.util.NetUtil;
32 import org.crosswire.common.util.Reporter;
33 import org.crosswire.jsword.JSMsg;
34 import org.crosswire.jsword.JSOtherMsg;
35 import org.crosswire.jsword.book.BookException;
36 import org.crosswire.jsword.passage.DefaultKeyList;
37 import org.crosswire.jsword.passage.Key;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41
49 public class TreeKeyIndex implements Activatable {
50
55 public TreeKeyIndex(SwordBookMetaData sbmd) {
56 bmd = sbmd;
57 }
58
59
63 public TreeNode getRoot() throws IOException {
64 return getTreeNode(getOffset(0));
65 }
66
67
75 public TreeNode getParent(TreeNode node) throws IOException {
76 return getTreeNode(getOffset(node.getParent()));
77 }
78
79
87 public TreeNode getFirstChild(TreeNode node) throws IOException {
88 return getTreeNode(getOffset(node.getFirstChild()));
89 }
90
91
99 public TreeNode getNextSibling(TreeNode node) throws IOException {
100 return getTreeNode(getOffset(node.getNextSibling()));
101 }
102
103
111 private int getOffset(int index) throws IOException {
112 if (index == -1) {
113 return -1;
114 }
115
116 checkActive();
117 byte[] buffer = SwordUtil.readRAF(idxRaf, index, 4);
118 return SwordUtil.decodeLittleEndian32(buffer, 0);
119 }
120
121
129 private TreeNode getTreeNode(int offset) throws IOException {
130 TreeNode node = new TreeNode(offset);
131
132 if (offset == -1) {
133 return node;
134 }
135
136 checkActive();
137 byte[] buffer = SwordUtil.readRAF(datRaf, offset, 12);
138 node.setParent(SwordUtil.decodeLittleEndian32(buffer, 0));
139 node.setNextSibling(SwordUtil.decodeLittleEndian32(buffer, 4));
140 node.setFirstChild(SwordUtil.decodeLittleEndian32(buffer, 8));
141
142 buffer = SwordUtil.readUntilRAF(datRaf, (byte) 0);
143 int size = buffer.length;
144 if (buffer[size - 1] == 0) {
145 size--;
146 }
147
148 Key key = new DefaultKeyList(null, bmd.getName());
149 node.setName(SwordUtil.decode(key.getName(), buffer, size, bmd.getBookCharset()).trim());
151
152 buffer = SwordUtil.readNextRAF(datRaf, 2);
153 int userDataSize = SwordUtil.decodeLittleEndian16(buffer, 0);
154 if (userDataSize > 0) {
155 node.setUserData(SwordUtil.readNextRAF(datRaf, userDataSize));
156 }
157
158 return node;
159 }
160
161
164 public final void activate(Lock lock) {
165 String path = null;
166 try {
167 path = getExpandedDataPath();
168 } catch (BookException e) {
169 Reporter.informUser(this, e);
170 return;
171 }
172
173 idxFile = new File(path + EXTENSION_INDEX);
174 datFile = new File(path + EXTENSION_DATA);
175
176 if (!idxFile.canRead()) {
177 Reporter.informUser(this, new BookException(JSMsg.gettext("Error reading {0}", idxFile.getAbsolutePath())));
180 return;
181 }
182
183 if (!datFile.canRead()) {
184 Reporter.informUser(this, new BookException(JSMsg.gettext("Error reading {0}", datFile.getAbsolutePath())));
187 return;
188 }
189
190 try {
191 idxRaf = new RandomAccessFile(idxFile, FileUtil.MODE_READ);
192 datRaf = new RandomAccessFile(datFile, FileUtil.MODE_READ);
193 } catch (IOException ex) {
194 log.error("failed to open files", ex);
195 idxRaf = null;
196 datRaf = null;
197 }
198 active = true;
199 }
200
201
204 public final void deactivate(Lock lock) {
205 try {
206 if (idxRaf != null) {
207 idxRaf.close();
208 }
209 if (datRaf != null) {
210 datRaf.close();
211 }
212 } catch (IOException ex) {
213 log.error("failed to close nt files", ex);
214 } finally {
215 idxRaf = null;
216 datRaf = null;
217 }
218 active = false;
219 }
220
221
224 protected final void checkActive() {
225 if (!active) {
226 Activator.activate(this);
227 }
228 }
229
230 private String getExpandedDataPath() throws BookException {
231 URI loc = NetUtil.lengthenURI(bmd.getLibrary(), bmd.getProperty(SwordBookMetaData.KEY_DATA_PATH));
232
233 if (loc == null) {
234 throw new BookException(JSOtherMsg.lookupText("Missing data files for old and new testaments in {0}."));
236 }
237
238 return new File(loc.getPath()).getAbsolutePath();
239 }
240
241 private static final String EXTENSION_INDEX = ".idx";
242 private static final String EXTENSION_DATA = ".dat";
243
244 private SwordBookMetaData bmd;
245 private File idxFile;
246 private File datFile;
247 private RandomAccessFile idxRaf;
248 private RandomAccessFile datRaf;
249 private boolean active;
250
251
254 private static final Logger log = LoggerFactory.getLogger(TreeKeyIndex.class);
255 }
256