001package org.hl7.fhir.r4.utils;
002
003import java.io.FileInputStream;
004import java.io.FileWriter;
005import java.io.IOException;
006import java.io.InputStream;
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.Collections;
010import java.util.Comparator;
011import java.util.HashMap;
012import java.util.List;
013import java.util.Map;
014
015import org.hl7.fhir.exceptions.FHIRFormatError;
016import org.hl7.fhir.r4.formats.JsonParser;
017import org.hl7.fhir.r4.model.CodeType;
018import org.hl7.fhir.r4.model.ElementDefinition;
019import org.hl7.fhir.r4.model.SearchParameter;
020import org.hl7.fhir.r4.model.StructureDefinition;
021import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
022import org.hl7.fhir.r4.utils.IntegrityChecker.SearchParameterNode;
023import org.hl7.fhir.r4.utils.IntegrityChecker.SearchParameterNodeSorter;
024import org.hl7.fhir.r4.utils.IntegrityChecker.SearchParameterParamNode;
025import org.hl7.fhir.r4.utils.IntegrityChecker.SearchParameterParamNodeSorter;
026import org.hl7.fhir.r4.utils.IntegrityChecker.StructureDefinitionNode;
027import org.hl7.fhir.r4.utils.IntegrityChecker.StructureDefinitionNodeComparer;
028import org.hl7.fhir.utilities.Utilities;
029import org.hl7.fhir.utilities.npm.NpmPackage;
030
031public class IntegrityChecker {
032
033  public class SearchParameterNodeSorter implements Comparator<SearchParameterNode> {
034
035    @Override
036    public int compare(SearchParameterNode o1, SearchParameterNode o2) {
037      return o1.name.compareTo(o2.name);
038    }
039
040  }
041
042  public class SearchParameterParamNodeSorter implements Comparator<SearchParameterParamNode> {
043
044    @Override
045    public int compare(SearchParameterParamNode o1, SearchParameterParamNode o2) {
046       return o1.sp.getCode().compareTo(o2.sp.getCode());
047    }
048
049  }
050
051  public class SearchParameterParamNode {
052    SearchParameter sp;
053    boolean only;
054    public SearchParameterParamNode(SearchParameter sp, boolean only) {
055      super();
056      this.sp = sp;
057      this.only = only;
058    }
059    
060
061  }
062
063  public class SearchParameterNode {
064
065    private String name;
066    private List<SearchParameterParamNode> params = new ArrayList<>();
067
068    public SearchParameterNode(String name) {
069      this.name = name;
070    }
071
072  }
073
074  public class StructureDefinitionNodeComparer implements Comparator<StructureDefinitionNode> {
075
076    @Override
077    public int compare(StructureDefinitionNode arg0, StructureDefinitionNode arg1) {
078      if ( arg0.sd.getType().equals(arg1.sd.getType())) {
079        return arg0.sd.getName().compareTo(arg1.sd.getName());        
080      } else {
081        return arg0.sd.getType().compareTo(arg1.sd.getType());
082      }
083    }
084
085  }
086
087  public class StructureDefinitionNode {
088    StructureDefinition sd;
089    List<StructureDefinitionNode> children = new ArrayList<>();
090    
091    public StructureDefinitionNode(StructureDefinition sd) {
092      this.sd = sd;
093    }
094
095  }
096
097  private NpmPackage npm;
098
099  public static void main(String[] args) throws Exception {
100    IntegrityChecker check = new IntegrityChecker();
101    check.load(args[0]);
102    check.check();
103  }
104
105  private void check() throws IOException {
106    dumpSD(new FileWriter("/Users/grahamegrieve/temp/r4-dump.txt"));
107//    checkSD();
108//    checkSP();
109  }
110  
111
112
113  private void dumpSD(FileWriter w) throws FHIRFormatError, IOException {
114    Map<String, StructureDefinition> map = new HashMap<>();
115    for (String sdn : npm.listResources("StructureDefinition")) {
116      InputStream s = npm.load(sdn);
117      StructureDefinition sd = (StructureDefinition) new JsonParser().parse(s);
118      map.put(sd.getUrl(), sd);
119    }
120    msg("Loaded "+map.size()+" Structures");
121    List<String> structures = new ArrayList<>();
122    for (StructureDefinition sd : map.values()) {
123      structures.add(sd.getUrl());
124    }
125    Collections.sort(structures);
126    
127    for (String sdn : structures) {
128      dumpSD(map.get(sdn), map, w);
129    }
130  }
131
132  private void dumpSD(StructureDefinition sd, Map<String, StructureDefinition> map, FileWriter w) throws IOException {
133    if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
134      StructureDefinition base = sd.hasBaseDefinition() ? map.get(sd.getBaseDefinition()) : null;
135      System.out.println(sd.getType()+(base == null ? "" : " : "+base.getType()));
136      w.append(sd.getType()+(base == null ? "" : " : "+base.getType())+"\r\n");
137      for (ElementDefinition ed : sd.getSnapshot().getElement()) {
138        w.append("  "+Utilities.padLeft("", ' ', Utilities.charCount(ed.getPath(), '.'))+tail(ed.getPath())+" : "+ed.typeSummary()+" ["+ed.getMin()+".."+ed.getMax()+"]"+"\r\n");
139      }
140    }
141  }
142  
143  private String tail(String path) {
144    return path.contains(".") ? path.substring(path.lastIndexOf('.')+1) : path;
145  }
146  
147  private void checkSP() throws IOException {
148    List<SearchParameter> list =  new ArrayList<>();
149    for (String sdn : npm.listResources("SearchParameter")) {
150      InputStream s = npm.load(sdn);
151      SearchParameter sp = (SearchParameter) new JsonParser().parse(s);
152      list.add(sp);
153    }
154    msg("Loaded "+list.size()+" resources");
155    Map<String, SearchParameterNode> map = new HashMap<>();
156    for (SearchParameter sp : list) {
157      for (CodeType c : sp.getBase()) {
158        String s = c.primitiveValue();
159        if (!map.containsKey(s)) {
160          map.put(s, new SearchParameterNode(s));
161        }
162        addNode(sp, sp.getBase().size() == 1, map.get(s));
163      }
164    }   
165    for (SearchParameterNode node : sort(map.values())) {
166      dump(node);
167    }
168  }
169
170
171  private void dump(SearchParameterNode node) {
172    msg(node.name);
173    for (SearchParameterParamNode p : sortP(node.params)) {
174      String exp = p.sp.getExperimental() ? "  **exp!" : "";
175      if (p.only) {
176        msg("  "+p.sp.getCode()+exp);
177      } else {        
178        msg("  *"+p.sp.getCode()+exp);
179      }
180    }
181    
182  }
183
184  private List<SearchParameterParamNode> sortP(List<SearchParameterParamNode> params) {
185    List<SearchParameterParamNode> res = new ArrayList<>();
186    res.addAll(params);
187    Collections.sort(res, new SearchParameterParamNodeSorter());
188    return res;
189  }
190
191  private List<SearchParameterNode> sort(Collection<SearchParameterNode> values) {
192    List<SearchParameterNode> res = new ArrayList<>();
193    res.addAll(values);
194    Collections.sort(res, new SearchParameterNodeSorter());
195    return res;
196  }
197
198  private void addNode(SearchParameter sp, boolean b, SearchParameterNode node) {
199    node.params.add(new SearchParameterParamNode(sp, b));   
200  }
201
202  private void checkSD() throws IOException {
203    Map<String, StructureDefinition> map = new HashMap<>();
204    for (String sdn : npm.listResources("StructureDefinition")) {
205      InputStream s = npm.load(sdn);
206      StructureDefinition sd = (StructureDefinition) new JsonParser().parse(s);
207      map.put(sd.getUrl(), sd);
208    }
209    msg("Loaded "+map.size()+" resources");
210    List<StructureDefinitionNode> roots = new ArrayList<>();
211    for (StructureDefinition sd : map.values()) {
212      if (sd.getBaseDefinition() == null || !map.containsKey(sd.getBaseDefinition())) {
213        StructureDefinitionNode root = new StructureDefinitionNode(sd);
214        roots.add(root);
215        analyse(root, map);
216      }
217    }
218    sort(roots);
219    for (StructureDefinitionNode root : roots) {
220      describe(root, 0);
221    }
222  }
223
224  private void sort(List<StructureDefinitionNode> list) {
225    Collections.sort(list, new StructureDefinitionNodeComparer());
226    
227  }
228
229  private void analyse(StructureDefinitionNode node, Map<String, StructureDefinition> map) {
230    for (StructureDefinition sd : map.values()) {
231      if (node.sd.getUrl().equals(sd.getBaseDefinition())) {
232        StructureDefinitionNode c = new StructureDefinitionNode(sd);
233        node.children.add(c);
234        analyse(c, map);
235      }
236    }
237    sort(node.children);
238  }
239
240  private void describe(StructureDefinitionNode node, int level) {
241    describe(node.sd, level);
242    for (StructureDefinitionNode c : node.children) {
243      describe(c, level+1);
244    }
245  }
246
247  private void describe(StructureDefinition sd, int level) {
248    String exp = sd.getExperimental() ? "  **exp!" : "";
249    if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
250      msg(Utilities.padLeft("", ' ', level)+sd.getType()+" / "+sd.getName()+" ("+sd.getUrl()+")"+exp);      
251    } else {
252      msg(Utilities.padLeft("", ' ', level)+sd.getType()+" : "+sd.getKind()+exp);
253    }
254  }
255
256//  private int analyse(Map<String, StructureDefinition> map, List<StructureDefinition> list, StructureDefinition sd) {
257//    if (!list.contains(sd)) {
258//      int level = 0;
259//      if (sd.hasBaseDefinition()) {
260//        StructureDefinition p = map.get(sd.getBaseDefinition());
261//        if (p == null) {
262//          msg("Can't find parent "+sd.getBaseDefinition()+" for "+sd.getUrl());
263//        } else {
264//          level = analyse(map, list, p) + 1;
265//        }
266//      }
267//      list.add(sd);
268//      sd.setUserData("level", level);
269//    }
270//  }
271
272  private void msg(String string) {
273    System.out.println(string);    
274  }
275
276  private void load(String folder) throws IOException {
277    msg("Loading resources from "+folder);
278    npm = NpmPackage.fromFolder(folder);
279  }
280}