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