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