001    /**
002     * The contents of this file are subject to the Mozilla Public License Version 1.1
003     * (the "License"); you may not use this file except in compliance with the License.
004     * You may obtain a copy of the License at http://www.mozilla.org/MPL/
005     * Software distributed under the License is distributed on an "AS IS" basis,
006     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
007     * specific language governing rights and limitations under the License.
008     *
009     * The Original Code is "Terser.java".  Description:
010     * "Wraps a message to provide access to fields using a more terse syntax."
011     *
012     * The Initial Developer of the Original Code is University Health Network. Copyright (C)
013     * 2002.  All Rights Reserved.
014     *
015     * Contributor(s): ______________________________________.
016     *
017     * Alternatively, the contents of this file may be used under the terms of the
018     * GNU General Public License (the  ?GPL?), in which case the provisions of the GPL are
019     * applicable instead of those above.  If you wish to allow use of your version of this
020     * file only under the terms of the GPL and not to allow others to use your version
021     * of this file under the MPL, indicate your decision by deleting  the provisions above
022     * and replace  them with the notice and other provisions required by the GPL License.
023     * If you do not delete the provisions above, a recipient may use your version of
024     * this file under either the MPL or the GPL.
025     *
026     */
027    
028    package ca.uhn.hl7v2.util;
029    
030    import ca.uhn.hl7v2.model.*;
031    import ca.uhn.hl7v2.HL7Exception;
032    import java.util.StringTokenizer;
033    import ca.uhn.log.*;
034    
035    /**
036     * <p>Wraps a message to provide access to fields using a terse location
037     * specification syntax.  For example: </p>
038     * <p><code>terser.set("MSH-9-3", "ADT_A01");</code>  <br>
039     * can be used instead of <br>
040     * <code>message.getMSH().getMessageType().getMessageStructure().setValue("ADT_A01"); </code> </p>
041     * <p>The syntax of a location spec is as follows: </p>
042     * <p>location_spec: <code>segment_path_spec "-" field ["(" rep ")"] ["-" component ["-" subcomponent]] </code></p>
043     * <p>... where rep, field, component, and subcomponent are integers (representing, respectively,
044     * the field repetition (starting at 0), and the field number, component number, and subcomponent
045     * numbers (starting at 1).  Omitting the rep is equivalent to specifying 0; omitting the
046     * component or subcomponent is equivalent to specifying 1.</p>
047     * <p>The syntax for the segment_path_spec is as follows: </p>
048     * <p>segment_path_spec: </code> ["/"] (group_spec ["(" rep ")"] "/")* segment_spec ["(" rep ")"]</code></p>
049     * <p> ... where rep has the same meaning as for fields.  A leading "/" indicates that navigation to the
050     * location begins at the root of the message; ommitting this indicates that navigation begins at the
051     * current location of the underlying SegmentFinder (see getFinder() -- this allows manual navigation
052     * if desired).  The syntax for group_spec is: </p>
053     * <p>group_spec: <code>["."] group_name_pattern</code></p>
054     * <p>Here, a . indicates that the group should be searched for (using a SegmentFinder) starting at the
055     * current location in the message.  The wildcards "*" and "?" represent any number of arbitrary characters, 
056     * and a single arbitrary character, respectively.  For example, "M*" and "?S?" match MSH.  The first
057     * group with a name that matches the given group_name_pattern will be matched.  </p>
058     * <p>The segment_spec is analogous to the group_spec. </p>
059     * <p>As another example, the following subcomponent in an SIU_S12 message: <p>
060     * <p><code>msg.getSIU_S12_RGSAISNTEAIGNTEAILNTEAIPNTE(1).getSIU_S12_AIGNTE().getAIG().getResourceGroup(1).getIdentifier();</code></p>
061     * </p> ... is referenced by all of the following location_spec: </p>
062     * <p><code>/SIU_S12_RGSAISNTEAIGNTEAILNTEAIPNTE(1)/SIU_S12_AIGNTE/AIG-5(1)-1 <br>
063     * /*AIG*(1)/SIU_S12_AIGNTE/AIG-5(1)-1 <br>
064     * /*AIG*(1)/.AIG-5(1) <code></p>
065     * <p>The search function only iterates through rep 0 of each group.  Thus if rep 0 of the first group
066     * in this example was desired instead of rep 1, the following syntax would also work (since there is
067     * only one AIG segment position in SUI_S12): </p>
068     * <p><code>/.AIG-5(1)</code></p>
069     * @author Bryan Tripp
070     */
071    public class Terser {
072        
073        private SegmentFinder finder;
074        private static HapiLog log = HapiLogFactory.getHapiLog(Terser.class);
075        
076        /** Creates a new instance of Terser */
077        public Terser(Message message) {
078            finder = new SegmentFinder(message);
079        }
080        
081        /**
082         * Returns the string value of the Primitive at the given location.
083         * @param segment the segment from which to get the primitive
084         * @param field the field number
085         * @param rep the field repetition
086         * @param component the component number (use 1 for primitive field)
087         * @param subcomponent the subcomponent number (use 1 for primitive component)
088         */
089        public static String get(Segment segment, int field, int rep, int component, int subcomponent) throws HL7Exception {
090            Primitive prim = getPrimitive(segment, field, rep, component, subcomponent);
091            return prim.getValue();
092        }
093        
094        /**
095         * Sets the string value of the Primitive at the given location.
096         */
097        public static void set(Segment segment, int field, int rep, int component, int subcomponent, String value) throws HL7Exception {
098            Primitive prim = getPrimitive(segment, field, rep, component, subcomponent);
099            prim.setValue(value);
100        }
101        
102        /**
103         * Returns the Primitive object at the given location.
104         */
105        private static Primitive getPrimitive(Segment segment, int field, int rep, int component, int subcomponent) throws HL7Exception {
106            Type type = segment.getField(field, rep);
107            return getPrimitive(type, component, subcomponent);
108        }
109        
110        /**
111         * Returns the Primitive object at the given location in the given field.  
112         * It is intended that the given type be at the field level, although extra components 
113         * will be added blindly if, for example, you provide a primitive subcomponent instead 
114         * and specify component or subcomponent > 1
115         */
116        public static Primitive getPrimitive(Type type, int component, int subcomponent) {
117            Type comp = getComponent(type, component);
118            Type sub = getComponent(comp, subcomponent);
119            return getPrimitive(sub);
120        }
121        
122        /** 
123         * Attempts to extract a Primitive from the given type. If it's a composite, 
124         * drills down through first components until a primitive is reached. 
125         */
126        private static Primitive getPrimitive(Type type) {
127            Primitive p = null;
128            if (Varies.class.isAssignableFrom(type.getClass())) {
129                p = getPrimitive(((Varies) type).getData());
130            } else if (Composite.class.isAssignableFrom(type.getClass())) {
131                try {
132                    p = getPrimitive(((Composite) type).getComponent(0));            
133                } catch (HL7Exception e) {
134                    throw new Error("Internal error: HL7Exception thrown on Composite.getComponent(0)."); 
135                }
136            } else if (type instanceof Primitive) {
137                p = (Primitive) type;
138            } 
139            return p;
140        }
141        
142        /**
143         * Returns the component (or sub-component, as the case may be) at the given
144         * index.  If it does not exist, it is added as an "extra component".  
145         * If comp > 1 is requested from a Varies with GenericPrimitive data, the 
146         * data is set to GenericComposite (this avoids the creation of a chain of 
147         * ExtraComponents on GenericPrimitives).  
148         * Components are numbered from 1.  
149         */
150        private static Type getComponent(Type type, int comp) {
151            Type ret = null;
152            if (Varies.class.isAssignableFrom(type.getClass())) {
153                Varies v = (Varies) type;
154                
155                try {
156                    if (comp > 1 && GenericPrimitive.class.isAssignableFrom(v.getData().getClass())) 
157                        v.setData(new GenericComposite(v.getMessage()));
158                } catch (DataTypeException de) {
159                    String message = "Unexpected exception copying data to generic composite: " + de.getMessage(); 
160                    log.error(message, de);
161                    throw new Error(message);
162                }
163                
164                ret = getComponent(v.getData(), comp);
165            } else {
166                if (Primitive.class.isAssignableFrom(type.getClass()) && comp == 1) {
167                    ret = type;
168                } else if (GenericComposite.class.isAssignableFrom(type.getClass()) 
169                    || (Composite.class.isAssignableFrom(type.getClass()) && comp <= numStandardComponents(type))) {
170                    //note that GenericComposite can return components > number of standard components
171                    
172                    try {
173                        ret = ((Composite) type).getComponent(comp - 1);
174                    } catch (Exception e) {
175                        throw new Error("Internal error: HL7Exception thrown on getComponent(x) where x < # standard components.", e);
176                    }
177                } else {
178                    ret = type.getExtraComponents().getComponent(comp - numStandardComponents(type) - 1);
179                }
180            }
181            return ret;
182        }
183        
184        /**
185         * <p>Gets the string value of the field specified.  See the class docs for syntax
186         * of the location spec.  </p>
187         * <p>If a repetition is omitted for a repeating segment or field, the first rep is used.
188         * If the component or subcomponent is not specified for a composite field, the first
189         * component is used (this allows one to write code that will work with later versions of
190         * the HL7 standard).
191         */
192        public String get(String spec) throws HL7Exception {
193            StringTokenizer tok = new StringTokenizer(spec, "-", false);
194            Segment segment = getSegment(tok.nextToken());
195            
196            int[] ind = getIndices(spec);
197            return get(segment, ind[0], ind[1], ind[2], ind[3]);
198        }
199        
200        /** 
201         * Returns the segment specified in the given segment_path_spec. 
202         */
203        public Segment getSegment(String segSpec) throws HL7Exception {
204            Segment seg = null;
205            
206            if (segSpec.substring(0, 1).equals("/")) {
207                getFinder().reset();
208            }
209            
210            StringTokenizer tok = new StringTokenizer(segSpec, "/", false);
211            SegmentFinder finder = getFinder();
212            while(tok.hasMoreTokens()) {
213                String pathSpec = tok.nextToken();
214                Terser.PathSpec ps = parsePathSpec(pathSpec);
215                if (tok.hasMoreTokens()) {
216                    ps.isGroup = true;
217                } else {
218                    ps.isGroup = false;
219                }
220                
221                if (ps.isGroup) {
222                    Group g = null;
223                    if (ps.find) {
224                        g = finder.findGroup(ps.pattern, ps.rep);
225                    } else {
226                        g = finder.getGroup(ps.pattern, ps.rep);
227                    }
228                    finder = new SegmentFinder(g);
229                } else {
230                    if (ps.find) {
231                        seg = finder.findSegment(ps.pattern, ps.rep);
232                    } else {
233                        seg = finder.getSegment(ps.pattern, ps.rep);
234                    }
235                }
236            }
237            
238            return seg;
239        }
240        
241        /** Gets path information from a path spec. */
242        private PathSpec parsePathSpec(String spec) throws HL7Exception {
243            PathSpec ps = new PathSpec();
244            
245            if (spec.startsWith(".")) {
246                ps.find = true;
247                spec = spec.substring(1);
248            } else {
249                ps.find = false;
250            }
251            
252            if (spec.length() == 0) {
253                throw new HL7Exception("Invalid path (some path element is either empty or contains only a dot)");
254            }
255            StringTokenizer tok = new StringTokenizer(spec, "()", false);
256            ps.pattern = tok.nextToken();
257            if (tok.hasMoreTokens()) {
258                String repString = tok.nextToken();
259                try {
260                    ps.rep = Integer.parseInt(repString);
261                } catch (NumberFormatException e) {
262                    throw new HL7Exception(repString + " is not a valid rep #", HL7Exception.APPLICATION_INTERNAL_ERROR);
263                }
264            } else {
265                ps.rep = 0;
266            }
267            return ps;
268        }
269        
270        /** 
271         * Given a Terser path, returns an array containing field num, field rep, 
272         * component, and subcomponent.  
273         */
274        public static int[] getIndices(String spec) throws HL7Exception {
275            StringTokenizer tok = new StringTokenizer(spec, "-", false);
276            tok.nextToken();  //skip over segment
277            if (!tok.hasMoreTokens())
278                throw new HL7Exception("Must specify field in spec " + spec, HL7Exception.APPLICATION_INTERNAL_ERROR);
279            
280            int[] ret = null;
281            try {
282                StringTokenizer fieldSpec = new StringTokenizer(tok.nextToken(), "()", false);
283                int fieldNum = Integer.parseInt(fieldSpec.nextToken());
284                int fieldRep = 0;
285                if (fieldSpec.hasMoreTokens()) {
286                    fieldRep = Integer.parseInt(fieldSpec.nextToken());
287                }
288                
289                int component = 1;
290                if (tok.hasMoreTokens()) {
291                    component = Integer.parseInt(tok.nextToken());
292                }
293                
294                int subcomponent = 1;
295                if (tok.hasMoreTokens()) {
296                    subcomponent = Integer.parseInt(tok.nextToken());
297                }
298                int[] result = {fieldNum, fieldRep, component, subcomponent};
299                ret = result;
300            } catch (NumberFormatException e) {
301                throw new HL7Exception("Invalid integer in spec " + spec, HL7Exception.APPLICATION_INTERNAL_ERROR);
302            }
303            
304            return ret;
305        }
306        
307        /**
308         * Sets the string value of the field specified.  See class docs for location spec syntax.
309         */
310        public void set(String spec, String value) throws HL7Exception {
311            StringTokenizer tok = new StringTokenizer(spec, "-", false);
312            Segment segment = getSegment(tok.nextToken());
313            
314            int[] ind = getIndices(spec);
315            if (log.isDebugEnabled()) {
316                log.debug("Setting " + spec + " seg: " + segment.getName() + " ind: " + ind[0] + " " + ind[1] + " " + ind[2] + " " + ind[3]);            
317            }
318            set(segment, ind[0], ind[1], ind[2], ind[3], value);
319        }
320        
321        /**
322         * Returns the number of components in the given field, i.e. the
323         * number of standard components (e.g. 6 for CE) plus any extra components that
324         * have been added at runtime.  This may vary by repetition, as different reps
325         * may have different extra components.
326         */
327        /*public static int numComponents(Type field) throws HL7Exception {
328            return numComponents(seg.getField(field, rep));
329        }*/
330        
331        /**
332         * Returns the number of sub-components in the specified component, i.e. 
333         * the number of standard sub-components (e.g. 6 for CE) plus any extra components that
334         * that have been added at runtime.
335         * @param component numbered from 1 
336         */
337        public static int numSubComponents(Type type, int component) {
338            int n = -1;
339            if (component == 1 && Primitive.class.isAssignableFrom(type.getClass())) {
340                //note that getComponent(primitive, 1) below returns the primitive 
341                //itself -- if we do numComponents on it, we'll end up with the 
342                //number of components in the field, not the number of subcomponents
343                n = 1;
344            } else {
345                Type comp = getComponent(type, component);
346                n = numComponents(comp);
347            }
348            return n;
349            /*
350            //Type t = seg.getField(field, rep);
351            if (Varies.class.isAssignableFrom(type.getClass())) {
352                return numSubComponents(((Varies) type).getData(), component);
353            } else if (Primitive.class.isAssignableFrom(type.getClass()) && component == 1) {
354                n = 1;  
355            } else if (Composite.class.isAssignableFrom(type.getClass()) && component <= numStandardComponents(t)) {
356                n = numComponents(((Composite) type).getComponent(component - 1));
357            } else { //we're being asked about subcomponents of an extra component
358                n = numComponents(t.getExtraComponents().getComponent(component - numStandardComponents(t) - 1));
359            }
360            return n;
361             */
362        }
363        
364        /**
365         * Returns the number of components in the given type, i.e. the
366         * number of standard components (e.g. 6 for CE) plus any extra components that
367         * have been added at runtime.  
368         */
369        public static int numComponents(Type type) {
370            if (Varies.class.isAssignableFrom(type.getClass())) {
371                return numComponents(((Varies) type).getData());
372            } else {
373                return numStandardComponents(type) + type.getExtraComponents().numComponents();
374            }
375        }
376        
377        private static int numStandardComponents(Type t) {
378            int n = 0;
379            if (Varies.class.isAssignableFrom(t.getClass())) {
380                n = numStandardComponents(((Varies) t).getData());
381            } else if (Composite.class.isAssignableFrom(t.getClass())) {
382                n = ((Composite) t).getComponents().length;
383            } else {
384                n = 1;
385            }
386            return n;
387        }
388        
389        /**
390         * Returns the segment finder used by this Terser.  Navigating the
391         * finder will influence the behaviour of the Terser accordingly.  Ie
392         * when the full path of the segment is not specified the segment will
393         * be sought beginning at the current location of the finder.
394         */
395        public SegmentFinder getFinder() {
396            return finder;
397        }
398        
399        /** Struct for information about a step in a segment path. */
400        private class PathSpec {
401            public String pattern;
402            public boolean isGroup;
403            public boolean find;
404            public int rep;
405        }
406    }