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 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