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