package ca.uhn.fhir.tinder; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.apache.maven.plugin.MojoFailureException; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.tools.generic.EscapeTool; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.dstu.resource.ValueSet; import ca.uhn.fhir.model.dstu.resource.ValueSet.ComposeInclude; import ca.uhn.fhir.model.dstu.resource.ValueSet.Define; import ca.uhn.fhir.model.dstu.resource.ValueSet.DefineConcept; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystem; import ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept; import ca.uhn.fhir.model.dstu2.resource.ValueSet.ComposeIncludeConcept; import ca.uhn.fhir.model.primitive.CodeDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.tinder.TinderStructuresMojo.ValueSetFileDefinition; import ca.uhn.fhir.tinder.model.BaseRootType; import ca.uhn.fhir.tinder.model.ValueSetTm; import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingSpreadsheet; import ca.uhn.fhir.tinder.parser.TargetType; public class ValueSetGenerator { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValueSetGenerator.class); private int myConceptCount; private Set<ValueSetTm> myMarkedValueSets = new HashSet<ValueSetTm>(); private List<ValueSetFileDefinition> myResourceValueSetFiles; private int myValueSetCount; private Map<String, ValueSetTm> myValueSets = new HashMap<String, ValueSetTm>(); private String myVersion; private String myFilenamePrefix = ""; private String myFilenameSuffix = ""; private String myTemplate = null; private File myTemplateFile = null; private String myVelocityPath = null; private String myVelocityProperties = null; public ValueSetGenerator(String theVersion) { myVersion = theVersion; } private void addDefinedConcept(ValueSetTm vs, String system, CodeSystemConcept nextConcept) { String nextCodeValue = nextConcept.getCode(); String nextCodeDisplay = StringUtils.defaultString(nextConcept.getDisplay()); String nextCodeDefinition = StringUtils.defaultString(nextConcept.getDefinition()); vs.addConcept(system, nextCodeValue, nextCodeDisplay, nextCodeDefinition); for (CodeSystemConcept nextChild : nextConcept.getConcept()) { addDefinedConcept(vs, system, nextChild); } } private void addDefinedConcept(ValueSetTm vs, String system, DefineConcept nextConcept) { String nextCodeValue = nextConcept.getCode().getValue(); String nextCodeDisplay = StringUtils.defaultString(nextConcept.getDisplay().getValue()); String nextCodeDefinition = StringUtils.defaultString(nextConcept.getDefinition().getValue()); vs.addConcept(system, nextCodeValue, nextCodeDisplay, nextCodeDefinition); for (DefineConcept nextChild : nextConcept.getConcept()) { addDefinedConcept(vs, system, nextChild); } } public String getClassForValueSetIdAndMarkAsNeeded(String theId) { ValueSetTm vs = myValueSets.get(theId); if (vs == null) { return null; } else { myMarkedValueSets.add(vs); return vs.getClassName(); } } public Map<String, ValueSetTm> getValueSets() { return myValueSets; } public void parse() throws FileNotFoundException, IOException { FhirContext ctx = "dstu".equals(myVersion) ? FhirContext.forDstu1() : FhirContext.forDstu2(); IParser newXmlParser = ctx.newXmlParser(); newXmlParser.setParserErrorHandler(new LenientErrorHandler(false)); ourLog.info("Parsing built-in ValueSets"); String version = myVersion; if (version.equals("dev")) { version = "dstu2"; } String name = "/vs/" + version + "/all-valuesets-bundle.xml"; if (version.equals("dstu2")) { name = "/org/hl7/fhir/instance/model/valueset/valuesets.xml"; } if (version.equals("dstu3")) { name = "/org/hl7/fhir/instance/model/dstu3/valueset/valuesets.xml"; } ourLog.info("Loading valuesets from: {}", name); InputStream is = ValueSetGenerator.class.getResourceAsStream(name); if (null == is) { ourLog.error("Failed loading valuesets from: " + name); throw new FileNotFoundException(name); } String vs = IOUtils.toString(is, Charset.defaultCharset()); if ("dstu".equals(myVersion)) { Bundle bundle = newXmlParser.parseBundle(vs); for (BundleEntry next : bundle.getEntries()) { ValueSet nextVs = (ValueSet) next.getResource(); parseValueSet(nextVs); } } else { ca.uhn.fhir.model.dstu2.resource.Bundle bundle = newXmlParser.parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, vs); for (Entry nextEntry : bundle.getEntry()) { ca.uhn.fhir.model.dstu2.resource.ValueSet nextVs = (ca.uhn.fhir.model.dstu2.resource.ValueSet) nextEntry.getResource(); parseValueSet(nextVs); } } if (myResourceValueSetFiles != null) { for (ValueSetFileDefinition next : myResourceValueSetFiles) { File file = new File(next.getValueSetFile()); ourLog.info("Parsing ValueSet file: {}" + file.getName()); vs = IOUtils.toString(new FileReader(file)); ValueSetTm tm; if ("dstu".equals(myVersion)) { ValueSet nextVs = (ValueSet) newXmlParser.parseResource(ValueSet.class, vs); tm = parseValueSet(nextVs); } else { ca.uhn.fhir.model.dstu2.resource.ValueSet nextVs = (ca.uhn.fhir.model.dstu2.resource.ValueSet) newXmlParser.parseResource(ca.uhn.fhir.model.dstu2.resource.ValueSet.class, vs); tm = parseValueSet(nextVs); } if (tm != null) { myMarkedValueSets.add(tm); } } } /* * Purge empty valuesets */ for (Iterator<java.util.Map.Entry<String, ValueSetTm>> iter = myValueSets.entrySet().iterator(); iter.hasNext(); ) { java.util.Map.Entry<String, ValueSetTm> next = iter.next(); if (next.getValue().getCodes().isEmpty()) { iter.remove(); continue; } } // File[] files = new // File(myResourceValueSetFiles).listFiles((FilenameFilter) new // WildcardFileFilter("*.xml")); // for (File file : files) { // ourLog.info("Parsing ValueSet file: {}" + file.getName()); // vs = IOUtils.toString(new FileReader(file)); // ValueSet nextVs = (ValueSet) newXmlParser.parseResource(vs); // parseValueSet(nextVs); // } } private ValueSetTm parseValueSet(ca.uhn.fhir.model.dstu2.resource.ValueSet nextVs) { myConceptCount += nextVs.getCodeSystem().getConcept().size(); ourLog.debug("Parsing ValueSetTm #{} - {} - {} concepts total", myValueSetCount++, nextVs.getName(), myConceptCount); // output.addConcept(next.getCode().getValue(), // next.getDisplay().getValue(), next.getDefinition()); ValueSetTm vs = new ValueSetTm(); vs.setName(nextVs.getName()); vs.setDescription(nextVs.getDescription()); vs.setId(StringUtils.defaultString(nextVs.getIdentifier().getValue())); vs.setClassName(toClassName(nextVs.getName())); { CodeSystem define = nextVs.getCodeSystem(); String system = define.getSystemElement().getValueAsString(); for (CodeSystemConcept nextConcept : define.getConcept()) { addDefinedConcept(vs, system, nextConcept); } } for (ca.uhn.fhir.model.dstu2.resource.ValueSet.ComposeInclude nextInclude : nextVs.getCompose().getInclude()) { String system = nextInclude.getSystemElement().getValueAsString(); for (ComposeIncludeConcept nextConcept : nextInclude.getConcept()) { String nextCodeValue = nextConcept.getCode(); vs.addConcept(system, nextCodeValue, null, null); } } // if (vs.getCodes().isEmpty()) { // ourLog.info("ValueSet " + nextVs.getName() + " has no codes, not going to generate any code for it"); // return null; // } if (myValueSets.containsKey(vs.getName())) { ourLog.warn("Duplicate Name: " + vs.getName()); } else { myValueSets.put(vs.getName(), vs); } // This is hackish, but deals with "Administrative Gender Codes" vs "AdministrativeGender" if (vs.getName().endsWith(" Codes")) { myValueSets.put(vs.getName().substring(0, vs.getName().length() - 6).replace(" ", ""), vs); } myValueSets.put(vs.getName().replace(" ", ""), vs); return vs; } private ValueSetTm parseValueSet(ValueSet nextVs) { myConceptCount += nextVs.getDefine().getConcept().size(); ourLog.debug("Parsing ValueSetTm #{} - {} - {} concepts total", myValueSetCount++, nextVs.getName().getValue(), myConceptCount); // output.addConcept(next.getCode().getValue(), // next.getDisplay().getValue(), next.getDefinition()); ValueSetTm vs = new ValueSetTm(); vs.setName(nextVs.getName().getValue()); vs.setDescription(nextVs.getDescription().getValue()); vs.setId(nextVs.getIdentifier().getValue()); vs.setClassName(toClassName(nextVs.getName().getValue())); { Define define = nextVs.getDefine(); String system = define.getSystem().getValueAsString(); for (DefineConcept nextConcept : define.getConcept()) { addDefinedConcept(vs, system, nextConcept); } } for (ComposeInclude nextInclude : nextVs.getCompose().getInclude()) { String system = nextInclude.getSystem().getValueAsString(); for (CodeDt nextConcept : nextInclude.getCode()) { String nextCodeValue = nextConcept.getValue(); vs.addConcept(system, nextCodeValue, null, null); } } // if (vs.getCodes().isEmpty()) { // ourLog.info("ValueSet " + nextVs.getName() + " has no codes, not going to generate any code for it"); // return null; // } if (myValueSets.containsKey(vs.getName())) { ourLog.warn("Duplicate Name: " + vs.getName()); } else { myValueSets.put(vs.getName(), vs); } // This is hackish, but deals with "Administrative Gender Codes" vs "AdministrativeGender" if (vs.getName().endsWith(" Codes")) { myValueSets.put(vs.getName().substring(0, vs.getName().length() - 6).replace(" ", ""), vs); } myValueSets.put(vs.getName().replace(" ", ""), vs); return vs; } public void setResourceValueSetFiles(List<ValueSetFileDefinition> theResourceValueSetFiles) { myResourceValueSetFiles = theResourceValueSetFiles; } private String toClassName(String theValue) { StringBuilder b = new StringBuilder(); for (String next : theValue.split("\\s+")) { next = next.trim(); if (StringUtils.isBlank(next)) { continue; } if (next.startsWith("(") && next.endsWith(")")) { continue; } next = next.replace("/", ""); next = next.replace("-", ""); next = next.replace(',', '_'); next = next.replace('.', '_'); if (next.contains(" ")) { next = WordUtils.capitalizeFully(next); } b.append(next); } b.append("Enum"); return b.toString(); } public void setFilenamePrefix(String theFilenamePrefix) { myFilenamePrefix = theFilenamePrefix; } public void setFilenameSuffix(String theFilenameSuffix) { myFilenameSuffix = theFilenameSuffix; } public void setTemplate(String theTemplate) { myTemplate = theTemplate; } public void setTemplateFile (File theTemplateFile) { myTemplateFile = theTemplateFile; } public void setVelocityPath(String theVelocityPath) { myVelocityPath = theVelocityPath; } public void setVelocityProperties(String theVelocityProperties) { myVelocityProperties = theVelocityProperties; } public void write(Collection<ValueSetTm> theValueSets, File theOutputDirectory, String thePackageBase) throws IOException { write(TargetType.SOURCE, theValueSets, theOutputDirectory, thePackageBase); } public void write(TargetType theTarget, Collection<ValueSetTm> theValueSets, File theOutputDirectory, String thePackageBase) throws IOException { for (ValueSetTm nextValueSetTm : theValueSets) { write(theTarget, nextValueSetTm, theOutputDirectory, thePackageBase); } } // private void setValueSetName(String theString) { // myValueSetName = theString; // } private void write(TargetType theTarget, ValueSetTm theValueSetTm, File theOutputDirectory, String thePackageBase) throws IOException { if (!theOutputDirectory.exists()) { theOutputDirectory.mkdirs(); } if (!theOutputDirectory.isDirectory()) { throw new IOException(theOutputDirectory + " is not a directory"); } String valueSetName = theValueSetTm.getClassName(); String prefix = myFilenamePrefix; String suffix = myFilenameSuffix; if (theTarget == TargetType.SOURCE) { if (!suffix.endsWith(".java")) { suffix += ".java"; } } String fileName = prefix + valueSetName + suffix; File f = new File(theOutputDirectory, fileName); OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(f, false), "UTF-8"); ourLog.debug("Writing file: {}", f.getAbsolutePath()); VelocityContext ctx = new VelocityContext(); InputStream templateIs = null; ctx.put("valueSet", theValueSetTm); ctx.put("packageBase", thePackageBase); ctx.put("esc", new EscapeTool()); VelocityEngine v = VelocityHelper.configureVelocityEngine(myTemplateFile, myVelocityPath, myVelocityProperties); if (myTemplateFile != null) { templateIs = new FileInputStream(myTemplateFile); } else { String templateName = myTemplate; if (null == templateName) { templateName = "/vm/valueset.vm"; } templateIs = this.getClass().getResourceAsStream(templateName); } InputStreamReader templateReader = new InputStreamReader(templateIs, "UTF-8"); v.evaluate(ctx, w, "", templateReader); w.close(); } public void writeMarkedValueSets(File theOutputDirectory, String thePackageBase) throws MojoFailureException { writeMarkedValueSets(TargetType.SOURCE, theOutputDirectory, thePackageBase); } public void writeMarkedValueSets(TargetType theTarget, File theOutputDirectory, String thePackageBase) throws MojoFailureException { try { write(theTarget, myMarkedValueSets, theOutputDirectory, thePackageBase); } catch (IOException e) { throw new MojoFailureException("Failed to write valueset", e); } } public static void main(String[] args) throws FileNotFoundException, IOException { ValueSetGenerator p = new ValueSetGenerator("dstu1"); p.parse(); } }