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