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.versification;
21  
22  import java.util.Locale;
23  import java.util.regex.Pattern;
24  
25  import org.crosswire.common.util.StringUtil;
26  import org.crosswire.jsword.book.CaseType;
27  
28  /**
29   * BookName represents the different ways a book of the bible is named.
30   * 
31   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
32   * @author DM Smith
33   */
34  public final class BookName {
35      /**
36       * Create a BookName for a Book of the Bible in a given language.
37       * 
38       * @param locale
39       *            the language of this BookName
40       * @param book
41       *            the Book's canonical number
42       * @param longName
43       *            the Book's long name
44       * @param shortName
45       *            the Book's short name, if any
46       * @param alternateNames
47       *            optional comma separated list of alternates for the Book
48       */
49      public BookName(Locale locale, BibleBook book, String longName, String shortName, String alternateNames) {
50          this.locale = locale;
51          this.book = book;
52          this.longName = longName;
53          this.normalizedLongName = normalize(longName, locale);
54          this.shortName = shortName;
55          this.normalizedShortName = normalize(shortName, locale);
56  
57          if (alternateNames != null) {
58              this.alternateNames = StringUtil.split(normalize(alternateNames, locale), ',');
59          }
60      }
61  
62      /**
63       * Get the BibleBook to which this set of names is tied.
64       * 
65       * @return The book
66       */
67      public BibleBook getBook() {
68          return book;
69      }
70  
71      /**
72       * Get the preferred name of a book. Altered by the case setting (see
73       * setBookCase() and isFullBookName())
74       * 
75       * @return The preferred name of the book
76       */
77      public String getPreferredName() {
78          if (BookName.isFullBookName()) {
79              return getLongName();
80          }
81          return getShortName();
82      }
83  
84      /**
85       * Get the full name of a book (e.g. "Genesis"). Altered by the case setting
86       * (see setBookCase())
87       * 
88       * @return The full name of the book
89       */
90      public String getLongName() {
91          CaseType caseType = BookName.getDefaultCase();
92  
93          if (caseType == CaseType.LOWER) {
94              return longName.toLowerCase(locale);
95          }
96  
97          if (caseType == CaseType.UPPER) {
98              return longName.toUpperCase(locale);
99          }
100 
101         return longName;
102     }
103 
104     /**
105      * Get the short name of a book (e.g. "Gen"). Altered by the case setting
106      * (see setBookCase())
107      * 
108      * @return The short name of the book
109      */
110     public String getShortName() {
111         CaseType caseType = BookName.getDefaultCase();
112 
113         if (caseType == CaseType.LOWER) {
114             return shortName.toLowerCase(locale);
115         }
116 
117         if (caseType == CaseType.UPPER) {
118             return shortName.toUpperCase(locale);
119         }
120 
121         return shortName;
122     }
123 
124     /**
125      * @return the normalizedLongName
126      */
127     public String getNormalizedLongName() {
128         return normalizedLongName;
129     }
130 
131     /**
132      * @return the normalizedShortName
133      */
134     public String getNormalizedShortName() {
135         return normalizedShortName;
136     }
137 
138     /**
139      * Match the normalized name as closely as possible. It will match if:
140      * <ol>
141      * <li>it is a prefix of a normalized alternate name</li>
142      * <li>a normalized alternate name is a prefix of it</li>
143      * <li>it is a prefix of a normalized long name</li>
144      * <li>it is a prefix of a normalized short name</li>
145      * <li>a normalized short name is a prefix of it</li>
146      * </ol>
147      * 
148      * @param normalizedName
149      *            the already normalized name to match against.
150      * @return true of false
151      */
152     public boolean match(String normalizedName) {
153         // Does it match one of the alternative versions
154         for (int j = 0; j < alternateNames.length; j++) {
155             String targetBookName = alternateNames[j];
156             if (targetBookName.startsWith(normalizedName) || normalizedName.startsWith(targetBookName)) {
157                 return true;
158             }
159         }
160 
161         // Does it match a long version of the book
162         if (normalizedLongName.startsWith(normalizedName)) {
163             return true;
164         }
165 
166         // or a short version
167         if (normalizedShortName.startsWith(normalizedName) || (normalizedShortName.length() > 0 && normalizedName.startsWith(normalizedShortName))) {
168             return true;
169         }
170 
171         return false;
172     }
173 
174     /*
175      * (non-Javadoc)
176      * 
177      * @see java.lang.Object#hashCode()
178      */
179     @Override
180     public int hashCode() {
181         return book.hashCode();
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) {
196             return false;
197         }
198 
199         if (getClass() != obj.getClass()) {
200             return false;
201         }
202 
203         final BookName other = (BookName) obj;
204         return book == other.book;
205     }
206 
207     /*
208      * (non-Javadoc)
209      * 
210      * @see java.lang.Object#toString()
211      */
212     @Override
213     public String toString() {
214         return getPreferredName();
215     }
216 
217     /**
218      * Normalize by stripping punctuation and whitespace and lowercasing.
219      * 
220      * @param str
221      *            the string to normalize
222      * @param locale the locale that should be used for normalization
223      * @return the normalized string
224      */
225     public static String normalize(String str, Locale locale) {
226         return normPattern.matcher(str).replaceAll("").toLowerCase(locale);
227     }
228 
229     /**
230      * This is only used by config.
231      * 
232      * @param bookCase
233      *            The new case to use for reporting book names
234      * @exception IllegalArgumentException
235      *                If the case is not between 0 and 2
236      * @see #getCase()
237      */
238     public static void setCase(int bookCase) {
239         BookName.bookCase = CaseType.fromInteger(bookCase);
240     }
241 
242     /**
243      * How do we report the names of the books?. These are static. This is on
244      * the assumption that we will not want to have different sections of the
245      * app using a different format. I expect this to be a good assumption, and
246      * it saves passing a Book class around everywhere. CaseType.MIXED is not
247      * allowed
248      * 
249      * @param newBookCase
250      *            The new case to use for reporting book names
251      * @exception IllegalArgumentException
252      *                If the case is not between 0 and 2
253      * @see #getCase()
254      */
255     public static void setCase(CaseType newBookCase) {
256         BookName.bookCase = newBookCase;
257     }
258 
259     /**
260      * This is only used by config
261      * 
262      * @return The current case setting
263      * @see #setCase(CaseType)
264      */
265     public static int getCase() {
266         return BookName.bookCase.toInteger();
267     }
268 
269     /**
270      * This is only used by config
271      * 
272      * @return Whether the name is long or short. Default is Full (true).
273      * @see #setFullBookName(boolean)
274      */
275     public static boolean isFullBookName() {
276         return BookName.fullBookName;
277     }
278 
279     /**
280      * Set whether the name should be full or abbreviated, long or short.
281      * 
282      * @param fullName
283      *            The new case to use for reporting book names
284      * @see #isFullBookName()
285      */
286     public static void setFullBookName(boolean fullName) {
287         BookName.fullBookName = fullName;
288     }
289 
290     /**
291      * How do we report the names of the books?.
292      * 
293      * @return The current case setting
294      * @see #setCase(int)
295      */
296     public static CaseType getDefaultCase() {
297         return BookName.bookCase;
298     }
299 
300     /** remove spaces and some punctuation in Book Name (make sure , is allowed) */
301     private static Pattern normPattern = Pattern.compile("[. ]");
302 
303     private BibleBook book;
304     private String longName;
305     private String normalizedLongName;
306     private String shortName;
307     private String normalizedShortName;
308     private String[] alternateNames;
309 
310     /** The locale for the Book Name */
311     private Locale locale;
312 
313     /** How the book names are reported. */
314     private static CaseType bookCase = CaseType.SENTENCE;
315 
316     /** Whether long or short, full or abbreviated names are used. */
317     private static boolean fullBookName = true;
318 
319 }
320