/******************************************************************************* * Copyright (c) 2012, Directors of the Tyndale STEP Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * Neither the name of the Tyndale House, Cambridge (www.TyndaleHouse.com) * nor the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ package com.tyndalehouse.step.core.utils; import static java.util.Collections.sort; import static org.crosswire.jsword.book.OSISUtil.OSIS_ELEMENT_VERSE; import java.util.*; import org.crosswire.common.util.Language; import org.crosswire.common.util.Languages; import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.BookData; import org.crosswire.jsword.book.BookException; import org.crosswire.jsword.book.FeatureType; import org.crosswire.jsword.book.basic.AbstractPassageBook; import org.crosswire.jsword.passage.Key; import org.crosswire.jsword.passage.KeyUtil; import org.crosswire.jsword.passage.NoSuchKeyException; import org.crosswire.jsword.passage.PassageKeyFactory; import org.crosswire.jsword.passage.Verse; import org.crosswire.jsword.versification.BibleBook; import com.tyndalehouse.step.core.models.BibleVersion; import com.tyndalehouse.step.core.service.helpers.VersionResolver; import org.crosswire.jsword.versification.Versification; import org.jdom2.Element; import org.jdom2.filter.ElementFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * a set of utility methods to manipulate the JSword objects coming out * * @author chrisburrell * */ public final class JSwordUtils { private static final String BOOK_CHAPTER_OSIS_FORMAT = "%s.%d"; private static final Logger LOGGER = LoggerFactory.getLogger(JSwordUtils.class); private static final String ANCIENT_GREEK = "grc"; private static final String ANCIENT_HEBREW = "he"; /** * hiding implementaiton */ private JSwordUtils() { // no implementation } /** * returns a sorted list from another list, with only the required information * * @param bibles a list of jsword bibles * @param userLocale the local for the user * @param resolver resolves the version to the longer name known by JSword * @return the list of bibles */ public static List<BibleVersion> getSortedSerialisableList(final Collection<Book> bibles, final Locale userLocale, final VersionResolver resolver) { // final List<BibleVersion> versions = new ArrayList<BibleVersion>(); final Map<String, BibleVersion> versions = new HashMap<>(); // we only send back what we need for (final Book b : bibles) { final BibleVersion v = new BibleVersion(); final String shortName = (String) b.getProperty("shortName"); v.setName(shortName != null ? shortName : b.getName()); v.setInitials(b.getInitials()); v.setShortInitials(resolver.getShortName(b.getInitials())); v.setQuestionable(b.isQuestionable()); v.setCategory(b.getBookCategory().name()); final Language language = b.getLanguage(); if (language != null) { v.setLanguageCode(language.getCode()); final Locale versionLanguage = new Locale(language.getCode()); if (versionLanguage != null) { final String displayLanguage = versionLanguage.getDisplayLanguage(userLocale); if(language.getCode() != null && language.getCode().equals(displayLanguage)) { v.setLanguageName(Languages.AllLanguages.getName(displayLanguage)); } else { v.setLanguageName(displayLanguage); } } //also get the original language name v.setOriginalLanguage(versionLanguage.getDisplayLanguage(versionLanguage)); } if (v.getLanguageCode() == null || v.getLanguageName() == null) { v.setLanguageCode(userLocale.getLanguage()); v.setLanguageName(userLocale.getDisplayLanguage(userLocale)); } v.setHasStrongs(b.hasFeature(FeatureType.STRONGS_NUMBERS)); v.setHasMorphology(b.hasFeature(FeatureType.MORPHOLOGY)); v.setHasRedLetter(b.hasFeature(FeatureType.WORDS_OF_CHRIST)); v.setHasHeadings(b.hasFeature(FeatureType.HEADINGS)); v.setHasNotes(b.hasFeature(FeatureType.FOOTNOTES) || b.hasFeature(FeatureType.SCRIPTURE_REFERENCES)); v.setHasSeptuagintTagging(resolver.isSeptuagintTagging(b)); //now only put the version in if // a- it is not in the map already // b- it is in the map, but the initials of the one being put in are different, meaning STEP // has a better version that is overwriting the existing version if(!versions.containsKey(v.getShortInitials()) || !v.getShortInitials().equalsIgnoreCase(v.getInitials())) { versions.put(v.getShortInitials(), v); } } // finally sort by initials final List<BibleVersion> values = new ArrayList<>(versions.values()); sort(values, new Comparator<BibleVersion>() { @Override public int compare(final BibleVersion o1, final BibleVersion o2) { return o1.getShortInitials().compareTo(o2.getShortInitials()); } }); return values; } /** * Returns true if the bible book is the Introduction to the Bible, to the New Testament or to the Old * Testament * * @param bb the bb * @return true, if is intro */ public static boolean isIntro(final BibleBook bb) { return BibleBook.INTRO_BIBLE.equals(bb) || BibleBook.INTRO_NT.equals(bb) || BibleBook.INTRO_OT.equals(bb); } /** * Ascertains if it is an ancient book, i.e. Greek or Hebrew * @param book the book we are considering * @return true to indicate Greek or Hebrew */ public static boolean isAncientBook(Book book) { return isAncientHebrewBook(book) || isAncientGreekBook(book); } /** * Ascertains whether the book(s) is Hebrew. If several books, then returns true if any book matches * @param books the book we are considering * @return true if Hebrew book */ public static boolean isAncientHebrewBook(Book... books) { boolean ancientHebrew = false; for(Book b : books) { //hard coding in the exception boolean isHebrew = ANCIENT_HEBREW.equals(b.getLanguage().getCode()) && !"HebModern".equals(b.getInitials()); if(isHebrew) { return true; } } return ancientHebrew; } /** * Ascertains whether the book is Greek, returning true if any books match the said criteria * @param books the book we are considering * @return true if Hebrew book */ public static boolean isAncientGreekBook(Book... books) { boolean ancientGreek = false; for(Book b : books) { boolean isGreek = ANCIENT_GREEK.equals(b.getLanguage().getCode()); if(isGreek) { return true; } } return ancientGreek; } /** * Gets the osis elements. * * @return the osis elements * @throws org.crosswire.jsword.passage.NoSuchKeyException the no such key exception * @throws org.crosswire.jsword.book.BookException the book exception */ @SuppressWarnings({"unchecked", "serial"}) public static List<Element> getOsisElements(BookData data) throws NoSuchKeyException, BookException { return data.getOsisFragment().getContent( new ElementFilter(OSIS_ELEMENT_VERSE)); } /** * Helper method that wraps around getValidKey which catches all exceptions * @param v11n the versification * @param reference the reference * @return the key, or an empty key */ public static Key getSafeKey(final Versification v11n, final String reference) { final PassageKeyFactory factory = PassageKeyFactory.instance(); try { return factory.getValidKey(v11n, reference); } catch(Exception ex) { //catching and logging exception here as intended to be called from XSLT LOGGER.error(ex.getMessage(), ex); return factory.createEmptyKeyList(v11n); } } /** * Checks for the presence of the book first. If the book is present, then continues to check that at least 1 verse * in the scope is present. If it is, then returns true immediately. * <p/> * If it isn't, the continues through all the keys in the key( this could be a lot, but the assumption is that if the book * exists, then it's unlikely to have just the last chapter? * * @param master the master book * @param k the key to be tested * @return true if the key is present in the master book */ public static boolean containsAny(Book master, Key k) { if(k.isEmpty()) { return false; } if(!(master instanceof AbstractPassageBook)) { return master.contains(k); } final Set<BibleBook> books = ((AbstractPassageBook) master).getBibleBooks(); try { final Verse firstVerse = KeyUtil.getVerse(k); if (!books.contains(firstVerse.getBook())) { //the books of the module do not contain the book referred to by the verse return false; } //we're still here, so the books do exist //so let's now examine the keys one by one Iterator<Key> keys = k.iterator(); while (keys.hasNext()) { if (master.contains(keys.next())) { return true; } } } catch(ArrayIndexOutOfBoundsException a) { return false; } return false; } /** * Gets the chapter OSIS in the form of Gen.1, except for short books, where it is the single chapter * @param bibleBook * @param chapterNumber * @return */ public static String getChapterOsis(final BibleBook bibleBook, final int chapterNumber) { return bibleBook.isShortBook() ? bibleBook.getOSIS() : String.format(BOOK_CHAPTER_OSIS_FORMAT, bibleBook.getOSIS(), chapterNumber); } }