1
2 package ca.uhn.hl7v2.preparser;
3
4
5 import java.util.ArrayList;
6
7 /** An object of this class represents a variable-size path for identifying
8 the location of a datum within an HL7 message, which we can use for
9 maintaining parser state and for generating a suitable string key (in the
10 ZYX[a]-b[c]-d-e style) for a piece of data in the message (see toString()).
11
12 The elements are:
13 segmentID / segmentRepIdx / fieldIdx / fieldRepIdx / compIdx / subcompIdx
14
15 ("rep" means "repetition")
16
17 segmentID is a String, the rest are Integers.
18
19 It is variable-size path-style in that if it has a size of 1, the one element
20 will be the segmentID; if it has a size of two, element 0 will be the segmentID
21 and element 1 will be the segmentRepIdx, etc. This class can't represent a
22 fieldIdx without having segmentID / segmentRepIdx, etc. etc.
23
24 possible sizes: 0 to 6 inclusive
25
26 As toString() simply converts this's integer values to strings (1 => "1"), and
27 since for some reason the ZYX[a]-b[c]-d-e style counts b, d, e starting from 1
28 and a, c from 0 -- it is intended that one store the numeric values in this
29 class starting from 1 for fieldIdx (element 2), compIdx (4) and subcompIdx
30 (5), and from 0 for segmentRepIdx (1) and fieldRepIdx (3). default values
31 provided by setSize() and by toString() do this.
32 */
33 public class DatumPath implements Cloneable {
34
35 public static final int s_maxSize = 6;
36
37 protected ArrayList<Object> m_path;
38
39 public DatumPath()
40 {
41 m_path = new ArrayList<>(s_maxSize);
42 }
43
44 /** copy constructor */
45 public DatumPath href="../../../../ca/uhn/hl7v2/preparser/DatumPath.html#DatumPath">DatumPath(DatumPath other)
46 {
47 this();
48 copy(other);
49 }
50
51 public boolean equals(Object otherObject)
52 {
53 if (otherObject == null) return false;
54 if (!getClass().equals(otherObject.getClass())) return false;
55 DatumPath/../../../ca/uhn/hl7v2/preparser/DatumPath.html#DatumPath">DatumPath other = (DatumPath)otherObject;
56 return m_path.equals(other.m_path);
57 }
58
59 /** Works like String.startsWith:
60 returns true iff prefix.size() <= this.size()
61 AND if, for 0 <= i < prefix.size(), this.get(i).equals(prefix.get(i))
62 */
63 public boolean startsWith(DatumPath prefix)
64 {
65 boolean ret = false;
66 if(prefix.size() <= this.size()) {
67 ret = true;
68 for(int i=0; i<prefix.size(); ++i)
69 ret &= this.get(i).equals(prefix.get(i));
70 }
71 return ret;
72 }
73
74 /** like a copy constructor without the constructor */
75 public void copy(DatumPath other)
76 {
77 setSize(0);
78 for(int i=0; i<other.size(); ++i)
79 add(other.get(i));
80 }
81
82 /** set() sets an element of the path.
83
84 idx must be in [0, size()). else => IndexOutOfBoundsException.
85
86 (new_value == null) => NullPointerException
87
88 new_value must be either a String or an Integer depending on what part
89 of the path you're setting:
90
91 (idx == 0) => String
92 (idx >= 1) => Integer
93
94 If new_value can't be cast to the appropriate type, a ClassCastException
95 is thrown before new_value is stored.
96
97 Of course, on success, this will discard it's reference that used to be at
98 position idx.
99 */
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