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