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}