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