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 "DefaultValidator.java".  Description: 
010    "A default conformance validator." 
011    
012    The Initial Developer of the Original Code is University Health Network. Copyright (C) 
013    2003.  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.conf.check;
029    
030    import java.io.BufferedReader;
031    import java.io.File;
032    import java.io.FileReader;
033    import java.io.IOException;
034    import java.util.ArrayList;
035    
036    import org.slf4j.Logger;
037    import org.slf4j.LoggerFactory;
038    
039    import ca.uhn.hl7v2.HL7Exception;
040    import ca.uhn.hl7v2.conf.ProfileException;
041    import ca.uhn.hl7v2.conf.parser.ProfileParser;
042    import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
043    import ca.uhn.hl7v2.conf.spec.message.AbstractComponent;
044    import ca.uhn.hl7v2.conf.spec.message.AbstractSegmentContainer;
045    import ca.uhn.hl7v2.conf.spec.message.Component;
046    import ca.uhn.hl7v2.conf.spec.message.Field;
047    import ca.uhn.hl7v2.conf.spec.message.ProfileStructure;
048    import ca.uhn.hl7v2.conf.spec.message.Seg;
049    import ca.uhn.hl7v2.conf.spec.message.SegGroup;
050    import ca.uhn.hl7v2.conf.spec.message.StaticDef;
051    import ca.uhn.hl7v2.conf.spec.message.SubComponent;
052    import ca.uhn.hl7v2.conf.store.CodeStore;
053    import ca.uhn.hl7v2.conf.store.ProfileStoreFactory;
054    import ca.uhn.hl7v2.model.Composite;
055    import ca.uhn.hl7v2.model.DataTypeException;
056    import ca.uhn.hl7v2.model.Group;
057    import ca.uhn.hl7v2.model.Message;
058    import ca.uhn.hl7v2.model.Primitive;
059    import ca.uhn.hl7v2.model.Segment;
060    import ca.uhn.hl7v2.model.Structure;
061    import ca.uhn.hl7v2.model.Type;
062    import ca.uhn.hl7v2.parser.EncodingCharacters;
063    import ca.uhn.hl7v2.parser.GenericParser;
064    import ca.uhn.hl7v2.parser.Parser;
065    import ca.uhn.hl7v2.parser.PipeParser;
066    import ca.uhn.hl7v2.util.Terser;
067    
068    /**
069     * A default conformance validator.
070     * @author Bryan Tripp
071     */
072    public 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    }