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