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