001package ca.uhn.hl7v2.preparser; 002 003import java.util.*; 004 005/** An object of this class represents a variable-size path for identifying 006the location of a datum within an HL7 message, which we can use for 007maintaining parser state and for generating a suitable string key (in the 008ZYX[a]-b[c]-d-e style) for a piece of data in the message (see toString()). 009 010The elements are: 011segmentID / segmentRepIdx / fieldIdx / fieldRepIdx / compIdx / subcompIdx 012 013("rep" means "repetition") 014 015segmentID is a String, the rest are Integers. 016 017It is variable-size path-style in that if it has a size of 1, the one element 018will be the segmentID; if it has a size of two, element 0 will be the segmentID 019and element 1 will be the segmentRepIdx, etc. This class can't represent a 020fieldIdx without having segmentID / segmentRepIdx, etc. etc. 021 022possible sizes: 0 to 6 inclusive 023 024As toString() simply converts this's integer values to strings (1 => "1"), and 025since for some reason the ZYX[a]-b[c]-d-e style counts b, d, e starting from 1 026and a, c from 0 -- it is intended that one store the numeric values in this 027class starting from 1 for fieldIdx (element 2), compIdx (4) and subcompIdx 028(5), and from 0 for segmentRepIdx (1) and fieldRepIdx (3). default values 029provided by setSize() and by toString() do this. 030*/ 031public class DatumPath implements Cloneable { 032 033 public static final int s_maxSize = 6; 034 035 protected ArrayList<Object> m_path = null; 036 037 public DatumPath() 038 { 039 m_path = new ArrayList<Object>(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 164 StringBuffer strbuf = new StringBuffer(15); 165 166 if(m_path.size() >= 1) { 167 DatumPath extendedCopy = (DatumPath)this.clone(); 168 extendedCopy.setSize(s_maxSize); 169 170 for(int i=0; i<extendedCopy.size(); ++i) { 171 if(i == 0) 172 strbuf.append("" + ((String)extendedCopy.get(0))); 173 else if((i == 1) || (i == 3)) 174 strbuf.append("[" + ((Integer)extendedCopy.get(i)).intValue() + "]"); 175 else if((i == 2) || (i == 4) || (i == 5)) 176 strbuf.append("-" + (((Integer)extendedCopy.get(i)).intValue())); 177 } 178 } 179 else 180 throw new IndexOutOfBoundsException(); 181 182 return "" + strbuf; 183 } 184 185 /** add() grows this by 1, inserting newValue at the end. 186 newValue must be a String or an Integer depending on the index where it will 187 be inserted, as noted at DatumPath.set(). 188 returns this. 189 (newValue == null) => NullPointerException 190 */ 191 public DatumPath add(Object newValue) 192 { 193// m_path.ensureCapacity(m_path.size() + 1); 194// set(m_path.size() - 1, newValue); 195 m_path.add(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 while (m_path.size() < newSize) { 241 m_path.add(null); 242 } 243 244 while (m_path.size() > newSize) { 245 m_path.remove(m_path.size() - 1); 246 } 247 248 if(newSize > oldSize) { 249 // give the new elements some default values: 250 for(int i=oldSize; i<newSize; ++i) { 251 if(i == 0) 252 set(i, ""); 253 else 254 set(i, new Integer((i==1 || i==3) ? 0 : 1)); 255 } 256 } 257 258 return this; 259 } 260 261 /** setSize(0). returns this. */ 262 public DatumPath clear() 263 { 264 setSize(0); 265 return this; 266 } 267 268 public Object clone() 269 { 270 return new DatumPath(this); 271 } 272 273 /* Compare the numeric parts of "this" and "other". string-style, start from 274 the left: if this[1] < other[1], then return true, if this[1] > other[1] then 275 return false, else repeat with [2] ... if we compare all elements, then return 276 false (they're the same.) 277 278 What are actually compared are copies of this and other that have been grown 279 to s_maxSize (default values in effect), so they'll have the same size. 280 281 This is just a little thing that gets used in the class XML. Look there for 282 a justification of it's existence. 283 284 ex. [1, 1, 1, 1] < [1, 1, 1, 2] 285 [1, 2, 1, 1] < [1, 2, 1, 2] 286 [1, 1, 5, 5] < [1, 2] 287 [1, 1] < [1, 1, 5, 5] 288 */ 289 public boolean numbersLessThan(DatumPath other) 290 { 291 DatumPath extendedCopyThis = new DatumPath(this); 292 extendedCopyThis.setSize(s_maxSize); 293 294 DatumPath extendedCopyOther = new DatumPath(other); 295 extendedCopyOther.setSize(s_maxSize); 296 297 boolean lessThan = false; 298 for(int i=1; !lessThan && (i<s_maxSize); ++i) { 299 int this_i = ((Integer)extendedCopyThis.get(i)).intValue(); 300 int other_i = ((Integer)extendedCopyOther.get(i)).intValue(); 301 lessThan |= (this_i < other_i); 302 } 303 304 return lessThan; 305 } 306 307 public static void main(String args[]) 308 { 309 DatumPath dp = new DatumPath(); 310 dp.add(new String("ZYX")); 311 dp.add(new Integer(42)); 312 313 DatumPath dp2 = (new DatumPath()).add(new String()).add(-42); 314 315 System.out.println(dp); 316 System.out.println(dp2); 317 } 318} 319