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 Vector m_path = null;
036    
037            public DatumPath()
038            {
039                    m_path = new Vector(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                    String ret = null;
164    
165                    StringBuffer strbuf = new StringBuffer(15);
166    
167                    if(m_path.size() >= 1) {
168                            DatumPath extendedCopy = (DatumPath)this.clone();
169                            extendedCopy.setSize(s_maxSize);
170    
171                            for(int i=0; i<extendedCopy.size(); ++i) {
172                                    if(i == 0)
173                                            strbuf.append("" + ((String)extendedCopy.get(0)));
174                                    else if((i == 1) || (i == 3))
175                                            strbuf.append("[" + ((Integer)extendedCopy.get(i)).intValue() + "]");
176                                    else if((i == 2) || (i == 4) || (i == 5))
177                                            strbuf.append("-" + (((Integer)extendedCopy.get(i)).intValue()));
178                            }
179                    }
180                    else 
181                            throw new IndexOutOfBoundsException();  
182    
183                    return "" + strbuf;
184            }
185    
186            /** add() grows this by 1, inserting newValue at the end.
187            newValue must be a String or an Integer depending on the index where it will
188            be inserted, as noted at DatumPath.set().  
189            returns this.
190            (newValue == null) => NullPointerException 
191            */
192            public DatumPath add(Object newValue)
193            {
194                    m_path.setSize(m_path.size() + 1);
195                    set(m_path.size() - 1, 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                    m_path.setSize(newSize);
241    
242                    if(newSize > oldSize) {
243                            // give the new elements some default values: 
244                            for(int i=oldSize; i<newSize; ++i) {
245                                    if(i == 0)
246                                            set(i, "");
247                                    else
248                                            set(i, new Integer((i==1 || i==3) ? 0 : 1));
249                            }
250                    }
251    
252                    return this;
253            }
254    
255            /** setSize(0).  returns this. */
256            public DatumPath clear() 
257            {
258                    setSize(0);
259                    return this;
260            }
261    
262            public Object clone()
263            {
264                    return new DatumPath(this);
265            }
266    
267            /* Compare the numeric parts of "this" and "other".  string-style, start from
268            the left: if this[1] < other[1], then return true, if this[1] > other[1] then
269            return false, else repeat with [2] ... if we compare all elements, then return
270            false (they're the same.)
271    
272            What are actually compared are copies of this and other that have been grown
273            to s_maxSize (default values in effect), so they'll have the same size.
274            
275            This is just a little thing that gets used in the class XML.  Look there for 
276            a justification of it's existence.
277    
278            ex. [1, 1, 1, 1] < [1, 1, 1, 2] 
279            [1, 2, 1, 1] < [1, 2, 1, 2]
280            [1, 1, 5, 5] < [1, 2]
281            [1, 1] < [1, 1, 5, 5] 
282            */
283            public boolean numbersLessThan(DatumPath other)
284            {
285                    DatumPath extendedCopyThis = new DatumPath(this);
286                    extendedCopyThis.setSize(s_maxSize);
287    
288                    DatumPath extendedCopyOther = new DatumPath(other);
289                    extendedCopyOther.setSize(s_maxSize);
290    
291                    boolean lessThan = false;
292                    for(int i=1; !lessThan && (i<s_maxSize); ++i) {
293                            int this_i = ((Integer)extendedCopyThis.get(i)).intValue();
294                            int other_i = ((Integer)extendedCopyOther.get(i)).intValue();
295                            lessThan |= (this_i < other_i);
296                    }
297    
298                    return lessThan;
299            }
300    
301            public static void main(String args[])
302            {
303                    DatumPath dp = new DatumPath();
304                    dp.add(new String("ZYX"));
305                    dp.add(new Integer(42));
306    
307                    DatumPath dp2 = (new DatumPath()).add(new String()).add(-42);
308    
309                    System.out.println(dp);
310            }
311    }
312