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