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