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.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.r4.context.IWorkerContext;
043import org.hl7.fhir.r4.model.CodeableConcept;
044import org.hl7.fhir.r4.model.ElementDefinition;
045import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
046import org.hl7.fhir.r4.model.Identifier;
047import org.hl7.fhir.r4.model.Observation;
048import org.hl7.fhir.r4.model.StructureDefinition;
049import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
050import org.hl7.fhir.r4.model.Type;
051import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
052import org.hl7.fhir.r4.profilemodel.PEBuilder;
053import org.hl7.fhir.r4.profilemodel.PEBuilder.PEElementPropertiesPolicy;
054import org.hl7.fhir.r4.profilemodel.gen.PECodeGenerator.ExtensionPolicy;
055import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
056import org.hl7.fhir.r4.profilemodel.PEDefinition;
057import org.hl7.fhir.r4.profilemodel.PEInstance;
058import org.hl7.fhir.r4.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.r4.model.Enumerations.BindingStrength.REQUIRED && binding.hasValueSet()) {
241          org.hl7.fhir.r4.model.ValueSet vs = workerContext.fetchResource(org.hl7.fhir.r4.model.ValueSet.class, binding.getValueSet());
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
402        if ("BackboneElement".equals(type)) {
403          w(load, "      "+name+".add(("+type+") item.asElement());");          
404        } else {
405          w(load, "      "+name+".add(("+type+") item.asDataType());");
406        }
407        w(load, "    }");
408      } else if (isEnum) {
409        w(load, "    if (src.hasChild(\""+name+"\")) {");
410        if ("CodeableConcept".equals(typeInfo.getName())) {
411        w(load, "      "+name+" = "+type+".fromCodeableConcept((CodeableConcept) src.child(\""+name+"\").asDataType());");
412        } else if ("Coding".equals(typeInfo.getName())) {
413          w(load, "      "+name+" = "+type+".fromCoding((Coding) src.child(\""+name+"\").asDataType());");
414        } else {
415          w(load, "      "+name+" = "+type+".fromCode(src.child(\""+name+"\").asDataType().primitiveValue());");
416        }  
417        w(load, "    }");      
418      } else if (isPrim) {
419        w(load, "    if (src.hasChild(\""+name+"\")) {");
420        if ("CodeType".equals(type)) {
421          // might be code or enum 
422          w(load, "      "+name+" = src.child(\""+name+"\").asDataType().primitiveValue();");
423        } else {
424          w(load, "      "+name+" = (("+type+") src.child(\""+name+"\").asDataType()).getValue();");
425        }
426        w(load, "    }");      
427      } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) {
428        w(load, "    if (src.hasChild(\""+name+"\")) {");
429        w(load, "      "+name+" = "+type+".fromSource(src.child(\""+name+"\"));");
430        w(load, "    }");
431      } else {
432        w(load, "    if (src.hasChild(\""+name+"\")) {");      
433        if ("BackboneElement".equals(type)) {
434          w(load, "      "+name+" = ("+type+") src.child(\""+name+"\").asElement();");
435        } else {
436          w(load, "      "+name+" = ("+type+") src.child(\""+name+"\").asDataType();");
437        }
438        w(load, "    }");
439      }
440    }
441
442    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) {
443      w(save, "    tgt.clear(\""+sname+"\");");
444      if (isList) {
445        w(save, "    for ("+type+" item : "+name+") {");
446        if (isExtension) {
447          w(save, "      tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", item);");          
448        } else {
449          w(save, "      tgt.addChild(\""+sname+"\", item);");
450        }
451        w(save, "    }");
452      } else if (isEnum) {
453        w(save, "    if ("+name+" != null) {");
454        if ("CodeableConcept".equals(typeInfo.getName())) {
455          w(save, "      tgt.addChild(\""+sname+"\", "+name+".toCodeableConcept());");
456        } else if ("Coding".equals(typeInfo.getName())) {
457          w(save, "      tgt.addChild(\""+sname+"\", "+name+".toCoding());");
458        } else {
459          w(save, "      tgt.addChild(\""+sname+"\", "+name+".toCode());");
460        }  
461        w(save, "    }");      
462      } else if (isPrim) {
463        w(save, "    if ("+name+" != null) {");
464        if (isExtension) {
465          w(save, "      tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", new "+type+"("+name+"));");
466        } else if (Utilities.existsInList(type, "DateType", "InstantType", "DateTimeType")) {
467          w(save, "      tgt.addChild(\""+sname+"\", new "+type+"("+name+"));");          
468        } else {
469          w(save, "      tgt.makeChild(\""+sname+"\").data().setProperty(\"value\", new "+type+"("+name+"));");
470        }
471        w(save, "    }");      
472      } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) {
473        w(save, "    if ("+name+" != null) {");
474        w(save, "      "+name+".save(tgt.makeChild(\""+sname+"\"), nulls);");
475        w(save, "    }");
476      } else if (isExtension) {
477        w(save, "    if ("+name+" != null) {");
478        w(save, "      tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", "+name+");");
479        w(save, "    }");
480      } else {
481        w(save, "    if ("+name+" != null) {");
482        w(save, "      tgt.addChild(\""+sname+"\", "+name+");");
483        w(save, "    }");
484      }
485    }
486
487    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) {
488      jdoc(accessors, doco, 2, true);
489      if ((isEnum || isPrim) && extensionPolicy != ExtensionPolicy.Primitives && !isList) {
490        w(accessors, "  public "+ptype+" get"+cname+"() {");
491        w(accessors, "    return "+name+";");
492        w(accessors, "  }");
493        w(accessors);
494        if (isFixed) {
495          w(accessors, "  public boolean has"+cname+"() {");
496          w(accessors, "    return true;");
497          w(accessors, "  }");  
498        } else {
499          w(accessors, "  public "+this.name+" set"+cname+"("+ptype+" value) {");
500          w(accessors, "    this."+name+" = value;");
501          w(accessors, "    return this;");
502          w(accessors, "  }");
503          w(accessors);
504          w(accessors, "  public boolean has"+cname+"() {");
505          w(accessors, "    return "+name+" != null;");
506          w(accessors, "  }");  
507        }
508      } else {
509        if (isPrim && !isList) {
510          w(accessors, "  public "+ptype+" get"+cname+"() {");
511          w(accessors, "    if ("+name+" == null) { "+name+" = new "+type+"(); }");
512          w(accessors, "    return "+name+".getValue();");
513          w(accessors, "  }");
514          w(accessors, "  public "+ltype+" get"+cname+"Element() {");
515        } else if (isAbstract && !isList) {
516          w(accessors, "  public @Nullable "+ltype+" get"+cname+"() { // "+ltype+" is abstract ");
517        } else {
518          w(accessors, "  public "+ltype+" get"+cname+"() {");
519        }
520        if (isList) {
521          w(accessors, "    if ("+name+" == null) { "+name+" = "+init+"; }");
522        } else if (!isAbstract) {
523          w(accessors, "    if ("+name+" == null) { "+name+" = new "+type+"(); }");
524        }
525        w(accessors, "    return "+name+";");
526        w(accessors, "  }");
527        w(accessors);
528        if (isList) {
529          w(accessors, "  public boolean has"+cname+"() {");
530          w(accessors, "    return "+name+" != null && !"+name+".isEmpty();");
531          w(accessors, "  }");
532          w(accessors);
533          if (!isAbstract) {
534            w(accessors, "  public "+type+" add"+csname+"() {");
535            w(accessors, "    "+type+" theThing = new "+type+"();");
536            w(accessors, "    get"+cname+"().add(theThing);");
537            w(accessors, "    return theThing;");
538            w(accessors, "  }");
539            w(accessors); 
540          }
541          w(accessors, "  public boolean has"+csname+"("+type+" item) {");
542          w(accessors, "    return has"+cname+"() && "+name+".contains(item);");
543          w(accessors, "  }");
544          w(accessors);        
545          w(accessors, "  public void remove"+csname+"("+type+" item) {");
546          w(accessors, "    if (has"+csname+"(item)) {");
547          w(accessors, "      "+name+".remove(item);");
548          w(accessors, "    }");
549          w(accessors, "  }");
550          w(accessors);        
551        } else if (isPrim) {
552          if (!isFixed) {
553            w(accessors, "  public "+this.name+" set"+cname+"("+ptype+" value) {");
554            w(accessors, "    if ("+name+" == null) { "+name+" = new "+type+"(); }");
555            w(accessors, "    "+name+".setValue(value);");
556            w(accessors, "    return this;");
557            w(accessors, "  }");
558            w(accessors, "  public "+this.name+" set"+cname+"Element("+type+" value) {");
559            w(accessors, "    this."+name+" = value;");
560            w(accessors, "    return this;");
561            w(accessors, "  }");
562          }
563          w(accessors, "  public boolean has"+cname+"() {");
564          w(accessors, "    return "+name+" != null && "+name+".hasValue();");
565          w(accessors, "  }");
566          w(accessors); 
567        } else {
568          if (!isFixed) {
569            w(accessors, "  public "+this.name+" set"+cname+"("+type+" value) {");
570            w(accessors, "    this."+name+" = value;");
571            w(accessors, "    return this;");
572            w(accessors, "  }");
573          }
574          w(accessors, "  public boolean has"+cname+"() {");
575          w(accessors, "    return "+name+" != null;");
576          w(accessors, "  }");
577        }
578      }
579      w(accessors);
580    }
581
582    private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco, int min, int max, ElementDefinition ed) {
583//      jdoc(fields, shortDoco, 2, true);
584      w(fields, "  @Min(\""+min+"\") @Max(\""+(max == Integer.MAX_VALUE ? "*" : max) +"\")"+(" @Doco(\""+Utilities.escapeJava(shortDoco)+"\")"));
585      if (ed != null) {
586        if (ed.hasBinding() && ed.getBinding().hasValueSet()) {
587          w(fields, "  @BindingStrength(\""+ed.getBinding().getStrength().toCode()+"\") @ValueSet(\""+ed.getBinding().getValueSet()+"\")");
588        }
589        if (ed.getMustSupport()) {
590          w(fields, "  @MustSupport(true)");          
591        }
592        if (ed.hasLabel() || ed.hasDefinition()) {
593          String s = "";
594          if (ed.hasLabel()) {
595            s = s + " @Label(\""+Utilities.escapeJava(ed.getLabel())+"\")";
596          }
597          if (ed.hasDefinition()) {
598            s = s + " @Definition(\""+Utilities.escapeJava(ed.getDefinition())+"\")";
599          }
600          w(fields, " "+s);          
601        }
602      }
603      if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) {
604        w(fields, "  private "+ptype+" "+name+";"+nn+"  // "+shortDoco);
605      } else if (isList) {
606        w(fields, "  private "+ltype+" "+name+" = new ArrayList<>();"+nn+"  // "+shortDoco);
607      } else {
608        w(fields, "  private "+ltype+" "+name+";"+nn+"  // "+shortDoco);             
609      }
610      w(fields, "");
611    }
612
613
614    private void genFixed(String name, String pType, Type fixedValue) {
615      if ("String".equals(pType)) {
616        w(inits, "    "+name+" = \""+Utilities.escapeJava(fixedValue.primitiveValue())+"\";");                     
617      } else {
618        unfixed.add(name);
619        System.out.println("Unable to handle the fixed value for "+name+" of type "+pType+" = "+fixedValue.toString());
620      }
621    }
622  }
623
624  private String folder;
625  private IWorkerContext workerContext;
626  private String canonical;
627  private String pkgName;
628  private String version = "r4";
629  
630  // options:
631  private ExtensionPolicy extensionPolicy;
632  private boolean narrative;
633  private boolean contained;
634  private boolean meta;
635  private String language;
636  private boolean keyElementsOnly;
637  private String genDate = DEFAULT_DATE();
638
639
640  public PECodeGenerator(IWorkerContext workerContext) {
641    super();
642    this.workerContext = workerContext;
643  }
644
645  public String getFolder() {
646    return folder;
647  }
648
649
650  public void setFolder(String folder) {
651    this.folder = folder;
652  }
653
654
655  public String getVersion() {
656    return version;
657  }
658
659  public void setVersion(String version) {
660    this.version = version;
661  }
662
663  public String getCanonical() {
664    return canonical;
665  }
666
667  public void setCanonical(String canonical) {
668    this.canonical = canonical;
669  }
670
671
672  public String getPkgName() {
673    return pkgName;
674  }
675
676  public void setPkgName(String pkgName) {
677    this.pkgName = pkgName;
678  }
679
680  public ExtensionPolicy getExtensionPolicy() {
681    return extensionPolicy;
682  }
683
684  public void setExtensionPolicy(ExtensionPolicy extensionPolicy) {
685    this.extensionPolicy = extensionPolicy;
686  }
687
688  public boolean isNarrative() {
689    return narrative;
690  }
691
692  public void setNarrative(boolean narrative) {
693    this.narrative = narrative;
694  }
695
696  public boolean isMeta() {
697    return meta;
698  }
699
700  public void setMeta(boolean meta) {
701    this.meta = meta;
702  }
703
704  public String getLanguage() {
705    return language;
706  }
707
708  public void setLanguage(String language) {
709    this.language = language;
710  }
711
712  public boolean isKeyElementsOnly() {
713    return keyElementsOnly;
714  }
715
716  public void setKeyElementsOnly(boolean keyElementsOnly) {
717    this.keyElementsOnly = keyElementsOnly;
718  }
719
720  public boolean isContained() {
721    return contained;
722  }
723
724  public void setContained(boolean contained) {
725    this.contained = contained;
726  }
727
728  public String getGenDate() {
729    return genDate;
730  }
731
732  public void setGenDate(String genDate) {
733    this.genDate = genDate;
734  }
735
736  private StringBuilder imports = new StringBuilder();
737
738  /**
739   * @throws IOException 
740   * 
741   */
742  public String execute() throws IOException {
743    imports = new StringBuilder();
744    
745    PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical);
746    w(imports, "import java.util.List;");
747    w(imports, "import java.util.ArrayList;");
748    w(imports, "import javax.annotation.Nullable;");
749    w(imports, "import java.util.Date;\r\n");
750    w(imports);
751    w(imports, "import org.hl7.fhir."+version+".context.IWorkerContext;");
752    w(imports, "import org.hl7.fhir."+version+".model.*;");
753    w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder;");
754    w(imports, "import org.hl7.fhir."+version+".profilemodel.PEInstance;");
755    w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder.PEElementPropertiesPolicy;");
756    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.PEGeneratedBase;");
757    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Min;");
758    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Max;");
759    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Label;");
760    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Doco;");
761    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.BindingStrength;");
762    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.ValueSet;");
763    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.MustSupport;");
764    w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Definition;");
765      
766      
767    PEGenClass cls = genClass(source);
768    StringBuilder b = new StringBuilder();
769    w(b, "package "+pkgName+";");
770    w(b);
771    if (source.getProfile().hasCopyright()) {
772      jdoc(b, source.getProfile().getCopyright(), 0, false);
773    }
774    w(b, imports.toString());
775    cls.write(b, source.getProfile().getCopyright());
776    TextFile.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java"));
777    return cls.name+".java";
778  }
779
780  public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) {
781    if (!Utilities.noString(doco)) {
782      String pfx = Utilities.padLeft("", ' ', indent);
783      w(b, pfx+"/*"+(jdoc ? "*" : ""));
784      for (String line : doco.split("\\R")) {
785        for (String nl : naturalLines(line))
786          w(b, pfx+" * "+nl);
787        w(b, pfx+" *");
788      }
789      w(b, pfx+" */");
790    }    
791  }
792
793  private List<String> naturalLines(String line) {
794    List<String> lines = new ArrayList<>();
795    while (line.length() > 80) {
796      int cutpoint = 80;
797      while (cutpoint > 0 && line.charAt(cutpoint) != ' ') {
798        cutpoint--;
799      }
800      if (cutpoint == 0) {
801        cutpoint = 80;
802      } else {
803        cutpoint++;
804      }
805      lines.add(line.substring(0, cutpoint));
806      line = line.substring(cutpoint);
807    }
808    lines.add(line);
809    return lines;
810  }
811
812  private void w(StringBuilder b) {
813    b.append("\r\n");
814
815  }
816
817  private void w(StringBuilder b, String line) {
818    b.append(line);
819    w(b);    
820  }
821
822  private PEGenClass genClass(PEDefinition source) {
823    PEGenClass cls = new PEGenClass();
824    cls.name = source.getProfile().getName();
825    cls.base = source.getProfile().getType();
826    cls.doco = source.documentation();
827    cls.url = source.getProfile().getVersionedUrl();
828    cls.isResource = source.getProfile().getKind() == StructureDefinitionKind.RESOURCE;
829    cls.genId();
830    for (PEDefinition child : source.children()) {
831      if (genForField(source, child)) {
832        cls.defineField(source, child);
833      }
834    }    
835    return cls;
836  }
837
838  private boolean genForField(PEDefinition source, PEDefinition child) {
839    if (child.definition().getBase().getPath().equals("Resource.meta")) {
840      return meta;
841    }
842    if (child.definition().getBase().getPath().equals("DomainResource.text")) {
843      return narrative;
844    }
845    if (child.definition().getBase().getPath().equals("Resource.language")) {
846      return language == null;
847    }
848    if (child.definition().getBase().getPath().endsWith(".extension") || child.definition().getBase().getPath().endsWith(".modifierExtension")) {
849      return extensionPolicy == ExtensionPolicy.Complexes;
850    }
851    if (child.definition().getBase().getPath().equals("DomainResource.contained")) {
852      return contained;
853    }
854    return !keyElementsOnly || (child.isKeyElement());
855  }
856
857
858  private String getPrimitiveType(StructureDefinition sd) {
859
860    if (sd.getType().equals("string"))
861      return "String";
862    if (sd.getType().equals("code"))
863      return "String";
864    if (sd.getType().equals("markdown"))
865      return "String";
866    if (sd.getType().equals("base64Binary"))
867      return "byte[]";
868    if (sd.getType().equals("uri"))
869      return "String";
870    if (sd.getType().equals("url"))
871      return "String";
872    if (sd.getType().equals("canonical"))
873      return "String";
874    if (sd.getType().equals("oid"))
875      return "String";
876    if (sd.getType().equals("integer"))
877      return "int";
878    if (sd.getType().equals("integer64"))
879      return "long";
880    if (sd.getType().equals("unsignedInt"))
881      return "int";
882    if (sd.getType().equals("positiveInt"))
883      return "int";
884    if (sd.getType().equals("boolean"))
885      return "boolean";
886    if (sd.getType().equals("decimal"))
887      return "BigDecimal";
888    if (sd.getType().equals("dateTime"))
889      return "Date";
890    if (sd.getType().equals("date"))
891      return "Date";
892    if (sd.getType().equals("id"))
893      return "String";
894    if (sd.getType().equals("instant"))
895      return "Date";
896    if (sd.getType().equals("time"))
897      return "String";
898
899    return "??";
900  }
901
902}