| StrongsNumber.java |
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 as published by
5 * the Free Software Foundation. This program is distributed in the hope
6 * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7 * 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 * Copyright: 2007
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: StrongsNumber.java 2099 2011-03-07 17:13:00Z dmsmith $
21 */
22 package org.crosswire.jsword.book.study;
23
24 import java.text.DecimalFormat;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28 import org.crosswire.jsword.JSMsg;
29 import org.crosswire.jsword.book.BookException;
30
31 /**
32 * A Strong's Number is either Greek or Hebrew, where the actual numbers for
33 * each start at 1. This class can parse Strong's Numbers that begin with G, g,
34 * H or h and are immediately followed by a number. That number can have leading
35 * 0's. It can be followed by an OSISref extension of !a, !b, which is ignored.
36 *
37 * <p>
38 * The canonical representation of the number is a G or H followed by 4 digits,
39 * with leading 0's as needed.
40 * </p>
41 *
42 * <p>
43 * Numbers that exist:
44 * <ul>
45 * <li>Hebrew: 1-8674
46 * <li>Greek: 1-5624 (but not 1418, 2717, 3203-3302, 4452)
47 * </ul>
48 * </p>
49 *
50 * @see gnu.lgpl.License for license details.<br>
51 * The copyright to this program is held by it's authors.
52 * @author DM Smith [dmsmith555 at yahoo dot com]
53 */
54 public class StrongsNumber {
55 /**
56 * Build an immutable Strong's Number. Anything that does not match causes a
57 * BookException.
58 *
59 * @param input
60 * a string that needs to be parsed.
61 * @throws BookException
62 */
63 public StrongsNumber(String input) throws BookException {
64 parse(input);
65 validate();
66 }
67
68 /**
69 * Build an immutable Strong's Number. If the language is not 'G' or 'H' or
70 * the number is invalid, throw a BookException.
71 *
72 * @param language
73 * @param strongsNumber
74 * @throws BookException
75 */
76 public StrongsNumber(char language, short strongsNumber) throws BookException {
77 this(language, strongsNumber, null);
78 }
79
80 /**
81 * Build an immutable Strong's Number. If the language is not 'G' or 'H' or
82 * the number is invalid, throw a BookException.
83 *
84 * @param language
85 * @param strongsNumber
86 * @throws BookException
87 */
88 public StrongsNumber(char language, short strongsNumber, String part) throws BookException {
89 this.language = language;
90 this.strongsNumber = strongsNumber;
91 this.part = part;
92 validate();
93 }
94
95 /**
96 * Return the canonical form of a Strong's Number, without the part.
97 *
98 * @return the strongsNumber
99 */
100 public String getStrongsNumber() {
101 StringBuilder buf = new StringBuilder(5);
102 buf.append(language);
103 buf.append(ZERO_PAD.format(strongsNumber));
104 return buf.toString();
105 }
106
107 /**
108 * Return the canonical form of a Strong's Number, with the part, if any
109 *
110 * @return the strongsNumber
111 */
112 public String getFullStrongsNumber() {
113 StringBuilder buf = new StringBuilder(5);
114 buf.append(language);
115 buf.append(ZERO_PAD.format(strongsNumber));
116 if (part != null) {
117 buf.append(part);
118 }
119 return buf.toString();
120 }
121
122 /**
123 * @return true if the Strong's number is for Greek
124 */
125 public boolean isGreek() {
126 return language == 'G';
127 }
128
129 /**
130 * @return true if the Strong's number is for Hebrew
131 */
132 public boolean isHebrew() {
133 return language == 'H';
134 }
135
136 /**
137 * @return true if this Strong's number is identified by a sub part
138 */
139 public boolean isPart() {
140 return part != null;
141 }
142
143 /**
144 * Validates the number portion of this StrongsNumber.
145 * <ul>
146 * <li>Hebrew Strong's numbers are in the range of: 1-8674.</li>
147 * <li>Greek Strong's numbers in the range of: 1-5624
148 * (but not 1418, 2717, 3203-3302, 4452).</li>
149 * </ul>
150 *
151 * @return true if the Strong's number is in range.
152 */
153 public boolean isValid() {
154 if (language == 'H'
155 && strongsNumber >= 1
156 && strongsNumber <= 8674)
157 {
158 return true;
159 }
160
161 if (language == 'G'
162 && ((strongsNumber >= 1 && strongsNumber < 1418)
163 || (strongsNumber > 1418 && strongsNumber < 2717)
164 || (strongsNumber > 2717 && strongsNumber < 3203)
165 || (strongsNumber > 3302 && strongsNumber <= 5624)))
166 {
167 return true;
168 }
169
170 return false;
171 }
172
173 /*
174 * (non-Javadoc)
175 *
176 * @see java.lang.Object#hashCode()
177 */
178 @Override
179 public int hashCode() {
180 int result = 31 + language;
181 return 31 * result + strongsNumber;
182 }
183
184 /*
185 * (non-Javadoc)
186 *
187 * @see java.lang.Object#equals(java.lang.Object)
188 */
189 @Override
190 public boolean equals(Object obj) {
191 if (this == obj) {
192 return true;
193 }
194
195 if (obj == null || getClass() != obj.getClass()) {
196 return false;
197 }
198
199 final StrongsNumber other = (StrongsNumber) obj;
200
201 return language == other.language && strongsNumber == other.strongsNumber;
202 }
203
204 /*
205 * (non-Javadoc)
206 *
207 * @see java.lang.Object#toString()
208 */
209 @Override
210 public String toString() {
211 return getStrongsNumber();
212 }
213
214 /**
215 * Do the actual parsing. Anything that does not match causes a
216 * BookException.
217 *
218 * @param input
219 * @throws BookException
220 */
221 private void parse(String input) throws BookException {
222 String text = input;
223
224 // Does it match
225 Matcher m = STRONGS_PATTERN.matcher(text);
226 if (!m.lookingAt()) {
227 // TRANSLATOR: User error condition: Indicates that what was given is not a Strong's Number. {0} is a placeholder for the bad Strong's Number.
228 throw new BookException(JSMsg.gettext("Not a valid Strong's Number \"{0}\"", input));
229 }
230
231 String lang = m.group(1);
232 language = lang.charAt(0);
233 switch (language) {
234 case 'g':
235 language = 'G';
236 break;
237 case 'h':
238 language = 'H';
239 break;
240 default:
241 // pass through
242 }
243
244 // Get the number after the G or H
245 strongsNumber = Short.parseShort(m.group(2));
246
247 // FYI: OSIS refers to what follows a ! as a grain
248 part = m.group(3);
249 }
250
251 private void validate() throws BookException {
252 if (language != 'G' && language != 'H') {
253 // TRANSLATOR: User error condition: Indicates that what was given is not a Strong's Number. {0} is a placeholder for the bad Strong's Number.
254 throw new BookException(JSMsg.gettext("Not a valid Strong's Number \"{0}\"", toString()));
255 }
256 }
257
258 /**
259 * Whether it is Greek (G) or Hebrew (H).
260 */
261 private char language;
262
263 /**
264 * The Strong's Number.
265 */
266 private short strongsNumber;
267
268 /**
269 * The part if any.
270 */
271 private String part;
272
273 /**
274 * The pattern of an acceptable Strong's number.
275 */
276 private static final Pattern STRONGS_PATTERN = Pattern.compile("([GgHh])0*([1-9][0-9]*)!?([A-Za-z]+)?");
277 private static final DecimalFormat ZERO_PAD = new DecimalFormat("0000");
278 }
279