View Javadoc

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 = null;
38  
39  	public DatumPath()
40  	{
41  		m_path = new ArrayList<Object>(s_maxSize);
42  	}
43  
44  	/** copy constructor */
45  	public 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 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 				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