
001package org.hl7.fhir.r5.profilemodel.gen; 002 003import java.io.IOException; 004import java.text.SimpleDateFormat; 005import java.util.ArrayList; 006import java.util.Date; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Locale; 010import java.util.Set; 011import java.util.TimeZone; 012 013import org.hl7.fhir.r5.context.IWorkerContext; 014import org.hl7.fhir.r5.model.DataType; 015import org.hl7.fhir.r5.model.ElementDefinition; 016import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 017import org.hl7.fhir.r5.model.StructureDefinition; 018import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 019import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 020import org.hl7.fhir.r5.profilemodel.PEBuilder; 021import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy; 022import org.hl7.fhir.r5.profilemodel.PEDefinition; 023import org.hl7.fhir.r5.profilemodel.PEType; 024import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 025import org.hl7.fhir.r5.utils.UserDataNames; 026import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 027import org.hl7.fhir.utilities.FileUtilities; 028import org.hl7.fhir.utilities.Utilities; 029 030/** 031 * 032 * The easiest way to generate code is to use the FHIR Validator, which can generate java classes for profiles 033 * using this code. Parameters: 034 * 035 * -codegen -version r4 -ig hl7.fhir.dk.core#3.2.0 -profiles http://hl7.dk/fhir/core/StructureDefinition/dk-core-gln-identifier,http://hl7.dk/fhir/core/StructureDefinition/dk-core-patient -output /Users/grahamegrieve/temp/codegen -package-name org.hl7.fhir.test 036 * 037 * Parameter Documentation: 038 * -codegen: tells the validator to generate code 039 * -version {r4|5}: which version to generate for 040 * -ig {name}: loads an IG (and it's dependencies) - see -ig documentation for the validator 041 * -profiles {list}: a comma separated list of profile URLs to generate code for 042 * -output {folder}: the folder where to generate the output java class source code 043 * -package-name {name}: the name of the java package to generate in 044 * 045 * options 046 * -option {name}: a code generation option, one of: 047 * 048 * narrative: generate code for the resource narrative (recommended: don't - leave that for the native resource level) 049 * meta: generate code the what's in meta 050 * contained: generate code for contained resources 051 * all-elements: generate code for all elements, not just the key elements (makes the code verbose) 052 */ 053 054public class PECodeGenerator { 055 056 057 public static final String DEFAULT_DATE() { 058 SimpleDateFormat sdf = new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")); 059 sdf.setTimeZone(TimeZone.getTimeZone("UTC")); 060 return sdf.format(new Date()); 061 } 062 063 064 public enum ExtensionPolicy { 065 None, Complexes, Primitives; 066 } 067 068 private class PEGenClass { 069 private String name; 070 private String base; 071 private String doco; 072 private String url; 073 private boolean isResource; 074 private Set<String> unfixed = new HashSet<>(); 075 private Set<String> enumNames = new HashSet<>(); 076 077 private StringBuilder inits = new StringBuilder(); 078 private StringBuilder fields = new StringBuilder(); 079 private StringBuilder enums = new StringBuilder(); 080 private StringBuilder load = new StringBuilder(); 081 private StringBuilder save = new StringBuilder(); 082 private StringBuilder clear = new StringBuilder(); 083 private StringBuilder copy = new StringBuilder(); 084 private StringBuilder accessors = new StringBuilder(); 085 private StringBuilder hash = new StringBuilder(); 086 public void genId() { 087 if (isResource) { 088 genField(true, "id", "String", "id", "", false, "", 0, 1, null); 089 genAccessors(true, false, "id", "id", "String", "", "String", "String", "Id", "Ids", false, "", false, false, null); 090 genLoad(true, false, "id", "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, null, false); 091 genSave(true, false, "id", "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, false, null, false); 092 genClear(false, "id", "String"); 093 } 094 } 095 public void write(StringBuilder b, String copyright) { 096 w(b); 097 if (copyright != null) { 098 w(b, "/*"); 099 w(b, copyright); 100 w(b, " */"); 101 w(b); 102 } 103 w(b, "// Generated by the HAPI Java Profile Generator, "+genDate); 104 w(b); 105 jdoc(b, doco, 0, true); 106 w(b, "public class "+name+" extends PEGeneratedBase {"); 107 w(b); 108 if (url != null) { 109 w(b, " public static final String CANONICAL_URL = \""+url+"\";"); 110 w(b); 111 } 112 if (enums.length() > 0) { 113 w(b, enums.toString()); 114 } 115 w(b, fields.toString()); 116 if (unfixed.isEmpty()) { 117 jdoc(b, "Parameter-less constructor.", 2, true); 118 } else { 119 jdoc(b, "Parameter-less constructor. If you use this, the fixed values on "+CommaSeparatedStringBuilder.join(",", unfixed)+" won't be filled out - they'll be missing. They'll be filled in if/when you call build, so they won't be missing from the resource, only from this particular object model", 2, true); 120 } 121 w(b, " public "+name+"() {"); 122 if (inits.length() > 0) { 123 w(b, " initFixedValues();"); 124 } 125 w(b, " }"); 126 w(b); 127 if (isResource) { 128 jdoc(b, "Construct an instance of the object, and fill out all the fixed values ", 2, true); 129 w(b, " public "+name+"(IWorkerContext context) {"); 130 if (inits.length() > 0) { 131 w(b, " initFixedValues();"); 132 } 133 w(b, " workerContext = context;"); 134 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 135 w(b, " PEInstance src = builder.buildPEInstance(CANONICAL_URL, builder.createResource(CANONICAL_URL, false));"); 136 w(b, " load(src);"); 137 w(b, " }"); 138 w(b); 139 jdoc(b, "Populate an instance of the object based on this source object ", 2, true); 140 w(b, " public static "+name+" fromSource(IWorkerContext context, "+base+" source) {"); 141 w(b, " "+name+" theThing = new "+name+"();"); 142 w(b, " theThing.workerContext = context;"); 143 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 144 w(b, " PEInstance src = builder.buildPEInstance(CANONICAL_URL, source);"); 145 w(b, " theThing.load(src);"); 146 w(b, " return theThing;"); 147 w(b, " }"); 148 w(b); 149 } else { 150 jdoc(b, "Used when loading other models ", 2, true); 151 w(b, " public static "+name+" fromSource(PEInstance source) {"); 152 w(b, " "+name+" theThing = new "+name+"();"); 153 w(b, " theThing.workerContext = source.getContext();"); 154 w(b, " theThing.load(source);"); 155 w(b, " return theThing;"); 156 w(b, " }"); 157 } 158 w(b); 159 w(b, " public void load(PEInstance src) {"); 160 w(b, " clear();"); 161 w(b, load.toString()); 162 w(b, " }"); 163 w(b); 164 165 if (isResource) { 166 jdoc(b, "Build a instance of the underlying object based on this wrapping object ", 2, true); 167 w(b, " public "+base+" build(IWorkerContext context) {"); 168 w(b, " workerContext = context;"); 169 w(b, " return build();"); 170 w(b, " }"); 171 w(b); 172 jdoc(b, "Build a instance of the underlying object based on this wrapping object ", 2, true); 173 w(b, " public "+base+" build() {"); 174 w(b, " "+base+" theThing = new "+base+"();"); 175 w(b, " PEBuilder builder = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true);"); 176 w(b, " PEInstance tgt = builder.buildPEInstance(CANONICAL_URL, theThing);"); 177 w(b, " save(tgt, false);"); 178 w(b, " return theThing;"); 179 w(b, " }"); 180 w(b); 181 jdoc(b, "Save this profile class into an existing resource (overwriting anything that exists in the profile) ", 2, true); 182 w(b, " public void save(IWorkerContext context, "+base+" dest, boolean nulls) {"); 183 w(b, " workerContext = context;"); 184 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 185 w(b, " PEInstance tgt = builder.buildPEInstance(CANONICAL_URL, dest);"); 186 w(b, " save(tgt, nulls);"); 187 w(b, " }"); 188 w(b); 189 } 190 w(b, " public void save(PEInstance tgt, boolean nulls) {"); 191 w(b, save.toString()); 192 w(b, " }"); 193 w(b); 194 if (inits.length() > 0) { 195 w(b, " private void initFixedValues() {"); 196 w(b, inits.toString()); 197 w(b, " }"); 198 w(b); 199 } 200 w(b, accessors.toString()); 201 w(b); 202 w(b, " public void clear() {"); 203 w(b, clear.toString()); 204 w(b, " }"); 205 w(b); 206 w(b, "}"); 207 } 208 209 private String generateEnum(PEDefinition source, PEDefinition field) { 210 if (field.definition().hasBinding() && !field.hasFixedValue()) { 211 ElementDefinitionBindingComponent binding = field.definition().getBinding(); 212 if (binding.getStrength() == org.hl7.fhir.r5.model.Enumerations.BindingStrength.REQUIRED && binding.hasValueSet()) { 213 org.hl7.fhir.r5.model.ValueSet vs = workerContext.fetchResource(org.hl7.fhir.r5.model.ValueSet.class, binding.getValueSet(), field.getProfile()); 214 if (vs != null) { 215 ValueSetExpansionOutcome vse = workerContext.expandVS(vs, false, false); 216 Set<String> codes = new HashSet<>(); 217 boolean hasDups = false; 218 if (vse.isOk()) { 219 String baseName = Utilities.nmtokenize(Utilities.singularise(vs.getName())); 220 String name = baseName; 221 if (workerContext.getResourceNames().contains(name)) { 222 name = name+"Type"; 223 } 224 int c = 0; 225 while (enumNames.contains(name)) { 226 c++; 227 name = baseName+c; 228 } 229 w(enums, " public enum "+name+" {"); 230 for (int i = 0; i < vse.getValueset().getExpansion().getContains().size(); i++) { 231 ValueSetExpansionContainsComponent cc = vse.getValueset().getExpansion().getContains().get(i); 232 String code = Utilities.javaTokenize(cc.getCode(), true).toUpperCase(); 233 if (Utilities.isInteger(code)) { 234 code = "C_"+code; 235 } 236 if (cc.getAbstract()) { 237 code = "_"+code; 238 } 239 if (codes.contains(code)) { 240 char sfx = 'A'; 241 while (codes.contains(code+sfx)) { 242 sfx++; 243 } 244 code = code + sfx; 245 hasDups = true; 246 } 247 codes.add(code); 248 cc.setUserData(UserDataNames.java_code, code); 249 w(enums, " "+code+(i < vse.getValueset().getExpansion().getContains().size() - 1 ? "," : ";")+" // \""+cc.getDisplay()+"\" = "+cc.getSystem()+"#"+cc.getCode()); 250 } 251 w(enums, ""); 252 if (!hasDups) { 253 w(enums, " public static "+name+" fromCode(String s) {"); 254 w(enums, " switch (s) {"); 255 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 256 w(enums, " case \""+cc.getCode()+"\": return "+cc.getUserString(UserDataNames.java_code)+";"); 257 } 258 w(enums, " default: return null;"); 259 w(enums, " }"); 260 w(enums, " }"); 261 w(enums, ""); 262 } 263 w(enums, " public static "+name+" fromCoding(Coding c) {"); 264 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 265 if (cc.hasVersion()) { 266 w(enums, " if (\""+cc.getSystem()+"\".equals(c.getSystem()) && \""+cc.getCode()+"\".equals(c.getCode()) && (!c.hasVersion() || \""+cc.getVersion()+"\".equals(c.getVersion()))) {"); 267 } else { 268 w(enums, " if (\""+cc.getSystem()+"\".equals(c.getSystem()) && \""+cc.getCode()+"\".equals(c.getCode())) {"); 269 } 270 w(enums, " return "+cc.getUserString(UserDataNames.java_code)+";"); 271 w(enums, " }"); 272 } 273 w(enums, " return null;"); 274 w(enums, " }"); 275 w(enums, ""); 276 w(enums, " public static "+name+" fromCodeableConcept(CodeableConcept cc) {"); 277 w(enums, " for (Coding c : cc.getCoding()) {"); 278 w(enums, " "+name+" v = fromCoding(c);"); 279 w(enums, " if (v != null) {"); 280 w(enums, " return v;"); 281 w(enums, " }"); 282 w(enums, " }"); 283 w(enums, " return null;"); 284 w(enums, " }"); 285 w(enums, ""); 286 287 w(enums, " public String toDisplay() {"); 288 w(enums, " switch (this) {"); 289 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 290 w(enums, " case "+cc.getUserString(UserDataNames.java_code)+": return \""+Utilities.escapeJava(cc.getDisplay())+"\";"); 291 } 292 w(enums, " default: return null;"); 293 w(enums, " }"); 294 w(enums, " }"); 295 w(enums, ""); 296 297 if (!hasDups) { 298 w(enums, " public String toCode() {"); 299 w(enums, " switch (this) {"); 300 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 301 w(enums, " case "+cc.getUserString(UserDataNames.java_code)+": return \""+cc.getCode()+"\";"); 302 } 303 w(enums, " default: return null;"); 304 w(enums, " }"); 305 w(enums, " }"); 306 w(enums, ""); 307 } 308 w(enums, " public Coding toCoding() {"); 309 w(enums, " switch (this) {"); 310 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 311 if (cc.hasVersion()) { 312 w(enums, " case "+cc.getUserString(UserDataNames.java_code)+": return new Coding().setSystem(\""+cc.getSystem()+"\").setVersion(\""+cc.getVersion()+"\").setCode()\""+cc.getCode()+"\";"); 313 } else { 314 w(enums, " case "+cc.getUserString(UserDataNames.java_code)+": return new Coding().setSystem(\""+cc.getSystem()+"\").setCode(\""+cc.getCode()+"\");"); 315 } 316 } 317 w(enums, " default: return null;"); 318 w(enums, " }"); 319 w(enums, " }"); 320 w(enums, ""); 321 w(enums, " public CodeableConcept toCodeableConcept() {"); 322 w(enums, " Coding c = toCoding();"); 323 w(enums, " return c == null ? null : new CodeableConcept().addCoding(c);"); 324 w(enums, " }"); 325 w(enums, " }"); 326 return name; 327 } 328 } 329 } 330 } 331 return null; 332 } 333 334 private void defineField(PEDefinition source, PEDefinition field) { 335 if (field.types().size() == 1) { 336 StructureDefinition sd = workerContext.fetchTypeDefinition(field.types().get(0).getUrl()); 337 if (sd != null) { 338 String enumName = generateEnum(source, field); 339 boolean isPrim = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 340 boolean isAbstract = sd.getAbstract(); 341 String name = Utilities.javaTokenize(field.name().replace("[x]", ""), false); 342 String sname = name; 343 String type = null; 344 String init = ""; 345 String ptype = type; 346 boolean isEnum = false; 347 if (enumName != null) { 348 type = enumName; 349 ptype = enumName; 350 isEnum = true; 351 } else if (isPrim) { 352 // todo: are we extension-less? 353 type = Utilities.capitalize(field.types().get(0).getName()+"Type"); 354 ptype = getPrimitiveType(sd); 355 } else { 356 type = Utilities.javaTokenize(field.types().get(0).getName(), true); 357 } 358 String ltype = type; 359 if (field.isList()) { 360 ltype = "List<"+type+">"; 361 init = "new ArrayList<>()"; 362 if (!Utilities.existsInList(name, "contained")) { 363 name = Utilities.pluralize(name, 2); 364 } 365 } 366 String cname = Utilities.capitalize(name); 367 String csname = Utilities.capitalize(sname); 368 String nn = field.min() == 1 ? "// @NotNull" : ""; 369 boolean isExtension = field.isExtension(); 370 genField(isPrim, name, ptype, ltype, nn, field.isList(), field.shortDocumentation(), field.min(), field.max(), field.definition()); 371 if (isPrim && field.hasFixedValue()) { 372 genFixed(name, ptype, field.getFixedValue()); 373 } 374 genAccessors(isPrim, isAbstract, name, field.name(), type, init, ptype, ltype, cname, csname, field.isList(), field.documentation(), field.hasFixedValue(), isEnum, field.definition()); 375 genLoad(isPrim, isAbstract, name, sname, field.name(), type, init, ptype, ltype, cname, csname, field.isList(), field.hasFixedValue(), field.types().get(0), isEnum); 376 genSave(isPrim, isAbstract, name, sname, field.name(), type, init, ptype, ltype, cname, csname, field.isList(), field.hasFixedValue(), isExtension, field.types().get(0), isEnum); 377 genClear(field.isList(), name, ptype); 378 } 379 } else { 380 // ignoring polymorphics for now 381 } 382 } 383 384 private void genClear(boolean list, String name, String ptype) { 385 if (list) { 386 w(clear, " "+name+".clear();"); 387 } else if ("boolean".equals(ptype)) { 388 w(clear, " "+name+" = false;"); 389 } else if ("int".equals(ptype)) { 390 w(clear, " "+name+" = 0;"); 391 } else { 392 w(clear, " "+name+" = null;"); 393 } 394 } 395 396 private void genLoad(boolean isPrim, boolean isAbstract, String name, String sname, String fname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, PEType typeInfo, boolean isEnum) { 397 if (isList) { 398 w(load, " for (PEInstance item : src.children(\""+fname+"\")) {"); 399 if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 400 w(load, " "+name+".add("+type+".fromSource(src.child(\""+fname+"\")));"); 401 402 } else if ("BackboneElement".equals(type)) { 403 w(load, " "+name+".add(("+type+") item.asElement());"); 404 } else if (isEnum) { 405 if ("CodeableConcept".equals(typeInfo.getName())) { 406 w(load, " "+name+".add("+type+".fromCodeableConcept((CodeableConcept) item.asDataType()));"); 407 } else if ("Coding".equals(typeInfo.getName())) { 408 w(load, " "+name+".add("+type+".fromCoding((Coding) item.asDataType()));"); 409 } else { 410 w(load, " "+name+".add("+type+".fromCode(item.asDataType().primitiveValue()));"); 411 } 412 } else { 413 w(load, " "+name+".add(("+type+") item.asDataType());"); 414 } 415 w(load, " }"); 416 } else if (isEnum) { 417 w(load, " if (src.hasChild(\""+fname+"\")) {"); 418 if ("CodeableConcept".equals(typeInfo.getName())) { 419 w(load, " "+name+" = "+type+".fromCodeableConcept((CodeableConcept) src.child(\""+fname+"\").asDataType());"); 420 } else if ("Coding".equals(typeInfo.getName())) { 421 w(load, " "+name+" = "+type+".fromCoding((Coding) src.child(\""+fname+"\").asDataType());"); 422 } else { 423 w(load, " "+name+" = "+type+".fromCode(src.child(\""+fname+"\").asDataType().primitiveValue());"); 424 } 425 w(load, " }"); 426 } else if (isPrim) { 427 w(load, " if (src.hasChild(\""+fname+"\")) {"); 428 if ("CodeType".equals(type)) { 429 // might be code or enum 430 w(load, " "+name+" = src.child(\""+fname+"\").asDataType().primitiveValue();"); 431 } else { 432 w(load, " "+name+" = (("+type+") src.child(\""+fname+"\").asDataType()).getValue();"); 433 } 434 w(load, " }"); 435 } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 436 w(load, " if (src.hasChild(\""+fname+"\")) {"); 437 w(load, " "+name+" = "+type+".fromSource(src.child(\""+fname+"\"));"); 438 w(load, " }"); 439 } else { 440 w(load, " if (src.hasChild(\""+fname+"\")) {"); 441 if ("BackboneElement".equals(type)) { 442 w(load, " "+name+" = ("+type+") src.child(\""+fname+"\").asElement();"); 443 } else if (Utilities.existsInList(type, workerContext.getResourceNames())) { 444 w(load, " "+name+" = ("+type+") src.child(\""+fname+"\").asResource();"); 445 } else if("Reference".equals(type)) { 446 w(load, " "+type+" ref = ("+type+") src.child(\""+fname+"\").asDataType();"); 447 w(load, " if(!ref.isEmpty())"); 448 w(load, " "+name+" = ref;"); 449 } else { 450 w(load, " "+name+" = ("+type+") src.child(\""+fname+"\").asDataType();"); 451 } 452 w(load, " }"); 453 } 454 } 455 456 private void genSave(boolean isPrim, boolean isAbstract, String name, String sname, String fname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, boolean isExtension, PEType typeInfo, boolean isEnum) { 457 w(save, " tgt.clear(\""+fname+"\");"); 458 if (isList) { 459 w(save, " for ("+type+" item : "+name+") {"); 460 if (isExtension) { 461 if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 462 w(save, " item.save(tgt.makeChild(\""+fname+"\"), false);"); 463 } else { 464 w(save, " tgt.makeChild(\""+fname+"\").data().setProperty(\"value[x]\", item);"); 465 } 466 } else if (isEnum) { 467 if ("CodeableConcept".equals(typeInfo.getName())) { 468 w(save, " tgt.addChild(\""+fname+"\", item.toCodeableConcept());"); 469 } else if ("Coding".equals(typeInfo.getName())) { 470 w(save, " tgt.addChild(\""+fname+"\", item.toCoding());"); 471 } else { 472 w(save, " tgt.addChild(\""+fname+"\", item.toCode());"); 473 } 474 } else { 475 w(save, " tgt.addChild(\""+fname+"\", item);"); 476 } 477 w(save, " }"); 478 } else if (isEnum) { 479 w(save, " if ("+name+" != null) {"); 480 if ("CodeableConcept".equals(typeInfo.getName())) { 481 w(save, " tgt.addChild(\""+fname+"\", "+name+".toCodeableConcept());"); 482 } else if ("Coding".equals(typeInfo.getName())) { 483 w(save, " tgt.addChild(\""+fname+"\", "+name+".toCoding());"); 484 } else { 485 w(save, " tgt.addChild(\""+fname+"\", "+name+".toCode());"); 486 } 487 w(save, " }"); 488 } else if (isPrim) { 489 if ("boolean".equals(ptype)) { 490 w(save, " if (true) { // for now, at least"); 491 } else if ("int".equals(ptype)) { 492 w(save, " if ("+name+" != 0) {"); 493 } else { 494 w(save, " if ("+name+" != null) {"); 495 } 496 if (isExtension) { 497 w(save, " tgt.makeChild(\""+fname+"\").data().setProperty(\"value[x]\", new "+type+"("+name+"));"); 498 } else if (Utilities.existsInList(type, "DateType", "InstantType", "DateTimeType")) { 499 w(save, " tgt.addChild(\""+fname+"\", new "+type+"("+name+"));"); 500 } else { 501 w(save, " tgt.makeChild(\""+fname+"\").data().setProperty(\"value\", new "+type+"("+name+"));"); 502 } 503 w(save, " }"); 504 } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 505 w(save, " if ("+name+" != null) {"); 506 w(save, " "+name+".save(tgt.makeChild(\""+fname+"\"), nulls);"); 507 w(save, " }"); 508 } else if (isExtension) { 509 w(save, " if ("+name+" != null) {"); 510 w(save, " tgt.makeChild(\""+fname+"\").data().setProperty(\"value[x]\", "+name+");"); 511 w(save, " }"); 512 } else { 513 w(save, " if ("+name+" != null) {"); 514 w(save, " tgt.addChild(\""+fname+"\", "+name+");"); 515 w(save, " }"); 516 } 517 } 518 519 private void genAccessors(boolean isPrim, boolean isAbstract, String name, String fname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, String shortDoco, boolean isFixed, boolean isEnum, ElementDefinition ed) { 520 if (ed != null) { 521 jdoc(accessors, ed.getDefinition(), 2, true); 522 } 523 if ((isEnum || isPrim) && extensionPolicy != ExtensionPolicy.Primitives && !isList) { 524 w(accessors, " public "+ptype+" get"+cname+"() {"); 525 w(accessors, " return "+name+";"); 526 w(accessors, " }"); 527 w(accessors); 528 if (isFixed) { 529 w(accessors, " public boolean has"+cname+"() {"); 530 w(accessors, " return true;"); 531 w(accessors, " }"); 532 } else { 533 w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); 534 w(accessors, " this."+name+" = value;"); 535 w(accessors, " return this;"); 536 w(accessors, " }"); 537 w(accessors); 538 w(accessors, " public boolean has"+cname+"() {"); 539 if ("boolean".equals(ptype)) { 540 w(accessors, " return true; // not "+name+" != false ?"); 541 } else if ("int".equals(ptype)) { 542 w(accessors, " return "+name+" != 0;"); 543 } else { 544 w(accessors, " return "+name+" != null;"); 545 } 546 w(accessors, " }"); 547 } 548 } else { 549 if (isPrim && !isList) { 550 w(accessors, " public "+ptype+" get"+cname+"() {"); 551 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 552 w(accessors, " return "+name+".getValue();"); 553 w(accessors, " }"); 554 w(accessors, " public "+ltype+" get"+cname+"Element() {"); 555 } else if (isAbstract && !isList) { 556 w(accessors, " public @Nullable "+ltype+" get"+cname+"() { // "+ltype+" is abstract "); 557 } else { 558 w(accessors, " public "+ltype+" get"+cname+"() {"); 559 } 560 if (isList) { 561 w(accessors, " if ("+name+" == null) { "+name+" = "+init+"; }"); 562 } else if (!isAbstract) { 563 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 564 } 565 w(accessors, " return "+name+";"); 566 w(accessors, " }"); 567 w(accessors); 568 if (isList) { 569 w(accessors, " public boolean has"+cname+"() {"); 570 w(accessors, " return "+name+" != null && !"+name+".isEmpty();"); 571 w(accessors, " }"); 572 w(accessors); 573 if (!isAbstract) { 574 if (!isEnum) { 575 w(accessors, " public "+type+" add"+csname+"() {"); 576 w(accessors, " "+type+" theThing = new "+type+"();"); 577 w(accessors, " get"+cname+"().add(theThing);"); 578 w(accessors, " return theThing;"); 579 w(accessors, " }"); 580 w(accessors); 581 } else { 582 w(accessors, " public void add"+csname+"("+type+" theThing) {"); 583 w(accessors, " get"+cname+"().add(theThing);"); 584 w(accessors, " }"); 585 w(accessors); 586 } 587 } 588 w(accessors, " public boolean has"+csname+"("+type+" item) {"); 589 w(accessors, " return has"+cname+"() && "+name+".contains(item);"); 590 w(accessors, " }"); 591 w(accessors); 592 w(accessors, " public void remove"+csname+"("+type+" item) {"); 593 w(accessors, " if (has"+csname+"(item)) {"); 594 w(accessors, " "+name+".remove(item);"); 595 w(accessors, " }"); 596 w(accessors, " }"); 597 w(accessors); 598 } else if (isPrim) { 599 if (!isFixed) { 600 w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); 601 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 602 w(accessors, " "+name+".setValue(value);"); 603 w(accessors, " return this;"); 604 w(accessors, " }"); 605 w(accessors, " public "+this.name+" set"+cname+"Element("+type+" value) {"); 606 w(accessors, " this."+name+" = value;"); 607 w(accessors, " return this;"); 608 w(accessors, " }"); 609 } 610 w(accessors, " public boolean has"+cname+"() {"); 611 w(accessors, " return "+name+" != null && "+name+".hasValue();"); 612 w(accessors, " }"); 613 w(accessors); 614 } else { 615 if (!isFixed) { 616 w(accessors, " public "+this.name+" set"+cname+"("+type+" value) {"); 617 w(accessors, " this."+name+" = value;"); 618 w(accessors, " return this;"); 619 w(accessors, " }"); 620 } 621 w(accessors, " public boolean has"+cname+"() {"); 622 w(accessors, " return "+name+" != null;"); 623 w(accessors, " }"); 624 } 625 } 626 w(accessors); 627 } 628 629 private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco, int min, int max, ElementDefinition ed) { 630// jdoc(fields, shortDoco, 2, true); 631 w(fields, " @Min(\""+min+"\") @Max(\""+(max == Integer.MAX_VALUE ? "*" : max) +"\")"+(" @Doco(\""+Utilities.escapeJava(shortDoco)+"\")")); 632 if (ed != null) { 633 if (ed.hasBinding() && ed.getBinding().hasValueSet()) { 634 w(fields, " @BindingStrength(\""+ed.getBinding().getStrength().toCode()+"\") @ValueSet(\""+ed.getBinding().getValueSet()+"\")"); 635 } 636 if (ed.getMustSupport()) { 637 w(fields, " @MustSupport(true)"); 638 } 639 if (ed.hasLabel() || ed.hasDefinition()) { 640 String s = ""; 641 if (ed.hasLabel()) { 642 s = s + " @Label(\""+Utilities.escapeJava(ed.getLabel())+"\")"; 643 } 644 if (ed.hasDefinition()) { 645 s = s + " @Definition(\""+Utilities.escapeJava(ed.getDefinition())+"\")"; 646 } 647 w(fields, " "+s); 648 } 649 } 650 if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) { 651 w(fields, " private "+ptype+" "+name+";"+nn+" // "+shortDoco); 652 } else if (isList) { 653 w(fields, " private "+ltype+" "+name+" = new ArrayList<>();"+nn+" // "+shortDoco); 654 } else { 655 w(fields, " private "+ltype+" "+name+";"+nn+" // "+shortDoco); 656 } 657 w(fields, ""); 658 } 659 660 661 private void genFixed(String name, String pType, DataType fixedValue) { 662 if ("String".equals(pType)) { 663 w(inits, " "+name+" = \""+Utilities.escapeJava(fixedValue.primitiveValue())+"\";"); 664 } else { 665 unfixed.add(name); 666 System.out.println("Unable to handle the fixed value for "+name+" of type "+pType+" = "+fixedValue.toString()); 667 } 668 } 669 } 670 671 private String folder; 672 private IWorkerContext workerContext; 673 private String canonical; 674 private String pkgName; 675 private String version = "r5"; 676 677 // options: 678 private ExtensionPolicy extensionPolicy; 679 private boolean narrative; 680 private boolean contained; 681 private boolean meta; 682 private String language; 683 private boolean keyElementsOnly; 684 private String genDate = DEFAULT_DATE(); 685 686 687 public PECodeGenerator(IWorkerContext workerContext) { 688 super(); 689 this.workerContext = workerContext; 690 } 691 692 public String getFolder() { 693 return folder; 694 } 695 696 697 public void setFolder(String folder) { 698 this.folder = folder; 699 } 700 701 702 public String getVersion() { 703 return version; 704 } 705 706 public void setVersion(String version) { 707 this.version = version; 708 } 709 710 public String getCanonical() { 711 return canonical; 712 } 713 714 public void setCanonical(String canonical) { 715 this.canonical = canonical; 716 } 717 718 719 public String getPkgName() { 720 return pkgName; 721 } 722 723 public void setPkgName(String pkgName) { 724 this.pkgName = pkgName; 725 } 726 727 public ExtensionPolicy getExtensionPolicy() { 728 return extensionPolicy; 729 } 730 731 public void setExtensionPolicy(ExtensionPolicy extensionPolicy) { 732 this.extensionPolicy = extensionPolicy; 733 } 734 735 public boolean isNarrative() { 736 return narrative; 737 } 738 739 public void setNarrative(boolean narrative) { 740 this.narrative = narrative; 741 } 742 743 public boolean isMeta() { 744 return meta; 745 } 746 747 public void setMeta(boolean meta) { 748 this.meta = meta; 749 } 750 751 public String getLanguage() { 752 return language; 753 } 754 755 public void setLanguage(String language) { 756 this.language = language; 757 } 758 759 public boolean isKeyElementsOnly() { 760 return keyElementsOnly; 761 } 762 763 public void setKeyElementsOnly(boolean keyElementsOnly) { 764 this.keyElementsOnly = keyElementsOnly; 765 } 766 767 public boolean isContained() { 768 return contained; 769 } 770 771 public void setContained(boolean contained) { 772 this.contained = contained; 773 } 774 775 public String getGenDate() { 776 return genDate; 777 } 778 779 public void setGenDate(String genDate) { 780 this.genDate = genDate; 781 } 782 783 private StringBuilder imports = new StringBuilder(); 784 785 /** 786 * @throws IOException 787 * 788 */ 789 public String execute() throws IOException { 790 imports = new StringBuilder(); 791 792 PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical); 793 w(imports, "import java.util.List;"); 794 w(imports, "import java.util.ArrayList;"); 795 w(imports, "import java.util.Date;\r\n"); 796 w(imports, "import java.math.BigDecimal;"); 797 w(imports, "import javax.annotation.Nullable;"); 798 w(imports); 799 w(imports, "import org.hl7.fhir."+version+".context.IWorkerContext;"); 800 w(imports, "import org.hl7.fhir."+version+".model.*;"); 801 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder;"); 802 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEInstance;"); 803 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder.PEElementPropertiesPolicy;"); 804 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.PEGeneratedBase;"); 805 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Min;"); 806 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Max;"); 807 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Label;"); 808 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Doco;"); 809 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.BindingStrength;"); 810 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.ValueSet;"); 811 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.MustSupport;"); 812 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Definition;"); 813 814 815 PEGenClass cls = genClass(source); 816 StringBuilder b = new StringBuilder(); 817 w(b, "package "+pkgName+";"); 818 w(b); 819 if (source.getProfile().hasCopyright()) { 820 jdoc(b, source.getProfile().getCopyright(), 0, false); 821 } 822 w(b, imports.toString()); 823 cls.write(b, source.getProfile().getCopyright()); 824 FileUtilities.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java")); 825 return cls.name+".java"; 826 } 827 828 public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) { 829 if (!Utilities.noString(doco)) { 830 String pfx = Utilities.padLeft("", ' ', indent); 831 w(b, pfx+"/*"+(jdoc ? "*" : "")); 832 for (String line : doco.split("\\R")) { 833 for (String nl : naturalLines(line)) 834 w(b, pfx+" * "+nl); 835 w(b, pfx+" *"); 836 } 837 w(b, pfx+" */"); 838 } 839 } 840 841 private List<String> naturalLines(String line) { 842 List<String> lines = new ArrayList<>(); 843 while (line.length() > 80) { 844 int cutpoint = 80; 845 while (cutpoint > 0 && line.charAt(cutpoint) != ' ') { 846 cutpoint--; 847 } 848 if (cutpoint == 0) { 849 cutpoint = 80; 850 } else { 851 cutpoint++; 852 } 853 lines.add(line.substring(0, cutpoint)); 854 line = line.substring(cutpoint); 855 } 856 lines.add(line); 857 return lines; 858 } 859 860 private void w(StringBuilder b) { 861 b.append("\r\n"); 862 863 } 864 865 private void w(StringBuilder b, String line) { 866 b.append(line); 867 w(b); 868 } 869 870 private PEGenClass genClass(PEDefinition source) { 871 PEGenClass cls = new PEGenClass(); 872 cls.name = Utilities.javaTokenize(source.getProfile().getName(), true); 873 cls.base = source.getProfile().getType(); 874 cls.doco = source.documentation(); 875 cls.url = source.getProfile().getVersionedUrl(); 876 cls.isResource = source.getProfile().getKind() == StructureDefinitionKind.RESOURCE; 877 cls.genId(); 878 for (PEDefinition child : source.children()) { 879 if (genForField(source, child)) { 880 cls.defineField(source, child); 881 } 882 } 883 return cls; 884 } 885 886 private boolean genForField(PEDefinition source, PEDefinition child) { 887 if (child.definition().getBase().getPath().equals("Resource.meta")) { 888 return meta; 889 } 890 if (child.definition().getBase().getPath().equals("DomainResource.text")) { 891 return narrative; 892 } 893 if (child.definition().getBase().getPath().equals("Resource.language")) { 894 return language == null; 895 } 896 if (child.definition().getBase().getPath().endsWith(".extension") || child.definition().getBase().getPath().endsWith(".modifierExtension")) { 897 return extensionPolicy == ExtensionPolicy.Complexes; 898 } 899 if (child.definition().getBase().getPath().equals("DomainResource.contained")) { 900 return contained; 901 } 902 return !keyElementsOnly || (child.isKeyElement()); 903 } 904 905 906 private String getPrimitiveType(StructureDefinition sd) { 907 908 if (sd.getType().equals("string")) 909 return "String"; 910 if (sd.getType().equals("code")) 911 return "String"; 912 if (sd.getType().equals("markdown")) 913 return "String"; 914 if (sd.getType().equals("base64Binary")) 915 return "byte[]"; 916 if (sd.getType().equals("uri")) 917 return "String"; 918 if (sd.getType().equals("url")) 919 return "String"; 920 if (sd.getType().equals("canonical")) 921 return "String"; 922 if (sd.getType().equals("oid")) 923 return "String"; 924 if (sd.getType().equals("integer")) 925 return "int"; 926 if (sd.getType().equals("integer64")) 927 return "long"; 928 if (sd.getType().equals("unsignedInt")) 929 return "int"; 930 if (sd.getType().equals("positiveInt")) 931 return "int"; 932 if (sd.getType().equals("boolean")) 933 return "boolean"; 934 if (sd.getType().equals("decimal")) 935 return "BigDecimal"; 936 if (sd.getType().equals("dateTime")) 937 return "Date"; 938 if (sd.getType().equals("date")) 939 return "Date"; 940 if (sd.getType().equals("id")) 941 return "String"; 942 if (sd.getType().equals("instant")) 943 return "Date"; 944 if (sd.getType().equals("time")) 945 return "String"; 946 947 return "??"; 948 } 949 950}