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
028package ca.uhn.hl7v2.util;
029
030import ca.uhn.hl7v2.model.*;
031import ca.uhn.hl7v2.HL7Exception;
032import 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 */
038public 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 wildcard * means any number 
053     *      of arbitrary characters; the wildcard ? 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}