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.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.r4.context.IWorkerContext; 043import org.hl7.fhir.r4.model.CodeableConcept; 044import org.hl7.fhir.r4.model.ElementDefinition; 045import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 046import org.hl7.fhir.r4.model.Identifier; 047import org.hl7.fhir.r4.model.Observation; 048import org.hl7.fhir.r4.model.StructureDefinition; 049import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 050import org.hl7.fhir.r4.model.Type; 051import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 052import org.hl7.fhir.r4.profilemodel.PEBuilder; 053import org.hl7.fhir.r4.profilemodel.PEBuilder.PEElementPropertiesPolicy; 054import org.hl7.fhir.r4.profilemodel.gen.PECodeGenerator.ExtensionPolicy; 055import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 056import org.hl7.fhir.r4.profilemodel.PEDefinition; 057import org.hl7.fhir.r4.profilemodel.PEInstance; 058import org.hl7.fhir.r4.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.r4.model.Enumerations.BindingStrength.REQUIRED && binding.hasValueSet()) { 241 org.hl7.fhir.r4.model.ValueSet vs = workerContext.fetchResource(org.hl7.fhir.r4.model.ValueSet.class, binding.getValueSet()); 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 402 if ("BackboneElement".equals(type)) { 403 w(load, " "+name+".add(("+type+") item.asElement());"); 404 } else { 405 w(load, " "+name+".add(("+type+") item.asDataType());"); 406 } 407 w(load, " }"); 408 } else if (isEnum) { 409 w(load, " if (src.hasChild(\""+name+"\")) {"); 410 if ("CodeableConcept".equals(typeInfo.getName())) { 411 w(load, " "+name+" = "+type+".fromCodeableConcept((CodeableConcept) src.child(\""+name+"\").asDataType());"); 412 } else if ("Coding".equals(typeInfo.getName())) { 413 w(load, " "+name+" = "+type+".fromCoding((Coding) src.child(\""+name+"\").asDataType());"); 414 } else { 415 w(load, " "+name+" = "+type+".fromCode(src.child(\""+name+"\").asDataType().primitiveValue());"); 416 } 417 w(load, " }"); 418 } else if (isPrim) { 419 w(load, " if (src.hasChild(\""+name+"\")) {"); 420 if ("CodeType".equals(type)) { 421 // might be code or enum 422 w(load, " "+name+" = src.child(\""+name+"\").asDataType().primitiveValue();"); 423 } else { 424 w(load, " "+name+" = (("+type+") src.child(\""+name+"\").asDataType()).getValue();"); 425 } 426 w(load, " }"); 427 } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 428 w(load, " if (src.hasChild(\""+name+"\")) {"); 429 w(load, " "+name+" = "+type+".fromSource(src.child(\""+name+"\"));"); 430 w(load, " }"); 431 } else { 432 w(load, " if (src.hasChild(\""+name+"\")) {"); 433 if ("BackboneElement".equals(type)) { 434 w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asElement();"); 435 } else { 436 w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asDataType();"); 437 } 438 w(load, " }"); 439 } 440 } 441 442 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) { 443 w(save, " tgt.clear(\""+sname+"\");"); 444 if (isList) { 445 w(save, " for ("+type+" item : "+name+") {"); 446 if (isExtension) { 447 w(save, " tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", item);"); 448 } else { 449 w(save, " tgt.addChild(\""+sname+"\", item);"); 450 } 451 w(save, " }"); 452 } else if (isEnum) { 453 w(save, " if ("+name+" != null) {"); 454 if ("CodeableConcept".equals(typeInfo.getName())) { 455 w(save, " tgt.addChild(\""+sname+"\", "+name+".toCodeableConcept());"); 456 } else if ("Coding".equals(typeInfo.getName())) { 457 w(save, " tgt.addChild(\""+sname+"\", "+name+".toCoding());"); 458 } else { 459 w(save, " tgt.addChild(\""+sname+"\", "+name+".toCode());"); 460 } 461 w(save, " }"); 462 } else if (isPrim) { 463 w(save, " if ("+name+" != null) {"); 464 if (isExtension) { 465 w(save, " tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", new "+type+"("+name+"));"); 466 } else if (Utilities.existsInList(type, "DateType", "InstantType", "DateTimeType")) { 467 w(save, " tgt.addChild(\""+sname+"\", new "+type+"("+name+"));"); 468 } else { 469 w(save, " tgt.makeChild(\""+sname+"\").data().setProperty(\"value\", new "+type+"("+name+"));"); 470 } 471 w(save, " }"); 472 } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { 473 w(save, " if ("+name+" != null) {"); 474 w(save, " "+name+".save(tgt.makeChild(\""+sname+"\"), nulls);"); 475 w(save, " }"); 476 } else if (isExtension) { 477 w(save, " if ("+name+" != null) {"); 478 w(save, " tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", "+name+");"); 479 w(save, " }"); 480 } else { 481 w(save, " if ("+name+" != null) {"); 482 w(save, " tgt.addChild(\""+sname+"\", "+name+");"); 483 w(save, " }"); 484 } 485 } 486 487 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) { 488 jdoc(accessors, doco, 2, true); 489 if ((isEnum || isPrim) && extensionPolicy != ExtensionPolicy.Primitives && !isList) { 490 w(accessors, " public "+ptype+" get"+cname+"() {"); 491 w(accessors, " return "+name+";"); 492 w(accessors, " }"); 493 w(accessors); 494 if (isFixed) { 495 w(accessors, " public boolean has"+cname+"() {"); 496 w(accessors, " return true;"); 497 w(accessors, " }"); 498 } else { 499 w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); 500 w(accessors, " this."+name+" = value;"); 501 w(accessors, " return this;"); 502 w(accessors, " }"); 503 w(accessors); 504 w(accessors, " public boolean has"+cname+"() {"); 505 w(accessors, " return "+name+" != null;"); 506 w(accessors, " }"); 507 } 508 } else { 509 if (isPrim && !isList) { 510 w(accessors, " public "+ptype+" get"+cname+"() {"); 511 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 512 w(accessors, " return "+name+".getValue();"); 513 w(accessors, " }"); 514 w(accessors, " public "+ltype+" get"+cname+"Element() {"); 515 } else if (isAbstract && !isList) { 516 w(accessors, " public @Nullable "+ltype+" get"+cname+"() { // "+ltype+" is abstract "); 517 } else { 518 w(accessors, " public "+ltype+" get"+cname+"() {"); 519 } 520 if (isList) { 521 w(accessors, " if ("+name+" == null) { "+name+" = "+init+"; }"); 522 } else if (!isAbstract) { 523 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 524 } 525 w(accessors, " return "+name+";"); 526 w(accessors, " }"); 527 w(accessors); 528 if (isList) { 529 w(accessors, " public boolean has"+cname+"() {"); 530 w(accessors, " return "+name+" != null && !"+name+".isEmpty();"); 531 w(accessors, " }"); 532 w(accessors); 533 if (!isAbstract) { 534 w(accessors, " public "+type+" add"+csname+"() {"); 535 w(accessors, " "+type+" theThing = new "+type+"();"); 536 w(accessors, " get"+cname+"().add(theThing);"); 537 w(accessors, " return theThing;"); 538 w(accessors, " }"); 539 w(accessors); 540 } 541 w(accessors, " public boolean has"+csname+"("+type+" item) {"); 542 w(accessors, " return has"+cname+"() && "+name+".contains(item);"); 543 w(accessors, " }"); 544 w(accessors); 545 w(accessors, " public void remove"+csname+"("+type+" item) {"); 546 w(accessors, " if (has"+csname+"(item)) {"); 547 w(accessors, " "+name+".remove(item);"); 548 w(accessors, " }"); 549 w(accessors, " }"); 550 w(accessors); 551 } else if (isPrim) { 552 if (!isFixed) { 553 w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); 554 w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); 555 w(accessors, " "+name+".setValue(value);"); 556 w(accessors, " return this;"); 557 w(accessors, " }"); 558 w(accessors, " public "+this.name+" set"+cname+"Element("+type+" value) {"); 559 w(accessors, " this."+name+" = value;"); 560 w(accessors, " return this;"); 561 w(accessors, " }"); 562 } 563 w(accessors, " public boolean has"+cname+"() {"); 564 w(accessors, " return "+name+" != null && "+name+".hasValue();"); 565 w(accessors, " }"); 566 w(accessors); 567 } else { 568 if (!isFixed) { 569 w(accessors, " public "+this.name+" set"+cname+"("+type+" value) {"); 570 w(accessors, " this."+name+" = value;"); 571 w(accessors, " return this;"); 572 w(accessors, " }"); 573 } 574 w(accessors, " public boolean has"+cname+"() {"); 575 w(accessors, " return "+name+" != null;"); 576 w(accessors, " }"); 577 } 578 } 579 w(accessors); 580 } 581 582 private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco, int min, int max, ElementDefinition ed) { 583// jdoc(fields, shortDoco, 2, true); 584 w(fields, " @Min(\""+min+"\") @Max(\""+(max == Integer.MAX_VALUE ? "*" : max) +"\")"+(" @Doco(\""+Utilities.escapeJava(shortDoco)+"\")")); 585 if (ed != null) { 586 if (ed.hasBinding() && ed.getBinding().hasValueSet()) { 587 w(fields, " @BindingStrength(\""+ed.getBinding().getStrength().toCode()+"\") @ValueSet(\""+ed.getBinding().getValueSet()+"\")"); 588 } 589 if (ed.getMustSupport()) { 590 w(fields, " @MustSupport(true)"); 591 } 592 if (ed.hasLabel() || ed.hasDefinition()) { 593 String s = ""; 594 if (ed.hasLabel()) { 595 s = s + " @Label(\""+Utilities.escapeJava(ed.getLabel())+"\")"; 596 } 597 if (ed.hasDefinition()) { 598 s = s + " @Definition(\""+Utilities.escapeJava(ed.getDefinition())+"\")"; 599 } 600 w(fields, " "+s); 601 } 602 } 603 if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) { 604 w(fields, " private "+ptype+" "+name+";"+nn+" // "+shortDoco); 605 } else if (isList) { 606 w(fields, " private "+ltype+" "+name+" = new ArrayList<>();"+nn+" // "+shortDoco); 607 } else { 608 w(fields, " private "+ltype+" "+name+";"+nn+" // "+shortDoco); 609 } 610 w(fields, ""); 611 } 612 613 614 private void genFixed(String name, String pType, Type fixedValue) { 615 if ("String".equals(pType)) { 616 w(inits, " "+name+" = \""+Utilities.escapeJava(fixedValue.primitiveValue())+"\";"); 617 } else { 618 unfixed.add(name); 619 System.out.println("Unable to handle the fixed value for "+name+" of type "+pType+" = "+fixedValue.toString()); 620 } 621 } 622 } 623 624 private String folder; 625 private IWorkerContext workerContext; 626 private String canonical; 627 private String pkgName; 628 private String version = "r4"; 629 630 // options: 631 private ExtensionPolicy extensionPolicy; 632 private boolean narrative; 633 private boolean contained; 634 private boolean meta; 635 private String language; 636 private boolean keyElementsOnly; 637 private String genDate = DEFAULT_DATE(); 638 639 640 public PECodeGenerator(IWorkerContext workerContext) { 641 super(); 642 this.workerContext = workerContext; 643 } 644 645 public String getFolder() { 646 return folder; 647 } 648 649 650 public void setFolder(String folder) { 651 this.folder = folder; 652 } 653 654 655 public String getVersion() { 656 return version; 657 } 658 659 public void setVersion(String version) { 660 this.version = version; 661 } 662 663 public String getCanonical() { 664 return canonical; 665 } 666 667 public void setCanonical(String canonical) { 668 this.canonical = canonical; 669 } 670 671 672 public String getPkgName() { 673 return pkgName; 674 } 675 676 public void setPkgName(String pkgName) { 677 this.pkgName = pkgName; 678 } 679 680 public ExtensionPolicy getExtensionPolicy() { 681 return extensionPolicy; 682 } 683 684 public void setExtensionPolicy(ExtensionPolicy extensionPolicy) { 685 this.extensionPolicy = extensionPolicy; 686 } 687 688 public boolean isNarrative() { 689 return narrative; 690 } 691 692 public void setNarrative(boolean narrative) { 693 this.narrative = narrative; 694 } 695 696 public boolean isMeta() { 697 return meta; 698 } 699 700 public void setMeta(boolean meta) { 701 this.meta = meta; 702 } 703 704 public String getLanguage() { 705 return language; 706 } 707 708 public void setLanguage(String language) { 709 this.language = language; 710 } 711 712 public boolean isKeyElementsOnly() { 713 return keyElementsOnly; 714 } 715 716 public void setKeyElementsOnly(boolean keyElementsOnly) { 717 this.keyElementsOnly = keyElementsOnly; 718 } 719 720 public boolean isContained() { 721 return contained; 722 } 723 724 public void setContained(boolean contained) { 725 this.contained = contained; 726 } 727 728 public String getGenDate() { 729 return genDate; 730 } 731 732 public void setGenDate(String genDate) { 733 this.genDate = genDate; 734 } 735 736 private StringBuilder imports = new StringBuilder(); 737 738 /** 739 * @throws IOException 740 * 741 */ 742 public String execute() throws IOException { 743 imports = new StringBuilder(); 744 745 PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical); 746 w(imports, "import java.util.List;"); 747 w(imports, "import java.util.ArrayList;"); 748 w(imports, "import javax.annotation.Nullable;"); 749 w(imports, "import java.util.Date;\r\n"); 750 w(imports); 751 w(imports, "import org.hl7.fhir."+version+".context.IWorkerContext;"); 752 w(imports, "import org.hl7.fhir."+version+".model.*;"); 753 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder;"); 754 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEInstance;"); 755 w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder.PEElementPropertiesPolicy;"); 756 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.PEGeneratedBase;"); 757 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Min;"); 758 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Max;"); 759 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Label;"); 760 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Doco;"); 761 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.BindingStrength;"); 762 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.ValueSet;"); 763 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.MustSupport;"); 764 w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Definition;"); 765 766 767 PEGenClass cls = genClass(source); 768 StringBuilder b = new StringBuilder(); 769 w(b, "package "+pkgName+";"); 770 w(b); 771 if (source.getProfile().hasCopyright()) { 772 jdoc(b, source.getProfile().getCopyright(), 0, false); 773 } 774 w(b, imports.toString()); 775 cls.write(b, source.getProfile().getCopyright()); 776 TextFile.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java")); 777 return cls.name+".java"; 778 } 779 780 public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) { 781 if (!Utilities.noString(doco)) { 782 String pfx = Utilities.padLeft("", ' ', indent); 783 w(b, pfx+"/*"+(jdoc ? "*" : "")); 784 for (String line : doco.split("\\R")) { 785 for (String nl : naturalLines(line)) 786 w(b, pfx+" * "+nl); 787 w(b, pfx+" *"); 788 } 789 w(b, pfx+" */"); 790 } 791 } 792 793 private List<String> naturalLines(String line) { 794 List<String> lines = new ArrayList<>(); 795 while (line.length() > 80) { 796 int cutpoint = 80; 797 while (cutpoint > 0 && line.charAt(cutpoint) != ' ') { 798 cutpoint--; 799 } 800 if (cutpoint == 0) { 801 cutpoint = 80; 802 } else { 803 cutpoint++; 804 } 805 lines.add(line.substring(0, cutpoint)); 806 line = line.substring(cutpoint); 807 } 808 lines.add(line); 809 return lines; 810 } 811 812 private void w(StringBuilder b) { 813 b.append("\r\n"); 814 815 } 816 817 private void w(StringBuilder b, String line) { 818 b.append(line); 819 w(b); 820 } 821 822 private PEGenClass genClass(PEDefinition source) { 823 PEGenClass cls = new PEGenClass(); 824 cls.name = source.getProfile().getName(); 825 cls.base = source.getProfile().getType(); 826 cls.doco = source.documentation(); 827 cls.url = source.getProfile().getVersionedUrl(); 828 cls.isResource = source.getProfile().getKind() == StructureDefinitionKind.RESOURCE; 829 cls.genId(); 830 for (PEDefinition child : source.children()) { 831 if (genForField(source, child)) { 832 cls.defineField(source, child); 833 } 834 } 835 return cls; 836 } 837 838 private boolean genForField(PEDefinition source, PEDefinition child) { 839 if (child.definition().getBase().getPath().equals("Resource.meta")) { 840 return meta; 841 } 842 if (child.definition().getBase().getPath().equals("DomainResource.text")) { 843 return narrative; 844 } 845 if (child.definition().getBase().getPath().equals("Resource.language")) { 846 return language == null; 847 } 848 if (child.definition().getBase().getPath().endsWith(".extension") || child.definition().getBase().getPath().endsWith(".modifierExtension")) { 849 return extensionPolicy == ExtensionPolicy.Complexes; 850 } 851 if (child.definition().getBase().getPath().equals("DomainResource.contained")) { 852 return contained; 853 } 854 return !keyElementsOnly || (child.isKeyElement()); 855 } 856 857 858 private String getPrimitiveType(StructureDefinition sd) { 859 860 if (sd.getType().equals("string")) 861 return "String"; 862 if (sd.getType().equals("code")) 863 return "String"; 864 if (sd.getType().equals("markdown")) 865 return "String"; 866 if (sd.getType().equals("base64Binary")) 867 return "byte[]"; 868 if (sd.getType().equals("uri")) 869 return "String"; 870 if (sd.getType().equals("url")) 871 return "String"; 872 if (sd.getType().equals("canonical")) 873 return "String"; 874 if (sd.getType().equals("oid")) 875 return "String"; 876 if (sd.getType().equals("integer")) 877 return "int"; 878 if (sd.getType().equals("integer64")) 879 return "long"; 880 if (sd.getType().equals("unsignedInt")) 881 return "int"; 882 if (sd.getType().equals("positiveInt")) 883 return "int"; 884 if (sd.getType().equals("boolean")) 885 return "boolean"; 886 if (sd.getType().equals("decimal")) 887 return "BigDecimal"; 888 if (sd.getType().equals("dateTime")) 889 return "Date"; 890 if (sd.getType().equals("date")) 891 return "Date"; 892 if (sd.getType().equals("id")) 893 return "String"; 894 if (sd.getType().equals("instant")) 895 return "Date"; 896 if (sd.getType().equals("time")) 897 return "String"; 898 899 return "??"; 900 } 901 902}