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