From 4927d2327392fb50163f4b2bba6f635217647d80 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 12 Apr 2022 09:10:23 +1000 Subject: [PATCH] Add integrityt check utils for R4B integrity --- .../hl7/fhir/r4/utils/IntegrityChecker.java | 241 ++++++++++++++ .../hl7/fhir/r4b/utils/IntegrityChecker.java | 300 ++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/IntegrityChecker.java create mode 100644 org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/IntegrityChecker.java diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/IntegrityChecker.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/IntegrityChecker.java new file mode 100644 index 000000000..777fbebbb --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/IntegrityChecker.java @@ -0,0 +1,241 @@ +package org.hl7.fhir.r4.utils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.hl7.fhir.r4.formats.JsonParser; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.SearchParameter; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r4.utils.IntegrityChecker.SearchParameterNode; +import org.hl7.fhir.r4.utils.IntegrityChecker.SearchParameterNodeSorter; +import org.hl7.fhir.r4.utils.IntegrityChecker.SearchParameterParamNode; +import org.hl7.fhir.r4.utils.IntegrityChecker.SearchParameterParamNodeSorter; +import org.hl7.fhir.r4.utils.IntegrityChecker.StructureDefinitionNode; +import org.hl7.fhir.r4.utils.IntegrityChecker.StructureDefinitionNodeComparer; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.npm.NpmPackage; + +public class IntegrityChecker { + + public class SearchParameterNodeSorter implements Comparator { + + @Override + public int compare(SearchParameterNode o1, SearchParameterNode o2) { + return o1.name.compareTo(o2.name); + } + + } + + public class SearchParameterParamNodeSorter implements Comparator { + + @Override + public int compare(SearchParameterParamNode o1, SearchParameterParamNode o2) { + return o1.sp.getCode().compareTo(o2.sp.getCode()); + } + + } + + public class SearchParameterParamNode { + SearchParameter sp; + boolean only; + public SearchParameterParamNode(SearchParameter sp, boolean only) { + super(); + this.sp = sp; + this.only = only; + } + + + } + + public class SearchParameterNode { + + private String name; + private List params = new ArrayList<>(); + + public SearchParameterNode(String name) { + this.name = name; + } + + } + + public class StructureDefinitionNodeComparer implements Comparator { + + @Override + public int compare(StructureDefinitionNode arg0, StructureDefinitionNode arg1) { + if ( arg0.sd.getType().equals(arg1.sd.getType())) { + return arg0.sd.getName().compareTo(arg1.sd.getName()); + } else { + return arg0.sd.getType().compareTo(arg1.sd.getType()); + } + } + + } + + public class StructureDefinitionNode { + StructureDefinition sd; + List children = new ArrayList<>(); + + public StructureDefinitionNode(StructureDefinition sd) { + this.sd = sd; + } + + } + + private NpmPackage npm; + + public static void main(String[] args) throws Exception { + IntegrityChecker check = new IntegrityChecker(); + check.load(args[0]); + check.check(); + } + + private void check() throws IOException { + checkSD(); + checkSP(); + } + + + private void checkSP() throws IOException { + List list = new ArrayList<>(); + for (String sdn : npm.listResources("SearchParameter")) { + InputStream s = npm.load(sdn); + SearchParameter sp = (SearchParameter) new JsonParser().parse(s); + list.add(sp); + } + msg("Loaded "+list.size()+" resources"); + Map map = new HashMap<>(); + for (SearchParameter sp : list) { + for (CodeType c : sp.getBase()) { + String s = c.primitiveValue(); + if (!map.containsKey(s)) { + map.put(s, new SearchParameterNode(s)); + } + addNode(sp, sp.getBase().size() == 1, map.get(s)); + } + } + for (SearchParameterNode node : sort(map.values())) { + dump(node); + } + } + + + private void dump(SearchParameterNode node) { + msg(node.name); + for (SearchParameterParamNode p : sortP(node.params)) { + String exp = p.sp.getExperimental() ? " **exp!" : ""; + if (p.only) { + msg(" "+p.sp.getCode()+exp); + } else { + msg(" *"+p.sp.getCode()+exp); + } + } + + } + + private List sortP(List params) { + List res = new ArrayList<>(); + res.addAll(params); + Collections.sort(res, new SearchParameterParamNodeSorter()); + return res; + } + + private List sort(Collection values) { + List res = new ArrayList<>(); + res.addAll(values); + Collections.sort(res, new SearchParameterNodeSorter()); + return res; + } + + private void addNode(SearchParameter sp, boolean b, SearchParameterNode node) { + node.params.add(new SearchParameterParamNode(sp, b)); + } + + private void checkSD() throws IOException { + Map map = new HashMap<>(); + for (String sdn : npm.listResources("StructureDefinition")) { + InputStream s = npm.load(sdn); + StructureDefinition sd = (StructureDefinition) new JsonParser().parse(s); + map.put(sd.getUrl(), sd); + } + msg("Loaded "+map.size()+" resources"); + List roots = new ArrayList<>(); + for (StructureDefinition sd : map.values()) { + if (sd.getBaseDefinition() == null || !map.containsKey(sd.getBaseDefinition())) { + StructureDefinitionNode root = new StructureDefinitionNode(sd); + roots.add(root); + analyse(root, map); + } + } + sort(roots); + for (StructureDefinitionNode root : roots) { + describe(root, 0); + } + } + + private void sort(List list) { + Collections.sort(list, new StructureDefinitionNodeComparer()); + + } + + private void analyse(StructureDefinitionNode node, Map map) { + for (StructureDefinition sd : map.values()) { + if (node.sd.getUrl().equals(sd.getBaseDefinition())) { + StructureDefinitionNode c = new StructureDefinitionNode(sd); + node.children.add(c); + analyse(c, map); + } + } + sort(node.children); + } + + private void describe(StructureDefinitionNode node, int level) { + describe(node.sd, level); + for (StructureDefinitionNode c : node.children) { + describe(c, level+1); + } + } + + private void describe(StructureDefinition sd, int level) { + String exp = sd.getExperimental() ? " **exp!" : ""; + if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { + msg(Utilities.padLeft("", ' ', level)+sd.getType()+" / "+sd.getName()+" ("+sd.getUrl()+")"+exp); + } else { + msg(Utilities.padLeft("", ' ', level)+sd.getType()+" : "+sd.getKind()+exp); + } + } + +// private int analyse(Map map, List list, StructureDefinition sd) { +// if (!list.contains(sd)) { +// int level = 0; +// if (sd.hasBaseDefinition()) { +// StructureDefinition p = map.get(sd.getBaseDefinition()); +// if (p == null) { +// msg("Can't find parent "+sd.getBaseDefinition()+" for "+sd.getUrl()); +// } else { +// level = analyse(map, list, p) + 1; +// } +// } +// list.add(sd); +// sd.setUserData("level", level); +// } +// } + + private void msg(String string) { + System.out.println(string); + } + + private void load(String folder) throws IOException { + msg("Loading resources from "+folder); + npm = NpmPackage.fromFolder(folder); + } +} diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/IntegrityChecker.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/IntegrityChecker.java new file mode 100644 index 000000000..87ac3db70 --- /dev/null +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/IntegrityChecker.java @@ -0,0 +1,300 @@ +package org.hl7.fhir.r4b.utils; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.hl7.fhir.r4b.model.CodeType; +import org.hl7.fhir.r4b.model.SearchParameter; +import org.hl7.fhir.r4b.utils.IntegrityChecker.SearchParameterNode; +import org.hl7.fhir.r4b.utils.IntegrityChecker.SearchParameterNodeSorter; +import org.hl7.fhir.r4b.utils.IntegrityChecker.SearchParameterParamNode; +import org.hl7.fhir.r4b.utils.IntegrityChecker.SearchParameterParamNodeSorter; +import org.hl7.fhir.r4b.formats.JsonParser; +import org.hl7.fhir.r4b.formats.XmlParser; +import org.hl7.fhir.r4b.model.StructureDefinition; +import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r4b.utils.IntegrityChecker.StructureDefinitionNode; +import org.hl7.fhir.r4b.utils.IntegrityChecker.StructureDefinitionNodeComparer; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.npm.NpmPackage; + +public class IntegrityChecker { + + public class SearchParameterNodeSorter implements Comparator { + + @Override + public int compare(SearchParameterNode o1, SearchParameterNode o2) { + return o1.name.compareTo(o2.name); + } + + } + + public class SearchParameterParamNodeSorter implements Comparator { + + @Override + public int compare(SearchParameterParamNode o1, SearchParameterParamNode o2) { + return o1.sp.getCode().compareTo(o2.sp.getCode()); + } + + } + + public class SearchParameterParamNode { + SearchParameter sp; + boolean only; + public SearchParameterParamNode(SearchParameter sp, boolean only) { + super(); + this.sp = sp; + this.only = only; + } + + + } + + public class SearchParameterNode { + + private String name; + private List params = new ArrayList<>(); + + public SearchParameterNode(String name) { + this.name = name; + } + + } + + public class StructureDefinitionNodeComparer implements Comparator { + + @Override + public int compare(StructureDefinitionNode arg0, StructureDefinitionNode arg1) { + if ( arg0.sd.getType().equals(arg1.sd.getType())) { + return arg0.sd.getName().compareTo(arg1.sd.getName()); + } else { + return arg0.sd.getType().compareTo(arg1.sd.getType()); + } + } + + } + + public class StructureDefinitionNode { + StructureDefinition sd; + List children = new ArrayList<>(); + + public StructureDefinitionNode(StructureDefinition sd) { + this.sd = sd; + } + + } + + private NpmPackage npm; + + public static void main(String[] args) throws Exception { + IntegrityChecker check = new IntegrityChecker(); + check.load(args[0]); + check.check(args[1]); + } + + private void check(String dst) throws IOException { + checkSD(); + checkSP(); + checkExamplesXml(dst); + checkExamplesJson(dst); + } + + private Map loadZip(InputStream stream) throws IOException { + Map res = new HashMap(); + ZipInputStream zip = new ZipInputStream(stream); + ZipEntry ze; + while ((ze = zip.getNextEntry()) != null) { + int size; + byte[] buffer = new byte[2048]; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + BufferedOutputStream bos = new BufferedOutputStream(bytes, buffer.length); + + while ((size = zip.read(buffer, 0, buffer.length)) != -1) { + bos.write(buffer, 0, size); + } + bos.flush(); + bos.close(); + res.put(ze.getName(), bytes.toByteArray()); + + zip.closeEntry(); + } + zip.close(); + return res; + } + + private void checkExamplesJson(String dst) throws FileNotFoundException, IOException { + Map files = loadZip(new FileInputStream(Utilities.path(dst, "examples-json.zip"))); + for (Entry t : files.entrySet()) { + try { + new JsonParser().parse(t.getValue()); + System.out.print("."); + } catch (Exception e) { + System.out.println(""); + System.out.println("Error parsing "+t.getKey()+": "+e.getMessage()); + } + } + } + +private void checkExamplesXml(String dst) throws FileNotFoundException, IOException { + Map files = loadZip(new FileInputStream(Utilities.path(dst, "examples.zip"))); + for (Entry t : files.entrySet()) { + try { + new XmlParser().parse(t.getValue()); + System.out.print("."); + } catch (Exception e) { + System.out.println(""); + System.out.println("Error parsing "+t.getKey()+": "+e.getMessage()); + } + } + } + + private void checkSP() throws IOException { + List list = new ArrayList<>(); + for (String sdn : npm.listResources("SearchParameter")) { + InputStream s = npm.load(sdn); + SearchParameter sp = (SearchParameter) new JsonParser().parse(s); + list.add(sp); + } + msg("Loaded "+list.size()+" resources"); + Map map = new HashMap<>(); + for (SearchParameter sp : list) { + for (CodeType c : sp.getBase()) { + String s = c.primitiveValue(); + if (!map.containsKey(s)) { + map.put(s, new SearchParameterNode(s)); + } + addNode(sp, sp.getBase().size() == 1, map.get(s)); + } + } + for (SearchParameterNode node : sort(map.values())) { + dump(node); + } + } + + + private void dump(SearchParameterNode node) { + msg(node.name); + for (SearchParameterParamNode p : sortP(node.params)) { + String exp = p.sp.getExperimental() ? " **exp!" : ""; + if (p.only) { + msg(" "+p.sp.getCode()+exp); + } else { + msg(" *"+p.sp.getCode()+exp); + } + } + + } + + private List sortP(List params) { + List res = new ArrayList<>(); + res.addAll(params); + Collections.sort(res, new SearchParameterParamNodeSorter()); + return res; + } + + private List sort(Collection values) { + List res = new ArrayList<>(); + res.addAll(values); + Collections.sort(res, new SearchParameterNodeSorter()); + return res; + } + + private void addNode(SearchParameter sp, boolean b, SearchParameterNode node) { + node.params.add(new SearchParameterParamNode(sp, b)); + } + + private void checkSD() throws IOException { + Map map = new HashMap<>(); + for (String sdn : npm.listResources("StructureDefinition")) { + InputStream s = npm.load(sdn); + StructureDefinition sd = (StructureDefinition) new JsonParser().parse(s); + map.put(sd.getUrl(), sd); + } + msg("Loaded "+map.size()+" resources"); + List roots = new ArrayList<>(); + for (StructureDefinition sd : map.values()) { + if (sd.getBaseDefinition() == null || !map.containsKey(sd.getBaseDefinition())) { + StructureDefinitionNode root = new StructureDefinitionNode(sd); + roots.add(root); + analyse(root, map); + } + } + sort(roots); + for (StructureDefinitionNode root : roots) { + describe(root, 0); + } + } + + private void sort(List list) { + Collections.sort(list, new StructureDefinitionNodeComparer()); + + } + + private void analyse(StructureDefinitionNode node, Map map) { + for (StructureDefinition sd : map.values()) { + if (node.sd.getUrl().equals(sd.getBaseDefinition())) { + StructureDefinitionNode c = new StructureDefinitionNode(sd); + node.children.add(c); + analyse(c, map); + } + } + sort(node.children); + } + + private void describe(StructureDefinitionNode node, int level) { + describe(node.sd, level); + for (StructureDefinitionNode c : node.children) { + describe(c, level+1); + } + } + + private void describe(StructureDefinition sd, int level) { + String exp = sd.getExperimental() ? " **exp!" : ""; + if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { + msg(Utilities.padLeft("", ' ', level)+sd.getType()+" / "+sd.getName()+" ("+sd.getUrl()+")"+exp); + } else { + msg(Utilities.padLeft("", ' ', level)+sd.getType()+" : "+sd.getKind()+exp); + } + } + +// private int analyse(Map map, List list, StructureDefinition sd) { +// if (!list.contains(sd)) { +// int level = 0; +// if (sd.hasBaseDefinition()) { +// StructureDefinition p = map.get(sd.getBaseDefinition()); +// if (p == null) { +// msg("Can't find parent "+sd.getBaseDefinition()+" for "+sd.getUrl()); +// } else { +// level = analyse(map, list, p) + 1; +// } +// } +// list.add(sd); +// sd.setUserData("level", level); +// } +// } + + private void msg(String string) { + System.out.println(string); + } + + private void load(String folder) throws IOException { + msg("Loading resources from "+folder); + npm = NpmPackage.fromFolder(folder); + } + +} \ No newline at end of file