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