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