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