001/**
002The 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. 
004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005Software distributed under the License is distributed on an "AS IS" basis, 
006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007specific language governing rights and limitations under the License. 
008
009The Original Code is "DefaultValidator.java".  Description: 
010"A default conformance validator." 
011
012The Initial Developer of the Original Code is University Health Network. Copyright (C) 
0132003.  All Rights Reserved. 
014
015Contributor(s): ______________________________________. 
016
017Alternatively, the contents of this file may be used under the terms of the 
018GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
019applicable instead of those above.  If you wish to allow use of your version of this 
020file only under the terms of the GPL and not to allow others to use your version 
021of this file under the MPL, indicate your decision by deleting  the provisions above 
022and replace  them with the notice and other provisions required by the GPL License.  
023If you do not delete the provisions above, a recipient may use your version of 
024this file under either the MPL or the GPL. 
025
026*/
027
028package ca.uhn.hl7v2.conf.check;
029
030import java.io.BufferedReader;
031import java.io.File;
032import java.io.FileReader;
033import java.io.IOException;
034import java.util.ArrayList;
035
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import ca.uhn.hl7v2.HL7Exception;
040import ca.uhn.hl7v2.conf.ProfileException;
041import ca.uhn.hl7v2.conf.parser.ProfileParser;
042import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
043import ca.uhn.hl7v2.conf.spec.message.AbstractComponent;
044import ca.uhn.hl7v2.conf.spec.message.AbstractSegmentContainer;
045import ca.uhn.hl7v2.conf.spec.message.Component;
046import ca.uhn.hl7v2.conf.spec.message.Field;
047import ca.uhn.hl7v2.conf.spec.message.ProfileStructure;
048import ca.uhn.hl7v2.conf.spec.message.Seg;
049import ca.uhn.hl7v2.conf.spec.message.SegGroup;
050import ca.uhn.hl7v2.conf.spec.message.StaticDef;
051import ca.uhn.hl7v2.conf.spec.message.SubComponent;
052import ca.uhn.hl7v2.conf.store.CodeStore;
053import ca.uhn.hl7v2.conf.store.ProfileStoreFactory;
054import ca.uhn.hl7v2.model.Composite;
055import ca.uhn.hl7v2.model.DataTypeException;
056import ca.uhn.hl7v2.model.Group;
057import ca.uhn.hl7v2.model.Message;
058import ca.uhn.hl7v2.model.Primitive;
059import ca.uhn.hl7v2.model.Segment;
060import ca.uhn.hl7v2.model.Structure;
061import ca.uhn.hl7v2.model.Type;
062import ca.uhn.hl7v2.parser.EncodingCharacters;
063import ca.uhn.hl7v2.parser.GenericParser;
064import ca.uhn.hl7v2.parser.Parser;
065import ca.uhn.hl7v2.parser.PipeParser;
066import ca.uhn.hl7v2.util.Terser;
067
068/**
069 * A default conformance validator.
070 * @author Bryan Tripp
071 */
072public class DefaultValidator implements Validator {
073    
074    private EncodingCharacters enc;  //used to check for content in parts of a message
075    private static final Logger log = LoggerFactory.getLogger(DefaultValidator.class);
076    private boolean validateChildren = true;
077        private CodeStore codeStore;
078    
079
080        /** Creates a new instance of DefaultValidator */
081    public DefaultValidator() {
082        enc = new EncodingCharacters('|', null);  //the | is assumed later -- don't change
083    }
084    
085    /**
086     * If set to false (default is true), each testXX and validateXX method will only
087     * test the direct object it is responsible for, not its children.
088     */
089    public void setValidateChildren(boolean validateChildren) {
090        this.validateChildren = validateChildren;
091    }
092        
093    /**
094     * <p>
095     * Provides a code store to use to provide the code tables which will be
096     * used to validate coded value types. If a code store has not been set (which
097     * is the default), {@link ProfileStoreFactory} will be checked for an
098     * appropriate code store, and if none is found then coded values will not
099     * be validated.
100     * </p>
101     */
102    public void setCodeStore(CodeStore theCodeStore) {
103        codeStore = theCodeStore;
104    }
105    
106    /**
107     * @see Validator#validate
108     */
109    public HL7Exception[] validate(Message message, StaticDef profile) throws ProfileException, HL7Exception {
110        ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
111        Terser t = new Terser(message);
112        
113        //check msg type, event type, msg struct ID
114        String msgType = t.get("/MSH-9-1");
115        if (!msgType.equals(profile.getMsgType())) {
116            HL7Exception e =
117                new ProfileNotFollowedException("Message type " + msgType + " doesn't match profile type of " + profile.getMsgType());
118            exList.add(e);
119        }
120        
121        String evType = t.get("/MSH-9-2");
122        if (!evType.equals(profile.getEventType()) && !profile.getEventType().equalsIgnoreCase("ALL")) {
123            HL7Exception e =
124                new ProfileNotFollowedException("Event type " + evType + " doesn't match profile type of " + profile.getEventType());
125            exList.add(e);
126        }
127        
128        String msgStruct = t.get("/MSH-9-3");
129        if (msgStruct == null || !msgStruct.equals(profile.getMsgStructID())) {
130            HL7Exception e =
131                new ProfileNotFollowedException("Message structure " + msgStruct + " doesn't match profile type of " + profile.getMsgStructID());
132            exList.add(e);
133        }
134        
135//        if (validateChildren) {
136                HL7Exception[] childExceptions; 
137                childExceptions = doTestGroup(message, profile, profile.getIdentifier(), validateChildren);
138                for (int i = 0; i < childExceptions.length; i++) {
139                    exList.add(childExceptions[i]);
140//              }
141        }
142        
143        return toArray(exList);
144    }
145    
146    /**
147     * Tests a group against a group section of a profile.
148     */
149    public HL7Exception[] testGroup(Group group, AbstractSegmentContainer profile, String profileID) throws ProfileException {
150        return doTestGroup(group, profile, profileID, true);
151    }
152
153        private HL7Exception[] doTestGroup(Group group, AbstractSegmentContainer profile, String profileID, boolean theValidateChildren) throws ProfileException {
154                ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
155        ArrayList<String> allowedStructures = new ArrayList<String>(20);
156        
157        for (int i = 1; i <= profile.getChildren(); i++) {
158            ProfileStructure struct = profile.getChild(i);
159            
160            //only test a structure in detail if it isn't X
161            if (!struct.getUsage().equalsIgnoreCase("X")) {
162                allowedStructures.add(struct.getName());
163                
164                //see which instances have content
165                try {
166                    Structure[] instances = group.getAll(struct.getName());
167                    ArrayList<Structure> instancesWithContent = new ArrayList<Structure>(10);
168                    for (int j = 0; j < instances.length; j++) {
169                        if (hasContent(instances[j])) instancesWithContent.add(instances[j]);
170                    }
171                    
172                    HL7Exception ce = testCardinality(instancesWithContent.size(),
173                    struct.getMin(), struct.getMax(), struct.getUsage(), struct.getName());
174                    if (ce != null) exList.add(ce);
175                    
176                    //test children on instances with content
177                    if (theValidateChildren) {
178                            for (int j = 0; j < instancesWithContent.size(); j++) {
179                                Structure s = (Structure) instancesWithContent.get(j);
180                                HL7Exception[] childExceptions = testStructure(s, struct, profileID);
181                                addToList(childExceptions, exList);
182                            }
183                    }
184                    
185                } catch (HL7Exception he) {
186                    exList.add(new ProfileNotHL7CompliantException(struct.getName() + " not found in message"));
187                }
188            }
189        }
190     
191        //complain about X structures that have content
192        addToList(checkForExtraStructures(group, allowedStructures), exList);
193        
194        return toArray(exList);
195        }
196    
197    /**
198     * Checks a group's children against a list of allowed structures for the group 
199     * (ie those mentioned in the profile with usage other than X).  Returns 
200     * a list of exceptions representing structures that appear in the message  
201     * but are not supposed to.  
202     */
203    private HL7Exception[] checkForExtraStructures(Group group, ArrayList<String> allowedStructures) throws ProfileException {
204        ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
205        String[] childNames = group.getNames();
206        for (int i = 0; i < childNames.length; i++) {
207            if (!allowedStructures.contains(childNames[i])) {
208                try {
209                    Structure[] reps = group.getAll(childNames[i]);
210                    for (int j = 0; j < reps.length; j++) {
211                        if (hasContent(reps[j])) {
212                            HL7Exception e =
213                                new XElementPresentException("The structure " 
214                                    + childNames[i] 
215                                    + " appears in the message but not in the profile");
216                            exList.add(e);
217                        }
218                    }
219                } catch (HL7Exception he) {
220                    throw new ProfileException("Problem checking profile", he);
221                }
222            }
223        }
224        return toArray(exList);
225    }
226    
227    /**
228     * Checks cardinality and creates an appropriate exception if out 
229     * of bounds.  The usage code is needed because if min cardinality
230     * is > 0, the min # of reps is only required if the usage code
231     * is 'R' (see HL7 v2.5 section 2.12.6.4).  
232     * @param reps the number of reps
233     * @param min the minimum number of reps
234     * @param max the maximum number of reps (-1 means *)
235     * @param usage the usage code 
236     * @param name the name of the repeating structure (used in exception msg)
237     * @return null if cardinality OK, exception otherwise
238     */
239    protected HL7Exception testCardinality(int reps, int min, int max, String usage, String name) {
240        HL7Exception e = null;
241        if (reps < min && usage.equalsIgnoreCase("R")) {
242            e = new ProfileNotFollowedException(name + " must have at least " + min + " repetitions (has " + reps + ")");
243        } else if (max > 0 && reps > max) {
244            e = new ProfileNotFollowedException(name + " must have no more than " + max + " repetitions (has " + reps + ")");
245        }
246        return e;
247    }
248    
249    /**
250     * Tests a structure (segment or group) against the corresponding part of a profile.  
251     */
252    public HL7Exception[] testStructure(Structure s, ProfileStructure profile, String profileID) throws ProfileException {
253        ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
254        if (profile instanceof Seg) {
255            if (Segment.class.isAssignableFrom(s.getClass())) {
256                addToList(doTestSegment((Segment) s, (Seg) profile, profileID, validateChildren), exList);
257            } else {
258                exList.add(new ProfileNotHL7CompliantException("Mismatch between a segment in the profile and the structure " 
259                        + s.getClass().getName() + " in the message"));
260            }
261        } else if (profile instanceof SegGroup) {
262            if (Group.class.isAssignableFrom(s.getClass())) {
263                addToList(testGroup((Group) s, (SegGroup) profile, profileID), exList);
264            } else {
265                exList.add(new ProfileNotHL7CompliantException("Mismatch between a group in the profile and the structure " 
266                        + s.getClass().getName() + " in the message"));
267            }
268        }
269        return toArray(exList);
270    }
271    
272    /**
273     * Tests a segment against a segment section of a profile.
274     */
275    public HL7Exception[] testSegment(ca.uhn.hl7v2.model.Segment segment, Seg profile, String profileID) throws ProfileException {
276        return doTestSegment(segment, profile, profileID, true);
277    }
278
279        private HL7Exception[] doTestSegment(ca.uhn.hl7v2.model.Segment segment, Seg profile, String profileID, boolean theValidateChildren) throws ProfileException {
280                ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
281        ArrayList<Integer> allowedFields = new ArrayList<Integer>(20);
282        
283        for (int i = 1; i <= profile.getFields(); i++) {
284            Field field = profile.getField(i);
285            
286            //only test a field in detail if it isn't X
287            if (!field.getUsage().equalsIgnoreCase("X")) {
288                allowedFields.add(new Integer(i));
289                
290                //see which instances have content
291                try {
292                    Type[] instances = segment.getField(i);
293                    ArrayList<Type> instancesWithContent = new ArrayList<Type>(10);
294                    for (int j = 0; j < instances.length; j++) {
295                        if (hasContent(instances[j])) instancesWithContent.add(instances[j]);
296                    }
297                    
298                    HL7Exception ce = testCardinality(instancesWithContent.size(),
299                    field.getMin(), field.getMax(), field.getUsage(), field.getName());
300                    if (ce != null) {
301                        ce.setFieldPosition(i);
302                        exList.add(ce);
303                    }
304                    
305                    //test field instances with content
306                    if (theValidateChildren) {
307                            for (int j = 0; j < instancesWithContent.size(); j++) {
308                                Type s = (Type) instancesWithContent.get(j);
309                                
310                                boolean escape = true; //escape field value when checking length
311                                if (profile.getName().equalsIgnoreCase("MSH") && i < 3) {
312                                    escape = false;
313                                }
314                                HL7Exception[] childExceptions = doTestField(s, field, escape, profileID, validateChildren);
315                                for (int k = 0; k < childExceptions.length; k++) {
316                                    childExceptions[k].setFieldPosition(i);
317                                }
318                                addToList(childExceptions, exList);
319                            }
320                    }
321                    
322                } catch (HL7Exception he) {
323                    exList.add(new ProfileNotHL7CompliantException("Field " + i + " not found in message"));
324                }
325            }
326            
327        }
328        
329        //complain about X fields with content
330        this.addToList(checkForExtraFields(segment, allowedFields), exList);
331        
332        HL7Exception[] ret = toArray(exList);        
333        for (int i = 0; i < ret.length; i++) {
334            ret[i].setSegmentName(profile.getName());
335        }
336        return ret;
337        }
338    
339    /**
340     * Checks a segment against a list of allowed fields 
341     * (ie those mentioned in the profile with usage other than X).  Returns 
342     * a list of exceptions representing field that appear 
343     * but are not supposed to.  
344     * @param allowedFields an array of Integers containing field #s of allowed fields
345     */
346    private HL7Exception[] checkForExtraFields(Segment segment, ArrayList<Integer> allowedFields) throws ProfileException {
347        ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
348        for (int i = 1; i <= segment.numFields(); i++) {
349            if (!allowedFields.contains(new Integer(i))) {
350                try {
351                    Type[] reps = segment.getField(i);
352                    for (int j = 0; j < reps.length; j++) {
353                        if (hasContent(reps[j])) {
354                            HL7Exception e =  new XElementPresentException(
355                                "Field " + i + " in " + segment.getName() + " appears in the message but not in the profile");
356                            exList.add(e);
357                        }
358                    }
359                } catch (HL7Exception he) {
360                    throw new ProfileException("Problem testing against profile", he);
361                }
362            }
363        }     
364        return this.toArray(exList);
365    }
366    
367    /**
368     * Tests a Type against the corresponding section of a profile.
369     * @param encoded optional encoded form of type (if you want to specify this -- if null,  
370     *      default pipe-encoded form is used to check length and constant val)
371     */
372    public HL7Exception[] testType(Type type, AbstractComponent profile, String encoded, String profileID) {
373        ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
374        if (encoded == null) encoded = PipeParser.encode(type, this.enc);
375
376        HL7Exception ue = testUsage(encoded, profile.getUsage(), profile.getName());
377        if (ue != null) exList.add(ue);
378
379        if ( !profile.getUsage().equals("X") ) {
380            //check datatype
381            String typeName = type.getName();
382            if (!typeName.equals(profile.getDatatype())) {
383                exList.add(new ProfileNotHL7CompliantException("HL7 datatype " + typeName + " doesn't match profile datatype " + profile.getDatatype()));
384            }
385            
386            //check length
387            if (encoded.length() > profile.getLength())
388                exList.add(new ProfileNotFollowedException("The type " + profile.getName() + " has length " + encoded.length() + " which exceeds max of " + profile.getLength()));
389            
390            //check constant value
391            if (profile.getConstantValue() != null && profile.getConstantValue().length() > 0) {
392                if (!encoded.equals(profile.getConstantValue()))
393                    exList.add(new ProfileNotFollowedException("'" + encoded + "' doesn't equal constant value of '" + profile.getConstantValue() + "'"));
394            }
395            
396            HL7Exception[] te = testTypeAgainstTable(type, profile, profileID);
397            for (int i = 0; i < te.length; i++) {
398                exList.add(te[i]);
399            }
400        }
401    
402        return this.toArray(exList);
403    }
404    
405    /**
406     * Tests whether the given type falls within a maximum length.  
407     * @return null of OK, an HL7Exception otherwise 
408     */
409    public HL7Exception testLength(Type type, int maxLength) {
410        HL7Exception e = null;
411        String encoded = PipeParser.encode(type, this.enc);
412        if (encoded.length() > maxLength) {
413            e = new ProfileNotFollowedException("Length of " + encoded.length() + " exceeds maximum of " + maxLength);
414        }
415        return e;
416    }
417    
418    /**
419     * Tests an element against the corresponding usage code.  The element 
420     * is required in its encoded form.  
421     * @param encoded the pipe-encoded message element 
422     * @param usage the usage code (e.g. "CE")
423     * @param name the name of the element (for use in exception messages)
424     * @returns null if there is no problem, an HL7Exception otherwise 
425     */
426    private HL7Exception testUsage(String encoded, String usage, String name) {
427        HL7Exception e = null;
428        if (usage.equalsIgnoreCase("R")) {
429            if (encoded.length() == 0) 
430                e = new ProfileNotFollowedException("Required element " + name + " is missing");
431        } else if (usage.equalsIgnoreCase("RE")) {
432            //can't test anything 
433        } else if (usage.equalsIgnoreCase("O")) {
434            //can't test anything
435        } else if (usage.equalsIgnoreCase("C")) {
436            //can't test anything yet -- wait for condition syntax in v2.6 
437        } else if (usage.equalsIgnoreCase("CE")) {
438            //can't test anything
439        } else if (usage.equalsIgnoreCase("X")) {
440            if (encoded.length() > 0) 
441                e = new XElementPresentException("Element \"" + name + "\" is present but specified as not used (X)");
442        } else if (usage.equalsIgnoreCase("B")) {
443            //can't test anything 
444        }                
445        return e;
446    }
447    
448    /**
449     * Tests table values for ID, IS, and CE types.  An empty list is returned for 
450     * all other types or if the table name or number is missing.  
451     */
452    private HL7Exception[] testTypeAgainstTable(Type type, AbstractComponent profile, String profileID) {
453        ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
454        if (profile.getTable() != null && (type.getName().equals("IS") || type.getName().equals("ID"))) {
455            String codeSystem = makeTableName(profile.getTable());
456            String value = ((Primitive) type).getValue();
457            addTableTestResult(exList, profileID, codeSystem, value);
458        } else if (type.getName().equals("CE")) {
459            String value = Terser.getPrimitive(type, 1, 1).getValue();
460            String codeSystem = Terser.getPrimitive(type, 3, 1).getValue();
461            addTableTestResult(exList, profileID, codeSystem, value);
462
463            value = Terser.getPrimitive(type, 4, 1).getValue();
464            codeSystem = Terser.getPrimitive(type, 6, 1).getValue();
465            addTableTestResult(exList, profileID, codeSystem, value);
466        }
467        return this.toArray(exList);
468    }
469    
470    private void addTableTestResult(ArrayList<HL7Exception> exList, String profileID, String codeSystem, String value) {
471        if (codeSystem != null && value != null) {
472            HL7Exception e = testValueAgainstTable(profileID, codeSystem, value);
473            if (e != null) exList.add(e);
474        }        
475    }
476    
477    private HL7Exception testValueAgainstTable(String profileID, String codeSystem, String value) {
478        HL7Exception ret = null;
479        if (validateChildren == false) {
480                return ret;
481        }
482        
483        CodeStore store = codeStore;
484                if (codeStore == null) {
485                        store = ProfileStoreFactory.getCodeStore(profileID, codeSystem);
486                }
487                
488        if (store == null) {
489            log.warn("Not checking value {}: no code store was found for profile {} code system {}"
490                        , new Object[] {value, profileID, codeSystem});
491        } else {
492            if (!store.knowsCodes(codeSystem)) {
493                log.warn("Not checking value {}: Don't have a table for code system {}", value, codeSystem);
494            } else if (!store.isValidCode(codeSystem, value)) {
495                ret = new ProfileNotFollowedException("Code '" + value + "' not found in table " + codeSystem + ", profile " + profileID);
496            }
497        }
498        return ret;
499    }
500    
501    private String makeTableName(String hl7Table) {
502        StringBuffer buf = new StringBuffer("HL7");
503        if (hl7Table.length() < 4) buf.append("0");
504        if (hl7Table.length() < 3) buf.append("0");
505        if (hl7Table.length() < 2) buf.append("0");
506        buf.append(hl7Table);
507        return buf.toString();
508    }
509    
510    public HL7Exception[] testField(Type type, Field profile, boolean escape, String profileID) throws ProfileException {
511        return doTestField(type, profile, escape, profileID, true);
512    }
513
514        private HL7Exception[] doTestField(Type type, Field profile, boolean escape, String profileID, boolean theValidateChildren) throws ProfileException {
515                ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
516        
517        //account for MSH 1 & 2 which aren't escaped
518        String encoded = null;
519        if (!escape && Primitive.class.isAssignableFrom(type.getClass())) encoded = ((Primitive) type).getValue();
520        
521        addToList(testType(type, profile, encoded, profileID), exList);
522        
523        //test children
524        if (theValidateChildren) {
525                if (profile.getComponents() > 0 && !profile.getUsage().equals("X")) {
526                    if (Composite.class.isAssignableFrom(type.getClass())) {
527                        Composite comp = (Composite) type;
528                        for (int i = 1; i <= profile.getComponents(); i++) {
529                            Component childProfile = profile.getComponent(i);
530                            try {
531                                Type child = comp.getComponent(i-1);
532                                addToList(doTestComponent(child, childProfile, profileID, validateChildren), exList);
533                            } catch (DataTypeException de) {
534                                exList.add(new ProfileNotHL7CompliantException("More components in profile than allowed in message: " + de.getMessage()));
535                            }
536                        }
537                        addToList(checkExtraComponents(comp, profile.getComponents()), exList);
538                    } else {
539                        exList.add(new ProfileNotHL7CompliantException(
540                        "A field has type primitive " + type.getClass().getName() + " but the profile defines components"));
541                    }
542                }
543        }
544        
545        return toArray(exList);
546        }
547    
548    public HL7Exception[] testComponent(Type type, Component profile, String profileID) throws ProfileException {
549        return doTestComponent(type, profile, profileID, true);
550    }
551
552        private HL7Exception[] doTestComponent(Type type, Component profile, String profileID, boolean theValidateChildren) throws ProfileException {
553                ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
554        
555        addToList(testType(type, profile, null, profileID), exList);
556        
557        //test children
558        if (profile.getSubComponents() > 0 && !profile.getUsage().equals("X") && hasContent(type)) {
559            if (Composite.class.isAssignableFrom(type.getClass())) {
560                Composite comp = (Composite) type;
561                
562                if (theValidateChildren) {
563                        for (int i = 1; i <= profile.getSubComponents(); i++) {
564                            SubComponent childProfile = profile.getSubComponent(i);
565                            try {
566                                Type child = comp.getComponent(i-1);
567                                addToList(testType(child, childProfile, null, profileID), exList);
568                            } catch (DataTypeException de) {
569                                exList.add(new ProfileNotHL7CompliantException("More subcomponents in profile than allowed in message: " + de.getMessage()));
570                            }
571                        }
572                }
573                
574                addToList(checkExtraComponents(comp, profile.getSubComponents()), exList);
575            } else {
576                exList.add(new ProfileNotFollowedException(
577                "A component has primitive type " + type.getClass().getName() + " but the profile defines subcomponents"));
578            }
579        }
580        
581        return toArray(exList);
582        }
583    
584    /** Tests for extra components (ie any not defined in the profile) */
585    private HL7Exception[] checkExtraComponents(Composite comp, int numInProfile) throws ProfileException {
586        ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
587        
588        StringBuffer extra = new StringBuffer();
589        for (int i = numInProfile; i < comp.getComponents().length; i++) {
590            try {
591                String s = PipeParser.encode(comp.getComponent(i), enc);
592                if (s.length() > 0) {
593                    extra.append(s);
594                    extra.append(enc.getComponentSeparator());
595                }
596            } catch (DataTypeException de) {
597                throw new ProfileException("Problem testing against profile", de);
598            }
599        }
600        
601        if (extra.toString().length() > 0) {
602            exList.add(new XElementPresentException("The following components are not defined in the profile: " + extra.toString()));
603        }
604        
605        return toArray(exList);
606    }
607    
608    /**
609     * Tests a composite against the corresponding section of a profile.
610     */
611    /*public HL7Exception[] testComposite(Composite comp, AbstractComposite profile) {
612    }*/
613    
614    /**
615     * Tests a primitive datatype against a profile.  Tests include
616     * length, datatype, whether the profile defines any children
617     * (this would indicate an error), constant value if defined.
618     * Table values are not verified.
619     */
620    /*public Hl7Exception[] testPrimitive(Primitive, AbstractComponent profile) {
621     
622    }*/
623    
624    /** Returns true is there is content in the given structure */
625    private boolean hasContent(Structure struct) throws HL7Exception {
626        if (Group.class.isAssignableFrom(struct.getClass())) {
627            return hasContent( (Group) struct );
628        } else if (Segment.class.isAssignableFrom(struct.getClass())) {
629            return hasContent( (Segment) struct );
630        } else {
631            throw new HL7Exception("Structure " + struct.getClass().getName() + " not recognized", HL7Exception.APPLICATION_INTERNAL_ERROR);
632        }
633    }
634    
635    /** Returns true is there is content in the given group */
636    private boolean hasContent(Group group) throws HL7Exception {
637        boolean has = false;
638        String encoded = PipeParser.encode(group, enc);
639        if (encoded.indexOf('|') >= 0) has = true;
640        return has;
641    }
642    
643    /** Returns true is there is content in the given segment */
644    private boolean hasContent(Segment segment) {
645        boolean has = false;
646        String encoded = PipeParser.encode(segment, enc);
647        if (encoded != null && encoded.length() > 3) has = true;
648        return has;
649    }
650    
651    /** Returns true is there is content in the given type */
652    private boolean hasContent(Type type) {
653        boolean has = false;
654        String encoded = PipeParser.encode(type, enc);
655        if (encoded != null && encoded.length() > 0) has = true;
656        return has;
657    }
658    
659    /** Appends an array of HL7 exceptions to a list */
660    private void addToList(HL7Exception[] exceptions, ArrayList<HL7Exception> list) {
661        for (int i = 0; i < exceptions.length; i++) {
662            list.add(exceptions[i]);
663        }
664    }
665    
666    /** Returns the HL7 exceptions in the given ArrayList<HL7Exception> in an array */
667    private HL7Exception[] toArray(ArrayList<HL7Exception> list) {
668        return (HL7Exception[]) list.toArray(new HL7Exception[0]);
669    }
670    
671    public static void main(String args[]) {
672        
673        if (args.length != 2) {
674            System.out.println("Usage: DefaultValidator message_file profile_file");
675            System.exit(1);
676        }
677        
678        DefaultValidator val = new DefaultValidator();
679        try {
680            String msgString = loadFile(args[0]);
681            Parser parser = new GenericParser();
682            Message message = parser.parse(msgString);
683            
684            String profileString = loadFile(args[1]);
685            ProfileParser profParser = new ProfileParser(true);
686            RuntimeProfile profile = profParser.parse(profileString);
687            
688            HL7Exception[] exceptions = val.validate(message, profile.getMessage());
689            
690            System.out.println("Exceptions: "); 
691            for (int i = 0; i < exceptions.length; i++) {
692                System.out.println((i + 1) + ". " + exceptions[i].getMessage());
693            }
694        } catch (Exception e) {
695            e.printStackTrace();
696        }
697    }
698    
699    /** loads file at the given path */
700    private static String loadFile(String path) throws IOException {
701        File file = new File(path);
702        //char[] cbuf = new char[(int) file.length()];
703        BufferedReader in = new BufferedReader(new FileReader(file));
704        StringBuffer buf = new StringBuffer(5000);
705        int c = -1;
706        while ( (c = in.read()) != -1) {
707            buf.append( (char) c );
708        }
709        //in.read(cbuf, 0, (int) file.length());
710        in.close();
711        //return String.valueOf(cbuf);
712        return buf.toString();
713    }
714    
715}