001 package ca.uhn.hl7v2.preparser; 002 003 import java.util.*; 004 005 /** An object of this class represents a variable-size path for identifying 006 the location of a datum within an HL7 message, which we can use for 007 maintaining parser state and for generating a suitable string key (in the 008 ZYX[a]-b[c]-d-e style) for a piece of data in the message (see toString()). 009 010 The elements are: 011 segmentID / segmentRepIdx / fieldIdx / fieldRepIdx / compIdx / subcompIdx 012 013 ("rep" means "repetition") 014 015 segmentID is a String, the rest are Integers. 016 017 It is variable-size path-style in that if it has a size of 1, the one element 018 will be the segmentID; if it has a size of two, element 0 will be the segmentID 019 and element 1 will be the segmentRepIdx, etc. This class can't represent a 020 fieldIdx without having segmentID / segmentRepIdx, etc. etc. 021 022 possible sizes: 0 to 6 inclusive 023 024 As toString() simply converts this's integer values to strings (1 => "1"), and 025 since for some reason the ZYX[a]-b[c]-d-e style counts b, d, e starting from 1 026 and a, c from 0 -- it is intended that one store the numeric values in this 027 class starting from 1 for fieldIdx (element 2), compIdx (4) and subcompIdx 028 (5), and from 0 for segmentRepIdx (1) and fieldRepIdx (3). default values 029 provided by setSize() and by toString() do this. 030 */ 031 public class DatumPath implements Cloneable { 032 033 public static final int s_maxSize = 6; 034 035 protected Vector m_path = null; 036 037 public DatumPath() 038 { 039 m_path = new Vector(s_maxSize); 040 } 041 042 /** copy constructor */ 043 public DatumPath(DatumPath other) 044 { 045 this(); 046 047 copy(other); 048 } 049 050 public boolean equals(Object otherObject) 051 { 052 boolean ret = false; 053 DatumPath other = (DatumPath)otherObject; 054 if(this.size() == other.size()) { 055 ret = true; 056 for(int i=0; i<this.size(); ++i) 057 ret &= this.get(i).equals(other.get(i)); 058 } 059 060 return ret; 061 } 062 063 /** Works like String.startsWith: 064 returns true iff prefix.size() <= this.size() 065 AND if, for 0 <= i < prefix.size(), this.get(i).equals(prefix.get(i)) 066 */ 067 public boolean startsWith(DatumPath prefix) 068 { 069 boolean ret = false; 070 if(prefix.size() <= this.size()) { 071 ret = true; 072 for(int i=0; i<prefix.size(); ++i) 073 ret &= this.get(i).equals(prefix.get(i)); 074 } 075 return ret; 076 } 077 078 /** like a copy constructor without the constructor */ 079 public void copy(DatumPath other) 080 { 081 setSize(0); 082 for(int i=0; i<other.size(); ++i) 083 add(other.get(i)); 084 } 085 086 /** set() sets an element of the path. 087 088 idx must be in [0, size()). else => IndexOutOfBoundsException. 089 090 (new_value == null) => NullPointerException 091 092 new_value must be either a String or an Integer depending on what part 093 of the path you're setting: 094 095 (idx == 0) => String 096 (idx >= 1) => Integer 097 098 If new_value can't be cast to the appropriate type, a ClassCastException 099 is thrown before new_value is stored. 100 101 Of course, on success, this will discard it's reference that used to be at 102 position idx. 103 */ 104 public void set(int idx, Object new_value) 105 { 106 if((0 <= idx) && (idx < m_path.size())) { 107 if(new_value != null) { 108 if(idx == 0) 109 m_path.set(idx, (String)new_value); 110 else if(idx >= 1) 111 m_path.set(idx, (Integer)new_value); 112 } 113 else 114 throw new NullPointerException(); 115 } 116 else 117 throw new IndexOutOfBoundsException(); 118 } 119 120 /** get() returns an element, which will be either a String or an Integer. 121 122 ((idx == 0) => String 123 (idx >= 1) => Integer 124 ((idx < 0) || (idx >= size())) => IndexOutOfBoundsException 125 126 We will attempt to cast the gotten object to the appropriate type before 127 returning it as an Object. That way, if there's an object of the wrong type 128 in the wrong place in here (that got past set() somehow), then a 129 ClassCastException will be thrown even if the caller of this function 130 doesn't try to cast it. (consider System.out.println("val: " + path.get(n)) 131 nothing would barf it this get() wasn't vigilant.) 132 */ 133 public Object get(int idx) 134 { 135 Object gottenObj = m_path.get(idx); 136 if(idx == 0) 137 return (String)gottenObj; 138 else 139 return (Object)gottenObj; 140 } 141 142 public int size() { return m_path.size(); } 143 144 /** toString() outputs the path (from segmentID onward) in the ZYX[a]-b[c]-d-e 145 style (TODO: give it a name), suitable for a key in a map of 146 message datum paths to values. 147 148 Integer values are converted to strings directly (1 => "1") so when you 149 constructed this you should have started counting from 1 for everything but 150 the "repeat" fields, if you truly want the ZYX[a]-b[c]-d-e style. 151 152 If toString() is called when this has a size in [1, 6) (=> missing numeric 153 elments), then we act as though the elements in [size(), 6) are 0 or 1 as 154 appropriate for each element. We don't provide a default for the element 0 155 (the String element): will throw an IndexOutOfBoundsException if (size() == 156 1). 157 158 eg. a (new DatumPath()).add(new String("ZYX")).add(2).add(6).toString() 159 would yield "ZYX[2]-6[0]-1-1" 160 */ 161 public String toString() 162 { 163 String ret = null; 164 165 StringBuffer strbuf = new StringBuffer(15); 166 167 if(m_path.size() >= 1) { 168 DatumPath extendedCopy = (DatumPath)this.clone(); 169 extendedCopy.setSize(s_maxSize); 170 171 for(int i=0; i<extendedCopy.size(); ++i) { 172 if(i == 0) 173 strbuf.append("" + ((String)extendedCopy.get(0))); 174 else if((i == 1) || (i == 3)) 175 strbuf.append("[" + ((Integer)extendedCopy.get(i)).intValue() + "]"); 176 else if((i == 2) || (i == 4) || (i == 5)) 177 strbuf.append("-" + (((Integer)extendedCopy.get(i)).intValue())); 178 } 179 } 180 else 181 throw new IndexOutOfBoundsException(); 182 183 return "" + strbuf; 184 } 185 186 /** add() grows this by 1, inserting newValue at the end. 187 newValue must be a String or an Integer depending on the index where it will 188 be inserted, as noted at DatumPath.set(). 189 returns this. 190 (newValue == null) => NullPointerException 191 */ 192 public DatumPath add(Object newValue) 193 { 194 m_path.setSize(m_path.size() + 1); 195 set(m_path.size() - 1, newValue); 196 return this; 197 } 198 199 /** Like add(String). convenient wrapper for add(Object), when the object 200 to be added must be an Integer anyway (size() > 0 on entry). 201 202 For the user, it turns 203 path.add(new Integer(i)).add(new Integer(j)).add(new Integer(k)) 204 into 205 path.add(i).add(j).add(k), that's all. 206 207 size() == 0 on entry throws a ClassCastException (which it is, kindof), 208 otherwise calls add(new Integer(new_value)). 209 */ 210 public DatumPath add(int new_value) 211 { 212 if(size() > 0) 213 add(new Integer(new_value)); 214 else 215 throw new ClassCastException(); 216 217 return this; 218 } 219 220 /** convenience! Like add(int), but the other way around. */ 221 public DatumPath add(String new_value) 222 { 223 if(size() == 0) 224 add((Object)new_value); 225 else 226 throw new ClassCastException(); 227 228 return this; 229 } 230 231 /** setSize(): resize. If this will grow the object, then we put default 232 values into the new elements: "" into the String element, Integer(1) into the 233 elements 2, 4, and 5, and Integer(0) into elements 1 and 3. 234 returns this. 235 */ 236 public DatumPath setSize(int newSize) 237 { 238 int oldSize = m_path.size(); 239 240 m_path.setSize(newSize); 241 242 if(newSize > oldSize) { 243 // give the new elements some default values: 244 for(int i=oldSize; i<newSize; ++i) { 245 if(i == 0) 246 set(i, ""); 247 else 248 set(i, new Integer((i==1 || i==3) ? 0 : 1)); 249 } 250 } 251 252 return this; 253 } 254 255 /** setSize(0). returns this. */ 256 public DatumPath clear() 257 { 258 setSize(0); 259 return this; 260 } 261 262 public Object clone() 263 { 264 return new DatumPath(this); 265 } 266 267 /* Compare the numeric parts of "this" and "other". string-style, start from 268 the left: if this[1] < other[1], then return true, if this[1] > other[1] then 269 return false, else repeat with [2] ... if we compare all elements, then return 270 false (they're the same.) 271 272 What are actually compared are copies of this and other that have been grown 273 to s_maxSize (default values in effect), so they'll have the same size. 274 275 This is just a little thing that gets used in the class XML. Look there for 276 a justification of it's existence. 277 278 ex. [1, 1, 1, 1] < [1, 1, 1, 2] 279 [1, 2, 1, 1] < [1, 2, 1, 2] 280 [1, 1, 5, 5] < [1, 2] 281 [1, 1] < [1, 1, 5, 5] 282 */ 283 public boolean numbersLessThan(DatumPath other) 284 { 285 DatumPath extendedCopyThis = new DatumPath(this); 286 extendedCopyThis.setSize(s_maxSize); 287 288 DatumPath extendedCopyOther = new DatumPath(other); 289 extendedCopyOther.setSize(s_maxSize); 290 291 boolean lessThan = false; 292 for(int i=1; !lessThan && (i<s_maxSize); ++i) { 293 int this_i = ((Integer)extendedCopyThis.get(i)).intValue(); 294 int other_i = ((Integer)extendedCopyOther.get(i)).intValue(); 295 lessThan |= (this_i < other_i); 296 } 297 298 return lessThan; 299 } 300 301 public static void main(String args[]) 302 { 303 DatumPath dp = new DatumPath(); 304 dp.add(new String("ZYX")); 305 dp.add(new Integer(42)); 306 307 DatumPath dp2 = (new DatumPath()).add(new String()).add(-42); 308 309 System.out.println(dp); 310 } 311 } 312