View Javadoc

1   /**
2   The contents of this file are subject to the Mozilla Public License Version 1.1 
3   (the "License"); you may not use this file except in compliance with the License. 
4   You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
5   Software distributed under the License is distributed on an "AS IS" basis, 
6   WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
7   specific language governing rights and limitations under the License. 
8   
9   The Original Code is "GroupGenerator.java".  Description: 
10  "Creates source code for Group classes - these are aggregations of 
11    segments and/or other groups that may repeat together within a message.
12    Source code is generated from the normative database" 
13  
14  The Initial Developer of the Original Code is University Health Network. Copyright (C) 
15  2001.  All Rights Reserved. 
16  
17  Contributor(s):  Eric Poiseau. 
18  
19  Alternatively, the contents of this file may be used under the terms of the 
20  GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
21  applicable instead of those above.  If you wish to allow use of your version of this 
22  file only under the terms of the GPL and not to allow others to use your version 
23  of this file under the MPL, indicate your decision by deleting  the provisions above 
24  and replace  them with the notice and other provisions required by the GPL License.  
25  If you do not delete the provisions above, a recipient may use your version of 
26  this file under either the MPL or the GPL. 
27  
28   */
29  
30  package ca.uhn.hl7v2.sourcegen;
31  
32  import java.io.BufferedWriter;
33  import java.io.File;
34  import java.io.FileOutputStream;
35  import java.io.OutputStreamWriter;
36  import java.util.Arrays;
37  import java.util.List;
38  
39  import org.apache.velocity.Template;
40  import org.apache.velocity.VelocityContext;
41  import org.apache.velocity.context.Context;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  import ca.uhn.hl7v2.HL7Exception;
46  import ca.uhn.hl7v2.Version;
47  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
48  import ca.uhn.hl7v2.sourcegen.util.VelocityFactory;
49  
50  /**
51   * Creates source code for Group classes - these are aggregations of segments
52   * and/or other groups that may repeat together within a message. Source code is
53   * generated from the normative database.
54   * 
55   * @author Bryan Tripp (bryan_tripp@sourceforge.net)
56   * @author Eric Poiseau
57   */
58  public class GroupGenerator extends java.lang.Object {
59  
60      private static final Logger log = LoggerFactory.getLogger(GroupGenerator.class);
61  
62  
63      public static void writeGroup(String groupName, String fileName, GroupDef group, String version, String basePackageName, String theTemplatePackage, String theDescription) throws Exception {
64  
65          BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName, false), SourceGenerator.ENCODING));
66  
67          theTemplatePackage = theTemplatePackage.replace(".", "/");
68          Template template = VelocityFactory.getClasspathTemplateInstance(theTemplatePackage + "/group.vsm");
69          Context ctx = new VelocityContext();
70          ctx.put("groupName", groupName);
71          ctx.put("specVersion", version);
72          ctx.put("typeDescription", theDescription);
73          ctx.put("basePackageName", basePackageName);
74          ctx.put("groups", Arrays.asList(group.getStructures()));
75          ctx.put("chapter", "");
76          
77          template.merge(ctx, out);
78  
79          out.flush();
80          out.close();
81          
82  //        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName, false), SourceGenerator.ENCODING));
83  //        out.write(makePreamble(group, version, basePackageName));
84  //        out.write(makeConstructor(group, version));
85  //        StructureDef[] shallow = group.getStructures();
86  //        for (int i = 0; i < shallow.length; i++) {
87  //            out.write(makeAccessor(group, i));
88  //        }
89  //        out.write("}\r\n");
90  //        out.flush();
91  //        out.close();
92      }
93  
94  
95      /** Creates new GroupGenerator */
96      public GroupGenerator() {
97      }
98  
99  
100     /**
101      * <p>
102      * Creates source code for a Group and returns a GroupDef object that
103      * describes the Group's name, optionality, repeatability. The source code
104      * is written under the given directory.
105      * </p>
106      * <p>
107      * The structures list may contain [] and {} pairs representing nested
108      * groups and their optionality and repeastability. In these cases this
109      * method is called recursively.
110      * </p>
111      * <p>
112      * If the given structures list begins and ends with repetition and/or
113      * optionality markers the repetition and optionality of the returned
114      * GroupDef are set accordingly.
115      * </p>
116      * 
117      * @param structures
118      *            a list of the structures that comprise this group - must be at
119      *            least 2 long
120      * @param baseDirectory
121      *            the directory to which files should be written
122      * @param message
123      *            the message to which this group belongs
124      * @param theTemplatePackage 
125      * @throws Exception 
126      */
127     public static GroupDef writeGroup(StructureDef[] structures, String groupName, String baseDirectory, String version, String message, String theTemplatePackage, String theFileExt) throws Exception {
128 
129         // make base directory
130         if (!(baseDirectory.endsWith("\\") || baseDirectory.endsWith("/"))) {
131             baseDirectory = baseDirectory + "/";
132         }
133         File targetDir = SourceGenerator.makeDirectory(baseDirectory + DefaultModelClassFactory.getVersionPackagePath(version) + "group");
134 
135         GroupDef group = getGroupDef(structures, groupName, baseDirectory, version, message, theTemplatePackage, theFileExt);
136 
137         String fileName = targetDir.getPath() + "/" + group.getName() + "." + theFileExt;
138         writeGroup(group.getName(), fileName, group, version, DefaultModelClassFactory.getVersionPackageName(version), theTemplatePackage, group.getDescription());
139 
140         return group;
141     }
142 
143 
144     /**
145      * <p>
146      * Given a list of structures defining the deep content of a group (as
147      * provided in the normative database, some being pairs of optionality and
148      * repetition markers and segments nested within) returns a GroupDef
149      * including a short list of the shallow contents of the group (including
150      * segments and groups that are immediate children).
151      * </p>
152      * <p>
153      * For example given MSH [PID PV1] {[ERR NTE]}, short list would be
154      * something like MSH PID_GROUP ERR_GROUP (with PID_GROUP marked as optional
155      * and ERR_GROUP marked as optional and repeating).
156      * </p>
157      * <p>
158      * This method calls writeGroup(...) where necessary in order to create
159      * source code for any nested groups before returning corresponding
160      * GroupDefs.
161      * </p>
162      */
163     public static GroupDef getGroupDef(StructureDef[] structures, String groupName, String baseDirectory, String version, String message, String theTemplatePackage, String theFileExt) throws Exception {
164         GroupDef ret = null;
165         boolean required = true;
166         boolean repeating = false;
167         boolean rep_opt = false;
168 
169         /*
170          * Fix issues
171          */
172         // See bug 3373654 
173         // !"RESPONSE".equals(((SegmentDef)structures[5]).getGroupName())
174         if (null == (groupName) && "2.5".equals(version) && "ORL_O34".equals(message) && !structuresContainsSegmentWithGroupName(structures, "RESPONSE")) {
175         	List<StructureDef> tmpStructures = new java.util.ArrayList<StructureDef>(java.util.Arrays.asList(structures));
176         	tmpStructures.add(new SegmentDef("]", "RESPONSE", false, false, false, ""));
177         	tmpStructures.add(5, new SegmentDef("[", "RESPONSE", false, false, false, ""));
178         	structures = (StructureDef[]) tmpStructures.toArray(new StructureDef[structures.length]);
179         }
180         
181         int len = structures.length;
182         StructureDef[] shortList = new StructureDef[len]; // place to put final
183                                                           // list of
184                                                           // groups/seg's w/o
185                                                           // opt & rep markers
186         int currShortListPos = 0;
187         int currLongListPos = 0;
188 
189         try {
190             // check for rep and opt (see if start & end elements are [] or {}
191             // AND they are each others' pair) ...
192             // System.out.println(len + " " + structures[0].getName()
193             // +structures[1].getName()+ ".." +structures[len-2].getName() +
194             // structures[len-1].getName()+ " " + message);
195             String struct0name = structures[0].getName();
196             String struct1name = structures[1].getName();
197             String structLastName = structures[len - 1].getName();
198             String structSecondLastName = structures[len - 2].getName();
199 
200             if (optMarkers(struct0name, structLastName) && (findGroupEnd(message, structures, 0) == len - 1))
201                 required = false;
202             if (repMarkers(struct0name, structLastName) && (findGroupEnd(message, structures, 0) == len - 1))
203                 repeating = true;
204             if (repoptMarkers(struct0name, structLastName) && (findGroupEnd(message, structures, 0) == len - 1))
205                 rep_opt = true;
206 
207             if (repeating || !required) {
208                 if (optMarkers(struct1name, structSecondLastName) && (findGroupEnd(message, structures, 1) == len - 2))
209                     required = false;
210                 if (repMarkers(struct1name, structSecondLastName) && (findGroupEnd(message, structures, 1) == len - 2))
211                     repeating = true;
212             }
213 
214             // loop through, recurse nested groups, and build short list of
215             // structures for this group
216             int skip = 0;
217             if (!required)
218                 skip++;
219             if (repeating)
220                 skip++;
221             if (rep_opt)
222                 skip++;
223             currLongListPos = skip;
224             while (currLongListPos < len - skip) {
225                 String currSegName = structures[currLongListPos].getName();
226                 if (currSegName.equals("[") || currSegName.equals("{") || currSegName.equals("[{")) {
227                     // this is the opening of a new group ...
228                     String name = ((SegmentDef) structures[currLongListPos]).getGroupName();
229                     
230                     // Fix mistakes in DB
231                     if (name != null) {
232                     	name = name.replace("TIIMING", "TIMING");
233 
234                         if ("OBSERVATION_REQUEST".equals(groupName)) {
235                         	if ("ORL_O34".equals(message)) {
236                         		if ("SPECIMEN".equals(name))
237                         		if (Version.versionOf(version) == Version.V251) {
238                         			name = "OBSERVATION_REQUEST_SPECIMEN";
239                         		}
240                         	}
241                         }
242 
243                     }
244                     
245 
246 //                    log.info("Name is: " + name + " - Message is: " + message);
247                     
248                     int endOfNewGroup = findGroupEnd(message, structures, currLongListPos);
249                     StructureDef[] newGroupStructures = new StructureDef[endOfNewGroup - currLongListPos + 1];
250                     System.arraycopy(structures, currLongListPos, newGroupStructures, 0, newGroupStructures.length);
251                     shortList[currShortListPos] = writeGroup(newGroupStructures, name, baseDirectory, version, message, theTemplatePackage, theFileExt);
252                     currLongListPos = endOfNewGroup + 1;
253                 } else {
254                     // copy verbatim into short list ...
255                     shortList[currShortListPos] = structures[currLongListPos];
256                     currLongListPos++;
257                 }
258                 currShortListPos++;
259             }
260         } catch (IllegalArgumentException e) {
261             throw new HL7Exception("Problem creating nested group: " + e.getClass().getName() + ": " + e.getMessage());
262         }
263 
264         if (rep_opt) {
265             ret = new GroupDef(message, groupName, false, true, "a Group object");
266         } else {
267             ret = new GroupDef(message, groupName, required, repeating, "a Group object");
268         }
269 
270         StructureDef[] finalList = new StructureDef[currShortListPos]; // note:
271                                                                        // incremented
272                                                                        // after
273                                                                        // last
274                                                                        // assignment
275         System.arraycopy(shortList, 0, finalList, 0, currShortListPos);
276         for (int i = 0; i < finalList.length; i++) {
277         	StructureDef nextStruct = finalList[i];
278         	
279         	// Fix mistakes in the DB
280 			if (nextStruct.getUnqualifiedName().equals("ED")) {
281         		continue;
282         	}
283         	if (nextStruct instanceof GroupDef && ((GroupDef)nextStruct).getRawGroupName() != null && ((GroupDef)nextStruct).getRawGroupName().contains("TIIMING")) {
284         		((GroupDef)nextStruct).setRawGroupName(((GroupDef)nextStruct).getRawGroupName().replace("TIIMING", "TIMING"));
285         	}
286         	
287         	/*
288         	 * Versions 2.5 through 2.6 have two definitions of RSP_K21 (the second is under
289         	 * trigger K22 - see chapter 3) and the second definition shows this group as
290         	 * repeatable. See bug 3520523.
291         	 * 
292         	 * This issue has been corrected in 2.7
293         	 */
294         	String nextName = nextStruct.getUnqualifiedName();
295             if ("QUERY_RESPONSE".equals(nextName)) {
296             	if ("RSP_K21".equals(message)) {
297             		if (Version.versionOf(version) == Version.V25 || Version.versionOf(version) == Version.V251
298             				|| Version.versionOf(version) == Version.V26) {
299                         log.info("Forcing repeatable group");
300             			((GroupDef)nextStruct).setRepeating(true);
301             		}
302             	}
303             }
304         	
305         	/*
306         	 * See 3538074
307         	 * 
308         	 * Current simplified definition (only relevant branch of message strucutre) is:
309 		 	 * ORL_O34 -> ORL_O34_RESPONSE -> ORL_O34_PATIENT->ORL_O34_SPECIMEN->ORL_O34_ORDER->ORL_O34_OBSERVATION_REQUEST->ORL_O34_SPECIMEN (!cycle reference).
310 			 * Instead of last ORL_O34_SPECIMEN there should be ORL_O34_OBSERVATION_REQUEST_SPECIMEN.
311         	 */
312             if ("OBSERVATION_REQUEST".equals(ret.getUnqualifiedName())) {
313             	if ("ORL_O34".equals(message)) {
314             		if ("SPECIMEN".equals(nextName))
315             		if (Version.versionOf(version) == Version.V251) {
316             			((GroupDef)nextStruct).setRawGroupName("OBSERVATION_REQUEST_SPECIMEN");
317             			log.info("Forcing name to " + ((GroupDef)nextStruct).getRawGroupName());
318             		}
319             	}
320             }
321 
322             ret.addStructure(nextStruct);
323         }
324 
325         return ret;
326     }
327 
328 
329     private static boolean structuresContainsSegmentWithGroupName(StructureDef[] theStructures, String theString) {
330     	for (StructureDef structureDef : theStructures) {
331 			if (theString.equals(((SegmentDef)structureDef).getGroupName())) {
332 				return true;
333 			}
334 		}
335 		return false;
336 	}
337 
338 
339 	/**
340      * Returns true if opening is "[{" and closing is "}]"
341      */
342     private static boolean repoptMarkers(String opening, String closing) {
343         boolean ret = false;
344         if (opening.equals("[{") && closing.equals("}]")) {
345             ret = true;
346         }
347         return ret;
348     }
349 
350 
351     /**
352      * Returns true if opening is "[" and closing is "]"
353      */
354     private static boolean optMarkers(String opening, String closing) {
355         boolean ret = false;
356         if (opening.equals("[") && closing.equals("]")) {
357             ret = true;
358         }
359         return ret;
360     }
361 
362 
363     /**
364      * Returns true if opening is "{" and closing is "}"
365      */
366     private static boolean repMarkers(String opening, String closing) {
367         boolean ret = false;
368         if (opening.equals("{") && closing.equals("}")) {
369             ret = true;
370         }
371         return ret;
372     }
373 
374 
375     /**
376      * Given a list of structures and the position of a SegmentDef that
377      * indicates the start of a group (ie "{" or "["), returns the position of
378      * the corresponding end of the group. Nested group markers are ignored.
379      * 
380      * @param message
381      *            The current message
382      * @throws IllegalArgumentException
383      *             if groupStart is out of range or does not point to a group
384      *             opening marker.
385      * @throws HL7Exception
386      *             if the end of the group is not found or if other pairs are
387      *             not properly nested inside this one.
388      */
389     public static int findGroupEnd(String message, StructureDef[] structures, int groupStart) throws IllegalArgumentException, HL7Exception {
390 
391         // {} is rep; [] is optionality
392         String endMarker = null;
393         String startMarker;
394         try {
395             startMarker = structures[groupStart].getName();
396             if (startMarker.equals("[")) {
397                 endMarker = "]";
398             } else if (startMarker.equals("{")) {
399                 endMarker = "}";
400             } else if (startMarker.equals("[{")) {
401                 endMarker = "}]";
402             } else {
403                 log.error("Problem starting at {}", groupStart);
404                 for (int i = 0; i < structures.length; i++) {
405                     log.error("Structure {}: ", i, structures[i].getName());
406                 }
407                 throw new IllegalArgumentException("The segment " + startMarker + " does not begin a group - must be [ or {");
408             }
409         } catch (IndexOutOfBoundsException e) {
410             throw new IllegalArgumentException("The given start location is out of bounds");
411         }
412 
413         // loop, increment and decrement opening and closing markers until we
414         // get back to 0
415         String segName = null;
416         int offset = 0;
417         try {
418             int nestedInside = 1;
419             while (nestedInside > 0) {
420                 offset++;
421                 segName = structures[groupStart + offset].getName();
422                 if (segName.equals("{") || segName.equals("[") || segName.equals("[{")) {
423                     nestedInside++;
424                 } else if (segName.equals("}") || segName.equals("]") || segName.equals("}]")) {
425                     nestedInside--;
426                 }
427             }
428         } catch (IndexOutOfBoundsException e) {
429             throw new HL7Exception("Couldn't find end of group index " + groupStart + " for msg " + message);
430         }
431         if (!endMarker.equals(segName)) {
432             StringBuffer buf = new StringBuffer();
433             for (int i = 0; i < structures.length; i++) {
434                 buf.append("\r\n").append(i).append(" - ").append(structures[i].toString());
435             }
436             String msg = "Group markers for group indexes " + groupStart + "-" + (groupStart + offset) + " are not nested properly for message " + message + ": " + buf.toString();
437             throw new HL7Exception(msg);
438         }
439         return groupStart + offset;
440     }
441 
442 }