1
2
3
4
5
6 package ca.uhn.hl7v2.sourcegen.conf;
7
8 import java.io.BufferedWriter;
9 import java.io.File;
10 import java.io.FileOutputStream;
11 import java.io.OutputStreamWriter;
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.Map;
17 import java.util.Set;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20
21 import org.apache.commons.io.FileUtils;
22 import org.apache.commons.lang.StringUtils;
23 import org.apache.velocity.Template;
24 import org.apache.velocity.VelocityContext;
25 import org.apache.velocity.context.Context;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import ca.uhn.hl7v2.conf.parser.ProfileParser;
30 import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
31 import ca.uhn.hl7v2.conf.spec.message.AbstractSegmentContainer;
32 import ca.uhn.hl7v2.conf.spec.message.Component;
33 import ca.uhn.hl7v2.conf.spec.message.Field;
34 import ca.uhn.hl7v2.conf.spec.message.ProfileStructure;
35 import ca.uhn.hl7v2.conf.spec.message.Seg;
36 import ca.uhn.hl7v2.conf.spec.message.SegGroup;
37 import ca.uhn.hl7v2.conf.spec.message.StaticDef;
38 import ca.uhn.hl7v2.conf.spec.message.SubComponent;
39 import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
40 import ca.uhn.hl7v2.sourcegen.DataTypeGenerator;
41 import ca.uhn.hl7v2.sourcegen.DatatypeComponentDef;
42 import ca.uhn.hl7v2.sourcegen.DatatypeDef;
43 import ca.uhn.hl7v2.sourcegen.GroupDef;
44 import ca.uhn.hl7v2.sourcegen.GroupGenerator;
45 import ca.uhn.hl7v2.sourcegen.MessageGenerator;
46 import ca.uhn.hl7v2.sourcegen.SegmentDef;
47 import ca.uhn.hl7v2.sourcegen.SegmentElement;
48 import ca.uhn.hl7v2.sourcegen.SegmentGenerator;
49 import ca.uhn.hl7v2.sourcegen.SourceGenerator;
50 import ca.uhn.hl7v2.sourcegen.util.VelocityFactory;
51
52
53
54
55 public class ProfileSourceGenerator {
56
57 private static final Logger ourLog = LoggerFactory.getLogger(ProfileSourceGenerator.class);
58
59 private final RuntimeProfile myProfile;
60 private String myTargetDirectory;
61 private String myBasePackage;
62 private String myMessageName;
63 private final Set<String> mySegmentDefNames;
64 private final Set<String> myGroupDefNames;
65 private final ArrayList<SegmentDef> mySegmentDefs;
66 private final Map<String, ArrayList<SegmentElement>> mySegmentNameToSegmentElements;
67 private final ArrayList<GroupDef> myGroupDefs;
68 private final GenerateDataTypesEnum myGenerateDataTypes;
69 private final String myTemplatePackage;
70 private final String myFileExt;
71
72 public ProfileSourceGenerator(RuntimeProfile theProfile, String theTargetDirectory, String theBasePackage, GenerateDataTypesEnum theGenDt, String theTemplatePackage,
73 String theFileExt) {
74 myProfile = theProfile;
75 myGenerateDataTypes = theGenDt;
76 myTemplatePackage = theTemplatePackage;
77 myFileExt = theFileExt;
78
79 myTargetDirectory = theTargetDirectory;
80 if (!myTargetDirectory.endsWith("/")) {
81 myTargetDirectory += "/";
82 }
83
84 myBasePackage = theBasePackage;
85 if (!myBasePackage.endsWith(".")) {
86 myBasePackage += ".";
87 }
88
89 myTargetDirectory += myBasePackage.replaceAll("\\.", "/");
90
91 mySegmentDefs = new ArrayList<>();
92 myGroupDefs = new ArrayList<>();
93 mySegmentNameToSegmentElements = new HashMap<>();
94 mySegmentDefNames = new HashSet<>();
95 myGroupDefNames = new HashSet<>();
96 }
97
98 public void generate() throws Exception {
99 StaticDef staticDef = myProfile.getMessage();
100 myMessageName = staticDef.getMsgStructID();
101
102 SourceGenerator.makeDirectory(myTargetDirectory + "/message");
103 SourceGenerator.makeDirectory(myTargetDirectory + "/segment");
104 SourceGenerator.makeDirectory(myTargetDirectory + "/group");
105
106 String chapter = "";
107 String version = myProfile.getHL7Version();
108 GroupDef group = convertToGroupDef(staticDef, version);
109
110 String basePackageName = DefaultModelClassFactory.getVersionPackageName(version);
111 String[] datatypePackages;
112
113 switch (myGenerateDataTypes) {
114 default:
115 case NONE:
116 datatypePackages = new String[] { basePackageName + "datatype" };
117 break;
118 case SINGLE:
119 datatypePackages = new String[] { myBasePackage + "datatype" };
120 SourceGenerator.makeDirectory(myTargetDirectory + "/datatype");
121 }
122
123 boolean haveGroups = myGroupDefs.size() > 0;
124
125
126 {
127 String parent = myTargetDirectory + "message/";
128 FileUtils.forceMkdir(new File(parent));
129 String fileName = parent + staticDef.getMsgStructID() + "." + myFileExt;
130 ourLog.debug("Writing Message file: " + fileName);
131 MessageGenerator.writeMessage(fileName, group.getStructures(), myMessageName, chapter, version, myBasePackage, haveGroups, myTemplatePackage, null);
132 }
133
134 for (GroupDef next : myGroupDefs) {
135 String parent = myTargetDirectory + "group/";
136 FileUtils.forceMkdir(new File(parent));
137 String fileName = parent + next.getName() + "." + myFileExt;
138 ourLog.debug("Writing Group file: " + fileName);
139 GroupGenerator.writeGroup(next.getName(), fileName, next, version, myBasePackage, myTemplatePackage, next.getDescription());
140 }
141
142
143 Set<String> alreadyWrittenDatatypes = new HashSet<>();
144 Set<String> alreadyWrittenSegments = new HashSet<>();
145 for (SegmentDef next : mySegmentDefs) {
146 alreadyWrittenSegments.add(next.getName());
147
148 String parent = myTargetDirectory + "segment/";
149 FileUtils.forceMkdir(new File(parent));
150 String fileName = parent + next.getName() + "." + myFileExt;
151 ourLog.debug("Writing Segment file: " + fileName);
152 String segmentName = next.getName();
153 String description = next.getDescription();
154 ArrayList<SegmentElement> elements = mySegmentNameToSegmentElements.get(segmentName);
155
156 for (SegmentElement nextElement : elements) {
157 if ("*".equals(nextElement.type) || "VARIES".equals(nextElement.type)) {
158 nextElement.type = "Varies";
159 }
160 }
161
162 SegmentGenerator.writeSegment(fileName, version, segmentName, elements, description, myBasePackage, datatypePackages, myTemplatePackage);
163
164 if (myGenerateDataTypes == GenerateDataTypesEnum.SINGLE) {
165 for (DatatypeDef nextFieldDef : next.getFieldDefs()) {
166 writeDatatype(nextFieldDef, alreadyWrittenDatatypes, version);
167 }
168 }
169
170 }
171
172 if ("json".equals(myFileExt)) {
173 String fileName = myTargetDirectory + "/structures." + myFileExt;
174 ourLog.debug("Writing Structures file: " + fileName);
175 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName, false), SourceGenerator.ENCODING));
176 String templatePackage = myTemplatePackage.replace(".", "/");
177 Template template = VelocityFactory.getClasspathTemplateInstance(templatePackage + "/available_structures.vsm");
178 Context ctx = new VelocityContext();
179 ctx.put("messages", Collections.singletonList(myMessageName));
180 ctx.put("segments", alreadyWrittenSegments);
181 ctx.put("datatypes", alreadyWrittenDatatypes);
182
183 template.merge(ctx, out);
184
185 out.flush();
186 out.close();
187 }
188
189 }
190
191 private void writeDatatype(DatatypeDef theFieldDef, Set<String> theAlreadyWrittenDatatypes, String version) throws Exception {
192 if (theAlreadyWrittenDatatypes != null) {
193 if (theAlreadyWrittenDatatypes.contains(theFieldDef.getType())) {
194 return;
195 } else {
196 theAlreadyWrittenDatatypes.add(theFieldDef.getType());
197 }
198 }
199
200 String parent = myTargetDirectory + "datatype/";
201 FileUtils.forceMkdir(new File(parent));
202 String fileName = parent + theFieldDef.getType() + "." + myFileExt;
203 DataTypeGenerator.writeDatatype(fileName, version, theFieldDef, myBasePackage, myTemplatePackage);
204
205 for (DatatypeDef next : theFieldDef.getSubComponentDefs()) {
206 writeDatatype(next, theAlreadyWrittenDatatypes, version);
207 }
208 }
209
210 private GroupDef convertToGroupDef(StaticDef staticDef, String theVersion) {
211
212 boolean required = true;
213 boolean repeating = true;
214 String description = staticDef.getDescription();
215 GroupDefpDef.html#GroupDef">GroupDef retVal = new GroupDef(myMessageName, null, required, repeating, description);
216
217 populateChildren(retVal, staticDef, theVersion);
218
219 return retVal;
220 }
221
222 private GroupDef./../ca/uhn/hl7v2/sourcegen/GroupDef.html#GroupDef">GroupDef convertToGroupDef(GroupDef theParent, SegGroup segGroup, String theVersion) {
223
224
225
226
227
228
229 boolean required = true;
230 if (segGroup.getChildren() == 1 && segGroup.getChild(1) instanceof SegGroup) {
231 required = segGroup.getMin() > 0;
232 segGroup = (SegGroup) segGroup.getChild(1);
233 }
234
235 String name = segGroup.getName();
236 required = required && (segGroup.getMin() > 0);
237 boolean repeating = segGroup.getMax() != 1;
238 String description = segGroup.getLongName();
239
240 GroupDefpDef.html#GroupDef">GroupDef retVal = new GroupDef(myMessageName, name, required, repeating, description);
241
242 populateChildren(retVal, segGroup, theVersion);
243
244 if (!myGroupDefNames.contains(name)) {
245 myGroupDefNames.add(name);
246 myGroupDefs.add(retVal);
247 }
248
249 return retVal;
250 }
251
252 private void populateChildren(GroupDef retVal, AbstractSegmentContainer segGroup, String theVersion) {
253
254 for (int i = 0; i < segGroup.getChildren(); i++) {
255 ProfileStructure nextProfileStructure = segGroup.getChild(i + 1);
256
257 if (nextProfileStructure instanceof SegGroup) {
258
259 SegGroup/../../ca/uhn/hl7v2/conf/spec/message/SegGroup.html#SegGroup">SegGroup nextSegGroup = (SegGroup) nextProfileStructure;
260 GroupDef nextGroupDef = convertToGroupDef(retVal, nextSegGroup, theVersion);
261 retVal.addStructure(nextGroupDef);
262
263 } else if (nextProfileStructure instanceof Seg) {
264
265 Seg="../../../../../ca/uhn/hl7v2/conf/spec/message/Seg.html#Seg">Seg nextSeg = (Seg) nextProfileStructure;
266 SegmentDef nextSegmentDef = convertToSegmentDef(retVal, nextSeg, theVersion);
267 retVal.addStructure(nextSegmentDef);
268
269 } else {
270
271 throw new IllegalStateException("Unknown profile structure: " + nextProfileStructure);
272
273 }
274
275 }
276
277 }
278
279 private SegmentDef convertToSegmentDef(GroupDef theParent, Seg nextSeg, String theVersion) {
280 String name = nextSeg.getName();
281 String groupName = null;
282 boolean required = nextSeg.getMin() > 0;
283 boolean repeating = nextSeg.getMax() > 1 || nextSeg.getMax() == -1;
284 String description = nextSeg.getLongName();
285
286 SegmentDeftDef.html#SegmentDef">SegmentDef retVal = new SegmentDef(name, groupName, required, repeating, false, description);
287 ArrayList<SegmentElement> segmentElements = new ArrayList<>();
288
289
290 for (int i = 0; i < nextSeg.getFields(); i++) {
291 Field nextField = nextSeg.getField(i + 1);
292
293 DatatypeDef nextFieldDef = convertToDatatypeDef(nextField);
294 retVal.addFieldDef(nextFieldDef);
295
296 SegmentElementgmentElement">SegmentElement nextSegmentElement = new SegmentElement(name, theVersion, i);
297
298 nextSegmentElement.desc = nextField.getName();
299 nextSegmentElement.field = i + 1;
300 nextSegmentElement.length = (int) nextField.getLength();
301 nextSegmentElement.opt = nextField.getUsage();
302 nextSegmentElement.rep = (nextField.getMax() != 1) ? "Y" : "N";
303 nextSegmentElement.repetitions = nextField.getMax();
304 nextSegmentElement.type = nextField.getDatatype();
305
306 if (nextSegmentElement.type.startsWith("CM_")) {
307 nextSegmentElement.type = nextSegmentElement.type.substring(3);
308 }
309
310 String table = nextField.getTable();
311 if (table != null && table.length() > 0) {
312 extractTableInfo(nextSegmentElement, table);
313 }
314
315 segmentElements.add(nextSegmentElement);
316 }
317
318 if (!mySegmentDefNames.contains(name)) {
319 mySegmentDefNames.add(name);
320 mySegmentDefs.add(retVal);
321 mySegmentNameToSegmentElements.put(name, segmentElements);
322 }
323
324 return retVal;
325 }
326
327 static void extractTableInfo(SegmentElement nextSegmentElement, String table) {
328 Pattern p = Pattern.compile("^([a-zA-Z]+)([0-9]+)$");
329 Matcher m = p.matcher(table);
330 if (m.find()) {
331 nextSegmentElement.tableNamespace = m.group(1);
332
333 String tableNum = m.group(2);
334 nextSegmentElement.table = Integer.parseInt(tableNum);
335
336 String alternateType = nextSegmentElement.getAlternateType();
337 if ("ID".equals(alternateType)) {
338 nextSegmentElement.setAlternateType("ca.uhn.hl7v2.model.primitive.IDWithNamespace");
339 } else if ("IS".equals(alternateType)) {
340 nextSegmentElement.setAlternateType("ca.uhn.hl7v2.model.primitive.IDWithNamespace");
341 }
342
343 } else {
344 try {
345 nextSegmentElement.table = Integer.parseInt(table);
346 } catch (NumberFormatException e) {
347 ourLog.warn("Unable to parse number out of table name: \"" + table +"\" for field \"" + nextSegmentElement.desc + "\". Ignoring this value and setting table number to 0.");
348 }
349 }
350 }
351
352 private DatatypeDef convertToDatatypeDef(Field theField) {
353 String type = theField.getDatatype();
354 String name = theField.getName();
355
356 DatatypeDefeDef.html#DatatypeDef">DatatypeDef retVal = new DatatypeDef(type, name);
357
358 for (int i = 0; i < theField.getComponents(); i++) {
359 Component nextComponent = theField.getComponent(i + 1);
360 DatatypeComponentDef nextDef = convertToDatatypeComponentDef(nextComponent, type, i);
361 retVal.addSubcomponentDef(nextDef);
362 }
363
364 return retVal;
365 }
366
367 private DatatypeComponentDef convertToDatatypeComponentDef(Component theComponent, String parentType, int indexWithinParent) {
368
369 String type = theComponent.getDatatype();
370 String name = theComponent.getName();
371
372 int table = 0;
373 if (StringUtils.isNotBlank(theComponent.getTable())) {
374 try {
375 table = Integer.parseInt(theComponent.getTable());
376 } catch (NumberFormatException e) {
377
378 }
379 }
380
381 DatatypeComponentDeftDef.html#DatatypeComponentDef">DatatypeComponentDef retVal = new DatatypeComponentDef(parentType, indexWithinParent, type, name, table);
382
383 for (int i = 0; i < theComponent.getSubComponents(); i++) {
384 SubComponent next = theComponent.getSubComponent(i + 1);
385 DatatypeComponentDef def = convertToDatatypeComponentDef(next, type, i);
386 retVal.addSubcomponentDef(def);
387 }
388
389 return retVal;
390 }
391
392 private DatatypeComponentDef convertToDatatypeComponentDef(SubComponent theComponent, String parentType, int indexWithinParent) {
393
394 String type = theComponent.getDatatype();
395 String desc = theComponent.getName();
396
397 int table = 0;
398 if (StringUtils.isNotBlank(theComponent.getTable())) {
399 try {
400 table = Integer.parseInt(theComponent.getTable());
401 } catch (NumberFormatException e) {
402
403 }
404 }
405
406 return new DatatypeComponentDef(parentType, indexWithinParent, type, desc, table);
407 }
408
409 public static void main(String[] args) throws Exception {
410 RuntimeProfile rp = new ProfileParser(false).parseClasspath("ca/uhn/hl7v2/conf/parser/ADT_A01.xml");
411 new ProfileSourceGenerator(rp, "hapi-test/target/generated-sources/confgen", "hapi.on.olis", GenerateDataTypesEnum.SINGLE, "ca.uhn.hl7v2.sourcegen.templates.json", "json")
412 .generate();
413 }
414 }