001package org.hl7.fhir.r5.profilemodel.gen; 002 003import java.io.File; 004import java.io.IOException; 005import java.text.SimpleDateFormat; 006import java.util.List; 007import java.util.Locale; 008import java.util.ArrayList; 009import java.util.Date; 010 011import org.hl7.fhir.r5.context.IWorkerContext; 012import org.hl7.fhir.r5.model.CodeableConcept; 013import org.hl7.fhir.r5.model.Identifier; 014import org.hl7.fhir.r5.model.Observation; 015import org.hl7.fhir.r5.model.StructureDefinition; 016import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 017import org.hl7.fhir.r5.profilemodel.PEBuilder; 018import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy; 019import org.hl7.fhir.r5.profilemodel.gen.PECodeGenerator.ExtensionPolicy; 020import org.hl7.fhir.r5.profilemodel.PEDefinition; 021import org.hl7.fhir.r5.profilemodel.PEInstance; 022import org.hl7.fhir.r5.profilemodel.PEType; 023import org.hl7.fhir.utilities.TextFile; 024import org.hl7.fhir.utilities.Utilities; 025 026public class PECodeGenerator { 027 028 public enum ExtensionPolicy { 029 None, Complexes, Primitives; 030 } 031 public static final SimpleDateFormat DATE_FORMAT() { 032 return new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")); 033 } 034 private class PEGenClass { 035 private String name; 036 private String base; 037 private String doco; 038 private String url; 039 private boolean isResource; 040 private StringBuilder fields = new StringBuilder(); 041 private StringBuilder load = new StringBuilder(); 042 private StringBuilder save = new StringBuilder(); 043 private StringBuilder clear = new StringBuilder(); 044 private StringBuilder copy = new StringBuilder(); 045 private StringBuilder accessors = new StringBuilder(); 046 private StringBuilder hash = new StringBuilder(); 047 048 public void genId() { 049 if (isResource) { 050 genField(true, "id", "String", "id", "", false, ""); 051 genAccessors(true, false, "id", "String", "", "String", "String", "Id", "Ids", false, "", false); 052 genLoad(true, false, "id", "IdType", "", "String", "String", "Id", "Ids", false, false, null); 053 genClear(false, "id"); 054 } 055 } 056 public void write(StringBuilder b) { 057 w(b); 058 w(b, "// Generated by the HAPI Java Profile Generator, "+DATE_FORMAT().format(new Date())); 059 w(b); 060 jdoc(b, doco, 0, true); 061 w(b, "public class "+name+" extends PEGeneratedBase {"); 062 w(b); 063 if (url != null) { 064 w(b, " private static final String CANONICAL_URL = \""+url+"\";"); 065 w(b); 066 } 067 w(b, fields.toString()); 068 jdoc(b, "Parameter-less constructor. If you use this, the fixed values 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); 069 w(b, " public "+name+"() {"); 070 w(b, " // todo"); 071 w(b, " }"); 072 w(b); 073 if (isResource) { 074 jdoc(b, "Construct an instance of the object, and fill out all the fixed values ", 2, true); 075 w(b, " public "+name+"(IWorkerContext context) {"); 076 w(b, " workerContext = context;"); 077 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 078 w(b, " PEInstance src = builder.buildPEInstance(CANONICAL_URL, builder.createResource(CANONICAL_URL, false));"); 079 w(b, " load(src);"); 080 w(b, " }"); 081 w(b); 082 jdoc(b, "Populate an instance of the object based on this source object ", 2, true); 083 w(b, " public static "+name+" fromSource(IWorkerContext context, "+base+" source) {"); 084 w(b, " "+name+" theThing = new "+name+"();"); 085 w(b, " theThing.workerContext = context;"); 086 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 087 w(b, " PEInstance src = builder.buildPEInstance(CANONICAL_URL, source);"); 088 w(b, " theThing.load(src);"); 089 w(b, " return theThing;"); 090 w(b, " }"); 091 w(b); 092 } else { 093 jdoc(b, "Used when loading other models ", 2, true); 094 w(b, " public static "+name+" fromSource(PEInstance source) {"); 095 w(b, " "+name+" theThing = new "+name+"();"); 096 w(b, " theThing.workerContext = source.getContext();"); 097 w(b, " theThing.load(source);"); 098 w(b, " return theThing;"); 099 w(b, " }"); 100 } 101 w(b); 102 w(b, " public void load(PEInstance src) {"); 103 w(b, " clear();"); 104 w(b, load.toString()); 105 w(b, " }"); 106 w(b); 107 108 if (isResource) { 109 jdoc(b, "Build an instance of the object based on this source object ", 2, true); 110 w(b, " public "+base+" build(IWorkerContext context) {"); 111 w(b, " workerContext = context;"); 112 w(b, " "+base+" theThing = new "+base+"();"); 113 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 114 w(b, " PEInstance tgt = builder.buildPEInstance(CANONICAL_URL, theThing);"); 115 w(b, " save(tgt, false);"); 116 w(b, " return theThing;"); 117 w(b, " }"); 118 w(b); 119 jdoc(b, "Save this profile class into an existing resource (overwriting enything that exists in the profile) ", 2, true); 120 w(b, " public void save(IWorkerContext context, "+base+" dest, boolean nulls) {"); 121 w(b, " workerContext = context;"); 122 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 123 w(b, " PEInstance tgt = builder.buildPEInstance(CANONICAL_URL, dest);"); 124 w(b, " save(tgt, nulls);"); 125 w(b, " }"); 126 w(b); 127 } 128 w(b, " public void save(PEInstance tgt, boolean nulls) {"); 129 w(b, save.toString()); 130 w(b, " }"); 131 w(b); 132 w(b, accessors.toString()); 133 w(b); 134 w(b, " public void clear() {"); 135 w(b, clear.toString()); 136 w(b, " }"); 137 w(b); 138 w(b, "}"); 139 } 140 141 private void defineField(PEDefinition source, PEDefinition field) { 142 if (field.types().size() == 1) { 143 StructureDefinition sd = workerContext.fetchTypeDefinition(field.types().get(0).getUrl()); 144 if (sd != null) { 145 boolean isPrim = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 146 boolean isAbstract = sd.getAbstract(); 147 String name = field.name().replace("[x]", ""); 148 String sname = name; 149 String type = null; 150 String init = ""; 151 String ptype = type; 152 if (isPrim) { 153 // todo: are we extension-less? 154 type = Utilities.capitalize(field.types().get(0).getName()+"Type"); 155 ptype = getPrimitiveType(sd); 156 } else { 157 type = field.types().get(0).getName(); 158 } 159 String ltype = type; 160 if (field.isList()) { 161 ltype = "List<"+type+">"; 162 init = "new ArrayList<>()"; 163 if (!Utilities.existsInList(name, "contained")) { 164 name = Utilities.pluralize(name, 2); 165 } 166 } 167 String cname = Utilities.capitalize(name); 168 String csname = Utilities.capitalize(sname); 169 String nn = field.min() == 1 ? "// @NotNull" : ""; 170 genField(isPrim, name, ptype, ltype, nn, field.isList(), field.shortDocumentation()); 171 genAccessors(isPrim, isAbstract, name, type, init, ptype, ltype, cname, csname, field.isList(), field.documentation(), field.fixedValue()); 172 genLoad(isPrim, isAbstract, name, type, init, ptype, ltype, cname, csname, field.isList(), field.fixedValue(), field.types().get(0)); 173 genClear(field.isList(), name); 174 } 175 } else { 176 // ignoring polymorphics for now 177 } 178 179 } 180 181 private void genClear(boolean list, String name) { 182 w(clear, " "+name+" = null;"); 183 } 184 private void genLoad(boolean isPrim, boolean isAbstract, String name, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, PEType typeInfo) { 185 if (isList) { 186 w(load, " for (PEInstance item : src.children(\""+name+"\")) {"); 187 w(load, " "+name+".add(("+type+") item.asDataType());"); 188 w(load, " }"); 189 } else if (isPrim) { 190 w(load, " if (src.hasChild(\""+name+"\")) {"); 191 w(load, " "+name+" = (("+type+") src.child(\""+name+"\").asDataType()).getValue();"); 192 w(load, " }"); 193 } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 194 w(load, " if (src.hasChild(\""+name+"\")) {"); 195 w(load, " "+name+" = "+type+".fromSource(src.child(\""+name+"\"));"); 196 w(load, " }"); 197 } else { 198 w(load, " if (src.hasChild(\""+name+"\")) {"); 199 w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asDataType();"); 200 w(load, " }"); 201 } 202 } 203 204 private void genAccessors(boolean isPrim, boolean isAbstract, String name, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, String shortDoco, boolean isFixed) { 205 jdoc(accessors, doco, 2, true); 206 if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) { 207 w(accessors, " public "+ptype+" get"+cname+"() {"); 208 w(accessors, " return "+name+";"); 209 w(accessors, " }"); 210 w(accessors); 211 w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); 212 w(accessors, " this."+name+" = value;"); 213 w(accessors, " return this;"); 214 w(accessors, " }"); 215 w(accessors); 216 w(accessors, " public boolean has"+cname+"() {"); 217 w(accessors, " return "+name+" != null;"); 218 w(accessors, " }"); 219 } else { 220 if (isPrim && !isList) { 221 w(accessors, " public "+ptype+" get"+cname+"() {"); 222 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 223 w(accessors, " return "+name+".getValue();"); 224 w(accessors, " }"); 225 w(accessors, " public "+ltype+" get"+cname+"Element() {"); 226 } else if (isAbstract && !isList) { 227 w(accessors, " public @Nullable "+ltype+" get"+cname+"() { // "+ltype+" is abstract "); 228 } else { 229 w(accessors, " public "+ltype+" get"+cname+"() {"); 230 } 231 if (isList) { 232 w(accessors, " if ("+name+" == null) { "+name+" = "+init+"; }"); 233 } else if (!isAbstract) { 234 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 235 } 236 w(accessors, " return "+name+";"); 237 w(accessors, " }"); 238 w(accessors); 239 if (isList) { 240 w(accessors, " public boolean has"+cname+"() {"); 241 w(accessors, " return "+name+" != null && !"+name+".isEmpty();"); 242 w(accessors, " }"); 243 w(accessors); 244 if (!isAbstract) { 245 w(accessors, " public "+type+" add"+csname+"() {"); 246 w(accessors, " "+type+" theThing = new "+type+"();"); 247 w(accessors, " get"+cname+"().add(theThing);"); 248 w(accessors, " return theThing;"); 249 w(accessors, " }"); 250 w(accessors); 251 } 252 w(accessors, " public boolean has"+csname+"("+type+" item) {"); 253 w(accessors, " return has"+cname+"() && "+name+".contains(item);"); 254 w(accessors, " }"); 255 w(accessors); 256 w(accessors, " public void remove"+csname+"("+type+" item) {"); 257 w(accessors, " if (has"+csname+"(item)) {"); 258 w(accessors, " "+name+".remove(item);"); 259 w(accessors, " }"); 260 w(accessors, " }"); 261 w(accessors); 262 } else if (isPrim) { 263 if (!isFixed) { 264 w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); 265 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 266 w(accessors, " "+name+".setValue(value);"); 267 w(accessors, " return this;"); 268 w(accessors, " }"); 269 w(accessors, " public "+this.name+" set"+cname+"Element("+type+" value) {"); 270 w(accessors, " this."+name+" = value;"); 271 w(accessors, " return this;"); 272 w(accessors, " }"); 273 } 274 w(accessors, " public boolean has"+cname+"() {"); 275 w(accessors, " return "+name+" != null && "+name+".hasValue();"); 276 w(accessors, " }"); 277 w(accessors); 278 } else { 279 if (!isFixed) { 280 w(accessors, " public "+this.name+" set"+cname+"("+type+" value) {"); 281 w(accessors, " this."+name+" = value;"); 282 w(accessors, " return this;"); 283 w(accessors, " }"); 284 } 285 w(accessors, " public boolean has"+cname+"() {"); 286 w(accessors, " return "+name+" != null;"); 287 w(accessors, " }"); 288 } 289 } 290 w(accessors); 291 } 292 293 private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco) { 294 // jdoc(fields, field.documentation(), 2, true); 295 if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) { 296 w(fields, " private "+ptype+" "+name+";"+nn+" // "+shortDoco); 297 } else { 298 w(fields, " private "+ltype+" "+name+";"+nn+" // "+shortDoco); 299 } 300 } 301 } 302 303 private String folder; 304 private IWorkerContext workerContext; 305 private String canonical; 306 private String pkgName; 307 308 // options: 309 private ExtensionPolicy extensionPolicy; 310 private boolean narrative; 311 private boolean contained; 312 private boolean meta; 313 private String language; 314 private boolean keyELementsOnly; 315 316 public PECodeGenerator(IWorkerContext workerContext) { 317 super(); 318 this.workerContext = workerContext; 319 } 320 321 public String getFolder() { 322 return folder; 323 } 324 325 326 public void setFolder(String folder) { 327 this.folder = folder; 328 } 329 330 331 public String getCanonical() { 332 return canonical; 333 } 334 335 public void setCanonical(String canonical) { 336 this.canonical = canonical; 337 } 338 339 340 public String getPkgName() { 341 return pkgName; 342 } 343 344 public void setPkgName(String pkgName) { 345 this.pkgName = pkgName; 346 } 347 348 public ExtensionPolicy getExtensionPolicy() { 349 return extensionPolicy; 350 } 351 352 public void setExtensionPolicy(ExtensionPolicy extensionPolicy) { 353 this.extensionPolicy = extensionPolicy; 354 } 355 356 public boolean isNarrative() { 357 return narrative; 358 } 359 360 public void setNarrative(boolean narrative) { 361 this.narrative = narrative; 362 } 363 364 public boolean isMeta() { 365 return meta; 366 } 367 368 public void setMeta(boolean meta) { 369 this.meta = meta; 370 } 371 372 public String getLanguage() { 373 return language; 374 } 375 376 public void setLanguage(String language) { 377 this.language = language; 378 } 379 380 public boolean isKeyELementsOnly() { 381 return keyELementsOnly; 382 } 383 384 public void setKeyELementsOnly(boolean keyELementsOnly) { 385 this.keyELementsOnly = keyELementsOnly; 386 } 387 388 public boolean isContained() { 389 return contained; 390 } 391 392 public void setContained(boolean contained) { 393 this.contained = contained; 394 } 395 396 private StringBuilder imports = new StringBuilder(); 397 398 /** 399 * @throws IOException 400 * 401 */ 402 public void execute() throws IOException { 403 PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical); 404 w(imports, "import java.util.List;"); 405 w(imports, "import java.util.ArrayList;"); 406 w(imports, "import javax.annotation.Nullable;"); 407 w(imports, "import java.util.Date;\r\n"); 408 w(imports); 409 w(imports, "import org.hl7.fhir.r5.context.IWorkerContext;"); 410 w(imports, "import org.hl7.fhir.r5.model.*;"); 411 w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder;"); 412 w(imports, "import org.hl7.fhir.r5.profilemodel.PEInstance;"); 413 w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;"); 414 w(imports, "import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;"); 415 PEGenClass cls = genClass(source); 416 StringBuilder b = new StringBuilder(); 417 w(b, "package "+pkgName+";"); 418 w(b); 419 if (source.getProfile().hasCopyright()) { 420 jdoc(b, source.getProfile().getCopyright(), 0, false); 421 } 422 w(b, imports.toString()); 423 cls.write(b); 424 TextFile.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java")); 425 } 426 427 public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) { 428 if (!Utilities.noString(doco)) { 429 String pfx = Utilities.padLeft("", ' ', indent); 430 w(b, pfx+"/*"+(jdoc ? "*" : "")); 431 for (String line : doco.split("\\R")) { 432 for (String nl : naturalLines(line)) 433 w(b, pfx+" * "+nl); 434 w(b, pfx+" *"); 435 } 436 w(b, pfx+" */"); 437 } 438 } 439 440 private List<String> naturalLines(String line) { 441 List<String> lines = new ArrayList<>(); 442 while (line.length() > 80) { 443 int cutpoint = 80; 444 while (cutpoint > 0 && line.charAt(cutpoint) != ' ') { 445 cutpoint--; 446 } 447 if (cutpoint == 0) { 448 cutpoint = 80; 449 } else { 450 cutpoint++; 451 } 452 lines.add(line.substring(0, cutpoint)); 453 line = line.substring(cutpoint); 454 } 455 lines.add(line); 456 return lines; 457 } 458 459 private void w(StringBuilder b) { 460 b.append("\r\n"); 461 462 } 463 464 private void w(StringBuilder b, String line) { 465 b.append(line); 466 w(b); 467 } 468 469 private PEGenClass genClass(PEDefinition source) { 470 PEGenClass cls = new PEGenClass(); 471 cls.name = source.getProfile().getName(); 472 cls.base = source.getProfile().getType(); 473 cls.doco = source.documentation(); 474 cls.url = source.getProfile().getVersionedUrl(); 475 cls.isResource = source.getProfile().getKind() == StructureDefinitionKind.RESOURCE; 476 cls.genId(); 477 for (PEDefinition child : source.children()) { 478 if (genForField(source, child)) { 479 cls.defineField(source, child); 480 } 481 } 482 return cls; 483 } 484 485 private boolean genForField(PEDefinition source, PEDefinition child) { 486 if (child.definition().getBase().getPath().equals("Resource.meta")) { 487 return meta; 488 } 489 if (child.definition().getBase().getPath().equals("DomainResource.text")) { 490 return narrative; 491 } 492 if (child.definition().getBase().getPath().equals("Resource.language")) { 493 return language == null; 494 } 495 if (child.definition().getBase().getPath().endsWith(".extension") || child.definition().getBase().getPath().endsWith(".modifierExtension")) { 496 return extensionPolicy == ExtensionPolicy.Complexes; 497 } 498 if (child.definition().getBase().getPath().equals("DomainResource.contained")) { 499 return contained; 500 } 501 return !keyELementsOnly || (child.isKeyElement()); 502 } 503 504 505 private String getPrimitiveType(StructureDefinition sd) { 506 507 if (sd.getType().equals("string")) 508 return "String"; 509 if (sd.getType().equals("code")) 510 return "String"; 511 if (sd.getType().equals("markdown")) 512 return "String"; 513 if (sd.getType().equals("base64Binary")) 514 return "byte[]"; 515 if (sd.getType().equals("uri")) 516 return "String"; 517 if (sd.getType().equals("url")) 518 return "String"; 519 if (sd.getType().equals("canonical")) 520 return "String"; 521 if (sd.getType().equals("oid")) 522 return "String"; 523 if (sd.getType().equals("integer")) 524 return "int"; 525 if (sd.getType().equals("integer64")) 526 return "long"; 527 if (sd.getType().equals("unsignedInt")) 528 return "int"; 529 if (sd.getType().equals("positiveInt")) 530 return "int"; 531 if (sd.getType().equals("boolean")) 532 return "boolean"; 533 if (sd.getType().equals("decimal")) 534 return "BigDecimal"; 535 if (sd.getType().equals("dateTime")) 536 return "Date"; 537 if (sd.getType().equals("date")) 538 return "Date"; 539 if (sd.getType().equals("id")) 540 return "String"; 541 if (sd.getType().equals("instant")) 542 return "Date"; 543 if (sd.getType().equals("time")) 544 return "String"; 545 546 return "??"; 547 } 548 549}