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 {
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;
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<>(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 = tmpStructures.toArray(new StructureDef[structures.length]);
179         }
180         
181         int len = structures.length;
182         StructureDefl#StructureDef">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;
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                     StructureDefreDef">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         StructureDefl#StructureDef">StructureDef[] finalList = new StructureDef[currShortListPos]; // note:
271                                                                        // incremented
272                                                                        // after
273                                                                        // last
274                                                                        // assignment
275         System.arraycopy(shortList, 0, finalList, 0, currShortListPos);
276         for (StructureDef nextStruct : finalList) {
277             // Fix mistakes in the DB
278             if (nextStruct.getUnqualifiedName().equals("ED")) {
279                 continue;
280             }
281             if (nextStruct instanceof GroupDef/.GroupDefuhn/hl7v2/sourcegen/GroupDef.html#GroupDef">GroupDef && ((GroupDef) GroupDefct).getRawGroupName() != null && ((GroupDef) nextStruct).getRawGroupName().contains("TIIMING")) {
282                 ((GroupDef2/sourcegen/GroupDef.html#GroupDef">GroupDef) nextStruct).setRawGroupName(((GroupDef) nextStruct).getRawGroupName().replace("TIIMING", "TIMING"));
283             }
284 
285             /*
286              * Versions 2.5 through 2.6 have two definitions of RSP_K21 (the second is under
287              * trigger K22 - see chapter 3) and the second definition shows this group as
288              * repeatable. See bug 3520523.
289              *
290              * This issue has been corrected in 2.7
291              */
292             String nextName = nextStruct.getUnqualifiedName();
293             if ("QUERY_RESPONSE".equals(nextName)) {
294                 if ("RSP_K21".equals(message)) {
295                     if (Version.versionOf(version) == Version.V25 || Version.versionOf(version) == Version.V251
296                             || Version.versionOf(version) == Version.V26) {
297                         log.info("Forcing repeatable group");
298                         ((GroupDef) nextStruct).setRepeating(true);
299                     }
300                 }
301             }
302 
303             /*
304              * See 3538074
305              *
306              * Current simplified definition (only relevant branch of message strucutre) is:
307              * ORL_O34 -> ORL_O34_RESPONSE -> ORL_O34_PATIENT->ORL_O34_SPECIMEN->ORL_O34_ORDER->ORL_O34_OBSERVATION_REQUEST->ORL_O34_SPECIMEN (!cycle reference).
308              * Instead of last ORL_O34_SPECIMEN there should be ORL_O34_OBSERVATION_REQUEST_SPECIMEN.
309              */
310             if ("OBSERVATION_REQUEST".equals(ret.getUnqualifiedName())) {
311                 if ("ORL_O34".equals(message)) {
312                     if ("SPECIMEN".equals(nextName))
313                         if (Version.versionOf(version) == Version.V251) {
314                             ((GroupDef) nextStruct).setRawGroupName("OBSERVATION_REQUEST_SPECIMEN");
315                             log.info("Forcing name to " + ((GroupDef) nextStruct).getRawGroupName());
316                         }
317                 }
318             }
319 
320             ret.addStructure(nextStruct);
321         }
322 
323         return ret;
324     }
325 
326 
327     private static boolean structuresContainsSegmentWithGroupName(StructureDef[] theStructures, String theString) {
328     	for (StructureDef structureDef : theStructures) {
329 			if (theString.equals(((SegmentDef)structureDef).getGroupName())) {
330 				return true;
331 			}
332 		}
333 		return false;
334 	}
335 
336 
337 	/**
338      * Returns true if opening is "[{" and closing is "}]"
339      */
340     private static boolean repoptMarkers(String opening, String closing) {
341         boolean ret = false;
342         if (opening.equals("[{") && closing.equals("}]")) {
343             ret = true;
344         }
345         return ret;
346     }
347 
348 
349     /**
350      * Returns true if opening is "[" and closing is "]"
351      */
352     private static boolean optMarkers(String opening, String closing) {
353         boolean ret = false;
354         if (opening.equals("[") && closing.equals("]")) {
355             ret = true;
356         }
357         return ret;
358     }
359 
360 
361     /**
362      * Returns true if opening is "{" and closing is "}"
363      */
364     private static boolean repMarkers(String opening, String closing) {
365         boolean ret = false;
366         if (opening.equals("{") && closing.equals("}")) {
367             ret = true;
368         }
369         return ret;
370     }
371 
372 
373     /**
374      * Given a list of structures and the position of a SegmentDef that
375      * indicates the start of a group (ie "{" or "["), returns the position of
376      * the corresponding end of the group. Nested group markers are ignored.
377      * 
378      * @param message
379      *            The current message
380      * @throws IllegalArgumentException
381      *             if groupStart is out of range or does not point to a group
382      *             opening marker.
383      * @throws HL7Exception
384      *             if the end of the group is not found or if other pairs are
385      *             not properly nested inside this one.
386      */
387     public static int findGroupEnd(String message, StructureDef[] structures, int groupStart) throws IllegalArgumentException, HL7Exception {
388 
389         // {} is rep; [] is optionality
390         String endMarker;
391         String startMarker;
392         try {
393             startMarker = structures[groupStart].getName();
394             switch (startMarker) {
395                 case "[":
396                     endMarker = "]";
397                     break;
398                 case "{":
399                     endMarker = "}";
400                     break;
401                 case "[{":
402                     endMarker = "}]";
403                     break;
404                 default:
405                     log.error("Problem starting at {}", groupStart);
406                     for (int i = 0; i < structures.length; i++) {
407                         log.error("Structure at index {}: {}", i, structures[i].getName());
408                     }
409                     throw new IllegalArgumentException("The segment " + startMarker + " does not begin a group - must be [ or {");
410             }
411         } catch (IndexOutOfBoundsException e) {
412             throw new IllegalArgumentException("The given start location is out of bounds");
413         }
414 
415         // loop, increment and decrement opening and closing markers until we
416         // get back to 0
417         String segName = null;
418         int offset = 0;
419         try {
420             int nestedInside = 1;
421             while (nestedInside > 0) {
422                 offset++;
423                 segName = structures[groupStart + offset].getName();
424                 if (segName.equals("{") || segName.equals("[") || segName.equals("[{")) {
425                     nestedInside++;
426                 } else if (segName.equals("}") || segName.equals("]") || segName.equals("}]")) {
427                     nestedInside--;
428                 }
429             }
430         } catch (IndexOutOfBoundsException e) {
431             throw new HL7Exception("Couldn't find end of group index " + groupStart + " for msg " + message);
432         }
433         if (!endMarker.equals(segName)) {
434             StringBuilder buf = new StringBuilder();
435             for (int i = 0; i < structures.length; i++) {
436                 buf.append("\r\n").append(i).append(" - ").append(structures[i].toString());
437             }
438             String msg = "Group markers for group indexes " + groupStart + "-" + (groupStart + offset) + " are not nested properly for message " + message + ": " + buf.toString();
439             throw new HL7Exception(msg);
440         }
441         return groupStart + offset;
442     }
443 
444 }