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 "GroupPointer.java".  Description:
010    "A GroupPointer is used when parsing traditionally encoded HL7 messages"
011    
012    The Initial Developer of the Original Code is University Health Network. Copyright (C)
013    2001.  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    package ca.uhn.hl7v2.parser;
028    
029    import java.io.BufferedReader;
030    import java.io.IOException;
031    import java.io.InputStreamReader;
032    import java.net.URL;
033    import java.net.URLConnection;
034    import java.util.ArrayList;
035    import java.util.Arrays;
036    import java.util.HashMap;
037    import java.util.Iterator;
038    import java.util.List;
039    import java.util.Map;
040    import java.util.Stack;
041    import java.util.StringTokenizer;
042    
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    import ca.uhn.hl7v2.HL7Exception;
047    import ca.uhn.hl7v2.model.Message;
048    import ca.uhn.hl7v2.model.Primitive;
049    import ca.uhn.hl7v2.model.Segment;
050    import ca.uhn.hl7v2.model.Type;
051    import ca.uhn.hl7v2.util.Terser;
052    
053    /**
054     * This class has been deprecated and should not be used.
055     * 
056     * PipeParser has been optimized and is now roughly 20% faster than FastParser (see
057     * FastParserTest for a test of this) 
058     *    
059     * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
060     * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
061     * 
062     * @deprecated
063     */
064    public class FastParser extends Parser {
065    
066        private static final Logger ourLog = LoggerFactory.getLogger(FastParser.class);
067        
068        private static char ourSegmentSeparator = '\r';
069        private Map<Object, StructRef> myEventGuideMap;
070        private PipeParser myPipeParser;
071        
072        /**
073         * @param theEventGuideMap a map with keys in the form "type^event" (like MSH-9 
074         *      components 1 and 2).  Values are corresponding parsing guides for those events.  
075         *      A parsing guide is a group of StructRef that identify which segments to parse, 
076         *      the relationships between them, and where to find them in a message hierarchy.
077         *      The value in the map is the RootRef of the message root.  It must return the 
078         *      StructRef for the MSH segment from getSuccessor("MSH").  References to other 
079         *      segments can be included as needed.   
080         */
081        public FastParser(Map<Object, StructRef> theEventGuideMap) {
082            this(null, theEventGuideMap);
083        }
084    
085        /**
086         * @param theFactory custom factory to use for model class lookup 
087         * @param theEventGuideMap a map with keys in the form "type^event" (like MSH-9 
088         *      components 1 and 2).  Values are corresponding parsing guides for those events.  
089         *      A parsing guide is a group of StructRef that identify which segments to parse, 
090         *      the relationships between them, and where to find them in a message hierarchy.
091         *      The value in the map is the RootRef of the message root.  It must return the 
092         *      StructRef for the MSH segment from getSuccessor("MSH").  References to other 
093         *      segments can be included as needed.   
094         */
095        public FastParser(ModelClassFactory theFactory, Map<Object, StructRef> theEventGuideMap) {
096            super(theFactory);
097            myEventGuideMap = theEventGuideMap;
098            myPipeParser = new PipeParser();
099        }
100        
101        /**
102         * Loads a parsing guide map (as required for FastParser instantiation).  The URL should 
103         * point to a file with one or more guides in sections delimited by blank lines.  Within 
104         * a section, the first line must contain an event name of the for "type^event".  Subsequent 
105         * lines define the parsed parts of messages with that event.  Each line begins with either 
106         * a segment name or "{" (indicating group start) or "}" (indicating group end).  Group  
107         * start lines then have whitespace and a Terser path to the group (relative to the closest 
108         * ancestor group listed in the parsin guide).  Segment lines then have whitespace and a 
109         * relative Terser path to the segment, followed by a colon and a comma-delimited list of field 
110         * numbers, which indicates which fields for that segment are to be parsed.  Within Terser
111         * paths, repetition numbers must be replaced with asterisks. An example follows: 
112         * 
113         * ORU^R01
114         * MSH MSH:9,12
115         * { ORU_R01_PIDNTEPV1ORCOBRNTEOBXNTE(*)
116         *     { ORU_R01_PIDNTEPV1
117         *         PID PID:3-5
118         *     }
119         *     { ORU_R01_ORCOBRNTEOBXNTE(*)
120         *         { ORU_R01_OBXNTE(*)
121         *             OBX OBX:2,5
122         *         }
123         *     }
124         * }
125         * 
126         * ADT^A01
127         * MSH MSH:9,12
128         * PID PID:3
129         * PV1 PV1:7-9
130         * 
131         * @param theMapURL an URL to a file of the form desribed above
132         * @return the corresponding Map 
133         */
134        public static Map<Object, StructRef> loadEventGuideMap(URL theMapURL) throws HL7Exception {
135            Map<Object, StructRef> result = new HashMap<Object, StructRef>();
136            
137            try {
138                URLConnection conn = theMapURL.openConnection();
139                BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
140                
141                String eventName = null;
142                StringBuffer spec = new StringBuffer();
143                String line = null;
144                while ((line = reader.readLine()) != null) {
145                    if (line.length() == 0) {
146                        finish(eventName, spec, result);
147                        eventName = null;
148                        spec = new StringBuffer();
149                    } else {
150                        if (eventName == null) {
151                            eventName = line;
152                        } else {
153                            spec.append(line + "\r");
154                        }
155                    }
156                }
157                reader.close();            
158                finish(eventName, spec, result);
159            } catch (IOException e) {
160                throw new HL7Exception(e);
161            }
162            
163            return result;
164        }
165        
166        private static void finish(String theEventName, StringBuffer theSpec, Map<Object, StructRef> theMap) {
167            if (theEventName != null) {
168                RootRef root = parseGuide(theSpec.toString());
169                theMap.put(theEventName, root);
170            }        
171        }
172        
173        private static RootRef parseGuide(String theSpec) {
174            StringTokenizer lines = new StringTokenizer(theSpec, "\r", false);
175            RootRef result = new RootRef();
176            Stack<StructRef> ancestry = new Stack<StructRef>();
177            ancestry.push(result);
178            Map<Object, StructRef> successors = new HashMap<Object, StructRef>();
179            
180            StructRef previous = result;
181            while (lines.hasMoreTokens()) {
182                String line = lines.nextToken();
183                StringTokenizer parts = new StringTokenizer(line, "\t ", false);
184                String segName = parts.nextToken();
185                String path = parts.hasMoreTokens() ? parts.nextToken() : "";
186                parts = new StringTokenizer(path, ":", false);
187                path = parts.hasMoreTokens() ? parts.nextToken() : null;
188                
189                int[] fields = getFieldList(parts.hasMoreTokens() ? parts.nextToken() : ""); 
190                
191                if (segName.equals("}")) {                
192                    StructRef parent = (StructRef) ancestry.pop();
193                    if (parent.getChildName() != null && parent.getRelativePath().indexOf('*') >= 0) { //repeating group
194                        previous.setSuccessor(parent.getChildName(), parent);                    
195                    }
196                } else {
197                    boolean isSegment = !(segName.equals("{"));
198                    StructRef ref = new StructRef((StructRef) ancestry.peek(), path, isSegment, fields);
199                    if (isSegment) {
200                        previous.setSuccessor(segName, ref);
201                        if (path.indexOf('*') >= 0) ref.setSuccessor(segName, ref);
202                        setGroupSuccessors(successors, segName);
203                    } else {
204                        successors.put(previous, ref);
205                    }
206                    if (!isSegment) ancestry.push(ref);
207                    previous = ref;
208                }
209            }
210            
211            return result;
212        }
213        
214        private static void setGroupSuccessors(Map<Object, StructRef> theSuccessors, String theSegName) {
215            for (Iterator<Object> it = theSuccessors.keySet().iterator(); it.hasNext(); ) {
216                StructRef from = (StructRef) it.next();
217                StructRef to = (StructRef) theSuccessors.get(from);
218                from.setSuccessor(theSegName, to);
219            }
220            theSuccessors.clear();
221        }
222        
223        private static int[] getFieldList(String theSpec) {
224            StringTokenizer tok = new StringTokenizer(theSpec, ",", false); 
225            List<Integer> fieldList = new ArrayList<Integer>(30);
226            while (tok.hasMoreTokens()) {
227                String token = tok.nextToken();
228                int index = token.indexOf('-');
229                if (index >= 0) { //it's a range
230                    int start = Integer.parseInt(token.substring(0, index));
231                    int end = Integer.parseInt(token.substring(index+1));
232                    for (int i = start; i <= end; i++) {
233                        fieldList.add(new Integer(i));
234                    }
235                } else {
236                    fieldList.add(Integer.valueOf(token));
237                }
238            }
239            
240            int[] result = new int[fieldList.size()];
241            for (int i = 0; i < result.length; i++) {
242                result[i] = ((Integer) fieldList.get(i)).intValue();
243            }
244            
245            return result;
246        }
247    
248        /** 
249         * @see ca.uhn.hl7v2.parser.Parser#getEncoding(java.lang.String)
250         */
251        public String getEncoding(String message) {
252            return myPipeParser.getEncoding(message);
253        }
254    
255        /** 
256         * @see ca.uhn.hl7v2.parser.Parser#supportsEncoding(java.lang.String)
257         */
258        public boolean supportsEncoding(String encoding) {
259            return myPipeParser.supportsEncoding(encoding);
260        }
261        
262        /**
263         * @return the preferred encoding of this Parser
264         */
265        public String getDefaultEncoding() {
266            return "VB";
267        }    
268    
269        /** 
270         * @see ca.uhn.hl7v2.parser.Parser#doParse(java.lang.String, java.lang.String)
271         */
272        protected Message doParse(String message, String version) throws HL7Exception, EncodingNotSupportedException {
273            Message result = null;
274            
275            char fieldSep = message.charAt(3);
276            EncodingCharacters ec = new EncodingCharacters(fieldSep, message.substring(4, 8));
277            
278            StringTokenizer tok = new StringTokenizer(message.substring(4), 
279                    String.valueOf(new char[]{fieldSep, ourSegmentSeparator}), true);
280            
281            String[] mshFields = getMSHFields(tok, fieldSep);
282            Object[] structure = getStructure(mshFields[8], ec.getComponentSeparator());
283            
284            StructRef root = (StructRef) myEventGuideMap.get(structure[0]);
285            if (root == null) {
286                ourLog.debug("FastParser delegating to PipeParser because no metadata available for event {}", 
287                        structure[0]);
288                result = myPipeParser.parse(message);
289            } else {
290    //            int csIndex = mshFields[11].indexOf(ec.getComponentSeparator());
291                result = instantiateMessage((String) structure[1], version, ((Boolean) structure[2]).booleanValue());
292                    
293                StructRef mshRef = null;
294                synchronized (root) {
295                    mshRef = root.getSuccessor("MSH");
296                    root.reset();
297                }
298                Segment msh = (Segment) result.get("MSH");
299                for (int i = 0; i < mshRef.getFields().length; i++) {
300                    int fieldNum = mshRef.getFields()[i];
301                    parse(mshFields[fieldNum-1], msh, fieldNum, ec);
302                }            
303                
304                parse(tok, result, root, ec);
305            }
306            
307            return result;
308        }
309        
310        private String[] getMSHFields(StringTokenizer tok, char fieldSep) {
311            String[] result = new String[21];
312            result[0] = String.valueOf(fieldSep);
313            String token = null;
314            int field = 1;
315            while (tok.hasMoreTokens() && (token = tok.nextToken()).charAt(0) != ourSegmentSeparator) {
316                if (token.charAt(0) == fieldSep) {
317                    field++;
318                } else {
319                    result[field] = token;
320                }
321            } 
322            return result;
323        }
324        
325        private void parse(StringTokenizer tok, Message message, StructRef root, EncodingCharacters ec) 
326                throws HL7Exception {
327            
328            Terser t = new Terser(message);
329            
330            synchronized (root) {
331                StructRef ref = root.getSuccessor("MSH");            
332                
333                int field = 0;
334                Segment segment = null;
335                int[] fields = new int[0];
336                
337                while (tok.hasMoreTokens()) {
338                    String token = tok.nextToken();
339                    if (token.charAt(0) == ec.getFieldSeparator()) {
340                        field++;
341                    } else if (token.charAt(0) == ourSegmentSeparator) {
342                        field = 0;
343                    } else if (field == 0) {
344                        StructRef newref = drill(ref, token);
345                        if (newref == null) {
346                            segment = null;
347                            fields = new int[0];
348                        } else {
349                            ref = newref;
350                            ourLog.debug("Parsing into segment {}", ref.getFullPath());
351                            segment = t.getSegment(ref.getFullPath());
352                            fields = ref.getFields();
353                        }
354                    } else if (segment != null && Arrays.binarySearch(fields, field) >= 0) {
355                        parse(token, segment, field, ec);
356                    }
357                }
358                root.reset();
359            }        
360        }
361        
362        //drill through groups to a segment 
363        private StructRef drill(StructRef ref, String name) {
364            ref = ref.getSuccessor(name);
365            while (ref != null && !ref.isSegment()) {
366                ref = ref.getSuccessor(name);
367            }
368            return ref;
369        }
370        
371        private void parse(String field, Segment segment, int num, EncodingCharacters ec) throws HL7Exception {
372            if (field != null) {
373                int rep = 0;
374                int component = 1;
375                int subcomponent = 1;
376                Type type = segment.getField(num, rep);
377                
378                String delim = String.valueOf(new char[]{ec.getRepetitionSeparator(), 
379                        ec.getComponentSeparator(), ec.getSubcomponentSeparator()});
380                for (StringTokenizer tok = new StringTokenizer(field, delim, true); tok.hasMoreTokens(); ) {
381                    String token = tok.nextToken();
382                    char c = token.charAt(0);
383                    if (c == ec.getRepetitionSeparator()) {
384                        rep++;
385                        component = 1;
386                        subcomponent = 1;
387                        type = segment.getField(num, rep);
388                    } else if (c == ec.getComponentSeparator()) {
389                        component++;
390                        subcomponent = 1;
391                    } else if (c == ec.getSubcomponentSeparator()) {
392                        subcomponent++;
393                    } else {
394                        Primitive p = Terser.getPrimitive(type, component, subcomponent);
395                        p.setValue(token);
396                    }
397                }               
398            }
399        }
400        
401        /**
402         * @returns the message structure from MSH-9-3
403         */
404        private Object[] getStructure(String msh9, char compSep) throws HL7Exception {
405            String structure = null;
406            String event = null;
407            
408            String[] components = new String[3];
409            StringTokenizer tok = new StringTokenizer(msh9, String.valueOf(compSep), true);
410            for (int i = 0; tok.hasMoreTokens() && i < components.length; ) {
411                String token = tok.nextToken();
412                if (token.charAt(0) == compSep) {
413                    i++;
414                } else {
415                    components[i] = token;                
416                }
417            }
418    
419            boolean explicitlyDefined = (components[2] == null) ? false : true;
420    
421            if (explicitlyDefined) {
422                structure = components[2];
423            } else if (components[0] != null && components[0].equals("ACK")) {
424                structure = "ACK";
425            } else if (components[0] != null && components[1] != null) {
426                structure = components[0] + "_" + components[1];
427            } else {
428                throw new HL7Exception("Can't determine message structure from MSH-9: " + msh9, 
429                        HL7Exception.UNSUPPORTED_MESSAGE_TYPE);
430            }
431            
432            if (components[1] == null) {
433                event = components[0];
434            } else {
435                event = components[0] + "^" + components[1];
436            }
437            
438            return new Object[] {event, structure, Boolean.valueOf(explicitlyDefined)};
439        }
440        
441    
442        /** 
443         * @see ca.uhn.hl7v2.parser.Parser#encode(ca.uhn.hl7v2.model.Message, java.lang.String)
444         */
445        protected String doEncode(Message source, String encoding) throws HL7Exception,
446                EncodingNotSupportedException {
447            return myPipeParser.doEncode(source, encoding);
448        }
449    
450        /** 
451         * @see ca.uhn.hl7v2.parser.Parser#encode(ca.uhn.hl7v2.model.Message)
452         */
453        protected String doEncode(Message source) throws HL7Exception {
454            return myPipeParser.doEncode(source);
455        }
456    
457        /** 
458         * @see ca.uhn.hl7v2.parser.Parser#getCriticalResponseData(java.lang.String)
459         */
460        public Segment getCriticalResponseData(String message) throws HL7Exception {
461            return myPipeParser.getCriticalResponseData(message);
462        }
463    
464        /** 
465         * @see ca.uhn.hl7v2.parser.Parser#getAckID(java.lang.String)
466         */
467        public String getAckID(String message) {
468            return myPipeParser.getAckID(message);
469        }
470    
471        /** 
472         * @see ca.uhn.hl7v2.parser.Parser#getVersion(java.lang.String)
473         */
474        public String getVersion(String message) throws HL7Exception {
475            return myPipeParser.getVersion(message);
476        }
477    
478        /**
479         * Not supported, throws UnsupportedOperationException
480         *
481         * @throws UnsupportedOperationException
482         */
483        @Override
484        public String doEncode(Segment structure, EncodingCharacters encodingCharacters) throws HL7Exception {
485            throw new UnsupportedOperationException("Not supported yet.");
486        }
487    
488        /**
489         * Not supported, throws UnsupportedOperationException
490         *
491         * @throws UnsupportedOperationException
492         */
493        @Override
494        public String doEncode(Type type, EncodingCharacters encodingCharacters) throws HL7Exception {
495            throw new UnsupportedOperationException("Not supported yet.");
496        }
497    
498        /**
499         * Not supported, throws UnsupportedOperationException
500         *
501         * @throws UnsupportedOperationException
502         */
503        @Override
504        public void parse(Type type, String string, EncodingCharacters encodingCharacters) throws HL7Exception {
505            throw new UnsupportedOperationException("Not supported yet.");
506        }
507    
508        /**
509         * Not supported, throws UnsupportedOperationException
510         *
511         * @throws UnsupportedOperationException
512         */
513        @Override
514        public void parse(Segment segment, String string, EncodingCharacters encodingCharacters) throws HL7Exception {
515            throw new UnsupportedOperationException("Not supported yet.");
516        }
517    
518        
519        /**
520         * Not supported, throws UnsupportedOperationException
521         *
522         * @throws UnsupportedOperationException
523         */
524        @Override
525        public void parse(Message message, String string) throws HL7Exception {
526            throw new UnsupportedOperationException("Not supported yet.");
527        }
528        
529        /**
530         * Throws unsupported operation exception
531         *
532         * @throws Unsupported operation exception
533         */
534        @Override
535            protected Message doParseForSpecificPackage(String theMessage, String theVersion, String thePackageName) throws HL7Exception, EncodingNotSupportedException {
536            throw new UnsupportedOperationException("Not supported yet.");
537            }
538        
539        /**
540         * A pointer to a distinct segment or group position in a message.  
541         *  
542         * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
543         * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
544         */
545        public static class StructRef {
546    
547            private StructRef myParent;
548            private String myRelativePath;
549            private Map<Object, StructRef> mySuccessors;
550            private int myRep;
551            private boolean mySegmentFlag;
552            //private boolean myResettableFlag;
553            private int[] myFields;
554            private List<StructRef> myChildren;
555            
556            /**
557             * @param theParent a StructRef for the parent Group of the referenced Structure
558             * @param theRelativePath the relative (from the parent) Terser path to the referenced 
559             *      structure.  If the structure repeats, the rep number should be replaced with "*"
560             *      (it will be incremented as needed). 
561             * @param isSegment true iff the referenced Structure is a Segment (rather than a Group)
562             * @param theFields a list of fields to be parsed for this segment (null or empty for groups)
563             */
564            public StructRef(StructRef theParent, String theRelativePath, boolean isSegment, int[] theFields) {
565                myParent = theParent;
566                myChildren = new ArrayList<StructRef>();
567                if (myParent != null) myParent.addChild(this);
568                
569                myRelativePath = theRelativePath;
570                if (!myRelativePath.startsWith("/")) {
571                    myRelativePath = "/" + myRelativePath;
572                }
573                mySegmentFlag = isSegment;
574                mySuccessors = new HashMap<Object, StructRef>();
575                myRep = -1;
576                if (mySegmentFlag) {
577                    myFields = theFields;
578                    Arrays.sort(myFields);                
579                } else {
580                    myFields = new int[0];
581                }
582                //myResettableFlag = (myParent == null) ? true : false;
583            }
584            
585            /**
586             * Indicates an immediately subsequent structure in parsing order.  A Structure in a list 
587             * should point to the next Structure in the list.  A Structure that repeats should point to 
588             * itself.  A Structure at the end of a repeating Group should point to the Group. 
589             * A Group should point to its first child.  
590             * 
591             * @param theName name of the next Segment in this direction (ie if the next structure is a group, 
592             *      not that one)
593             * @param theSuccessor the immediately next StructRef in that direction
594             */
595            public void setSuccessor(String theName, StructRef theSuccessor) {
596                mySuccessors.put(theName, theSuccessor);
597            }
598            
599            /**
600             * @return full Terser path, including parent and repetition information.  
601             */
602            public String getFullPath() {
603                return myParent.getFullPath() + myRelativePath.replaceAll("\\*", String.valueOf(myRep));
604            }
605            
606            /**
607             * @return relative Terser path as defined in constructor
608             */
609            public String getRelativePath() {
610                return myRelativePath;
611            }
612            
613            /**
614             * @param theName name of a successor in parse order, as set in setSuccessor()
615             * @return the StructRef under that name 
616             */
617            public StructRef getSuccessor(String theName) {
618                StructRef ref = (StructRef) mySuccessors.get(theName);
619                if (ref != null) {
620                    ref.next();
621                } 
622                return ref;
623            }
624            
625            /**
626             * @return name of first successor, if available and if this is not a segment reference, 
627             *      otherwise null 
628             */
629            public String getChildName() {
630                String result = null;
631                if (!mySegmentFlag && !mySuccessors.isEmpty()) {
632                    result = (String) mySuccessors.keySet().iterator().next();                
633                }
634                return result;
635            }
636            
637            /**
638             * @return true iff referenced Structure is a Segment 
639             */
640            public boolean isSegment() {
641                return mySegmentFlag;
642            }
643            
644            /**
645             * Increments the repetition number of the underlying Structure, which is used in getFullPath() 
646             */
647            private void next() {
648                myRep++;
649                resetChildren();
650            }
651            
652            private void addChild(StructRef theChild) {
653                if (!isSegment()) {
654                    myChildren.add(theChild);
655                }
656            }
657            
658            /**
659             * Resets the StructRef to its starting state, before its first iteration, and resets 
660             * its children as well.  
661             */
662            public void reset() {
663                myRep = -1;
664                resetChildren();
665            }
666            
667            private void resetChildren() {
668                for (int i = 0; i < myChildren.size(); i++) {
669                    StructRef child = (StructRef) myChildren.get(i);
670                    child.reset();
671                }            
672            }
673            
674            /**
675             * @return an ordered list of fields to be parsed for this segment (empty if not a segment)
676             */
677            public int[] getFields() {
678                return myFields;
679            }
680            
681        }
682        
683        /**
684         * A convenience StructRef that points to a message root.  
685         * 
686         * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
687         * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
688         */
689        public static class RootRef extends StructRef {
690            public RootRef() {
691                super(null, "", false, null);
692            }
693            
694            public String getFullPath() { 
695                return "";
696            }
697        }
698    
699    }