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 "SegmentFinder.java".  Description:
010     * "A tool for getting segments by name within a message or part of a message."
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.regex.*;
033    
034    /**
035     * A tool for getting segments by name within a message or part of a message.
036     * @author Bryan Tripp
037     */
038    public class SegmentFinder extends MessageNavigator {
039        
040        /**
041         * Creates a new instance of SegmentFinder.
042         * @param root the scope of searches -- may be a whole message or only a branch
043         */
044        public SegmentFinder(Group root) {
045            super(root);
046        }
047        
048        /**
049         * Returns the first segment with a name that matches the given pattern, in a depth-first search.  
050         * Repeated searches are initiated from the location just AFTER where the last segment was found.
051         * Call reset() is this is not desired.  Note: this means that the current location will not be found.
052         * @param segmentName the name of the segment to find.  The wildcad * means any number 
053         *      of arbitrary characters; the wildard ? one arbitrary character
054         *      (eg "P*" or "*ID" or "???" or "P??" would match on PID).
055         * @param rep the repetition of the segment to return
056         */
057        public Segment findSegment(String namePattern, int rep) throws HL7Exception {
058            Structure s = null;
059            do {
060                s = findStructure(namePattern, rep);
061            } while (!Segment.class.isAssignableFrom(s.getClass()));
062            return (Segment) s;
063        }
064        
065        /**
066         * As findSegment(), but will only return a group.
067         */
068        public Group findGroup(String namePattern, int rep) throws HL7Exception {
069            Structure s = null;
070            do {
071                s = findStructure(namePattern, rep);
072            } while (!Group.class.isAssignableFrom(s.getClass()));
073            return (Group) s;
074        }
075        
076        /**
077         * Returns the first matching structure AFTER the current position
078         */
079        private Structure findStructure(String namePattern, int rep) throws HL7Exception {
080            Structure s = null;
081            
082            while (s == null) {
083                iterate(false, false);
084                String currentName = getCurrentStructure(0).getName();
085                if (matches(namePattern, currentName)) {
086                    s = getCurrentStructure(rep);
087                }
088            }
089            return s;
090        }
091        
092        /**
093         * Returns the first segment with a name matching the given pattern that is a sibling of
094         * the structure at the current location.  Other parts of the message are
095         * not searched (in contrast to findSegment).
096         * As a special case, if the pointer is at the root, the children of the root
097         * are searched.
098         * @param segmentName the name of the segment to get.  The wildcad * means any number 
099         *      of arbitrary characters; the wildard ? one arbitrary character
100         *      (eg "P*" or "*ID" or "???" or "P??" would match on PID).
101         * @param rep the repetition of the segment to return
102         */
103        public Segment getSegment(String namePattern, int rep) throws HL7Exception {
104            Structure s = getStructure(namePattern, rep);
105            if (!Segment.class.isAssignableFrom(s.getClass())) {
106                throw new HL7Exception(s.getName() + " is not a segment", HL7Exception.APPLICATION_INTERNAL_ERROR);
107            }
108            return (Segment) s;
109        }
110        
111        /**
112         * As getSegment() but will only return a group.
113         */
114        public Group getGroup(String namePattern, int rep) throws HL7Exception {
115            Structure s = getStructure(namePattern, rep);
116            if (!Group.class.isAssignableFrom(s.getClass())) {
117                throw new HL7Exception(s.getName() + " is not a group", HL7Exception.APPLICATION_INTERNAL_ERROR);
118            }
119            return (Group) s;
120        }
121        
122        private Structure getStructure(String namePattern, int rep) throws HL7Exception {
123            Structure s = null;
124            
125            if (getCurrentStructure(0).equals(this.getRoot()))
126                drillDown(0);
127            
128            String[] names = getCurrentStructure(0).getParent().getNames();
129            for (int i = 0; i < names.length && s == null; i++) {
130                if (matches(namePattern, names[i])) {
131                    toChild(i);
132                    s = getCurrentStructure(rep);
133                }
134            }
135            
136            if (s == null)
137                throw new HL7Exception("Can't find " + namePattern + " as a direct child", HL7Exception.APPLICATION_INTERNAL_ERROR);
138            
139            return s;
140        }
141        
142        /**
143         * Tests whether the given name matches the given pattern.
144         */
145        /*private boolean matches(String pattern, String candidate) {
146            boolean matches = false;
147            boolean substring = false;
148            if (pattern.substring(0, 1).equals("*")) {
149                substring = true;
150                pattern = pattern.substring(1);
151            }
152            
153            if (substring && (candidate.indexOf(pattern) >= 0)) {
154                matches = true;
155            } else if (!substring && candidate.equals(pattern)) {
156                matches = true;
157            }
158            return matches;
159        }*/
160        
161        /**
162         * Tests whether the given name matches the given pattern.
163         */
164        private boolean matches(String pattern, String candidate) {
165            //shortcut ...
166            if (pattern.equals(candidate)) {
167                return true;
168            }
169            
170            if (!Pattern.matches("[\\w\\*\\?]*", pattern)) 
171                throw new IllegalArgumentException("The pattern " + pattern + " is not valid.  Only [\\w\\*\\?]* allowed.");
172            
173            pattern = Pattern.compile("\\*").matcher(pattern).replaceAll(".*");
174            pattern = Pattern.compile("\\?").matcher(pattern).replaceAll(".");
175            
176            return Pattern.matches(pattern, candidate);
177        }
178    }