Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DataEntry |
|
| 3.1;3.1 |
1 | /** | |
2 | * Distribution License: | |
3 | * JSword is free software; you can redistribute it and/or modify it under | |
4 | * the terms of the GNU Lesser General Public License, version 2.1 or later | |
5 | * as published by the Free Software Foundation. This program is distributed | |
6 | * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even | |
7 | * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
8 | * See the GNU Lesser General Public License for more details. | |
9 | * | |
10 | * The License is available on the internet at: | |
11 | * http://www.gnu.org/copyleft/lgpl.html | |
12 | * or by writing to: | |
13 | * Free Software Foundation, Inc. | |
14 | * 59 Temple Place - Suite 330 | |
15 | * Boston, MA 02111-1307, USA | |
16 | * | |
17 | * © CrossWire Bible Society, 2008 - 2016 | |
18 | * | |
19 | */ | |
20 | package org.crosswire.jsword.book.sword; | |
21 | ||
22 | import org.crosswire.common.crypt.Sapphire; | |
23 | ||
24 | /** | |
25 | * Data entry represents an entry in a Data file. The entry consists of a key | |
26 | * and an optional payload. | |
27 | * <p>The payload may be:</p> | |
28 | * <ul> | |
29 | * <li>the content, that is raw text</li> | |
30 | * <li>an alias (@LINK) for another entry</li> | |
31 | * <li>a block locator</li> | |
32 | * </ul> | |
33 | * | |
34 | * @see gnu.lgpl.License The GNU Lesser General Public License for details. | |
35 | * @author DM Smith | |
36 | */ | |
37 | public class DataEntry { | |
38 | /** | |
39 | * Construct a data entry. | |
40 | * | |
41 | * @param name | |
42 | * A name used for diagnostics. | |
43 | * @param data | |
44 | * The data for this entry. | |
45 | * @param charset | |
46 | * The character encoding for this entry. | |
47 | */ | |
48 | 0 | public DataEntry(String name, byte[] data, String charset) { |
49 | 0 | this.name = name; |
50 | 0 | this.data = data.clone(); |
51 | 0 | this.charset = charset; |
52 | // The key always ends with \n, typically \r\n | |
53 | 0 | this.keyEnd = SwordUtil.findByte(this.data, SEP_NL); |
54 | 0 | } |
55 | ||
56 | /** | |
57 | * Get the name, that is, the diagnostic label, for this DataEntry. | |
58 | * | |
59 | * @return the diagnostic name. | |
60 | */ | |
61 | public String getName() { | |
62 | 0 | return name; |
63 | } | |
64 | ||
65 | /** | |
66 | * Get the charset in which the data is encoded. | |
67 | * @return this entry's charset | |
68 | */ | |
69 | public String getCharset() { | |
70 | 0 | return charset; |
71 | } | |
72 | ||
73 | /** | |
74 | * Get the key from this DataEntry. | |
75 | * | |
76 | * @return the key | |
77 | */ | |
78 | public String getKey() { | |
79 | 0 | if (key == null) { |
80 | // Some entries are empty | |
81 | 0 | if (data.length == 0) { |
82 | 0 | key = ""; |
83 | 0 | return key; |
84 | } | |
85 | ||
86 | 0 | if (keyEnd < 0) { |
87 | 0 | key = ""; |
88 | 0 | return key; |
89 | } | |
90 | ||
91 | 0 | int end = keyEnd; |
92 | // remove trailing \r if present | |
93 | 0 | if (end > 0 && data[end - 1] == SEP_CR) { |
94 | 0 | --end; |
95 | } | |
96 | ||
97 | // for some weird reason plain text dictionaries | |
98 | // all get \ added to the ends of the index entries. | |
99 | 0 | if (end > 0 && data[end - 1] == SEP_BSLASH) { |
100 | 0 | --end; |
101 | } | |
102 | ||
103 | // If the end is 0 then we have an empty key. | |
104 | 0 | if (end == 0) { |
105 | 0 | key = ""; |
106 | 0 | return key; |
107 | } | |
108 | ||
109 | // The key may have whitespace, including \r on the end, | |
110 | // that is not actually part of the key. | |
111 | 0 | key = SwordUtil.decode(name, data, end, charset); |
112 | } | |
113 | ||
114 | 0 | return key; |
115 | } | |
116 | ||
117 | /** | |
118 | * Determine whether this entry is an alias for another. | |
119 | * | |
120 | * @return whether this is an alias entry | |
121 | */ | |
122 | public boolean isLinkEntry() { | |
123 | // 6 represents the length of "@LINK" when keyEnd is -1 | |
124 | 0 | return keyEnd + 6 < data.length |
125 | && data[keyEnd + 1] == '@' | |
126 | && data[keyEnd + 2] == 'L' | |
127 | && data[keyEnd + 3] == 'I' | |
128 | && data[keyEnd + 4] == 'N' | |
129 | && data[keyEnd + 5] == 'K'; | |
130 | } | |
131 | ||
132 | /** | |
133 | * Get the link target for this entry. One entry can be chained to another. | |
134 | * If the entry is not linked then it is an error to call this method. | |
135 | * | |
136 | * @return the key to look up | |
137 | * @see #isLinkEntry() | |
138 | */ | |
139 | public String getLinkTarget() { | |
140 | // 6 represents the length of "@LINK" + 1 to skip the last separator. | |
141 | 0 | int linkStart = keyEnd + 6; |
142 | 0 | int len = getLinkEnd() - linkStart + 1; |
143 | 0 | return SwordUtil.decode(name, data, linkStart, len, charset).trim(); |
144 | } | |
145 | ||
146 | /** | |
147 | * Get the raw text from this entry. | |
148 | * | |
149 | * @param cipherKey | |
150 | * the key, if any, to (un)lock the text | |
151 | * @return the raw text | |
152 | */ | |
153 | public String getRawText(byte[] cipherKey) { | |
154 | 0 | int textStart = keyEnd + 1; |
155 | 0 | cipher(cipherKey, textStart); |
156 | 0 | return SwordUtil.decode(name, data, textStart, data.length - textStart, charset).trim(); |
157 | } | |
158 | ||
159 | /** | |
160 | * Get the block start and entry position. | |
161 | * | |
162 | * @return the index of the block | |
163 | */ | |
164 | public DataIndex getBlockIndex() { | |
165 | 0 | int start = keyEnd + 1; |
166 | 0 | return new DataIndex(SwordUtil.decodeLittleEndian32(data, start), SwordUtil.decodeLittleEndian32(data, start + 4)); |
167 | } | |
168 | ||
169 | /** | |
170 | * Get the position of the second \n in the data. This represents the end of | |
171 | * the link and the start of the rest of the data. | |
172 | * | |
173 | * @return the end of the link or -1 if not found. | |
174 | */ | |
175 | private int getLinkEnd() { | |
176 | 0 | if (linkEnd == 0) { |
177 | 0 | linkEnd = SwordUtil.findByte(data, keyEnd + 1, SEP_NL); |
178 | 0 | if (linkEnd == -1) { |
179 | 0 | linkEnd = data.length - 1; |
180 | } | |
181 | } | |
182 | 0 | return linkEnd; |
183 | } | |
184 | ||
185 | /** | |
186 | * Decipher/Encipher the data in place, if there is a cipher key. | |
187 | * | |
188 | * @param cipherKey | |
189 | * the key to the cipher | |
190 | * @param offset | |
191 | * the start of the cipher data | |
192 | */ | |
193 | public void cipher(byte[] cipherKey, int offset) { | |
194 | 0 | if (cipherKey != null && cipherKey.length > 0) { |
195 | 0 | Sapphire cipherEngine = new Sapphire(cipherKey); |
196 | 0 | for (int i = offset; i < data.length; i++) { |
197 | 0 | data[i] = cipherEngine.cipher(data[i]); |
198 | } | |
199 | // destroy any evidence! | |
200 | 0 | cipherEngine.burn(); |
201 | } | |
202 | 0 | } |
203 | ||
204 | /** | |
205 | * Used to separate the key name from the key value Note: it may be \r\n or | |
206 | * just \n, so only need \n. ^M=CR=13=0x0d=\r ^J=LF=10=0x0a=\n | |
207 | */ | |
208 | private static final byte SEP_NL = (byte) '\n'; // 10; | |
209 | private static final byte SEP_CR = (byte) '\r'; // 13; | |
210 | private static final byte SEP_BSLASH = (byte) '\\'; // 92; | |
211 | /** | |
212 | * A diagnostic name. | |
213 | */ | |
214 | private String name; | |
215 | ||
216 | /** | |
217 | * The data entry as it comes out of the data file. | |
218 | */ | |
219 | private byte[] data; | |
220 | ||
221 | /** | |
222 | * The character set of the data entry. | |
223 | */ | |
224 | private String charset; | |
225 | ||
226 | /** | |
227 | * The key in the data entry. | |
228 | */ | |
229 | private String key; | |
230 | ||
231 | /** | |
232 | * The index of the separator between the key and the payload. | |
233 | */ | |
234 | private int keyEnd; | |
235 | ||
236 | /** | |
237 | * The index of the separator between the link and the payload. | |
238 | */ | |
239 | private int linkEnd; | |
240 | } |