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; 038 039 public DatumPath() 040 { 041 m_path = new ArrayList<>(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 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