View Javadoc

1   /**
2    *
3    * The contents of this file are subject to the Mozilla Public License Version 1.1
4    * (the "License"); you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at http://www.mozilla.org/MPL
6    * Software distributed under the License is distributed on an "AS IS" basis,
7    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
8    * specific language governing rights and limitations under the License.
9    *
10   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
11   * 2001.  All Rights Reserved.
12   *
13   * Alternatively, the contents of this file may be used under the terms of the
14   * GNU General Public License (the  "GPL"), in which case the provisions of the GPL are
15   * applicable instead of those above.  If you wish to allow use of your version of this
16   * file only under the terms of the GPL and not to allow others to use your version
17   * of this file under the MPL, indicate your decision by deleting  the provisions above
18   * and replace  them with the notice and other provisions required by the GPL License.
19   * If you do not delete the provisions above, a recipient may use your version of
20   * this file under either the MPL or the GPL.
21   */
22  package ca.uhn.hl7v2.testpanel.util.compare;
23  
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.commons.lang.StringUtils;
31  
32  import ca.uhn.hl7v2.HL7Exception;
33  import ca.uhn.hl7v2.model.AbstractGroup;
34  import ca.uhn.hl7v2.model.Composite;
35  import ca.uhn.hl7v2.model.Group;
36  import ca.uhn.hl7v2.model.Message;
37  import ca.uhn.hl7v2.model.Primitive;
38  import ca.uhn.hl7v2.model.Segment;
39  import ca.uhn.hl7v2.model.Structure;
40  import ca.uhn.hl7v2.model.Type;
41  import ca.uhn.hl7v2.model.Varies;
42  import ca.uhn.hl7v2.parser.EncodingCharacters;
43  import ca.uhn.hl7v2.parser.PipeParser;
44  import ca.uhn.hl7v2.testpanel.ex.UnexpectedTestFailureException;
45  import ca.uhn.hl7v2.testpanel.util.Pair;
46  import ca.uhn.hl7v2.validation.impl.ValidationContextImpl;
47  
48  public class Hl7V2MessageCompare {
49  	// ~ Instance fields
50  	// ------------------------------------------------------------------------------------------------
51  
52  	private Message myActualMessage;
53  	private GroupComparison myComparison;
54  
55  	private EncodingCharacters myEncodingCharacters = new EncodingCharacters('|', null);
56  
57  	private PipeParser myEncodingParser;
58  	private Message myExpectedMessage;
59  	private Set<String> myFieldsToIgnore;
60  	private String myExpectedDesc = BulkHl7V2Comparison.EXPECTED_DESC;
61  	private String myActualDesc = BulkHl7V2Comparison.ACTUAL_DESC;
62  
63  	/**
64  	 * Constructor
65  	 */
66  	public Hl7V2MessageCompare() {
67  		myEncodingParser = new PipeParser();
68  		myEncodingParser.setValidationContext(new ValidationContextImpl());
69  	}
70  
71  	/**
72  	 * Constructor
73  	 */
74  	public Hl7V2MessageCompare(PipeParser theParser) {
75  		myEncodingParser = theParser;
76  	}
77  
78  	// ~ Constructors
79  	// ---------------------------------------------------------------------------------------------------
80  
81  	private void addRemainingStructures(Group theStructure, int theStartingNameIndex, int theAfterEndingIndex, List<StructureComparison> theStructureComparisons, boolean theIsMessage1) throws HL7Exception {
82  		String[] names = theStructure.getNames();
83  
84  		for (int i = theStartingNameIndex; i < theAfterEndingIndex; i++) {
85  			Structure[] reps = theStructure.getAll(names[i]);
86  
87  			for (Structure structure : reps) {
88  				if (structure instanceof Group) {
89  					theStructureComparisons.add(new ExtraGroup((Group) structure, theIsMessage1));
90  				} else {
91  					if (theIsMessage1) {
92  						theStructureComparisons.add(new SegmentComparison(structure.getName(), (Segment) structure, null));
93  					} else {
94  						theStructureComparisons.add(new SegmentComparison(structure.getName(), null, (Segment) structure));
95  					}
96  				}
97  			}
98  		}
99  	}
100 
101 	// ~ Methods
102 	// --------------------------------------------------------------------------------------------------------
103 
104 	private void clearComponentIndex(Type[] theFieldReps, int theComponentIndex) throws HL7Exception {
105 
106 		for (Type type : theFieldReps) {
107 
108 			int extraComponentIndex = -1;
109 
110 			if (type instanceof Primitive) {
111 				Primitive p = (Primitive) type;
112 				if (theComponentIndex < 2) {
113 					p.setValue("");
114 				} else {
115 					extraComponentIndex = theComponentIndex - 1;
116 				}
117 			} else {
118 				Composite c = (Composite) type;
119 				if (theComponentIndex <= c.getComponents().length) {
120 					c.getComponents()[theComponentIndex - 1].parse("");
121 				} else {
122 					extraComponentIndex = theComponentIndex - c.getComponents().length;
123 				}
124 			}
125 
126 			if (extraComponentIndex != -1) {
127 				if (type.getExtraComponents().numComponents() >= (theComponentIndex)) {
128 					type.getExtraComponents().getComponent(extraComponentIndex).parse("");
129 				}
130 			}
131 
132 		}
133 
134 	}
135 
136 	/**
137 	 * {@inheritDoc }
138 	 */
139 	public void compare(Message theExpectMessage, Message theActualMessage) throws UnexpectedTestFailureException {
140 		try {
141 			myExpectedMessage = theExpectMessage;
142 			myActualMessage = theActualMessage;
143 
144 			myExpectedMessage = myEncodingParser.parse(theExpectMessage.encode());
145 			myActualMessage = myEncodingParser.parse(theActualMessage.encode());
146 
147 			stripEmptyStructures(theExpectMessage);
148 			stripEmptyStructures(theActualMessage);
149 
150 			myComparison = compareGroups(theExpectMessage, theActualMessage);
151 		} catch (HL7Exception ex) {
152 			throw new UnexpectedTestFailureException(ex);
153 		}
154 	}
155 
156 	private FieldComparison compareFields(Segment theSegment1, Segment theSegment2, int theI) throws HL7Exception {
157 
158 		String fieldDescriptor = theSegment1.getName() + "-" + (theI + 1);
159 
160 		// Check for fields
161 		boolean ignore = (myFieldsToIgnore != null) && (myFieldsToIgnore.contains(fieldDescriptor));
162 
163 		Type[] reps1 = theSegment1.getField(theI + 1);
164 		Type[] reps2 = theSegment2.getField(theI + 1);
165 
166 		// Check for components (e.g. PID-3-2)
167 		if (myFieldsToIgnore != null) {
168 			for (String fieldsToIgnore : myFieldsToIgnore) {
169 				if (fieldsToIgnore.length() > fieldDescriptor.length() + 1) {
170 					if (fieldsToIgnore.startsWith(fieldDescriptor)) {
171 						String componentIndexStr = fieldsToIgnore.substring(fieldDescriptor.length() + 1);
172 						int componentIndex = Integer.parseInt(componentIndexStr);
173 						clearComponentIndex(reps1, componentIndex);
174 						clearComponentIndex(reps2, componentIndex);
175 					}
176 				}
177 			}
178 		}
179 
180 		int maxReps = (reps1.length > reps2.length) ? reps1.length : reps2.length;
181 
182 		List<Type> sameFields = new ArrayList<Type>();
183 		List<Type> diffFields1 = new ArrayList<Type>();
184 		List<Type> diffFields2 = new ArrayList<Type>();
185 
186 		if (!ignore) {
187 			for (int i = 0; i < maxReps; i++) {
188 				if (i >= reps1.length) {
189 					if (reps2[i] == null || reps2[i].encode() == null || reps2[i].encode().isEmpty()) {
190 						sameFields.add(reps2[i]);
191 						diffFields1.add(null);
192 						diffFields2.add(null);
193 					} else {
194 						sameFields.add(null);
195 						diffFields1.add(null);
196 						diffFields2.add(reps2[i]);
197 					}
198 				} else if (i >= reps2.length) {
199 					Type type = reps1[i];
200 					if (type == null || type.encode() == null || type.encode().isEmpty()) {
201 						sameFields.add(reps1[i]);
202 						diffFields1.add(null);
203 						diffFields2.add(null);
204 					} else {
205 						sameFields.add(null);
206 						diffFields1.add(reps1[i]);
207 						diffFields2.add(null);
208 					}
209 				} else {
210 					if (compareTypes(reps1[i], reps2[i])) {
211 						sameFields.add(reps1[i]);
212 						diffFields1.add(null);
213 						diffFields2.add(null);
214 					} else {
215 						sameFields.add(null);
216 						diffFields1.add(reps1[i]);
217 						diffFields2.add(reps2[i]);
218 					}
219 				}
220 			}
221 		}
222 
223 		return new FieldComparison(theSegment1.getNames()[theI], sameFields, diffFields1, diffFields2);
224 	}
225 
226 	private GroupComparison compareGroups(Group theStructure1, Group theStructure2) throws HL7Exception {
227 		List<String> originalNames1 = Arrays.asList(theStructure1.getNames());
228 		ArrayList<String> names1 = new ArrayList<String>(originalNames1);
229 		for (Iterator<String> iter = names1.iterator(); iter.hasNext();) {
230 			String nextName = iter.next();
231 			if (theStructure1.getAll(nextName).length == 0) {
232 				iter.remove();
233 			}
234 		}
235 
236 		List<String> originalNames2 = Arrays.asList(theStructure2.getNames());
237 		ArrayList<String> names2 = new ArrayList<String>(originalNames2);
238 		for (Iterator<String> iter = names2.iterator(); iter.hasNext();) {
239 			String nextName = iter.next();
240 			if (theStructure2.getAll(nextName).length == 0) {
241 				iter.remove();
242 			}
243 		}
244 
245 		List<StructureComparison> structureComparisons = new ArrayList<StructureComparison>();
246 
247 		int nameIdx1 = 0;
248 		int nameIdx2 = 0;
249 
250 		while ((nameIdx1 < names1.size()) || (nameIdx2 < names2.size())) {
251 			Pair<Integer> nextSameIdx = findNextSameIndex(names1, names2, nameIdx1, nameIdx2);
252 
253 			// If we're at the end of the matching segments
254 			if (nextSameIdx == null) {
255 				addRemainingStructures(theStructure1, nameIdx1, names1.size(), structureComparisons, true);
256 				addRemainingStructures(theStructure2, nameIdx2, names2.size(), structureComparisons, false);
257 
258 				break;
259 			}
260 
261 			addRemainingStructures(theStructure1, nameIdx1, nextSameIdx.getValue1(), structureComparisons, true);
262 			addRemainingStructures(theStructure2, nameIdx2, nextSameIdx.getValue2(), structureComparisons, false);
263 
264 			Structure[] children1 = theStructure1.getAll(names1.get(nextSameIdx.getValue1()));
265 			Structure[] children2 = theStructure2.getAll(names2.get(nextSameIdx.getValue2()));
266 			int lowerCommonIndex = (children1.length < children2.length) ? children1.length : children2.length;
267 
268 			for (int i = 0; i < lowerCommonIndex; i++) {
269 				Structure child1 = children1[i];
270 				Structure child2 = children2[i];
271 
272 				if (child1 instanceof Segment) {
273 					structureComparisons.add(compareSegments((Segment) child1, (Segment) child2));
274 				} else {
275 					structureComparisons.add(compareGroups((Group) child1, (Group) child2));
276 				}
277 			}
278 
279 			for (int i = lowerCommonIndex; i < children1.length; i++) {
280 				if (children1[i] instanceof Segment) {
281 					structureComparisons.add(new SegmentComparison(children1[i].getName(), (Segment) children1[i], null));
282 				} else {
283 					structureComparisons.add(new GroupComparison((Group) children1[i], null));
284 				}
285 			}
286 
287 			for (int i = lowerCommonIndex; i < children2.length; i++) {
288 				if (children2[i] instanceof Segment) {
289 					structureComparisons.add(new SegmentComparison(children2[i].getName(), null, (Segment) children2[i]));
290 				} else {
291 					structureComparisons.add(new GroupComparison(null, (Group) children2[i]));
292 				}
293 			}
294 
295 			nameIdx1 = nextSameIdx.getValue1() + 1;
296 			nameIdx2 = nextSameIdx.getValue2() + 1;
297 		}
298 
299 		return new GroupComparison(structureComparisons);
300 	}
301 
302 	private SegmentComparison compareSegments(Segment theSegment1, Segment theSegment2) throws HL7Exception {
303 		assert theSegment1.getName().equals(theSegment2.getName());
304 
305 		List<FieldComparison> fieldComparisons = new ArrayList<FieldComparison>();
306 
307 		for (int i = 0; i < theSegment1.numFields(); i++) {
308 			FieldComparison nextFieldComparison = compareFields(theSegment1, theSegment2, i);
309 			fieldComparisons.add(nextFieldComparison);
310 		}
311 
312 		return new SegmentComparison(theSegment1.getName(), fieldComparisons);
313 	}
314 
315 	private boolean compareTypes(Type theType1, Type theType2) {
316 		if (theType1 instanceof Primitive && theType2 instanceof Primitive) {
317 			Primitive type1 = (Primitive) theType1;
318 			Primitive type2 = (Primitive) theType2;
319 
320 			return StringUtils.equals(type1.getValue(), type2.getValue());
321 		} else if (theType1 instanceof Varies && theType2 instanceof Varies) {
322 			Varies type1 = (Varies) theType1;
323 			Varies type2 = (Varies) theType2;
324 
325 			return compareTypes(type1.getData(), type2.getData());
326 		} else if (theType1 instanceof Composite && theType2 instanceof Composite) {
327 			Composite type1 = (Composite) theType1;
328 			Composite type2 = (Composite) theType2;
329 			Type[] components1 = type1.getComponents();
330 			Type[] components2 = type2.getComponents();
331 
332 			if (components1.length != components2.length) {
333 				return false;
334 			}
335 
336 			for (int i = 0; i < components1.length; i++) {
337 				if (!compareTypes(components1[i], components2[i])) {
338 					return false;
339 				}
340 			}
341 
342 			return true;
343 		} else {
344 			return false;
345 		}
346 	}
347 
348 	/**
349 	 * {@inheritDoc }
350 	 */
351 	public String describeDifference() {
352 		StringBuilder retVal = new StringBuilder();
353 
354 		for (SegmentComparison nextSegment : myComparison.flattenMessage()) {
355 			if (nextSegment.getExpectSegment() != null) {
356 				retVal.append(myExpectedDesc).append(": ").append(PipeParser.encode(nextSegment.getExpectSegment(), myEncodingCharacters)).append("\r\n");
357 			}
358 
359 			if (nextSegment.getActualSegment() != null) {
360 				retVal.append(myActualDesc).append(": ").append(PipeParser.encode(nextSegment.getActualSegment(), myEncodingCharacters)).append("\r\n");
361 			}
362 
363 			if (!nextSegment.isSame() && (nextSegment.getFieldComparisons() != null)) {
364 				int fieldIndex = 0;
365 
366 				for (FieldComparison next : nextSegment.getFieldComparisons()) {
367 					fieldIndex++;
368 
369 					for (int rep = 1; rep <= next.getDiffFieldsActual().size(); rep++) {
370 						if (next.getSameFields().get(rep - 1) == null) {
371 							retVal.append(nextSegment.getName());
372 							retVal.append("-");
373 							retVal.append(fieldIndex);
374 							retVal.append("(");
375 							retVal.append(rep);
376 							retVal.append(") - ");
377 							retVal.append(next.getFieldName());
378 							retVal.append(":\r\n");
379 
380 							Type expectedType = next.getDiffFieldsExpected().get(rep - 1);
381 							retVal.append("  ").append(myExpectedDesc).append(": ").append(encode(expectedType)).append("\r\n");
382 
383 							Type actualType = next.getDiffFieldsActual().get(rep - 1);
384 							retVal.append("  ").append(myActualDesc).append(": ").append(encode(actualType));
385 							retVal.append("\r\n");
386 						}
387 					}
388 				}
389 			}
390 		}
391 
392 		return retVal.toString();
393 	}
394 
395 	private String encode(Type theType) {
396 		if (theType == null) {
397 			return "";
398 		}
399 
400 		return PipeParser.encode(theType, myEncodingCharacters);
401 	}
402 
403 	/**
404 	 * @return the actualMessage
405 	 */
406 	public Message getActualMessage() {
407 		return myActualMessage;
408 	}
409 
410 	/**
411 	 * @return the expectedMessage
412 	 */
413 	public Message getExpectedMessage() {
414 		return myExpectedMessage;
415 	}
416 
417 	public GroupComparison getMessageComparison() {
418 		return myComparison;
419 	}
420 
421 	private boolean hasData(Structure theStructure) throws HL7Exception {
422 		if (theStructure instanceof Group) {
423 			Group g = (Group) theStructure;
424 			for (int nameIndex = 0; nameIndex < g.getNames().length; nameIndex++) {
425 				String nextName = g.getNames()[nameIndex];
426 				Structure[] nextReps = g.getAll(nextName);
427 				for (int repIndex = 0; repIndex < nextReps.length; repIndex++) {
428 					Structure nextRep = nextReps[repIndex];
429 					if (hasData(nextRep)) {
430 						return true;
431 					}
432 				}
433 			}
434 		} else {
435 			Segment s = (Segment) theStructure;
436 			for (int nameIndex = 0; nameIndex < s.getNames().length; nameIndex++) {
437 				Type[] nextReps = s.getField(nameIndex + 1);
438 				for (int repIndex = 0; repIndex < nextReps.length; repIndex++) {
439 					Type nextRep = nextReps[repIndex];
440 					if (StringUtils.isNotEmpty(nextRep.encode())) {
441 						return true;
442 					}
443 				}
444 			}
445 		}
446 
447 		return false;
448 	}
449 
450 	/**
451 	 * {@inheritDoc }
452 	 */
453 	public boolean isSame() {
454 		return myComparison.isSame();
455 	}
456 
457 	public void setFieldsToIgnore(Set<String> theFieldsToIgnore) {
458 		myFieldsToIgnore = theFieldsToIgnore;
459 	}
460 
461 	private void stripEmptyStructures(Group theMessage) throws HL7Exception {
462 		for (String nextName : theMessage.getNames()) {
463 			for (int i = 0; i < theMessage.getAll(nextName).length; i++) {
464 				Structure structure = theMessage.get(nextName, i);
465 				if (structure instanceof Group) {
466 					stripEmptyStructures((Group) structure);
467 				}
468 
469 				if (!hasData(structure) && !theMessage.isRequired(nextName)) {
470 					((AbstractGroup) theMessage).removeRepetition(nextName, i);
471 				}
472 			}
473 		}
474 	}
475 
476 	public static Pair<Integer> findNextSameIndex(ArrayList<String> theNames1, ArrayList<String> theNames2, int theStartingIndex1, int theStartingIndex2) {
477 		Pair<Integer> found1 = null;
478 		BOTH: for (int i1 = theStartingIndex1; i1 < theNames1.size(); i1++) {
479 			for (int i2 = theStartingIndex2; i2 < theNames2.size(); i2++) {
480 				if (nameIsEqual(theNames1, theNames2, i1, i2)) {
481 					found1 = new Pair<Integer>(i1, i2);
482 
483 					break BOTH;
484 				}
485 			}
486 		}
487 
488 		Pair<Integer> found2 = null;
489 		BOTH: for (int i2 = theStartingIndex2; i2 < theNames2.size(); i2++) {
490 			for (int i1 = theStartingIndex1; i1 < theNames1.size(); i1++) {
491 				if (nameIsEqual(theNames1, theNames2, i1, i2)) {
492 					found2 = new Pair<Integer>(i1, i2);
493 
494 					break BOTH;
495 				}
496 			}
497 		}
498 
499 		if (found1 == null) {
500 			return found2;
501 		} else if (found2 == null) {
502 			return found1;
503 		} else if (found1.getValue1() < found2.getValue1()) {
504 			return found1;
505 		} else {
506 			return found2;
507 		}
508 	}
509 
510 	private static boolean nameIsEqual(ArrayList<String> theNames1, ArrayList<String> theNames2, int i1, int i2) {
511 		String name1 = theNames1.get(i1);
512 		if (!name1.contains("_") && name1.length() > 3) {
513 			name1 = name1.substring(0, 3);
514 		}
515 
516 		String name2 = theNames2.get(i2);
517 		if (!name2.contains("_") && name2.length() > 3) {
518 			name2 = name2.substring(0, 3);
519 		}
520 
521 		return StringUtils.equals(name1, name2);
522 	}
523 
524 	/**
525 	 * @see BulkHl7V2Comparison#setActualAndExpectedDescription(String, String)
526 	 */
527 	public void setExpectedAndActualDescription(String theExpectedDesc, String theActualDesc) {
528 		myExpectedDesc = theExpectedDesc;
529 		myActualDesc = theActualDesc;
530 	}
531 
532 }