All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cqframework.cql.elm.requirements.ElmRequirements Maven / Gradle / Ivy

package org.cqframework.cql.elm.requirements;

import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import org.hl7.elm.r1.*;

public class ElmRequirements extends ElmRequirement {

    private HashSet requirements = new LinkedHashSet();

    public Iterable getRequirements() {
        return requirements;
    }

    public ElmRequirements(VersionedIdentifier libraryIdentifier, Element element) {
        super(libraryIdentifier, element);
    }

    public void reportRequirement(ElmRequirement requirement) {
        if (requirement instanceof ElmRequirements) {
            for (ElmRequirement r : ((ElmRequirements) requirement).getRequirements()) {
                reportRequirement(r);
            }
        } else {
            if (requirement != null) {
                requirements.add(requirement);
            }
        }
    }

    public Iterable getUsingDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof UsingDef)
                .collect(Collectors.toList());
    }

    public Iterable getIncludeDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof IncludeDef)
                .collect(Collectors.toList());
    }

    public Iterable getCodeSystemDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof CodeSystemDef)
                .collect(Collectors.toList());
    }

    public Iterable getValueSetDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof ValueSetDef)
                .collect(Collectors.toList());
    }

    public Iterable getCodeDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof CodeDef)
                .collect(Collectors.toList());
    }

    public Iterable getConceptDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof ConceptDef)
                .collect(Collectors.toList());
    }

    public Iterable getParameterDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof ParameterDef)
                .collect(Collectors.toList());
    }

    public Iterable getExpressionDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof ExpressionDef && !(x.getElement() instanceof FunctionDef))
                .collect(Collectors.toList());
    }

    public Iterable getFunctionDefs() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof FunctionDef)
                .collect(Collectors.toList());
    }

    public Iterable getRetrieves() {
        return requirements.stream()
                .filter(x -> x.getElement() instanceof Retrieve)
                .collect(Collectors.toList());
    }

    /*
    Collapse requirements: Determine the unique set of covering requirements given this set of requirements
    For dependencies, ensure dependencies are unique
    For parameters, unique by qualified name
    For expressions, unique by qualified name
    For data requirements, collapse according to the CQL specification: https://cql.hl7.org/05-languagesemantics.html#artifact-data-requirements
     */
    public ElmRequirements collapse(ElmRequirementsContext context) {
        ElmRequirements result = new ElmRequirements(this.libraryIdentifier, this.element);

        // UsingDefs
        Map models = new LinkedHashMap();
        for (ElmRequirement r : getUsingDefs()) {
            UsingDef ud = (UsingDef) r.getElement();
            String uri = ud.getUri() + (ud.getVersion() != null ? "|" + ud.getVersion() : "");
            if (!models.containsKey(uri)) {
                models.put(uri, r);
                // TODO: How to report duplicate references, potentially warn about different names?
            }
        }

        for (ElmRequirement r : models.values()) {
            result.reportRequirement(r);
        }

        // IncludeDefs
        Map libraries = new LinkedHashMap();
        for (ElmRequirement r : getIncludeDefs()) {
            IncludeDef id = (IncludeDef) r.getElement();
            String uri = id.getPath() + (id.getVersion() != null ? "|" + id.getVersion() : "");
            if (!libraries.containsKey(uri)) {
                libraries.put(uri, r);
                // TODO: How to report duplicate references, potentially warn about different names?
            }
        }

        for (ElmRequirement r : libraries.values()) {
            result.reportRequirement(r);
        }

        // CodeSystemDefs
        Map codeSystems = new LinkedHashMap();
        for (ElmRequirement r : getCodeSystemDefs()) {
            CodeSystemDef csd = (CodeSystemDef) r.getElement();
            String uri = csd.getId() + (csd.getVersion() != null ? "|" + csd.getVersion() : "");
            if (!codeSystems.containsKey(uri)) {
                codeSystems.put(uri, r);
                // TODO: How to report duplicate references, potentially warn about different names?
            }
        }

        for (ElmRequirement r : codeSystems.values()) {
            result.reportRequirement(r);
        }

        // ValueSetDefs
        Map valueSets = new LinkedHashMap();
        for (ElmRequirement r : getValueSetDefs()) {
            ValueSetDef vsd = (ValueSetDef) r.getElement();
            String uri = vsd.getId() + (vsd.getVersion() != null ? "|" + vsd.getVersion() : "");
            if (!valueSets.containsKey(uri)) {
                valueSets.put(uri, r);
                // TODO: How to report duplicate references, potentially warn about different names?
            }
        }

        for (ElmRequirement r : valueSets.values()) {
            result.reportRequirement(r);
        }

        // ConceptDefs
        Map concepts = new LinkedHashMap();
        for (ElmRequirement r : getConceptDefs()) {
            ConceptDef cd = (ConceptDef) r.getElement();
            String uri = String.format(
                    "%s%s.%s",
                    r.getLibraryIdentifier().getSystem() != null
                            ? r.getLibraryIdentifier().getSystem() + "."
                            : "",
                    r.getLibraryIdentifier().getId(),
                    cd.getName());
            if (!concepts.containsKey(uri)) {
                concepts.put(uri, r);
                // TODO: How to report duplicate references, potentially warn about different names?
            }
        }

        for (ElmRequirement r : concepts.values()) {
            result.reportRequirement(r);
        }

        // CodeDefs
        Map codes = new LinkedHashMap();
        for (ElmRequirement r : getCodeDefs()) {
            CodeDef cd = (CodeDef) r.getElement();
            String uri = String.format(
                    "%s#%s",
                    // TODO: Look up CodeSystemDef to determine code system URI
                    cd.getCodeSystem().getName(), cd.getId());

            if (!codes.containsKey(uri)) {
                codes.put(uri, r);
                // TODO: How to report duplicate references, potentially warn about different names?
            }
        }

        for (ElmRequirement r : codes.values()) {
            result.reportRequirement(r);
        }

        // ParameterDefs
        // NOTE: This purposely consolidates on unqualified name, the use case is from the perspective of a particular
        // artifact,
        // parameters of the same name should be bound to the same input (i.e. single input parameter namespace)
        Map parameters = new LinkedHashMap();
        for (ElmRequirement r : getParameterDefs()) {
            ParameterDef pd = (ParameterDef) r.getElement();
            String uri = pd.getName();

            if (!parameters.containsKey(uri)) {
                parameters.put(uri, r);
                // TODO: How to report duplicate references, potentially warn about different names?
                // TODO: Note that it is potentially a hidden error here if parameters with the same name have different
                // types
            }
        }

        for (ElmRequirement r : parameters.values()) {
            result.reportRequirement(r);
        }

        // ExpressionDefs
        Map expressions = new LinkedHashMap();
        for (ElmRequirement r : getExpressionDefs()) {
            ExpressionDef ed = (ExpressionDef) r.getElement();
            String uri = String.format(
                    "%s%s.%s",
                    r.getLibraryIdentifier().getSystem() != null
                            ? r.getLibraryIdentifier().getSystem() + "."
                            : "",
                    r.getLibraryIdentifier().getId(),
                    ed.getName());

            if (!expressions.containsKey(uri)) {
                expressions.put(uri, r);
                // TODO: Do we need to report all the libraries that referred to this?
            }
        }

        for (ElmRequirement r : expressions.values()) {
            result.reportRequirement(r);
        }

        // FunctionDefs
        Map functions = new LinkedHashMap();
        for (ElmRequirement r : getFunctionDefs()) {
            FunctionDef fd = (FunctionDef) r.getElement();
            // TODO: Include overloads...
            String uri = String.format(
                    "%s%s.%s()",
                    r.getLibraryIdentifier().getSystem() != null
                            ? r.getLibraryIdentifier().getSystem() + "."
                            : "",
                    r.getLibraryIdentifier().getId(),
                    fd.getName());

            if (!functions.containsKey(uri)) {
                functions.put(uri, r);
                // TODO: Do we need to report all the libraries that referred to this?
            }
        }

        for (ElmRequirement r : functions.values()) {
            result.reportRequirement(r);
        }

        // Retrieves
        // Sort retrieves by type/profile to reduce search space
        LinkedHashMap> retrievesByType = new LinkedHashMap>();
        List unboundRequirements = new ArrayList<>();
        for (ElmRequirement r : getRetrieves()) {
            Retrieve retrieve = (Retrieve) r.getElement();
            if (retrieve.getDataType() != null) {
                String typeUri = retrieve.getTemplateId() != null
                        ? retrieve.getTemplateId()
                        : retrieve.getDataType().getLocalPart();
                List typeRetrieves = null;
                if (retrievesByType.containsKey(typeUri)) {
                    typeRetrieves = retrievesByType.get(typeUri);
                } else {
                    typeRetrieves = new ArrayList();
                    retrievesByType.put(typeUri, typeRetrieves);
                }
                typeRetrieves.add(r);
            } else {
                unboundRequirements.add(r);
            }
        }

        // Distribute unbound property requirements
        // If an ElmDataRequirement has a retrieve that does not have a dataType (i.e. it is not a direct data access
        // layer retrieve
        // but rather is the result of requirements inference), then distribute the property references it contains to
        // all data layer-bound retrieves of the same type
        // In other words, we can't unambiguously tie the property reference to any particular retrieve of that type,
        // so apply it to all of them
        for (ElmRequirement requirement : unboundRequirements) {
            if (requirement instanceof ElmDataRequirement) {
                ElmDataRequirement dataRequirement = (ElmDataRequirement) requirement;
                if (dataRequirement.hasProperties()) {
                    String typeUri = context.getTypeResolver()
                            .getTypeUri(dataRequirement.getRetrieve().getResultType());
                    if (typeUri != null) {
                        List typeRequirements = retrievesByType.get(typeUri);
                        if (typeRequirements != null) {
                            for (ElmRequirement typeRequirement : typeRequirements) {
                                if (typeRequirement instanceof ElmDataRequirement) {
                                    ElmDataRequirement typeDataRequirement = (ElmDataRequirement) typeRequirement;
                                    for (Property p : dataRequirement.getProperties()) {
                                        typeDataRequirement.addProperty(p);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        // Equivalent
        // Has the same context, type/profile, code path and date path
        // If two retrieves are "equivalent" they can be merged
        // TODO: code/date-range consolidation
        Map requirementIdMap = new HashMap<>();
        for (Map.Entry> entry : retrievesByType.entrySet()) {
            // Determine unique set per type/profile
            CollapsedElmRequirements collapsedRetrieves = new CollapsedElmRequirements();
            for (ElmRequirement requirement : entry.getValue()) {
                collapsedRetrieves.add(requirement);
            }

            // Collect target mappings
            for (Map.Entry idMapEntry :
                    collapsedRetrieves.getRequirementIdMap().entrySet()) {
                requirementIdMap.put(idMapEntry.getKey(), idMapEntry.getValue());
            }

            for (ElmRequirement r : collapsedRetrieves.getUniqueRequirements()) {
                result.reportRequirement(r);
            }
        }

        // Fixup references in the resulting requirements
        for (ElmRequirement requirement : result.getRequirements()) {
            if (requirement.getElement() instanceof Retrieve) {
                Retrieve r = ((Retrieve) requirement.getElement());
                if (r.getIncludedIn() != null) {
                    String mappedId = requirementIdMap.get(r.getIncludedIn());
                    if (mappedId != null) {
                        r.setIncludedIn(mappedId);
                    }
                }

                for (IncludeElement includeElement : r.getInclude()) {
                    if (includeElement.getIncludeFrom() != null) {
                        String mappedId = requirementIdMap.get(includeElement.getIncludeFrom());
                        if (mappedId != null) {
                            includeElement.setIncludeFrom(mappedId);
                        }
                    }
                }
            }
        }

        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy