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