
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@SuppressWarnings("checkstyle:systemout") 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 if(base.equalsIgnoreCase("List")) 168 w(b, " public static "+name+" fromSource(IWorkerContext context, "+base+"Resource source) {"); 169 else 170 w(b, " public static "+name+" fromSource(IWorkerContext context, "+base+" source) {"); 171 w(b, " "+name+" theThing = new "+name+"();"); 172 w(b, " theThing.workerContext = context;"); 173 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 174 w(b, " PEInstance src = builder.buildPEInstance(CANONICAL_URL, source);"); 175 w(b, " theThing.load(src);"); 176 w(b, " return theThing;"); 177 w(b, " }"); 178 w(b); 179 } else { 180 jdoc(b, "Used when loading other models ", 2, true); 181 w(b, " public static "+name+" fromSource(PEInstance source) {"); 182 w(b, " "+name+" theThing = new "+name+"();"); 183 w(b, " theThing.workerContext = source.getContext();"); 184 w(b, " theThing.load(source);"); 185 w(b, " return theThing;"); 186 w(b, " }"); 187 } 188 w(b); 189 w(b, " public void load(PEInstance src) {"); 190 w(b, " clear();"); 191 w(b, load.toString()); 192 w(b, " }"); 193 w(b); 194 195 if (isResource) { 196 jdoc(b, "Build a instance of the underlying object based on this wrapping object ", 2, true); 197 if(base.equalsIgnoreCase("List")) 198 w(b, " public "+base+"Resource build(IWorkerContext context) {"); 199 else 200 w(b, " public "+base+" build(IWorkerContext context) {"); 201 w(b, " workerContext = context;"); 202 w(b, " return build();"); 203 w(b, " }"); 204 w(b); 205 jdoc(b, "Build a instance of the underlying object based on this wrapping object ", 2, true); 206 if(base.equalsIgnoreCase("List")) 207 w(b, " public "+base+"Resource build() {"); 208 else 209 w(b, " public "+base+" build() {"); 210 if(base.equalsIgnoreCase("List")) 211 w(b, " "+base+"Resource theThing = new "+base+"Resource();"); 212 else 213 w(b, " "+base+" theThing = new "+base+"();"); 214 w(b, " PEBuilder builder = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true);"); 215 w(b, " PEInstance tgt = builder.buildPEInstance(CANONICAL_URL, theThing);"); 216 w(b, " save(tgt, false);"); 217 w(b, " return theThing;"); 218 w(b, " }"); 219 w(b); 220 jdoc(b, "Save this profile class into an existing resource (overwriting anything that exists in the profile) ", 2, true); 221 if(base.equalsIgnoreCase("List")) 222 w(b, " public void save(IWorkerContext context, "+base+"Resource dest, boolean nulls) {"); 223 else 224 w(b, " public void save(IWorkerContext context, "+base+" dest, boolean nulls) {"); 225 w(b, " workerContext = context;"); 226 w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); 227 w(b, " PEInstance tgt = builder.buildPEInstance(CANONICAL_URL, dest);"); 228 w(b, " save(tgt, nulls);"); 229 w(b, " }"); 230 w(b); 231 } 232 w(b, " public void save(PEInstance tgt, boolean nulls) {"); 233 w(b, save.toString()); 234 w(b, " }"); 235 w(b); 236 if (inits.length() > 0) { 237 w(b, " private void initFixedValues() {"); 238 w(b, inits.toString()); 239 w(b, " }"); 240 w(b); 241 } 242 w(b, accessors.toString()); 243 w(b); 244 w(b, " public void clear() {"); 245 w(b, clear.toString()); 246 w(b, " }"); 247 w(b); 248 w(b, "}"); 249 } 250 251 private String generateEnum(PEDefinition source, PEDefinition field) { 252 if (field.definition().hasBinding() && !field.hasFixedValue()) { 253 ElementDefinitionBindingComponent binding = field.definition().getBinding(); 254 if (binding.getStrength() == org.hl7.fhir.r4.model.Enumerations.BindingStrength.REQUIRED && binding.hasValueSet()) { 255 org.hl7.fhir.r4.model.ValueSet vs = workerContext.fetchResource(org.hl7.fhir.r4.model.ValueSet.class, binding.getValueSet()); 256 if (vs != null) { 257 ValueSetExpansionOutcome vse = workerContext.expandVS(vs, false, false); 258 Set<String> codes = new HashSet<>(); 259 boolean hasDups = false; 260 if (vse.isOk()) { 261 String baseName = Utilities.nmtokenize(Utilities.singularise(vs.getName())); 262 String name = baseName; 263 if (workerContext.getResourceNames().contains(name)) { 264 name = name+"Type"; 265 } 266 int c = 0; 267 while (enumNames.contains(name)) { 268 c++; 269 name = baseName+c; 270 } 271 w(enums, " public enum "+name+" {"); 272 for (int i = 0; i < vse.getValueset().getExpansion().getContains().size(); i++) { 273 ValueSetExpansionContainsComponent cc = vse.getValueset().getExpansion().getContains().get(i); 274 String code = Utilities.javaTokenize(cc.getCode(), true).toUpperCase(); 275 if (Utilities.isInteger(code)) { 276 code = "C_"+code; 277 } 278 if (cc.getAbstract()) { 279 code = "_"+code; 280 } 281 if (codes.contains(code)) { 282 char sfx = 'A'; 283 while (codes.contains(code+sfx)) { 284 sfx++; 285 } 286 code = code + sfx; 287 hasDups = true; 288 } 289 codes.add(code); 290 cc.setUserData("java.code", code); 291 w(enums, " "+code+(i < vse.getValueset().getExpansion().getContains().size() - 1 ? "," : ";")+" // \""+cc.getDisplay()+"\" = "+cc.getSystem()+"#"+cc.getCode()); 292 } 293 w(enums, ""); 294 if (!hasDups) { 295 w(enums, " public static "+name+" fromCode(String s) {"); 296 w(enums, " switch (s) {"); 297 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 298 w(enums, " case \""+cc.getCode()+"\": return "+cc.getUserString("java.code")+";"); 299 } 300 w(enums, " default: return null;"); 301 w(enums, " }"); 302 w(enums, " }"); 303 w(enums, ""); 304 } 305 w(enums, " public static "+name+" fromCoding(Coding c) {"); 306 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 307 if (cc.hasVersion()) { 308 w(enums, " if (\""+cc.getSystem()+"\".equals(c.getSystem()) && \""+cc.getCode()+"\".equals(c.getCode()) && (!c.hasVersion() || \""+cc.getVersion()+"\".equals(c.getVersion()))) {"); 309 } else { 310 w(enums, " if (\""+cc.getSystem()+"\".equals(c.getSystem()) && \""+cc.getCode()+"\".equals(c.getCode())) {"); 311 } 312 w(enums, " return "+cc.getUserString("java.code")+";"); 313 w(enums, " }"); 314 } 315 w(enums, " return null;"); 316 w(enums, " }"); 317 w(enums, ""); 318 w(enums, " public static "+name+" fromCodeableConcept(CodeableConcept cc) {"); 319 w(enums, " for (Coding c : cc.getCoding()) {"); 320 w(enums, " "+name+" v = fromCoding(c);"); 321 w(enums, " if (v != null) {"); 322 w(enums, " return v;"); 323 w(enums, " }"); 324 w(enums, " }"); 325 w(enums, " return null;"); 326 w(enums, " }"); 327 w(enums, ""); 328 329 w(enums, " public String toDisplay() {"); 330 w(enums, " switch (this) {"); 331 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 332 w(enums, " case "+cc.getUserString("java.code")+": return \""+Utilities.escapeJava(cc.getDisplay())+"\";"); 333 } 334 w(enums, " default: return null;"); 335 w(enums, " }"); 336 w(enums, " }"); 337 w(enums, ""); 338 339 if (!hasDups) { 340 w(enums, " public String toCode() {"); 341 w(enums, " switch (this) {"); 342 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 343 w(enums, " case "+cc.getUserString("java.code")+": return \""+cc.getCode()+"\";"); 344 } 345 w(enums, " default: return null;"); 346 w(enums, " }"); 347 w(enums, " }"); 348 w(enums, ""); 349 } 350 w(enums, " public Coding toCoding() {"); 351 w(enums, " switch (this) {"); 352 for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) { 353 if (cc.hasVersion()) { 354 w(enums, " case "+cc.getUserString("java.code")+": return new Coding().setSystem(\""+cc.getSystem()+"\").setVersion(\""+cc.getVersion()+"\").setCode()\""+cc.getCode()+"\";"); 355 } else { 356 w(enums, " case "+cc.getUserString("java.code")+": return new Coding().setSystem(\""+cc.getSystem()+"\").setCode(\""+cc.getCode()+"\");"); 357 } 358 } 359 w(enums, " default: return null;"); 360 w(enums, " }"); 361 w(enums, " }"); 362 w(enums, ""); 363 w(enums, " public CodeableConcept toCodeableConcept() {"); 364 w(enums, " Coding c = toCoding();"); 365 w(enums, " return c == null ? null : new CodeableConcept().addCoding(c);"); 366 w(enums, " }"); 367 w(enums, " }"); 368 return name; 369 } 370 } 371 } 372 } 373 return null; 374 } 375 376 private void defineField(PEDefinition source, PEDefinition field) { 377 if (field.types().size() == 1) { 378 StructureDefinition sd = workerContext.fetchTypeDefinition(field.types().get(0).getUrl()); 379 if (sd != null) { 380 String enumName = generateEnum(source, field); 381 boolean isPrim = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 382 boolean isAbstract = sd.getAbstract(); 383 String name = Utilities.javaTokenize(field.name().replace("[x]", ""), false); 384 String sname = name; 385 String type = null; 386 String init = ""; 387 String ptype = type; 388 boolean isEnum = false; 389 if (enumName != null) { 390 type = enumName; 391 ptype = enumName; 392 isEnum = true; 393 } else if (isPrim) { 394 // todo: are we extension-less? 395 type = Utilities.capitalize(field.types().get(0).getName()+"Type"); 396 ptype = getPrimitiveType(sd); 397 } else { 398 type = Utilities.javaTokenize(field.types().get(0).getName(), true); 399 } 400 String ltype = type; 401 if (field.isList()) { 402 ltype = "List<"+type+">"; 403 init = "new ArrayList<>()"; 404 if (!Utilities.existsInList(name, "contained")) { 405 name = Utilities.pluralize(name, 2); 406 } 407 } 408 String cname = Utilities.capitalize(name); 409 String csname = Utilities.capitalize(sname); 410 String nn = field.min() == 1 ? "// @NotNull" : ""; 411 boolean isExtension = field.isExtension(); 412 genField(isPrim, name, ptype, ltype, nn, field.isList(), field.shortDocumentation(), field.min(), field.max(), field.definition()); 413 if (isPrim && field.hasFixedValue()) { 414 genFixed(name, ptype, field.getFixedValue()); 415 } 416 genAccessors(isPrim, isAbstract, name, field.name(), type, init, ptype, ltype, cname, csname, field.isList(), field.documentation(), field.hasFixedValue(), isEnum, field.definition()); 417 genLoad(isPrim, isAbstract, name, sname, field.name(), type, init, ptype, ltype, cname, csname, field.isList(), field.hasFixedValue(), field.types().get(0), isEnum); 418 genSave(isPrim, isAbstract, name, sname, field.name(), type, init, ptype, ltype, cname, csname, field.isList(), field.hasFixedValue(), isExtension, field.types().get(0), isEnum); 419 genClear(field.isList(), name, ptype); 420 } 421 } else { 422 // ignoring polymorphics for now 423 } 424 } 425 426 private void genClear(boolean list, String name, String ptype) { 427 if (list) { 428 w(clear, " "+name+".clear();"); 429 } else if ("boolean".equals(ptype)) { 430 w(clear, " "+name+" = false;"); 431 } else if ("int".equals(ptype)) { 432 w(clear, " "+name+" = 0;"); 433 } else { 434 w(clear, " "+name+" = null;"); 435 } 436 } 437 438 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) { 439 if (isList) { 440 w(load, " for (PEInstance item : src.children(\""+fname+"\")) {"); 441 if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 442 w(load, " "+name+".add("+type+".fromSource(src.child(\""+fname+"\")));"); 443 444 } else if ("BackboneElement".equals(type)) { 445 w(load, " "+name+".add(("+type+") item.asElement());"); 446 } else if (isEnum) { 447 if ("CodeableConcept".equals(typeInfo.getName())) { 448 w(load, " "+name+".add("+type+".fromCodeableConcept((CodeableConcept) item.asDataType()));"); 449 } else if ("Coding".equals(typeInfo.getName())) { 450 w(load, " "+name+".add("+type+".fromCoding((Coding) item.asDataType()));"); 451 } else { 452 w(load, " "+name+".add("+type+".fromCode(item.asDataType().primitiveValue()));"); 453 } 454 } else { 455 w(load, " "+name+".add(("+type+") item.asDataType());"); 456 } 457 w(load, " }"); 458 } else if (isEnum) { 459 w(load, " if (src.hasChild(\""+fname+"\")) {"); 460 if ("CodeableConcept".equals(typeInfo.getName())) { 461 w(load, " "+name+" = "+type+".fromCodeableConcept((CodeableConcept) src.child(\""+fname+"\").asDataType());"); 462 } else if ("Coding".equals(typeInfo.getName())) { 463 w(load, " "+name+" = "+type+".fromCoding((Coding) src.child(\""+fname+"\").asDataType());"); 464 } else { 465 w(load, " "+name+" = "+type+".fromCode(src.child(\""+fname+"\").asDataType().primitiveValue());"); 466 } 467 w(load, " }"); 468 } else if (isPrim) { 469 w(load, " if (src.hasChild(\""+fname+"\")) {"); 470 if ("CodeType".equals(type)) { 471 // might be code or enum 472 w(load, " "+name+" = src.child(\""+fname+"\").asDataType().primitiveValue();"); 473 } else { 474 w(load, " "+name+" = (("+type+") src.child(\""+fname+"\").asDataType()).getValue();"); 475 } 476 w(load, " }"); 477 } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 478 w(load, " if (src.hasChild(\""+fname+"\")) {"); 479 w(load, " "+name+" = "+type+".fromSource(src.child(\""+fname+"\"));"); 480 w(load, " }"); 481 } else { 482 w(load, " if (src.hasChild(\""+fname+"\")) {"); 483 if ("BackboneElement".equals(type)) { 484 w(load, " "+name+" = ("+type+") src.child(\""+fname+"\").asElement();"); 485 } else if (Utilities.existsInList(type, workerContext.getResourceNames())) { 486 w(load, " "+name+" = ("+type+") src.child(\""+fname+"\").asResource();"); 487 } else if("Reference".equals(type)) { 488 w(load, " "+type+" ref = ("+type+") src.child(\""+fname+"\").asDataType();"); 489 w(load, " if(!ref.isEmpty())"); 490 w(load, " "+name+" = ref;"); 491 } else { 492 w(load, " "+name+" = ("+type+") src.child(\""+fname+"\").asDataType();"); 493 } 494 w(load, " }"); 495 } 496 } 497 498 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) { 499 w(save, " tgt.clear(\""+fname+"\");"); 500 if (isList) { 501 w(save, " for ("+type+" item : "+name+") {"); 502 if (isExtension) { 503 if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 504 w(save, " item.save(tgt.makeChild(\""+fname+"\"), false);"); 505 } else { 506 w(save, " tgt.makeChild(\""+fname+"\").data().setProperty(\"value[x]\", item);"); 507 } 508 } else if (isEnum) { 509 if ("CodeableConcept".equals(typeInfo.getName())) { 510 w(save, " tgt.addChild(\""+fname+"\", item.toCodeableConcept());"); 511 } else if ("Coding".equals(typeInfo.getName())) { 512 w(save, " tgt.addChild(\""+fname+"\", item.toCoding());"); 513 } else { 514 w(save, " tgt.addChild(\""+fname+"\", item.toCode());"); 515 } 516 } else { 517 if(typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) 518 w(save, " tgt.addChild(\""+fname+"\", item);"); 519 else 520 w(save, " tgt.addChild(\""+fname+"\",(Type) item.getData());"); 521 522 } 523 w(save, " }"); 524 } else if (isEnum) { 525 w(save, " if ("+name+" != null) {"); 526 if ("CodeableConcept".equals(typeInfo.getName())) { 527 w(save, " tgt.addChild(\""+fname+"\", "+name+".toCodeableConcept());"); 528 } else if ("Coding".equals(typeInfo.getName())) { 529 w(save, " tgt.addChild(\""+fname+"\", "+name+".toCoding());"); 530 } else { 531 w(save, " tgt.addChild(\""+fname+"\", "+name+".toCode());"); 532 } 533 w(save, " }"); 534 } else if (isPrim) { 535 if ("boolean".equals(ptype)) { 536 w(save, " if (true) { // for now, at least"); 537 } else if ("int".equals(ptype)) { 538 w(save, " if ("+name+" != 0) {"); 539 } else { 540 w(save, " if ("+name+" != null) {"); 541 } 542 if (isExtension) { 543 w(save, " tgt.makeChild(\""+fname+"\").data().setProperty(\"value[x]\", new "+type+"("+name+"));"); 544 } else if (Utilities.existsInList(type, "DateType", "InstantType", "DateTimeType")) { 545 w(save, " tgt.addChild(\""+fname+"\", new "+type+"("+name+"));"); 546 } else { 547 w(save, " tgt.makeChild(\""+fname+"\").data().setProperty(\"value\", new "+type+"("+name+"));"); 548 } 549 w(save, " }"); 550 } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 551 w(save, " if ("+name+" != null) {"); 552 w(save, " "+name+".save(tgt.makeChild(\""+fname+"\"), nulls);"); 553 w(save, " }"); 554 } else if (isExtension) { 555 w(save, " if ("+name+" != null) {"); 556 w(save, " tgt.makeChild(\""+fname+"\").data().setProperty(\"value[x]\", "+name+");"); 557 w(save, " }"); 558 } else { 559 w(save, " if ("+name+" != null) {"); 560 w(save, " tgt.addChild(\""+fname+"\", "+name+");"); 561 w(save, " }"); 562 } 563 } 564 565 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) { 566 if (ed != null) { 567 jdoc(accessors, ed.getDefinition(), 2, true); 568 } 569 if ((isEnum || isPrim) && extensionPolicy != ExtensionPolicy.Primitives && !isList) { 570 w(accessors, " public "+ptype+" get"+cname+"() {"); 571 w(accessors, " return "+name+";"); 572 w(accessors, " }"); 573 w(accessors); 574 if (isFixed) { 575 w(accessors, " public boolean has"+cname+"() {"); 576 w(accessors, " return true;"); 577 w(accessors, " }"); 578 } else { 579 w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); 580 w(accessors, " this."+name+" = value;"); 581 w(accessors, " return this;"); 582 w(accessors, " }"); 583 w(accessors); 584 w(accessors, " public boolean has"+cname+"() {"); 585 if ("boolean".equals(ptype)) { 586 w(accessors, " return true; // not "+name+" != false ?"); 587 } else if ("int".equals(ptype)) { 588 w(accessors, " return "+name+" != 0;"); 589 } else { 590 w(accessors, " return "+name+" != null;"); 591 } 592 w(accessors, " }"); 593 } 594 } else { 595 if (isPrim && !isList) { 596 w(accessors, " public "+ptype+" get"+cname+"() {"); 597 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 598 w(accessors, " return "+name+".getValue();"); 599 w(accessors, " }"); 600 w(accessors, " public "+ltype+" get"+cname+"Element() {"); 601 } else if (isAbstract && !isList) { 602 w(accessors, " public @Nullable "+ltype+" get"+cname+"() { // "+ltype+" is abstract "); 603 } else { 604 w(accessors, " public "+ltype+" get"+cname+"() {"); 605 } 606 if (isList) { 607 w(accessors, " if ("+name+" == null) { "+name+" = "+init+"; }"); 608 } else if (!isAbstract) { 609 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 610 } 611 w(accessors, " return "+name+";"); 612 w(accessors, " }"); 613 w(accessors); 614 if (isList) { 615 w(accessors, " public boolean has"+cname+"() {"); 616 w(accessors, " return "+name+" != null && !"+name+".isEmpty();"); 617 w(accessors, " }"); 618 w(accessors); 619 if (!isAbstract) { 620 if (!isEnum) { 621 w(accessors, " public "+this.name+" add"+csname+"("+type+ " theThing) {"); 622 w(accessors, " get"+cname+"().add(theThing);"); 623 w(accessors, " return this;"); 624 w(accessors, " }"); 625 w(accessors); 626 } else { 627 w(accessors, " public void add"+csname+"("+type+" theThing) {"); 628 w(accessors, " get"+cname+"().add(theThing);"); 629 w(accessors, " }"); 630 w(accessors); 631 } 632 } 633 w(accessors, " public boolean has"+csname+"("+type+" item) {"); 634 w(accessors, " return has"+cname+"() && "+name+".contains(item);"); 635 w(accessors, " }"); 636 w(accessors); 637 w(accessors, " public void remove"+csname+"("+type+" item) {"); 638 w(accessors, " if (has"+csname+"(item)) {"); 639 w(accessors, " "+name+".remove(item);"); 640 w(accessors, " }"); 641 w(accessors, " }"); 642 w(accessors); 643 } else if (isPrim) { 644 if (!isFixed) { 645 w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); 646 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 647 w(accessors, " "+name+".setValue(value);"); 648 w(accessors, " return this;"); 649 w(accessors, " }"); 650 w(accessors, " public "+this.name+" set"+cname+"Element("+type+" value) {"); 651 w(accessors, " this."+name+" = value;"); 652 w(accessors, " return this;"); 653 w(accessors, " }"); 654 } 655 w(accessors, " public boolean has"+cname+"() {"); 656 w(accessors, " return "+name+" != null && "+name+".hasValue();"); 657 w(accessors, " }"); 658 w(accessors); 659 } else { 660 if (!isFixed) { 661 w(accessors, " public "+this.name+" set"+cname+"("+type+" value) {"); 662 w(accessors, " this."+name+" = value;"); 663 w(accessors, " return this;"); 664 w(accessors, " }"); 665 } 666 w(accessors, " public boolean has"+cname+"() {"); 667 w(accessors, " return "+name+" != null;"); 668 w(accessors, " }"); 669 } 670 } 671 w(accessors); 672 } 673 674 private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco, int min, int max, ElementDefinition ed) { 675// jdoc(fields, shortDoco, 2, true); 676 w(fields, " @Min(\""+min+"\") @Max(\""+(max == Integer.MAX_VALUE ? "*" : max) +"\")"+(" @Doco(\""+Utilities.escapeJava(shortDoco)+"\")")); 677 if (ed != null) { 678 if (ed.hasBinding() && ed.getBinding().hasValueSet()) { 679 w(fields, " @BindingStrength(\""+ed.getBinding().getStrength().toCode()+"\") @ValueSet(\""+ed.getBinding().getValueSet()+"\")"); 680 } 681 if (ed.getMustSupport()) { 682 w(fields, " @MustSupport(true)"); 683 } 684 if (ed.hasLabel() || ed.hasDefinition()) { 685 String s = ""; 686 if (ed.hasLabel()) { 687 s = s + " @Label(\""+Utilities.escapeJava(ed.getLabel())+"\")"; 688 } 689 if (ed.hasDefinition()) { 690 s = s + " @Definition(\""+Utilities.escapeJava(ed.getDefinition())+"\")"; 691 } 692 w(fields, " "+s); 693 } 694 } 695 if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) { 696 w(fields, " private "+ptype+" "+name+";"+nn+" // "+shortDoco); 697 } else if (isList) { 698 w(fields, " private "+ltype+" "+name+" = new ArrayList<>();"+nn+" // "+shortDoco); 699 } else { 700 w(fields, " private "+ltype+" "+name+";"+nn+" // "+shortDoco); 701 } 702 w(fields, ""); 703 } 704 705 706 private void genFixed(String name, String pType, Type fixedValue) { 707 if ("String".equals(pType)) { 708 w(inits, " "+name+" = \""+Utilities.escapeJava(fixedValue.primitiveValue())+"\";"); 709 } else { 710 unfixed.add(name); 711 System.out.println("Unable to handle the fixed value for "+name+" of type "+pType+" = "+fixedValue.toString()); 712 } 713 } 714 } 715 716 private String folder; 717 private IWorkerContext workerContext; 718 private String canonical; 719 private String pkgName; 720 private String version = "r4"; 721 722 // options: 723 private ExtensionPolicy extensionPolicy; 724 private boolean narrative; 725 private boolean contained; 726 private boolean meta; 727 private String language; 728 private boolean keyElementsOnly; 729 private String genDate = DEFAULT_DATE(); 730 731 732 public PECodeGenerator(IWorkerContext workerContext) { 733 super(); 734 this.workerContext = workerContext; 735 } 736 737 public String getFolder() { 738 return folder; 739 } 740 741 742 public void setFolder(String folder) { 743 this.folder = folder; 744 } 745 746 747 public String getVersion() { 748 return version; 749 } 750 751 public void setVersion(String version) { 752 this.version = version; 753 } 754 755 public String getCanonical() { 756 return canonical; 757 } 758 759 public void setCanonical(String canonical) { 760 this.canonical = canonical; 761 } 762 763 764 public String getPkgName() { 765 return pkgName; 766 } 767 768 public void setPkgName(String pkgName) { 769 this.pkgName = pkgName; 770 } 771 772 public ExtensionPolicy getExtensionPolicy() { 773 return extensionPolicy; 774 } 775 776 public void setExtensionPolicy(ExtensionPolicy extensionPolicy) { 777 this.extensionPolicy = extensionPolicy; 778 } 779 780 public boolean isNarrative() { 781 return narrative; 782 } 783 784 public void setNarrative(boolean narrative) { 785 this.narrative = narrative; 786 } 787 788 public boolean isMeta() { 789 return meta; 790 } 791 792 public void setMeta(boolean meta) { 793 this.meta = meta; 794 } 795 796 public String getLanguage() { 797 return language; 798 } 799 800 public void setLanguage(String language) { 801 this.language = language; 802 } 803 804 public boolean isKeyElementsOnly() { 805 return keyElementsOnly; 806 } 807 808 public void setKeyElementsOnly(boolean keyElementsOnly) { 809 this.keyElementsOnly = keyElementsOnly; 810 } 811 812 public boolean isContained() { 813 return contained; 814 } 815 816 public void setContained(boolean contained) { 817 this.contained = contained; 818 } 819 820 public String getGenDate() { 821 return genDate; 822 } 823 824 public void setGenDate(String genDate) { 825 this.genDate = genDate; 826 } 827 828 private StringBuilder imports = new StringBuilder(); 829 830 /** 831 * @throws IOException 832 * 833 */ 834 public String execute() throws IOException { 835 imports = new StringBuilder(); 836 837 PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical); 838 w(imports, "import java.util.List;"); 839 w(imports, "import java.util.ArrayList;"); 840 w(imports, "import java.util.Date;\r\n"); 841 w(imports, "import java.math.BigDecimal;"); 842 w(imports, "import javax.annotation.Nullable;"); 843 w(imports); 844 w(imports, "import org.hl7.fhir."+version+".context.IWorkerContext;"); 845 w(imports, "import org.hl7.fhir."+version+".model.*;"); 846 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder;"); 847 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEInstance;"); 848 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder.PEElementPropertiesPolicy;"); 849 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.PEGeneratedBase;"); 850 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Min;"); 851 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Max;"); 852 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Label;"); 853 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Doco;"); 854 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.BindingStrength;"); 855 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.ValueSet;"); 856 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.MustSupport;"); 857 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Definition;"); 858 859 860 PEGenClass cls = genClass(source); 861 StringBuilder b = new StringBuilder(); 862 w(b, "package "+pkgName+";"); 863 w(b); 864 if (source.getProfile().hasCopyright()) { 865 jdoc(b, source.getProfile().getCopyright(), 0, false); 866 } 867 w(b, imports.toString()); 868 cls.write(b, source.getProfile().getCopyright()); 869 FileUtilities.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java")); 870 return cls.name+".java"; 871 } 872 873 public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) { 874 if (!Utilities.noString(doco)) { 875 String pfx = Utilities.padLeft("", ' ', indent); 876 w(b, pfx+"/*"+(jdoc ? "*" : "")); 877 for (String line : doco.split("\\R")) { 878 for (String nl : naturalLines(line)) 879 w(b, pfx+" * "+nl); 880 w(b, pfx+" *"); 881 } 882 w(b, pfx+" */"); 883 } 884 } 885 886 private List<String> naturalLines(String line) { 887 List<String> lines = new ArrayList<>(); 888 while (line.length() > 80) { 889 int cutpoint = 80; 890 while (cutpoint > 0 && line.charAt(cutpoint) != ' ') { 891 cutpoint--; 892 } 893 if (cutpoint == 0) { 894 cutpoint = 80; 895 } else { 896 cutpoint++; 897 } 898 lines.add(line.substring(0, cutpoint)); 899 line = line.substring(cutpoint); 900 } 901 lines.add(line); 902 return lines; 903 } 904 905 private void w(StringBuilder b) { 906 b.append("\r\n"); 907 908 } 909 910 private void w(StringBuilder b, String line) { 911 b.append(line); 912 w(b); 913 } 914 915 private PEGenClass genClass(PEDefinition source) { 916 PEGenClass cls = new PEGenClass(); 917 cls.name = Utilities.javaTokenize(source.getProfile().getName(), true); 918 cls.base = source.getProfile().getType(); 919 cls.doco = source.documentation(); 920 cls.url = source.getProfile().getVersionedUrl(); 921 cls.isResource = source.getProfile().getKind() == StructureDefinitionKind.RESOURCE; 922 cls.genId(); 923 for (PEDefinition child : source.children()) { 924 if (genForField(source, child)) { 925 cls.defineField(source, child); 926 } 927 } 928 return cls; 929 } 930 931 private boolean genForField(PEDefinition source, PEDefinition child) { 932 if (child.definition().getBase().getPath().equals("Resource.meta")) { 933 return meta; 934 } 935 if (child.definition().getBase().getPath().equals("DomainResource.text")) { 936 return narrative; 937 } 938 if (child.definition().getBase().getPath().equals("Resource.language")) { 939 return language == null; 940 } 941 if (child.definition().getBase().getPath().endsWith(".extension") || child.definition().getBase().getPath().endsWith(".modifierExtension")) { 942 return extensionPolicy == ExtensionPolicy.Complexes; 943 } 944 if (child.definition().getBase().getPath().equals("DomainResource.contained")) { 945 return contained; 946 } 947 return !keyElementsOnly || (child.isKeyElement()); 948 } 949 950 951 private String getPrimitiveType(StructureDefinition sd) { 952 953 if (sd.getType().equals("string")) 954 return "String"; 955 if (sd.getType().equals("code")) 956 return "String"; 957 if (sd.getType().equals("markdown")) 958 return "String"; 959 if (sd.getType().equals("base64Binary")) 960 return "byte[]"; 961 if (sd.getType().equals("uri")) 962 return "String"; 963 if (sd.getType().equals("url")) 964 return "String"; 965 if (sd.getType().equals("canonical")) 966 return "String"; 967 if (sd.getType().equals("oid")) 968 return "String"; 969 if (sd.getType().equals("integer")) 970 return "int"; 971 if (sd.getType().equals("integer64")) 972 return "long"; 973 if (sd.getType().equals("unsignedInt")) 974 return "int"; 975 if (sd.getType().equals("positiveInt")) 976 return "int"; 977 if (sd.getType().equals("boolean")) 978 return "boolean"; 979 if (sd.getType().equals("decimal")) 980 return "BigDecimal"; 981 if (sd.getType().equals("dateTime")) 982 return "Date"; 983 if (sd.getType().equals("date")) 984 return "Date"; 985 if (sd.getType().equals("id")) 986 return "String"; 987 if (sd.getType().equals("instant")) 988 return "Date"; 989 if (sd.getType().equals("time")) 990 return "String"; 991 992 return "??"; 993 } 994 995}