Coverage Report - org.crosswire.jsword.versification.VersificationsMapper
 
Classes in this File Line Coverage Branch Coverage Complexity
VersificationsMapper
0%
0/74
0%
0/34
4.125
 
 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, 2013 - 2016
 18  
  *
 19  
  */
 20  
 package org.crosswire.jsword.versification;
 21  
 
 22  
 import java.io.IOException;
 23  
 import java.util.ArrayList;
 24  
 import java.util.HashMap;
 25  
 import java.util.Iterator;
 26  
 import java.util.List;
 27  
 import java.util.Map;
 28  
 import java.util.MissingResourceException;
 29  
 
 30  
 import org.crosswire.common.config.ConfigException;
 31  
 import org.crosswire.jsword.passage.Key;
 32  
 import org.crosswire.jsword.passage.Passage;
 33  
 import org.crosswire.jsword.passage.RangedPassage;
 34  
 import org.crosswire.jsword.passage.Verse;
 35  
 import org.crosswire.jsword.passage.VerseKey;
 36  
 import org.crosswire.jsword.versification.system.Versifications;
 37  
 import org.slf4j.Logger;
 38  
 import org.slf4j.LoggerFactory;
 39  
 
 40  
 /**
 41  
  * VersificationMapper maps a Verse or a Passage in one Versification (v11n)
 42  
  * to another, using the KJV v11n as an intermediary.
 43  
  * <p>
 44  
  * Practically speaking, a Verse in one v11n may map:
 45  
  * <ul>
 46  
  * <li>to the same verse in another v11n. This is the typical case.</li>
 47  
  * <li>to another verse in the same chapter. This is common and typically by one, shifting following verses.</li>
 48  
  * <li>to another verse in a different chapter. This is fairly common.</li>
 49  
  * <li>to two other verses. This is common in the Psalms and a few places elsewhere.</li>
 50  
  * </ul>
 51  
  * <p>
 52  
  * The internal details of the mapping can be found in VersificationToKJVMapper.
 53  
  * </p>
 54  
  * <p>
 55  
  * This transitive relationship is not perfect. It assumes that verses
 56  
  * outside of the KJV versification map 1:1 between the source and
 57  
  * target Versifications. That it uses the KJV as an intermediary is an
 58  
  * implementation detail that may change. Do not rely on it.
 59  
  * </p>
 60  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 61  
  * @author Chris Burrell
 62  
  */
 63  
 public final class VersificationsMapper {
 64  
     /**
 65  
      * Prevent instantiation
 66  
      */
 67  0
     private VersificationsMapper() {
 68  
         // we have no mapper for the KJV, since everything maps map to the KJV, so we'll simply add an entry
 69  
         // in there to avoid ever trying to load it
 70  0
         MAPPERS.put(KJV, null);
 71  0
     }
 72  
 
 73  
     /**
 74  
      * @return a singleton instance of the mapper -
 75  
      */
 76  
     public static VersificationsMapper instance() {
 77  0
         if (instance == null) {
 78  0
             synchronized (VersificationsMapper.class) {
 79  0
                 if (instance == null) {
 80  0
                     instance = new VersificationsMapper();
 81  
                 }
 82  0
             }
 83  
         }
 84  0
         return instance;
 85  
     }
 86  
 
 87  
     /**
 88  
      * Maps a whole passage, and does so verse by verse. We can't do any better, since, we may for
 89  
      * example have:
 90  
      * Ps.1.1-Ps.1.10 =&gt; Ps.1.2-Ps.1.11 so one would think we can simply map each of the start and end verses.
 91  
      * However, this would be inaccurate since verse 9 might map to verse 12, 13, etc.
 92  
      *
 93  
      * @param key    the key if the source versification
 94  
      * @param target the target versification
 95  
      * @return the new key in the new versification
 96  
      */
 97  
     public Passage map(final Passage key, final Versification target) {
 98  0
         if (key.getVersification().equals(target)) {
 99  0
             return key;
 100  
         }
 101  
 
 102  0
         Passage newPassage = new RangedPassage(target);
 103  0
         Iterator<Key> verses = key.iterator();
 104  0
         while (verses.hasNext()) {
 105  0
             Verse verse = (Verse) verses.next();
 106  0
             newPassage.addAll(this.mapVerse(verse, target));
 107  0
         }
 108  
 
 109  0
         return newPassage;
 110  
     }
 111  
 
 112  
     /**
 113  
      * @param v                   the verse
 114  
      * @param targetVersification the final versification that we want
 115  
      * @return the key for the verse
 116  
      */
 117  
     public VerseKey mapVerse(Verse v, Versification targetVersification) {
 118  0
         if (v.getVersification().equals(targetVersification)) {
 119  0
             return v;
 120  
         }
 121  
 
 122  0
         ensure(v.getVersification());
 123  0
         ensure(targetVersification);
 124  
 
 125  
         // caution, mappers can be null if they are missing or failed to load.
 126  
         // get the source mapper, to get to the KJV
 127  0
         VersificationToKJVMapper mapper = MAPPERS.get(v.getVersification());
 128  
 
 129  
         // mapped verses could be more than 1 verse in KJV
 130  
         List<QualifiedKey> kjvVerses;
 131  0
         if (mapper == null) {
 132  
             // we can't map to the KJV, so we're going to take a wild guess
 133  
             // and return the equivalent verse
 134  
             // and assume that it maps directly on to the KJV,
 135  
             // and thereby continue with the process
 136  0
             kjvVerses = new ArrayList<QualifiedKey>();
 137  0
             final Verse reversifiedVerse = v.reversify(KJV);
 138  
             //check that the key actually exists
 139  0
             if (reversifiedVerse != null) {
 140  0
                 kjvVerses.add(new QualifiedKey(reversifiedVerse));
 141  
             }
 142  0
         } else {
 143  
             //we need qualified keys back, so as to preserve parts
 144  0
             kjvVerses = mapper.map(new QualifiedKey(v));
 145  
         }
 146  
 
 147  0
         if (KJV.equals(targetVersification)) {
 148  
             // we're done, so simply return the key we have so far.
 149  0
             return getKeyFromQualifiedKeys(KJV, kjvVerses);
 150  
         }
 151  
 
 152  
         // we're continuing, so we need to unmap from the KJV qualified key onto
 153  
         // the new versification.
 154  0
         VersificationToKJVMapper targetMapper = MAPPERS.get(targetVersification);
 155  0
         if (targetMapper == null) {
 156  
             // failed to load, so we'll do our wild-guess again,
 157  
             // and assume that the KJV keys map to the target
 158  0
             return guessKeyFromKjvVerses(targetVersification, kjvVerses);
 159  
         }
 160  
 
 161  
         // we can use the unmap method for that. Since we have a list of
 162  
         // qualified keys, we do so for every qualified
 163  
         // key in the list - this means that parts would get transported as
 164  
         // well.
 165  0
         VerseKey finalKeys = new RangedPassage(targetVersification);
 166  0
         for (QualifiedKey qualifiedKey : kjvVerses) {
 167  0
             final VerseKey verseKey = targetMapper.unmap(qualifiedKey);
 168  0
             if (verseKey != null) {
 169  
                 //verse key exists in the target versification
 170  0
                 finalKeys.addAll(verseKey);
 171  
             }
 172  0
         }
 173  0
         return finalKeys;
 174  
     }
 175  
 
 176  
     /**
 177  
      * This is a last attempt at trying to get something, on the basis that
 178  
      * something is better than nothing.
 179  
      *
 180  
      * @param targetVersification the target versification
 181  
      * @param kjvVerses           the verses in the KJV versification.
 182  
      * @return the possible verses in the target versification, no guarantees
 183  
      *         made
 184  
      */
 185  
     private VerseKey guessKeyFromKjvVerses(final Versification targetVersification, final List<QualifiedKey> kjvVerses) {
 186  0
         final VerseKey finalKeys = new RangedPassage(targetVersification);
 187  0
         for (QualifiedKey qualifiedKey : kjvVerses) {
 188  0
             if (qualifiedKey.getKey() != null) {
 189  0
                 final VerseKey key = qualifiedKey.reversify(targetVersification).getKey();
 190  0
                 if (key != null) {
 191  
                     //verse key exists in target versification
 192  0
                     finalKeys.addAll(key);
 193  
                 }
 194  0
             }
 195  
         }
 196  0
         return finalKeys;
 197  
     }
 198  
 
 199  
     /**
 200  
      * @param kjvVerses the list of keys
 201  
      * @return the aggregate key
 202  
      */
 203  
     private VerseKey getKeyFromQualifiedKeys(Versification versification, final List<QualifiedKey> kjvVerses) {
 204  0
         final VerseKey finalKey = new RangedPassage(versification);
 205  0
         for (QualifiedKey k : kjvVerses) {
 206  
             // we simply ignore everything else at this stage. The other bits
 207  
             // and pieces are used while we're converting
 208  
             // from one to the other.
 209  0
             if (k.getKey() != null) {
 210  0
                 finalKey.addAll(k.getKey());
 211  
             }
 212  
         }
 213  0
         return finalKey;
 214  
     }
 215  
 
 216  
     /** 
 217  
      * Call this to ensure mapping data is loaded (maybe for newly installed books).  
 218  
      * Should normally be called from a background thread, not the ui thread.
 219  
 
 220  
      * @param versification the versification we want to load mapping data for
 221  
      */
 222  
     public void ensureMappingDataLoaded(Versification versification) {
 223  0
         ensure(versification);
 224  0
     }
 225  
 
 226  
     /**
 227  
      * Reads the mapping from file if it does not exist
 228  
      *
 229  
      * @param versification the versification we want to load
 230  
      */
 231  
     private void ensure(final Versification versification) {
 232  0
         if (MAPPERS.containsKey(versification)) {
 233  0
             return;
 234  
         }
 235  
 
 236  
         try {
 237  0
             MAPPERS.put(versification, new VersificationToKJVMapper(versification, new FileVersificationMapping(versification)));
 238  0
         } catch (IOException e) {
 239  
             // we've attempted to load it once, and that's all we'll do.
 240  0
             LOGGER.error("Failed to load versification mappings for versification [{}]", versification, e);
 241  0
             MAPPERS.put(versification, null);
 242  0
         } catch (ConfigException e) {
 243  
             // we've attempted to load it once, and that's all we'll do.
 244  0
             LOGGER.error("Failed to load versification mappings for versification [{}]", versification, e);
 245  0
             MAPPERS.put(versification, null);
 246  0
         } catch (MissingResourceException e) {
 247  
             // we've attempted to load it once, and that's all we'll do.
 248  0
             LOGGER.error("Failed to load versification mappings for versification [{}]", versification, e);
 249  0
             MAPPERS.put(versification, null);
 250  0
         }
 251  0
     }
 252  
 
 253  
     private static volatile VersificationsMapper instance;
 254  0
     private static final Versification KJV = Versifications.instance().getVersification(Versifications.DEFAULT_V11N);
 255  0
     private static final Map<Versification, VersificationToKJVMapper> MAPPERS = new HashMap<Versification, VersificationToKJVMapper>();
 256  0
     private static final Logger LOGGER = LoggerFactory.getLogger(VersificationsMapper.class);
 257  
 }