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