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 "DataTypeGenerator.java".  Description: 
10  "Generates skeletal source code for Datatype classes based on the 
11    HL7 database" 
12  
13  The Initial Developer of the Original Code is University Health Network. Copyright (C) 
14  2001.  All Rights Reserved. 
15  
16  Contributor(s):  James Agnew 
17  
18  Alternatively, the contents of this file may be used under the terms of the 
19  GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
20  applicable instead of those above.  If you wish to allow use of your version of this 
21  file only under the terms of the GPL and not to allow others to use your version 
22  of this file under the MPL, indicate your decision by deleting  the provisions above 
23  and replace  them with the notice and other provisions required by the GPL License.  
24  If you do not delete the provisions above, a recipient may use your version of 
25  this file under either the MPL or the GPL. 
26  
27   */
28  
29  package ca.uhn.hl7v2.mvnplugin;
30  
31  import java.io.File;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  
40  import org.apache.maven.plugin.AbstractMojo;
41  import org.apache.maven.plugin.MojoExecutionException;
42  import org.apache.maven.plugin.MojoFailureException;
43  import org.apache.maven.project.MavenProject;
44  import org.springframework.beans.factory.config.BeanDefinition;
45  import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
46  import org.springframework.core.io.DefaultResourceLoader;
47  import org.springframework.core.type.filter.AssignableTypeFilter;
48  
49  import ca.uhn.hl7v2.HL7Exception;
50  import ca.uhn.hl7v2.Version;
51  import ca.uhn.hl7v2.model.GenericComposite;
52  import ca.uhn.hl7v2.model.Group;
53  import ca.uhn.hl7v2.model.Message;
54  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
55  import ca.uhn.hl7v2.sourcegen.GroupDef;
56  import ca.uhn.hl7v2.sourcegen.GroupGenerator;
57  import ca.uhn.hl7v2.sourcegen.MessageGenerator;
58  import ca.uhn.hl7v2.sourcegen.SegmentDef;
59  import ca.uhn.hl7v2.sourcegen.StructureDef;
60  import ca.uhn.hl7v2.util.ReflectionUtil;
61  
62  /**
63   * Maven Plugin Mojo for generating HAPI HL7 message/segment/etc source files
64   * 
65   * @author <a href="mailto:jamesagnew@sourceforge.net">James Agnew</a>
66   * @goal superstructuregen
67   * @phase generate-sources
68   * @requiresDependencyResolution runtime
69   * @requiresProject
70   * @inheritedByDefault false
71   */
72  public class SuperStructureMojo extends AbstractMojo {
73  
74  	/**
75  	 * The maven project.
76  	 * 
77  	 * @parameter property="project"
78  	 * @required
79  	 * @readonly
80  	 */
81  	private MavenProject project;
82  
83  	/**
84  	 * Should build be skipped
85  	 * 
86  	 * @parameter
87  	 */
88  	private boolean skip;
89  
90  	/**
91  	 * Structures to merge
92  	 * 
93  	 * @parameter
94  	 */
95  	private List<String> structures;
96  
97  	/**
98  	 * The target directory for the generated source
99  	 * 
100 	 * @parameter
101 	 * @required
102 	 */
103 	private String targetDirectory;
104 
105 	/**
106 	 * The target structure name (e.g "ADT_AXX")
107 	 * 
108 	 * @parameter
109 	 * @required
110 	 */
111 	private String targetStructureName;
112 
113 	private String templatePackage = "ca.uhn.hl7v2.sourcegen.templates";
114 
115 	/**
116 	 * The version for the generated source
117 	 * 
118 	 * @parameter
119 	 */
120 	private String version;
121 
122 	/**
123 	 * {@inheritDoc}
124 	 */
125 	public void execute() throws MojoExecutionException, MojoFailureException {
126 
127 		if (skip) {
128 			getLog().warn("Configured to skip");
129 		}
130 
131 		try {
132 			List<String> allStructures = new ArrayList<String>();
133 			DefaultModelClassFactory mcf = new DefaultModelClassFactory();
134 
135 			// We want a sorted and unique list of all structures
136 			Version versionOf = Version.versionOf(version);
137 			if (versionOf == null) {
138 				throw new MojoExecutionException("Unknown version: " + version);
139 			}
140 
141 //			Map<String, String> eventMap = mcf.getEventMapForVersion(versionOf);
142 //			if (eventMap == null) {
143 //				throw new MojoExecutionException("Failed to load structures for version " + version + ". Do you have the right dependencies configured for this plugin?");
144 //			}
145 //
146 //			Set<String> allStructuresSet = new HashSet<String>();
147 //			allStructuresSet.addAll(eventMap.values());
148 //			allStructures.addAll(allStructuresSet);
149 //			Collections.sort(allStructures);
150 
151 			ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
152 			DefaultResourceLoader resourceLoader = new DefaultResourceLoader(GenericComposite.class.getClassLoader());
153 			scanner.setResourceLoader(resourceLoader);
154 			scanner.addIncludeFilter(new AssignableTypeFilter(Message.class));
155 			Set<BeanDefinition> components = scanner.findCandidateComponents("ca/uhn/hl7v2/model/" + versionOf.getPackageVersion() + "/message");
156 			for (BeanDefinition beanDefinition : components) {
157 				String nextName = Class.forName(beanDefinition.getBeanClassName()).getSimpleName();
158 				if (nextName.equals(targetStructureName)) {
159 					continue;
160 				}
161 				allStructures.add(nextName);
162 			}
163 			
164 			getLog().info("Found " + allStructures.size() + " message classes for version: " + version);
165 			
166 			List<Message> messagesToMerge = new ArrayList<Message>();
167 
168 			Collections.sort(allStructures);
169 			for (String nextStructure : allStructures) {
170 				for (String nextStructureToMerge : structures) {
171 					if (nextStructure.matches(nextStructureToMerge)) {
172 						Class<? extends Message> clazz = mcf.getMessageClass(nextStructure, version, true);
173 						messagesToMerge.add(ReflectionUtil.instantiateMessage(clazz, mcf));
174 					}
175 				}
176 			}
177 
178 			if (messagesToMerge.isEmpty()) {
179 				throw new MojoFailureException("No messages match pattern(s): " + structures);
180 			}
181 			
182 			ListOfStructureDefsAndMapOfStructreNames mergedMessages = mergeGroups(messagesToMerge, messagesToMerge);
183 			List<StructureDef> structures = mergedMessages.myStructureDefs;
184 			if (structures.isEmpty()) {
185 				throw new MojoExecutionException("No structures found matching structures to merge");
186 			}
187 
188 			getLog().info("Creating directory: " + targetDirectory);
189 			new File(targetDirectory).mkdirs();
190 
191 			boolean haveGroups = false;
192 			for (StructureDef structureDef : structures) {
193 				if (structureDef.isGroup()) {
194 					haveGroups = true;
195 					writeGroup((GroupDef) structureDef);
196 				}
197 			}
198 
199 			String fileName = MessageGenerator.determineTargetDir(targetDirectory + "/", version) + "/" + targetStructureName + ".java";
200 			getLog().info("Filename will be: " + fileName);
201 
202 			StructureDef[] contents = structures.toArray(new StructureDef[structures.size()]);
203 			String basePackageName = DefaultModelClassFactory.getVersionPackageName(version);
204 			MessageGenerator.writeMessage(fileName, contents, targetStructureName, "", version, basePackageName, haveGroups, templatePackage, mergedMessages.myStructureNameToChildNames);
205 
206 		} catch (Exception e) {
207 			throw new MojoFailureException("Failed to generate structure", e);
208 		}
209 		getLog().info("Adding " + targetDirectory + " to compile source root");
210 		project.addCompileSourceRoot(targetDirectory);
211 
212 	}
213 
214 	private void writeGroup(GroupDef theStructureDef) throws Exception {
215 
216 		StructureDef[] structures = theStructureDef.getStructures();
217 		String groupName = theStructureDef.getUnqualifiedName();
218 		GroupGenerator.writeGroup(structures, groupName, targetDirectory, version, targetStructureName, templatePackage, "java");
219 
220 		for (StructureDef structureDef : structures) {
221 			if (structureDef instanceof GroupDef) {
222 				writeGroup((GroupDef) structureDef);
223 			}
224 		}
225 
226 	}
227 
228 	private class ListOfStructureDefsAndMapOfStructreNames {
229 		private List<StructureDef> myStructureDefs;
230 		private Map<String, List<String>> myStructureNameToChildNames;
231 	}
232 
233 	private ListOfStructureDefsAndMapOfStructreNames mergeGroups(List<? extends Group> theGroupsToMerge, List<Message> theAssociatedStructures) throws HL7Exception {
234 		ArrayList<StructureDef> retValStructureDefs = new ArrayList<StructureDef>();
235 
236 		List<List<String>> allNameLists = new ArrayList<List<String>>();
237 		for (Group nextGroup : theGroupsToMerge) {
238 			List<String> nextList = Arrays.asList(nextGroup.getNames());
239 			// for (int i = 0; i < nextList.size(); i++) {
240 			// if (nextGroup.isGroup(nextList.get(i)) == false) {
241 			// nextList.set(i, nextList.get(i).substring(0, 3));
242 			// }
243 			// }
244 			allNameLists.add(nextList);
245 		}
246 
247 		ArrayList<String> structureNames = mergeStringLists(allNameLists);
248 		int currentStructureIdx = 0;
249 		for (String nextStructureName : structureNames) {
250 
251 			/*
252 			 * Don't have the same name a second time. This mainly prevents a
253 			 * second OBX in ADT messages, so it should be ok since there is an
254 			 * appropriate OBX in a PROCEDURE group right before the second
255 			 * one..?
256 			 */
257 			if (structureNames.subList(0, currentStructureIdx++).contains(nextStructureName)) {
258 				continue;
259 			}
260 
261 			boolean required = true;
262 			boolean repeating = false;
263 			boolean group = false;
264 			boolean choice = false;
265 			List<Group> childGroups = new ArrayList<Group>();
266 			List<Message> associatedChildStructures = new ArrayList<Message>();
267 
268 			int idx = 0;
269 			for (Group nextGroup : theGroupsToMerge) {
270 				if (Arrays.asList(nextGroup.getNames()).contains(nextStructureName)) {
271 
272 					if (theAssociatedStructures != null) {
273 						associatedChildStructures.add(theAssociatedStructures.get(idx));
274 					}
275 
276 					repeating |= nextGroup.isRepeating(nextStructureName);
277 					choice |= nextGroup.isChoiceElement(nextStructureName);
278 					required &= nextGroup.isRequired(nextStructureName);
279 					if (nextGroup.isGroup(nextStructureName)) {
280 						group = true;
281 						childGroups.add((Group) nextGroup.get(nextStructureName));
282 					}
283 				} else {
284 					required = false;
285 				}
286 
287 				idx++;
288 			}
289 
290 			if (group == false) {
291 				SegmentDef seg = new SegmentDef(nextStructureName.substring(0, 3), "", required, repeating, choice, "");
292 				retValStructureDefs.add(seg);
293 
294 				/*
295 				 * Use the event map to turn each asociated message (e.g.
296 				 * ADT_A01.class) into associated structure names (e.g.
297 				 * "ADT_A01", "ADT_A04")
298 				 */
299 				for (Message next : associatedChildStructures) {
300 					Map<String, String> evtMap = new DefaultModelClassFactory().getEventMapForVersion(Version.versionOf(version));
301 					for (Map.Entry<String, String> nextEntry : evtMap.entrySet()) {
302 						if (nextEntry.getValue().equals(next.getName())) {
303 							seg.addAssociatedStructure(nextEntry.getKey());
304 						}
305 					}
306 				}
307 
308 				seg.setIndexName(nextStructureName);
309 
310 				continue;
311 			}
312 
313 			if (group) {
314 				GroupDef grp = new GroupDef(targetStructureName, nextStructureName, required, repeating, "");
315 				grp.setIndexName(nextStructureName);
316 				List<StructureDef> children = mergeGroups(childGroups, null).myStructureDefs;
317 
318 				/*
319 				 * Use the event map to turn each asociated message (e.g.
320 				 * ADT_A01.class) into associated structure names (e.g.
321 				 * "ADT_A01", "ADT_A04")
322 				 */
323 				for (Message next : associatedChildStructures) {
324 					Map<String, String> evtMap = new DefaultModelClassFactory().getEventMapForVersion(Version.versionOf(version));
325 					for (Map.Entry<String, String> nextEntry : evtMap.entrySet()) {
326 						if (nextEntry.getValue().equals(next.getName())) {
327 							grp.addAssociatedStructure(nextEntry.getKey());
328 						}
329 					}
330 				}
331 
332 				for (StructureDef structureDef : children) {
333 					grp.addStructure(structureDef);
334 				}
335 				retValStructureDefs.add(grp);
336 			}
337 
338 		}
339 
340 		ListOfStructureDefsAndMapOfStructreNames retVal = new ListOfStructureDefsAndMapOfStructreNames();
341 		retVal.myStructureDefs = retValStructureDefs;
342 
343 		if (theAssociatedStructures != null) {
344 			HashMap<String, List<String>> retValMap = new HashMap<String, List<String>>();
345 			for (Message next : theAssociatedStructures) {
346 				retValMap.put(next.getName(), Arrays.asList(next.getNames()));
347 			}
348 			retVal.myStructureNameToChildNames = retValMap;
349 		}
350 
351 		return retVal;
352 	}
353 
354 	ArrayList<String> mergeStringLists(List<List<String>> allNameLists) {
355 		ArrayList<String> baseList = new ArrayList<String>(allNameLists.remove(0));
356 		getLog().debug("Base list is: "+ baseList);
357 
358 		for (List<String> nextCompareList : allNameLists) {
359 
360 			getLog().debug("Next compare list: "+ nextCompareList);
361 
362 			int baseIndex = 0;
363 			int compareIndex = 0;
364 
365 			while (compareIndex < nextCompareList.size()) {
366 
367 				String currentBase = baseList.get(baseIndex);
368 				String currentCompare = nextCompareList.get(compareIndex);
369 				if (currentBase.equals(currentCompare)) {
370 					if (baseIndex + 1 < baseList.size()) {
371 						baseIndex++;
372 					}
373 					compareIndex++;
374 					continue;
375 				}
376 
377 				List<String> subList = baseList.subList(baseIndex, baseList.size());
378 
379 				// Find next match
380 				List<String> toAdd = null;
381 				for (int searchCompareIndex = compareIndex + 1; searchCompareIndex < nextCompareList.size(); searchCompareIndex++) {
382 					String find = nextCompareList.get(searchCompareIndex);
383 					int foundAt = subList.indexOf(find);
384 					if (foundAt != -1) {
385 						foundAt += (baseIndex);
386 						toAdd = nextCompareList.subList(compareIndex, searchCompareIndex);
387 						break;
388 					}
389 				}
390 
391 				int addAtIndex = baseIndex;
392 
393 				if (toAdd == null) {
394 
395 					toAdd = nextCompareList.subList(compareIndex, nextCompareList.size());
396 					addAtIndex = baseList.size();
397 
398 					int foundInBaseAtIndex = subList.indexOf(currentCompare);
399 					if (foundInBaseAtIndex != -1) {
400 						baseIndex += foundInBaseAtIndex;
401 						compareIndex++;
402 						continue;
403 					}
404 
405 				} else {
406 
407 					int foundInBaseAtIndex = subList.indexOf(currentCompare);
408 					if (foundInBaseAtIndex != -1) {
409 						baseIndex += foundInBaseAtIndex;
410 						// compareIndex++;
411 						continue;
412 					}
413 
414 				}
415 
416 				baseList.addAll(addAtIndex, toAdd);
417 				baseIndex += toAdd.size();
418 				compareIndex += toAdd.size();
419 
420 				// if (toAdd.size() == 0) {
421 				// break;
422 				// }
423 			}
424 			getLog().debug("Base list is now: "+ baseList);
425 
426 		}
427 
428 		getLog().debug("Merged name list: "+ baseList);
429 		return baseList;
430 	}
431 
432 	public static void main(String[] args) throws MojoExecutionException, MojoFailureException {
433 		
434 		SuperStructureMojo m = new SuperStructureMojo();
435 		m.structures = new ArrayList<String>();
436 		m.structures.add("ADT_A[0-9]{2}");
437 
438 		m.targetDirectory = "target/merge";
439 		m.targetStructureName = "ADT_AXX";
440 		m.version = "2.3.1";
441 		m.execute();
442 	}
443 
444 }