1 2 package ca.uhn.hl7v2.preparser; 3 4 5 import java.util.ArrayList; 6 7 /** An object of this class represents a variable-size path for identifying 8 the location of a datum within an HL7 message, which we can use for 9 maintaining parser state and for generating a suitable string key (in the 10 ZYX[a]-b[c]-d-e style) for a piece of data in the message (see toString()). 11 12 The elements are: 13 segmentID / segmentRepIdx / fieldIdx / fieldRepIdx / compIdx / subcompIdx 14 15 ("rep" means "repetition") 16 17 segmentID is a String, the rest are Integers. 18 19 It is variable-size path-style in that if it has a size of 1, the one element 20 will be the segmentID; if it has a size of two, element 0 will be the segmentID 21 and element 1 will be the segmentRepIdx, etc. This class can't represent a 22 fieldIdx without having segmentID / segmentRepIdx, etc. etc. 23 24 possible sizes: 0 to 6 inclusive 25 26 As toString() simply converts this's integer values to strings (1 => "1"), and 27 since for some reason the ZYX[a]-b[c]-d-e style counts b, d, e starting from 1 28 and a, c from 0 -- it is intended that one store the numeric values in this 29 class starting from 1 for fieldIdx (element 2), compIdx (4) and subcompIdx 30 (5), and from 0 for segmentRepIdx (1) and fieldRepIdx (3). default values 31 provided by setSize() and by toString() do this. 32 */ 33 public class DatumPath implements Cloneable { 34 35 public static final int s_maxSize = 6; 36 37 protected ArrayList<Object> m_path; 38 39 public DatumPath() 40 { 41 m_path = new ArrayList<>(s_maxSize); 42 } 43 44 /** copy constructor */ 45 public DatumPath href="../../../../ca/uhn/hl7v2/preparser/DatumPath.html#DatumPath">DatumPath(DatumPath other) 46 { 47 this(); 48 copy(other); 49 } 50 51 public boolean equals(Object otherObject) 52 { 53 if (otherObject == null) return false; 54 if (!getClass().equals(otherObject.getClass())) return false; 55 DatumPath/../../../ca/uhn/hl7v2/preparser/DatumPath.html#DatumPath">DatumPath other = (DatumPath)otherObject; 56 return m_path.equals(other.m_path); 57 } 58 59 /** Works like String.startsWith: 60 returns true iff prefix.size() <= this.size() 61 AND if, for 0 <= i < prefix.size(), this.get(i).equals(prefix.get(i)) 62 */ 63 public boolean startsWith(DatumPath prefix) 64 { 65 boolean ret = false; 66 if(prefix.size() <= this.size()) { 67 ret = true; 68 for(int i=0; i<prefix.size(); ++i) 69 ret &= this.get(i).equals(prefix.get(i)); 70 } 71 return ret; 72 } 73 74 /** like a copy constructor without the constructor */ 75 public void copy(DatumPath other) 76 { 77 setSize(0); 78 for(int i=0; i<other.size(); ++i) 79 add(other.get(i)); 80 } 81 82 /** set() sets an element of the path. 83 84 idx must be in [0, size()). else => IndexOutOfBoundsException. 85 86 (new_value == null) => NullPointerException 87 88 new_value must be either a String or an Integer depending on what part 89 of the path you're setting: 90 91 (idx == 0) => String 92 (idx >= 1) => Integer 93 94 If new_value can't be cast to the appropriate type, a ClassCastException 95 is thrown before new_value is stored. 96 97 Of course, on success, this will discard it's reference that used to be at 98 position idx. 99 */ 100 public void set(int idx, Object new_value) 101 { 102 if((0 <= idx) && (idx < m_path.size())) { 103 if(new_value != null) { 104 m_path.set(idx, new_value); 105 } 106 else 107 throw new NullPointerException(); 108 } 109 else 110 throw new IndexOutOfBoundsException(); 111 } 112 113 /** get() returns an element, which will be either a String or an Integer. 114 115 ((idx == 0) => String 116 (idx >= 1) => Integer 117 ((idx < 0) || (idx >= size())) => IndexOutOfBoundsException 118 119 We will attempt to cast the gotten object to the appropriate type before 120 returning it as an Object. That way, if there's an object of the wrong type 121 in the wrong place in here (that got past set() somehow), then a 122 ClassCastException will be thrown even if the caller of this function 123 doesn't try to cast it. (consider System.out.println("val: " + path.get(n)) 124 nothing would barf it this get() wasn't vigilant.) 125 */ 126 public Object get(int idx) 127 { 128 return m_path.get(idx); 129 } 130 131 public int size() { return m_path.size(); } 132 133 /** toString() outputs the path (from segmentID onward) in the ZYX[a]-b[c]-d-e 134 style (TODO: give it a name), suitable for a key in a map of 135 message datum paths to values. 136 137 Integer values are converted to strings directly (1 => "1") so when you 138 constructed this you should have started counting from 1 for everything but 139 the "repeat" fields, if you truly want the ZYX[a]-b[c]-d-e style. 140 141 If toString() is called when this has a size in [1, 6) (=> missing numeric 142 elments), then we act as though the elements in [size(), 6) are 0 or 1 as 143 appropriate for each element. We don't provide a default for the element 0 144 (the String element): will throw an IndexOutOfBoundsException if (size() == 145 1). 146 147 eg. a (new DatumPath()).add(new String("ZYX")).add(2).add(6).toString() 148 would yield "ZYX[2]-6[0]-1-1" 149 */ 150 public String toString() 151 { 152 153 StringBuilder strbuf = new StringBuilder(); 154 155 if(m_path.size() >= 1) { 156 DatumPath extendedCopy = (DatumPath)this.clone(); 157 extendedCopy.setSize(s_maxSize); 158 159 for(int i=0; i<extendedCopy.size(); ++i) { 160 if(i == 0) 161 strbuf.append(extendedCopy.get(0)); 162 else if((i == 1) || (i == 3)) 163 strbuf.append("[").append(extendedCopy.get(i)).append("]"); 164 else if((i == 2) || (i == 4) || (i == 5)) 165 strbuf.append("-").append(extendedCopy.get(i)); 166 } 167 } 168 else 169 throw new IndexOutOfBoundsException(); 170 171 return strbuf.toString(); 172 } 173 174 /** add() grows this by 1, inserting newValue at the end. 175 newValue must be a String or an Integer depending on the index where it will 176 be inserted, as noted at DatumPath.set(). 177 returns this. 178 (newValue == null) => NullPointerException 179 */ 180 public DatumPath add(Object newValue) 181 { 182 // m_path.ensureCapacity(m_path.size() + 1); 183 // set(m_path.size() - 1, newValue); 184 m_path.add(newValue); 185 return this; 186 } 187 188 /** Like add(String). convenient wrapper for add(Object), when the object 189 to be added must be an Integer anyway (size() > 0 on entry). 190 191 For the user, it turns 192 path.add(new Integer(i)).add(new Integer(j)).add(new Integer(k)) 193 into 194 path.add(i).add(j).add(k), that's all. 195 196 size() == 0 on entry throws a ClassCastException (which it is, kindof), 197 otherwise calls add(new Integer(new_value)). 198 */ 199 public DatumPath add(int new_value) 200 { 201 if(size() > 0) 202 add(Integer.valueOf(new_value)); 203 else 204 throw new ClassCastException(); 205 206 return this; 207 } 208 209 /** convenience! Like add(int), but the other way around. */ 210 public DatumPath add(String new_value) 211 { 212 if(size() == 0) 213 add((Object)new_value); 214 else 215 throw new ClassCastException(); 216 217 return this; 218 } 219 220 /** setSize(): resize. If this will grow the object, then we put default 221 values into the new elements: "" into the String element, Integer(1) into the 222 elements 2, 4, and 5, and Integer(0) into elements 1 and 3. 223 returns this. 224 */ 225 public DatumPath setSize(int newSize) 226 { 227 int oldSize = m_path.size(); 228 229 while (m_path.size() < newSize) { 230 m_path.add(null); 231 } 232 233 while (m_path.size() > newSize) { 234 m_path.remove(m_path.size() - 1); 235 } 236 237 if(newSize > oldSize) { 238 // give the new elements some default values: 239 for(int i=oldSize; i<newSize; ++i) { 240 if(i == 0) 241 set(i, ""); 242 else 243 set(i, (i==1 || i==3) ? 0 : 1); 244 } 245 } 246 247 return this; 248 } 249 250 /** setSize(0). returns this. */ 251 public DatumPath clear() 252 { 253 setSize(0); 254 return this; 255 } 256 257 public Object clone() 258 { 259 return new DatumPath(this); 260 } 261 262 /* Compare the numeric parts of "this" and "other". string-style, start from 263 the left: if this[1] < other[1], then return true, if this[1] > other[1] then 264 return false, else repeat with [2] ... if we compare all elements, then return 265 false (they're the same.) 266 267 What are actually compared are copies of this and other that have been grown 268 to s_maxSize (default values in effect), so they'll have the same size. 269 270 This is just a little thing that gets used in the class XML. Look there for 271 a justification of it's existence. 272 273 ex. [1, 1, 1, 1] < [1, 1, 1, 2] 274 [1, 2, 1, 1] < [1, 2, 1, 2] 275 [1, 1, 5, 5] < [1, 2] 276 [1, 1] < [1, 1, 5, 5] 277 */ 278 public boolean numbersLessThan(DatumPath other) 279 { 280 DatumPath extendedCopyThis = new DatumPath(this); 281 extendedCopyThis.setSize(s_maxSize); 282 283 DatumPath extendedCopyOther = new DatumPath(other); 284 extendedCopyOther.setSize(s_maxSize); 285 286 boolean lessThan = false; 287 for(int i=1; !lessThan && (i<s_maxSize); ++i) { 288 int this_i = ((Integer)extendedCopyThis.get(i)); 289 int other_i = ((Integer)extendedCopyOther.get(i)); 290 lessThan = (this_i < other_i); 291 } 292 293 return lessThan; 294 } 295 296 public static void main(String[] args) 297 { 298 DatumPath dp = new DatumPath(); 299 dp.add("ZYX"); 300 dp.add(Integer.valueOf(42)); 301 302 DatumPath dp2 = new DatumPath().add(-42); 303 304 System.out.println(dp); 305 System.out.println(dp2); 306 } 307 } 308