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