QualifiedKey.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, 2013 - 2016 18 * 19 */ 20 package org.crosswire.jsword.versification; 21 22 import org.crosswire.jsword.passage.Verse; 23 import org.crosswire.jsword.passage.VerseKey; 24 import org.crosswire.jsword.passage.VerseRange; 25 import org.crosswire.jsword.versification.system.Versifications; 26 27 /** 28 * A QualifiedKey represents the various left and right sides of a map entry. 29 * <p> 30 * The QualifiedKey is Qualified: 31 * </p> 32 * <ul> 33 * <li><strong>DEFAULT</strong> - This QualifiedKey is either a Verse or a VerseRange.</li> 34 * <li><strong>ABSENT_IN_KJV</strong> - This QualifiedKey has a section name for what is absent in the KJV (the right hand of the map entry).</li> 35 * <li><strong>ABSENT_IN_LEFT</strong> - This QualifiedKey has no other content.</li> 36 * </ul> 37 * <p> 38 * The mapping can indicate a part of a verse. This is an internal implementation detail of the Versification mapping code. 39 * Here it is used to distinguish one QualifiedKey from another in equality tests and in containers. 40 * </p> 41 * 42 * @author Chris Burrell 43 * @see gnu.lgpl.License The GNU Lesser General Public License for details.<br> 44 * The copyright to this program is held by its authors. 45 */ 46 public final class QualifiedKey { 47 /** 48 * A Qualifier indicates whether the verse is numbered the same in both the KJV and the other, is missing in the KJV or the other. 49 */ 50 enum Qualifier { 51 /** 52 * The DEFAULT Qualifier indicates a Verse or a VerseRange. 53 */ 54 DEFAULT { 55 @Override 56 public String getDescription(QualifiedKey q) { 57 return ""; 58 } 59 }, 60 /** 61 * The ABSENT_IN_LEFT Qualifier indicates that the left side of the map has no equivalent on the right (KJV). 62 */ 63 ABSENT_IN_LEFT { 64 @Override 65 public String getDescription(QualifiedKey q) { 66 return "Absent in Left"; 67 } 68 }, 69 /** 70 * The ABSENT_IN_KJV Qualifier indicates that the right (KJV) side of the map has no equivalent on the left. 71 */ 72 ABSENT_IN_KJV { 73 @Override 74 public String getDescription(QualifiedKey q) { 75 return q != null && q.getSectionName() != null ? q.getSectionName() : "Missing section name"; 76 } 77 }; 78 79 /** 80 * @param q the QualifiedKey that this describes 81 * @return The description for the qualified key 82 */ 83 public abstract String getDescription(QualifiedKey q); 84 85 } 86 87 /** 88 * Construct a QualifiedKey from a Verse. 89 * 90 * @param key the verse from which to create this QualifiedKey 91 */ 92 protected QualifiedKey(Verse key) { 93 setKey(key); 94 this.absentType = Qualifier.DEFAULT; 95 } 96 97 /** 98 * Construct a QualifiedKey from a Verse. 99 * 100 * @param key the verse range from which to create this QualifiedKey 101 */ 102 public QualifiedKey(VerseRange key) { 103 setKey(key); 104 this.absentType = Qualifier.DEFAULT; 105 } 106 107 /** 108 * @param sectionName with a given section name, we assume absent in KJV 109 */ 110 public QualifiedKey(String sectionName) { 111 this.sectionName = sectionName; 112 this.absentType = Qualifier.ABSENT_IN_KJV; 113 } 114 115 /** 116 * Constructs the QualifiedKey with the ABSENT_IN_LEFT qualifier. 117 * This really means that there are no fields in this QualifiedKey. 118 */ 119 public QualifiedKey() { 120 this.absentType = Qualifier.ABSENT_IN_LEFT; 121 } 122 123 /** 124 * Create a QualifiedKey from a Verse or a VerseRange. 125 * 126 * @param k the Verse or VerseRange 127 * @return the created QualifiedKey 128 * @throws ClassCastException 129 */ 130 public static QualifiedKey create(VerseKey k) { 131 return k instanceof Verse ? new QualifiedKey((Verse) k) : new QualifiedKey((VerseRange) k); 132 } 133 134 /** 135 * @return * The internal key which is either a Verse or VerseRange 136 */ 137 public VerseKey getKey() { 138 return wholeKey; 139 } 140 141 /** 142 * @return * The internal key cast as a Verse 143 * @throws ClassCastException 144 */ 145 public Verse getVerse() { 146 return (Verse) wholeKey; 147 } 148 149 /** 150 * @return the type of the unknown qualifier 151 */ 152 public Qualifier getAbsentType() { 153 return absentType; 154 } 155 156 /** 157 * @return the name (any name) of the section represented within the KJV 158 */ 159 public String getSectionName() { 160 return sectionName; 161 } 162 163 /** 164 * A QualifiedKey is whole if it does not split part of a reference. 165 * 166 * @return whether this QualifiedKey has a whole reference 167 */ 168 public boolean isWhole() { 169 // If the reference is null, then it cannot be part or whole. 170 // But we say it is whole because the calls to this are really testing 171 // to see if it is a part. 172 return qualifiedKey == null || qualifiedKey.isWhole(); 173 } 174 175 /** 176 * Convert this QualifiedKey from one Versification to another. 177 * This is a potentially dangerous operation that does no mapping 178 * from one versification to another. Use it only when it is known 179 * to be safe. 180 * 181 * @param target The target versification 182 * @return The reversified QualifiedKey 183 */ 184 public QualifiedKey reversify(Versification target) { 185 // Only if it has a qualified key can it be reversified 186 if (this.qualifiedKey == null) { 187 return this; 188 } 189 190 final VerseKey reversifiedKey = qualifiedKey.reversify(target); 191 if (reversifiedKey != null) { 192 return create(reversifiedKey); 193 } 194 195 if (target.getName().equals(Versifications.DEFAULT_V11N)) { 196 //then we're absent in KJV 197 return new QualifiedKey(qualifiedKey.getOsisID()); 198 } 199 return new QualifiedKey(); 200 201 } 202 203 @Override 204 public String toString() { 205 StringBuilder buf = new StringBuilder(); 206 if (wholeKey != null) { 207 buf.append(qualifiedKey.getOsisRef()); 208 } 209 String desc = absentType.getDescription(this); 210 if (desc.length() > 0) { 211 if (buf.length() > 0) { 212 buf.append(": "); 213 } 214 buf.append(absentType.getDescription(this)); 215 } 216 return buf.toString(); 217 } 218 219 @Override 220 public int hashCode() { 221 // Use a prime number in case one of the values is not around 222 return (this.qualifiedKey == null ? 17 : qualifiedKey.hashCode()) 223 + (this.absentType == null ? 13 : this.absentType.ordinal()) 224 + (this.sectionName == null ? 19 : this.sectionName.hashCode()); 225 } 226 227 @Override 228 public boolean equals(final Object obj) { 229 if (obj instanceof QualifiedKey) { 230 final QualifiedKey otherKey = (QualifiedKey) obj; 231 return this.getAbsentType() == otherKey.getAbsentType() 232 && bothNullOrEqual(this.sectionName, otherKey.sectionName) 233 && bothNullOrEqual(this.qualifiedKey, otherKey.qualifiedKey); 234 } 235 return false; 236 } 237 238 /** 239 * Allow override of the key, particular useful if we're constructing in 2 stages like the offset mechanism 240 * 241 * @param key the new key 242 */ 243 private void setKey(final Verse key) { 244 this.qualifiedKey = key; 245 this.wholeKey = key.getWhole(); 246 } 247 248 /** 249 * Allow override of the key, particular useful if we're constructing in 2 stages like the offset mechanism 250 * 251 * @param key the new key 252 */ 253 private void setKey(final VerseRange key) { 254 if (key.getCardinality() == 1) { 255 this.qualifiedKey = key.getStart(); 256 } else { 257 this.qualifiedKey = key; 258 } 259 this.wholeKey = this.qualifiedKey.getWhole(); 260 } 261 262 /** 263 * Determine whether two objects are equal, allowing nulls 264 * 265 * @param x 266 * @param y 267 * @return true if both are null or the two are equal 268 */ 269 private static boolean bothNullOrEqual(Object x, Object y) { 270 return x == y || (x != null && x.equals(y)); 271 } 272 273 private VerseKey qualifiedKey; 274 private VerseKey wholeKey; 275 private String sectionName; 276 private Qualifier absentType; 277 278 } 279