diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..0227cb526 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,47 @@ +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + paths: + - "Src/java/**" + pull_request: + branches: [ "master" ] + paths: + - "Src/java/**" + schedule: + - cron: '36 15 * * 1' + +jobs: + CodeQLBuild: + runs-on: 'ubuntu-latest' + permissions: + security-events: write + packages: read + actions: read + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + cache: 'gradle' + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: java-kotlin + source-root: Src/java + - name: Autobuild CodeQL + uses: github/codeql-action/autobuild@v3 + with: + working-directory: Src/java + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:java-kotlin" diff --git a/.gitignore b/.gitignore index 417f110ae..6cc2d03ed 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ node_modules .nyc_output coverage gen +.kotlin diff --git a/Src/java-quickstart/README.md b/Src/java-quickstart/README.md index 94b8ecf21..ff3f9792d 100644 --- a/Src/java-quickstart/README.md +++ b/Src/java-quickstart/README.md @@ -20,18 +20,6 @@ To clean up the artifacts: ./gradlew clean -# Generating IDE Projects - -You can generate an IDE project for IntelliJ IDEA: - - ./gradlew idea - -This is preferred over having IDEA load the gradle file directly, because it (a) generates the CQL -libraries, and (b) generates the configuration for the IDEA ANTLR plugin. To open the project in -IntelliJ IDEA, launch IDEA and open the `java-quickstart.ipr` file. - -See further below for installing and using the ANTLR plugin for IDEA. - # Executing the Sample Code You can execute the sample code using the `gradlew` command or a script generated by gradle. diff --git a/Src/java/README.md b/Src/java/README.md index 83caae007..65921479a 100644 --- a/Src/java/README.md +++ b/Src/java/README.md @@ -19,7 +19,7 @@ It contains the following sub-projects: # Building the Project This project uses the [Gradle](http://www.gradle.org/) build system. A gradle wrapper, which automatically downloads -and uses an instance of gradle, is provided for convenience. To build the project, install the [JDK 11](https://adoptium.net/temurin/releases/?version=11), clone this +and uses an instance of gradle, is provided for convenience. To build the project, install the [JDK 17](https://adoptium.net/temurin/releases/?version=17), clone this repository, then execute this command from within this directory: ./gradlew build @@ -31,15 +31,6 @@ To clean up the build artifacts: ./gradlew clean -# Generating IDE Projects - -You can generate an IDE project for IntelliJ IDEa: - - ./gradlew idea - -In addition to creating project modules for cql, model, quick, elm, cql-to-elm, and the tools projects, this will also import project -modules for the CQL grammar and examples. - # Executing the Sample Code You can execute the sample code using the `gradlew` command or a script generated by gradle. diff --git a/Src/java/build.gradle b/Src/java/build.gradle deleted file mode 100644 index 938708ac2..000000000 --- a/Src/java/build.gradle +++ /dev/null @@ -1,55 +0,0 @@ -plugins { - id 'idea' - id 'eclipse' - id "org.sonarqube" version "4.4.1.3373" -} - -sonar { - properties { - property "sonar.projectKey", "cqframework_clinical_quality_language" - property "sonar.organization", "cqframework" - property "sonar.host.url", "https://sonarcloud.io" - } -} - -idea { - project { - languageLevel = JavaVersion.VERSION_11 - ipr { - withXml { provider -> - provider.node.component.find { it.@name == 'VcsDirectoryMappings' }.mapping.@vcs = 'Git' - } - - whenMerged { project -> - def examples = new org.gradle.plugins.ide.idea.model.Path('file://$PROJECT_DIR$/examples.iml', 'file://$PROJECT_DIR$/examples.iml', '$PROJECT_DIR$/examples.iml') - if ((project.modulePaths.findAll { p -> p.url == examples.url }).empty) project.modulePaths.add(examples) - - def grammar = new org.gradle.plugins.ide.idea.model.Path('file://$PROJECT_DIR$/grammar.iml', 'file://$PROJECT_DIR$/grammar.iml', '$PROJECT_DIR$/grammar.iml') - if ((project.modulePaths.findAll { p -> p.url == grammar.url }).empty) project.modulePaths.add(grammar) - - def cqllm = new org.gradle.plugins.ide.idea.model.Path('file://$PROJECT_DIR$/cql-lm.iml', 'file://$PROJECT_DIR$/cql-lm.iml', '$PROJECT_DIR$/cql-lm.iml') - if ((project.modulePaths.findAll { p -> p.url == cqllm.url }).empty) project.modulePaths.add(cqllm) - } - } - } - workspace { - iws { - withXml { provider -> - def props = provider.node.component.find { it.@name == 'PropertiesComponent' } - - def propMap = [ - '$PROJECT_DIR$/../grammar/cql.g4::/output-dir' : '$PROJECT_DIR$/cql/src/generated/java', - '$PROJECT_DIR$/../grammar/cql.g4::/lib-dir' : '$PROJECT_DIR$/../grammar', - '$PROJECT_DIR$/../grammar/cql.g4::/package' : 'org.cqframework.cql.gen', - '$PROJECT_DIR$/../grammar/cql.g4::/gen-listener' : 'true', - '$PROJECT_DIR$/../grammar/cql.g4::/gen-visitor' : 'true' - ] - - propMap.each() { key, value -> - if (! props.property.find { it.@name == key }) - props.appendNode('property', ['name' : key, 'value' : value]) - } - } - } - } -} \ No newline at end of file diff --git a/Src/java/build.gradle.kts b/Src/java/build.gradle.kts new file mode 100644 index 000000000..b4a913043 --- /dev/null +++ b/Src/java/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + kotlin("jvm") + id("org.sonarqube") version "4.4.1.3373" +} + +sonar { + properties { + property("sonar.projectKey", "cqframework_clinical_quality_language") + property("sonar.organization", "cqframework") + property("sonar.host.url", "https://sonarcloud.io") + } +} + +repositories { + mavenCentral() +} \ No newline at end of file diff --git a/Src/java/buildSrc/build.gradle b/Src/java/buildSrc/build.gradle deleted file mode 100644 index b7ecf8ca3..000000000 --- a/Src/java/buildSrc/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -plugins { - id 'groovy-gradle-plugin' -} - -repositories { - mavenCentral() - gradlePluginPortal() -} - -dependencies { - implementation 'net.ltgt.gradle:gradle-errorprone-plugin:3.1.0' - implementation 'ru.vyarus:gradle-animalsniffer-plugin:1.7.0' - implementation 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.14' - implementation 'com.diffplug.spotless:spotless-plugin-gradle:6.23.3' -} \ No newline at end of file diff --git a/Src/java/buildSrc/build.gradle.kts b/Src/java/buildSrc/build.gradle.kts new file mode 100644 index 000000000..3b916ec2e --- /dev/null +++ b/Src/java/buildSrc/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20") + implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.9.20") + implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.3") + implementation("net.ltgt.gradle:gradle-errorprone-plugin:3.1.0") + implementation("ru.vyarus:gradle-animalsniffer-plugin:1.7.2") + implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.14") + implementation("com.diffplug.spotless:spotless-plugin-gradle:6.25.0") + implementation("com.github.node-gradle:gradle-node-plugin:7.1.0") + implementation("org.jetbrains.kotlin:kotlin-serialization:2.0.20") +} + +kotlin { + jvmToolchain(17) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} \ No newline at end of file diff --git a/Src/java/buildSrc/settings.gradle.kts b/Src/java/buildSrc/settings.gradle.kts new file mode 100644 index 000000000..f58987ac8 --- /dev/null +++ b/Src/java/buildSrc/settings.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/groovy/cql.fhir-conventions.gradle b/Src/java/buildSrc/src/main/groovy/cql.fhir-conventions.gradle deleted file mode 100644 index 80622c10c..000000000 --- a/Src/java/buildSrc/src/main/groovy/cql.fhir-conventions.gradle +++ /dev/null @@ -1,31 +0,0 @@ -plugins { - id 'cql.library-conventions' -} - -ext { - hapiVersion = project['hapi.version'] -} - -dependencies { - api platform("ca.uhn.hapi.fhir:hapi-fhir-bom:${hapiVersion}") { - exclude group: 'org.eclipse.jetty' - exclude group: 'xpp3' - exclude group: 'org.junit' - } - - implementation "ca.uhn.hapi.fhir:hapi-fhir-base" - implementation "ca.uhn.hapi.fhir:hapi-fhir-converter" - implementation "ca.uhn.hapi.fhir:hapi-fhir-structures-hl7org-dstu2" - implementation "ca.uhn.hapi.fhir:hapi-fhir-structures-dstu2" - implementation "ca.uhn.hapi.fhir:hapi-fhir-structures-dstu3" - implementation "ca.uhn.hapi.fhir:hapi-fhir-structures-r4" - implementation "ca.uhn.hapi.fhir:hapi-fhir-structures-r5" - - // This is to align with the FHIR core dependencies - // Note that this dependency hasn't been updated since 2013 - // we probably need to standardize on a fork up the dependency chain - implementation ('org.ogce:xpp3:1.1.6') { - exclude group: 'org.junit' - exclude group: 'org.hamcrest' - } -} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/groovy/cql.java-conventions.gradle b/Src/java/buildSrc/src/main/groovy/cql.java-conventions.gradle deleted file mode 100644 index 5a8cfc4aa..000000000 --- a/Src/java/buildSrc/src/main/groovy/cql.java-conventions.gradle +++ /dev/null @@ -1,181 +0,0 @@ -plugins { - id 'java' - id 'maven-publish' - id 'jacoco' - id 'signing' - id 'cql.sca-conventions' - id 'com.diffplug.spotless' -} - -java { - withJavadocJar() - withSourcesJar() -} - -compileJava { - options.release = 11 -} - -repositories { - mavenLocal() - mavenCentral() - maven { - url "https://oss.sonatype.org/content/repositories/snapshots" - } -} - -dependencies { - implementation 'org.slf4j:slf4j-api:2.0.13' - testImplementation 'org.hamcrest:hamcrest-all:1.3' - testImplementation 'uk.co.datumedge:hamcrest-json:0.2' - testImplementation(platform('org.junit:junit-bom:5.10.2')) - testImplementation('org.junit.jupiter:junit-jupiter') - testImplementation 'org.slf4j:slf4j-simple:2.0.13' - - // These are JAXB dependencies excluded because the libraries need to work - // on Android. But for test purposes we use them pretty much everywhere. - testRuntimeOnly 'org.eclipse.persistence:org.eclipse.persistence.moxy:4.0.2' - testRuntimeOnly 'org.eclipse.parsson:parsson:1.1.5' - testRuntimeOnly('org.junit.platform:junit-platform-launcher') -} - -jar { - manifest { - attributes('Implementation-Title': project.name, - 'Implementation-Version': project.version, - 'Specification-Title': 'HL7 Clinical Quality Language (CQL)', - 'Specification-Version': project['specification.version']) - } -} - -jacoco { - toolVersion = "0.8.11" -} - -test { - useJUnitPlatform() - testLogging { - events "skipped", "failed" - } - finalizedBy jacocoTestReport // report is always generated after tests run -} - -jacocoTestReport { - reports { - xml.required = true - } - dependsOn test // tests are required to run before generating the report -} - -tasks.withType(Javadoc) { - options.addStringOption('Xdoclint:none', '-quiet') -} - -tasks.withType(JavaCompile) { - options.compilerArgs << '-Xlint:unchecked' - options.deprecation = true -} - -spotless { - java { - targetExclude '**/generated/**' - palantirJavaFormat() - } -} - -/* -A few things: - - You must have an OSSRH Jira account (https://issues.sonatype.org/secure/Signup!default.jspa) - - Your account must have privileges to upload info.cqframework artifacts (https://issues.sonatype.org/browse/OSSRH-15514) - - You must have a gpg key (http://central.sonatype.org/pages/working-with-pgp-signatures.html) - - You must set your account info and GPG key in your user's gradle.properties file. For example: - ossrhUsername=foo - ossrhPassword=b@r - signing.keyId=24875D73 - signing.password=secret - signing.secretKeyRingFile=/Users/me/.gnupg/secring.gpg - - If the library version ends with '-SNAPSHOT', it will be deployed to the snapshot repository, else it will be - deployed to the staging repository (which you then must manually release http://central.sonatype.org/pages/releasing-the-deployment.html). - - Repo for snapshots and releases for the translator modules: https://oss.sonatype.org/content/groups/public/info/cqframework/ - - Repo for snapshots, releases, and staged releases for the translator modules: https://oss.sonatype.org/content/groups/staging/info/cqframework/ - - Repo for snapshots and releases for the engine modules: https://oss.sonatype.org/content/groups/public/org/opencds/cqf/cql/ - - Repo for snapshots, releases, and staged releases for the engine modules: https://oss.sonatype.org/content/groups/staging/org/opencds/cqf/cql/ - */ -publishing { - publications { - mavenDeployer(MavenPublication) { - from components.java - - pom { - name = project.name - packaging = 'jar' - description = "The ${project.name} library for the Clinical Quality Language Java reference implementation" - url = 'http://cqframework.info' - - scm { - connection = 'scm:git:git@github.com:cqframework/clinical_quality_language.git' - developerConnection = 'scm:git:git@github.com:cqframework/clinical_quality_language.git' - url = 'git@github.com:cqframework/clinical_quality_language.git' - } - - licenses { - license { - name ='The Apache License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - - developers { - developer { - name = 'Bryn Rhodes' - } - developer { - name = 'Chris Moesel' - } - developer { - name = 'Rob Dingwell' - } - developer { - name = 'Jason Walonoski' - } - developer { - name = 'Marc Hadley' - } - developer { - name = 'Jonathan Percival' - } - } - } - } - } - repositories { - maven { - credentials { - username project.hasProperty("ossrhUsername") ? ossrhUsername : System.getenv("OSSRH_USERNAME") != null ? System.getenv("OSSRH_USERNAME") : "" - password project.hasProperty("ossrhPassword") ? ossrhPassword : System.getenv("OSSRH_TOKEN") != null ? System.getenv("OSSRH_TOKEN") : "" - } - - /* Use these to test locally (but don't forget to comment out others!) - def releasesRepoUrl = "file://${buildDir}/repo" - def snapshotsRepoUrl = "file://${buildDir}/ssRepo" - */ - - // change URLs to point to your repos, e.g. http://my.org/repo - def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - } - } -} - -signing { - if (!version.endsWith('SNAPSHOT')){ - sign publishing.publications.mavenDeployer - } -} - -javadoc { - if(JavaVersion.current().isJava9Compatible()) { - options.addBooleanOption('html5', true) - } -} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/groovy/cql.library-conventions.gradle b/Src/java/buildSrc/src/main/groovy/cql.library-conventions.gradle deleted file mode 100644 index 52f3ea967..000000000 --- a/Src/java/buildSrc/src/main/groovy/cql.library-conventions.gradle +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - id 'java-library' - id 'ru.vyarus.animalsniffer' - id 'cql.java-conventions' -} - -ext { - androidApiLevel = project['android.api.level'] -} - - -dependencies { - // Various libraries for Android signatures are available, Jackson uses this one - signature "com.toasttab.android:gummy-bears-api-${androidApiLevel}:0.5.0@signature" -} - -animalsniffer { - sourceSets = [sourceSets.main] -} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/groovy/cql.sca-conventions.gradle b/Src/java/buildSrc/src/main/groovy/cql.sca-conventions.gradle deleted file mode 100644 index d23a15b6b..000000000 --- a/Src/java/buildSrc/src/main/groovy/cql.sca-conventions.gradle +++ /dev/null @@ -1,34 +0,0 @@ -plugins { - id 'java' - id 'net.ltgt.errorprone' - id 'checkstyle' -} - -ext { - errorproneVersion = '2.24.1' -} - -dependencies { - errorprone "com.google.errorprone:error_prone_core:${errorproneVersion}" -} - -checkstyleMain.source = "src/main/java" -// TODO: Have a conversation with the team about enforcing checkstyle in tests -checkstyleTest.enabled = false - -tasks.withType(JavaCompile).configureEach { - // TODO: Change this once we fix all the errors - options.errorprone.disableAllWarnings = true - options.errorprone.disableWarningsInGeneratedCode = true - options.errorprone.disable("DoubleBraceInitialization") - // This applies recommended fixes to the source code in place - // options.errorprone.errorproneArgs = ["-XepPatchLocation:IN_PLACE"] -} - -tasks { - compileTestJava { - // TODO: Talk to the team about warnings in tests - options.errorprone.disableAllWarnings = true - options.errorprone.disableWarningsInGeneratedCode = true - } -} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/groovy/cql.xjc-conventions.gradle b/Src/java/buildSrc/src/main/groovy/cql.xjc-conventions.gradle deleted file mode 100644 index 2c598fff9..000000000 --- a/Src/java/buildSrc/src/main/groovy/cql.xjc-conventions.gradle +++ /dev/null @@ -1,69 +0,0 @@ -plugins { - id 'java' -} - -configurations { - xjc -} - -dependencies { - xjc 'codes.rafael.jaxb2_commons:jaxb2-basics-ant:3.0.0' - xjc 'codes.rafael.jaxb2_commons:jaxb2-basics:3.0.0' - xjc 'codes.rafael.jaxb2_commons:jaxb2-fluent-api:3.0.0' - // Eclipse has taken over all Java EE reference components - // https://www.infoworld.com/article/3310042/eclipse-takes-over-all-java-ee-reference-components.html - // https://wiki.eclipse.org/Jakarta_EE_Maven_Coordinates - xjc 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.1' - xjc 'org.glassfish.jaxb:jaxb-xjc:3.0.2' - xjc 'org.glassfish.jaxb:jaxb-runtime:4.0.3' - xjc 'org.eclipse.persistence:org.eclipse.persistence.moxy:4.0.2' - xjc 'org.slf4j:slf4j-simple:2.0.13' - api 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.1' - api 'codes.rafael.jaxb2_commons:jaxb2-basics-runtime:3.0.0' -} - -ext.xjc = [ - destDir: "${buildDir}/generated/sources/$name/main/java", - args: '-disableXmlSecurity -Xfluent-api -Xequals -XhashCode -XtoString -Xsetters -Xsetters-mode=direct' -] - - -task generateSources { - - outputs.dir xjc.destDir - - doLast { - file(xjc.destDir).mkdirs() - - ant.taskdef(name: 'xjc', classname: 'org.jvnet.jaxb2_commons.xjc.XJC2Task', classpath: configurations.xjc.asPath) - - /* The above sets up the task, but the real work of the task should be specified in the subproject using - generateSources.doLast. For example: - generateSources.doLast { - ant.xjc(destdir: xjc.destDir, schema: "${projectDir}/path/to/file.xsd") { - arg(line: xjc.args) - } - } - */ - } -} - -compileJava { - dependsOn generateSources -} - -sourcesJar { - dependsOn generateSources -} - -sourceSets { - main { - java { - srcDir(xjc.destDir) - } - } -} - -clean { - delete xjc.destDir -} diff --git a/Src/java/buildSrc/src/main/kotlin/XjcTask.kt b/Src/java/buildSrc/src/main/kotlin/XjcTask.kt new file mode 100644 index 000000000..837532c6b --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/XjcTask.kt @@ -0,0 +1,37 @@ +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.* +import org.gradle.process.ExecOperations; +import javax.inject.Inject; + +open class XjcTask @Inject constructor( + private val execOperations: ExecOperations +) : DefaultTask() { + @Input + lateinit var schema: String + + @Input + var extraArgs : List = emptyList() + + @Input + var binding: String = "" + + @OutputDirectory + lateinit var outputDir: String + + @TaskAction + fun generate() { + var bindingArgs : List = emptyList(); + if (binding.isNotBlank()) { + bindingArgs = listOf("-b", binding) + } + + val defaultArgs = listOf("-quiet", "-disableXmlSecurity", "-Xfluent-api", "-Xequals" ,"-XhashCode", "-XtoString" , "-Xsetters", "-Xsetters-mode=direct") + val options = listOf("-d", outputDir, schema) + bindingArgs + defaultArgs + extraArgs + + execOperations.javaexec { + mainClass.set("com.sun.tools.xjc.XJCFacade") + classpath = project.configurations.getByName("xjc") + args = options + } + } +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/kotlin/cql.fhir-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.fhir-conventions.gradle.kts new file mode 100644 index 000000000..af71bfe9a --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.fhir-conventions.gradle.kts @@ -0,0 +1,40 @@ +plugins { + id("cql.library-conventions") +} + +// bug fix for the hapi-bom +configurations.all { + resolutionStrategy { + eachDependency { + if (requested.group == "org.eclipse.jetty") { + useVersion("11.0.20") + because("jetty 12 is java 17") + } + } + } +} + +dependencies { + api(platform("ca.uhn.hapi.fhir:hapi-fhir-bom:${project.findProperty("hapi.version")}")) + + implementation("ca.uhn.hapi.fhir:hapi-fhir-base") { + exclude(group = "org.eclipse.jetty") + exclude(group = "xpp3") + exclude(group = "org.junit") + } + + implementation("ca.uhn.hapi.fhir:hapi-fhir-converter") + implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-hl7org-dstu2") + implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-dstu2") + implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-dstu3") + implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-r4") + implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-r5") + + // This is to align with the FHIR core dependencies + // Note that this dependency hasn"t been updated since 2013 + // we probably need to standardize on a fork up the dependency chain + implementation("org.ogce:xpp3:1.1.6") { + exclude(group = "org.junit") + exclude(group = "org.hamcrest") + } +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/kotlin/cql.java-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.java-conventions.gradle.kts new file mode 100644 index 000000000..92cb3c553 --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.java-conventions.gradle.kts @@ -0,0 +1,210 @@ +plugins { + kotlin("jvm") + id("maven-publish") + id("jacoco") + id("signing") + id("cql.sca-conventions") + id("com.diffplug.spotless") + id("org.jetbrains.dokka") + id("io.gitlab.arturbosch.detekt") +} + +kotlin { + jvmToolchain(17) +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://oss.sonatype.org/content/repositories/snapshots") + mavenContent { + snapshotsOnly() + } + } +} + +spotless { + java { + targetExclude("**/generated/**") + palantirJavaFormat() + } + kotlin { + targetExclude("**/generated/**") + ktfmt().kotlinlangStyle() + } +} + +dependencies { + implementation("org.slf4j:slf4j-api:2.0.13") + testImplementation("org.hamcrest:hamcrest-all:1.3") + testImplementation("uk.co.datumedge:hamcrest-json:0.2") + testImplementation(platform("org.junit:junit-bom:5.10.2")) + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.slf4j:slf4j-simple:2.0.13") + + // These are JAXB dependencies excluded because the libraries need to work + // on Android. But for test purposes we use them pretty much everywhere. + testRuntimeOnly("org.eclipse.persistence:org.eclipse.persistence.moxy:4.0.2") + testRuntimeOnly("org.eclipse.parsson:parsson:1.1.5") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +tasks.jar { + manifest { + attributes["Implementation-Title"] = project.name + attributes["Implementation-Version"] = project.version + attributes["Specification-Title"] = "HL7 Clinical Quality Language (CQL)" + attributes["Specification-Version"] = project.findProperty("specification.version") ?: "" + } +} + +tasks.register("dokkaHtmlJar") { + dependsOn(tasks.dokkaHtml) + from(tasks.dokkaHtml.flatMap { it.outputDirectory }) + archiveClassifier.set("html-docs") +} + +tasks.register("dokkaJavadocJar") { + dependsOn(tasks.dokkaJavadoc) + from(tasks.dokkaJavadoc.flatMap { it.outputDirectory }) + archiveClassifier.set("javadoc") +} + +jacoco { + toolVersion = "0.8.11" + +} + +tasks.withType { + configure { + excludes = listOf("org/hl7/fhir/**") + } + + useJUnitPlatform() + testLogging { + events("skipped", "failed") + } + finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run +} + +tasks.jacocoTestReport { + reports { + xml.required = true + } + dependsOn(tasks.test)// tests are required to run before generating the report +} + +tasks.javadoc { + options { + val standardOptions = this as StandardJavadocDocletOptions + standardOptions.addStringOption("Xdoclint:none", "-quiet") + } +} + +tasks.withType { + options.compilerArgs.add("-Xlint:unchecked") + options.isDeprecation = true +} + +/* +A few things: + - You must have an OSSRH Jira account (https://issues.sonatype.org/secure/Signup!valault.jspa) + - Your account must have privileges to upload info.cqframework artifacts (https://issues.sonatype.org/browse/OSSRH-15514) + - You must have a gpg key (http://central.sonatype.org/pages/working-with-pgp-signatures.html) + - You must set your account info and GPG key in your user"s gradle.properties file. For example: + ossrhUsername=foo + ossrhPassword=b@r + signing.keyId=24875D73 + signing.password=secret + signing.secretKeyRingFile=/Users/me/.gnupg/secring.gpg + - If the library version ends with "-SNAPSHOT", it will be deployed to the snapshot repository, else it will be + deployed to the staging repository (which you then must manually release http://central.sonatype.org/pages/releasing-the-deployment.html). + - Repo for snapshots and releases for the translator modules: https://oss.sonatype.org/content/groups/public/info/cqframework/ + - Repo for snpashots, releases, and staged releases for the translator modules: https://oss.sonatype.org/content/groups/staging/info/cqframework/ + - Repo for snapshots and releases for the engine modules: https://oss.sonatype.org/content/groups/public/org/opencds/cqf/cql/ + - Repo for snapshots, releases, and staged releases for the engine modules: https://oss.sonatype.org/content/groups/staging/org/opencds/cqf/cql/ + */ +publishing { + publications { + create("mavenJava") { + from(components["java"]) + + pom { + name = project.name + packaging = "jar" + description = + "The ${project.name} library for the Clinical Quality Language Java reference implementation" + url = "http://cqframework.info" + + scm { + connection = "scm:git:git@github.com:cqframework/clinical_quality_language.git" + developerConnection = "scm:git:git@github.com:cqframework/clinical_quality_language.git" + url = "git@github.com:cqframework/clinical_quality_language.git" + } + + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + + developers { + developer { + name = "Bryn Rhodes" + } + developer { + name = "Chris Moesel" + } + developer { + name = "Rob Dingwell" + } + developer { + name = "Jason Walonoski" + } + developer { + name = "Marc Hadley" + } + developer { + name = "Jonathan Percival" + } + } + } + } + } + repositories { + maven { + credentials { + username = project.findProperty("ossrhUsername") as String? ?: System.getenv("OSSRH_USERNAME") ?: "" + password = project.findProperty("ossrhPassword") as String? ?: System.getenv("OSSRH_TOKEN") ?: "" + } + + /* Use these to test locally (but don"t forget to comment out others!) + val releasesRepoUrl = "file://${buildDir}/repo" + val snapshotsRepoUrl = "file://${buildDir}/ssRepo" + */ + + // change URLs to point to your repos, e.g. http://my.org/repo + val releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + val snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" + if ((project.version as String).endsWith("SNAPSHOT")) { + url = uri(snapshotsRepoUrl) + } else { + url = uri(releasesRepoUrl) + } + } + } +} + +signing { + if (!(version as String).endsWith("SNAPSHOT")) { + sign(publishing.publications["mavenJava"]) + } +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/kotlin/cql.kotlin-multiplatform-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.kotlin-multiplatform-conventions.gradle.kts new file mode 100644 index 000000000..09553bb56 --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.kotlin-multiplatform-conventions.gradle.kts @@ -0,0 +1,112 @@ +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl + +plugins { + kotlin("multiplatform") + id("com.diffplug.spotless") + id("io.gitlab.arturbosch.detekt") + id("org.jetbrains.dokka") +} + +repositories { + mavenCentral() +} + +spotless { + kotlin { + ktfmt().kotlinlangStyle() + } +} + +kotlin { + jvmToolchain(17) + jvm { + withJava() + } + + // This adds JavaScript as build target. + // Running the build outputs packages in the build/js/packages directory. + // These packages can e.g. be required or imported in JS or TS projects. + // If you get `Task :kotlinStoreYarnLock FAILED` during the build, + // run the `:kotlinUpgradeYarnLock` task and build again. + // Run `jsRun --continuous` to start a local development server with + // live reloading (automatic re-build on file changes). The local server serves + // /src/jsMain/resources/index.html from the root. + js(IR) { + + // Output ES2015 modules (.mjs files) to build/js/packages//kotlin + // instead of default UMD (.js) modules. + useEsModules() + + // Set web browser environment as the target execution environment. + // This also runs webpack which bundles everything into a single + // /build/dist/js/productionExecutable/.js file. + // This file can be e.g. included in an HTML file and distributed + // via a CDN. + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + + // `nodejs {}` can be added here to set Node.js as the target execution + // environment. If no browser APIs are used, having just `browser {}` + // creates an isomorphic library. + + // Explicitly instruct the Kotlin compiler to emit executable JS code. + // If `binaries.library()` is used instead, the + // /build/dist/js/productionExecutable directory has + // un-webpacked JS. + binaries.executable() + + // Generate TypeScript definitions (.d.ts files) from Kotlin code. The files are + // finally saved to build/js/packages//kotlin. + generateTypeScriptDefinitions() + } + + // Add Kotlin/WASM compilation target. + // The output is in the JS packages directory. + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser { } + binaries.executable() + generateTypeScriptDefinitions() + } + + sourceSets { + commonTest { + dependencies { + implementation(kotlin("test")) + } + } + + jvmTest { + dependencies { + implementation(kotlin("test-junit5")) + } + } + + jsTest { + dependencies { + implementation(kotlin("test-js")) + } + } + } +} + +tasks.withType { + useJUnitPlatform() +} + +tasks.register("dokkaHtmlJar") { + dependsOn(tasks.dokkaHtml) + from(tasks.dokkaHtml.flatMap { it.outputDirectory }) + archiveClassifier.set("html-docs") +} + +tasks.register("dokkaJavadocJar") { + dependsOn(tasks.dokkaJavadoc) + from(tasks.dokkaJavadoc.flatMap { it.outputDirectory }) + archiveClassifier.set("javadoc") +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/kotlin/cql.library-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.library-conventions.gradle.kts new file mode 100644 index 000000000..3475fe507 --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.library-conventions.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("ru.vyarus.animalsniffer") + id("cql.java-conventions") +} + +dependencies { + // Various libraries for Android signatures are available, Jackson uses this one + signature("com.toasttab.android:gummy-bears-api-${project.findProperty("android.api.level")}:0.10.0:coreLib2@signature") +} + +tasks.animalsnifferTest { + enabled = false +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/kotlin/cql.sca-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.sca-conventions.gradle.kts new file mode 100644 index 000000000..852d48a89 --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.sca-conventions.gradle.kts @@ -0,0 +1,28 @@ +import net.ltgt.gradle.errorprone.errorprone + +plugins { + id("net.ltgt.errorprone") + id("checkstyle") +} + +repositories { + mavenCentral() +} +dependencies { + errorprone("com.google.errorprone:error_prone_core:2.29.2") +} + +tasks.named("checkstyleMain") { + exclude { it.file.path.contains("generated")} +} + +tasks.named("checkstyleTest") { + enabled = false +} + +tasks.withType().configureEach { + options.errorprone.disableAllWarnings = true + options.errorprone.disableWarningsInGeneratedCode = true + options.errorprone.disable("DoubleBraceInitialization") + // errorproneArgs = ["-XepPatchLocation:IN_PLACE"] +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/kotlin/cql.xjc-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.xjc-conventions.gradle.kts new file mode 100644 index 000000000..31371a713 --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.xjc-conventions.gradle.kts @@ -0,0 +1,53 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") +} + +val xjc: Configuration by configurations.creating + +dependencies { + xjc("codes.rafael.jaxb2_commons:jaxb2-basics-ant:3.0.0") + xjc("codes.rafael.jaxb2_commons:jaxb2-basics:3.0.0") + xjc("codes.rafael.jaxb2_commons:jaxb2-fluent-api:3.0.0") + // Eclipse has taken over all Java EE reference components + // https://www.infoworld.com/article/3310042/eclipse-takes-over-all-java-ee-reference-components.html + // https://wiki.eclipse.org/Jakarta_EE_Maven_Coordinates + xjc("jakarta.xml.bind:jakarta.xml.bind-api:4.0.1") + xjc("org.glassfish.jaxb:jaxb-xjc:3.0.2") + xjc("org.glassfish.jaxb:jaxb-runtime:4.0.3") + xjc("org.eclipse.persistence:org.eclipse.persistence.moxy:4.0.2") + xjc("org.slf4j:slf4j-simple:2.0.13") + xjc("org.apache.ant:ant:1.10.14") + + api("jakarta.xml.bind:jakarta.xml.bind-api:4.0.1") + api("codes.rafael.jaxb2_commons:jaxb2-basics-runtime:3.0.0") +} + +var buildDir = project.layout.buildDirectory.get().toString() +val destDir = "${buildDir}/generated/sources/$name/main/java" + +tasks.withType().configureEach { + dependsOn(tasks.withType()) +} + +tasks.withType().configureEach { + dependsOn(tasks.withType()) +} + +tasks.withType().configureEach { + outputDir = destDir + outputs.dir(outputDir) +} + +tasks.withType().configureEach { + delete(destDir) +} + +sourceSets { + main { + java { + srcDir(destDir) + } + } +} diff --git a/Src/java/buildSrc/src/main/kotlin/cql.xjc-temp-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.xjc-temp-conventions.gradle.kts new file mode 100644 index 000000000..bf62dfe7e --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.xjc-temp-conventions.gradle.kts @@ -0,0 +1,42 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") +} + +val xjc: Configuration by configurations.creating + +dependencies { + xjc("codes.rafael.jaxb2_commons:jaxb2-basics-ant:3.0.0") + xjc("codes.rafael.jaxb2_commons:jaxb2-basics:3.0.0") + xjc("codes.rafael.jaxb2_commons:jaxb2-fluent-api:3.0.0") + // Eclipse has taken over all Java EE reference components + // https://www.infoworld.com/article/3310042/eclipse-takes-over-all-java-ee-reference-components.html + // https://wiki.eclipse.org/Jakarta_EE_Maven_Coordinates + xjc("jakarta.xml.bind:jakarta.xml.bind-api:4.0.1") + xjc("org.glassfish.jaxb:jaxb-xjc:3.0.2") + xjc("org.glassfish.jaxb:jaxb-runtime:4.0.3") + xjc("org.eclipse.persistence:org.eclipse.persistence.moxy:4.0.2") + xjc("org.slf4j:slf4j-simple:1.7.36") + xjc("org.apache.ant:ant:1.10.14") +} + +var buildDir = project.layout.buildDirectory.get().toString() +val destDir = "${buildDir}/generated/sources/$name-xjc-temp/main/java" + +tasks.withType().configureEach { + dependsOn(tasks.withType()) +} + +tasks.withType().configureEach { + dependsOn(tasks.withType()) +} + +tasks.withType().configureEach { + outputDir = destDir + outputs.dir(outputDir) +} + +tasks.withType().configureEach { + delete(destDir) +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/kotlin/cql.xsd-kotlin-gen-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.xsd-kotlin-gen-conventions.gradle.kts new file mode 100644 index 000000000..985ac06b1 --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.xsd-kotlin-gen-conventions.gradle.kts @@ -0,0 +1,50 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import com.github.gradle.node.task.NodeTask +import org.gradle.kotlin.dsl.kotlin + +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + id("com.github.node-gradle.node") +} + +dependencies { + implementation("io.github.pdvrieze.xmlutil:core:0.90.3") + implementation("io.github.pdvrieze.xmlutil:serialization:0.90.3") + implementation("io.github.pdvrieze.xmlutil:serialization-jvm:0.90.3") + + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0") +} + +node { + download = true + nodeProjectDir.set(file("../../js/xsd-kotlin-gen")) +} + +val buildDir = project.layout.buildDirectory.get().toString() +val destDir = "${buildDir}/generated/sources/$name/main/kotlin" + +val runXsdKotlinGenTask = tasks.register("runXsdKotlinGen") { + dependsOn(tasks.npmInstall) + script.set(file("../../js/xsd-kotlin-gen/generate.js")) +} + +tasks.withType().configureEach { + dependsOn(runXsdKotlinGenTask) +} + +tasks.withType().configureEach { + dependsOn(runXsdKotlinGenTask) +} + +tasks.withType().configureEach { + delete(destDir) +} + +sourceSets { + main { + kotlin { + srcDir(destDir) + } + } +} \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/kotlin/cql.xsd-kotlin-multiplatform-gen-conventions.gradle.kts b/Src/java/buildSrc/src/main/kotlin/cql.xsd-kotlin-multiplatform-gen-conventions.gradle.kts new file mode 100644 index 000000000..e4236d868 --- /dev/null +++ b/Src/java/buildSrc/src/main/kotlin/cql.xsd-kotlin-multiplatform-gen-conventions.gradle.kts @@ -0,0 +1,56 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import com.github.gradle.node.task.NodeTask +import gradle.kotlin.dsl.accessors._2dcd0a84416f85634c85a37519569374.sourceSets +import org.gradle.kotlin.dsl.kotlin + +plugins { + id("cql.kotlin-multiplatform-conventions") + kotlin("plugin.serialization") + id("com.github.node-gradle.node") +} + +val buildDir = project.layout.buildDirectory.get().toString() +val destDir = "${buildDir}/generated/sources/$name/commonMain/kotlin" + +kotlin { + sourceSets { + commonMain { + kotlin { + srcDir(destDir) + } + dependencies { + implementation("io.github.pdvrieze.xmlutil:core:0.90.3") + implementation("io.github.pdvrieze.xmlutil:serialization:0.90.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0") + } + } + + jvmMain { + dependencies { + implementation("io.github.pdvrieze.xmlutil:serialization-jvm:0.90.3") + } + } + } +} + +node { + download = true + nodeProjectDir.set(file("../../js/xsd-kotlin-gen")) +} + +val runXsdKotlinGenTask = tasks.register("runXsdKotlinGen") { + dependsOn(tasks.npmInstall) + script.set(file("../../js/xsd-kotlin-gen/generate.js")) +} + +tasks.withType().configureEach { + dependsOn(runXsdKotlinGenTask) +} + +tasks.withType().configureEach { + dependsOn(runXsdKotlinGenTask) +} + +tasks.withType().configureEach { + delete(destDir) +} \ No newline at end of file diff --git a/Src/java/cqf-fhir-npm/build.gradle b/Src/java/cqf-fhir-npm/build.gradle deleted file mode 100644 index 89422d105..000000000 --- a/Src/java/cqf-fhir-npm/build.gradle +++ /dev/null @@ -1,10 +0,0 @@ -plugins { - id "cql.fhir-conventions" -} - -dependencies { - implementation project(':cql-to-elm') - implementation project(':cqf-fhir') - implementation 'com.google.code.gson:gson:2.9.1' - implementation 'org.apache.commons:commons-compress:1.24.0' -} diff --git a/Src/java/cqf-fhir-npm/build.gradle.kts b/Src/java/cqf-fhir-npm/build.gradle.kts new file mode 100644 index 000000000..7e418971f --- /dev/null +++ b/Src/java/cqf-fhir-npm/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("cql.fhir-conventions") +} + +dependencies { + implementation(project(":cql-to-elm")) + implementation(project(":cqf-fhir")) + implementation(project(":model-xmlutil")) + implementation("com.google.code.gson:gson:2.9.1") + implementation("org.apache.commons:commons-compress:1.24.0") +} \ No newline at end of file diff --git a/Src/java/cqf-fhir-npm/src/main/java/org/cqframework/fhir/npm/NpmLibrarySourceProvider.java b/Src/java/cqf-fhir-npm/src/main/java/org/cqframework/fhir/npm/NpmLibrarySourceProvider.java index bf35eb5cc..4a9fd140d 100644 --- a/Src/java/cqf-fhir-npm/src/main/java/org/cqframework/fhir/npm/NpmLibrarySourceProvider.java +++ b/Src/java/cqf-fhir-npm/src/main/java/org/cqframework/fhir/npm/NpmLibrarySourceProvider.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +import org.cqframework.cql.cql2elm.LibraryContentType; import org.cqframework.cql.cql2elm.LibrarySourceProvider; import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.fhir.r5.context.ILoggingService; @@ -67,4 +68,13 @@ public InputStream getLibrarySource(VersionedIdentifier identifier) { return null; } + + @Override + public InputStream getLibraryContent(VersionedIdentifier libraryIdentifier, LibraryContentType type) { + if (LibraryContentType.CQL == type) { + return getLibrarySource(libraryIdentifier); + } + + return null; + } } diff --git a/Src/java/cqf-fhir-npm/src/main/java/org/cqframework/fhir/npm/NpmModelInfoProvider.java b/Src/java/cqf-fhir-npm/src/main/java/org/cqframework/fhir/npm/NpmModelInfoProvider.java index 861deffd0..91513e93c 100644 --- a/Src/java/cqf-fhir-npm/src/main/java/org/cqframework/fhir/npm/NpmModelInfoProvider.java +++ b/Src/java/cqf-fhir-npm/src/main/java/org/cqframework/fhir/npm/NpmModelInfoProvider.java @@ -1,6 +1,8 @@ package org.cqframework.fhir.npm; -import jakarta.xml.bind.JAXB; +import static kotlinx.io.CoreKt.buffered; +import static kotlinx.io.JvmCoreKt.asSource; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -8,6 +10,7 @@ import org.hl7.cql.model.ModelIdentifier; import org.hl7.cql.model.ModelInfoProvider; import org.hl7.elm_modelinfo.r1.ModelInfo; +import org.hl7.elm_modelinfo.r1.serializing.xmlutil.XmlModelInfoReader; import org.hl7.fhir.r5.context.ILoggingService; import org.hl7.fhir.r5.model.Library; import org.hl7.fhir.utilities.npm.NpmPackage; @@ -33,10 +36,8 @@ public ModelInfo load(ModelIdentifier modelIdentifier) { // VersionedIdentifier.version: Version of the model for (NpmPackage p : packages) { try { - var identifier = new ModelIdentifier() - .withId(modelIdentifier.getId()) - .withVersion(modelIdentifier.getVersion()) - .withSystem(modelIdentifier.getSystem()); + var identifier = new ModelIdentifier( + modelIdentifier.getId(), modelIdentifier.getSystem(), modelIdentifier.getVersion()); if (identifier.getSystem() == null) { identifier.setSystem(p.canonical()); @@ -53,7 +54,10 @@ public ModelInfo load(ModelIdentifier modelIdentifier) { modelIdentifier.setSystem(identifier.getSystem()); } InputStream is = new ByteArrayInputStream(a.getData()); - return JAXB.unmarshal(is, ModelInfo.class); + var reader = new XmlModelInfoReader(); + + var source = buffered(asSource(is)); + return reader.read(source); } } } @@ -61,8 +65,7 @@ public ModelInfo load(ModelIdentifier modelIdentifier) { logger.logDebugMessage( ILoggingService.LogCategory.PROGRESS, String.format( - "Exceptions occurred attempting to load npm library for model %s", - modelIdentifier.toString())); + "Exceptions occurred attempting to load npm library for model %s", modelIdentifier)); } } diff --git a/Src/java/cqf-fhir-npm/src/test/java/org/cqframework/fhir/npm/NpmPackageManagerTests.java b/Src/java/cqf-fhir-npm/src/test/java/org/cqframework/fhir/npm/NpmPackageManagerTests.java index eecf860a0..891a0dc3a 100644 --- a/Src/java/cqf-fhir-npm/src/test/java/org/cqframework/fhir/npm/NpmPackageManagerTests.java +++ b/Src/java/cqf-fhir-npm/src/test/java/org/cqframework/fhir/npm/NpmPackageManagerTests.java @@ -1,6 +1,7 @@ package org.cqframework.fhir.npm; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -117,13 +118,11 @@ void modelInfoProviderLocal() { .parseResource(NpmPackageManagerTests.class.getResourceAsStream("testig.xml")); ImplementationGuide ig = (ImplementationGuide) convertor.convertResource(igResource); NpmPackageManager pm = new NpmPackageManager(ig); - assertTrue(pm.getNpmList().size() >= 1); + assertFalse(pm.getNpmList().isEmpty()); LibraryLoader reader = new LibraryLoader("5.0"); NpmModelInfoProvider mp = new NpmModelInfoProvider(pm.getNpmList(), reader, this); - ModelInfo mi = mp.load(new ModelIdentifier() - .withSystem("http://hl7.org/fhir/us/qicore") - .withId("QICore")); + ModelInfo mi = mp.load(new ModelIdentifier("QICore", "http://hl7.org/fhir/us/qicore", null)); assertNotNull(mi); assertEquals("QICore", mi.getName()); } diff --git a/Src/java/cqf-fhir/build.gradle b/Src/java/cqf-fhir/build.gradle deleted file mode 100644 index ece5a847e..000000000 --- a/Src/java/cqf-fhir/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id 'cql.fhir-conventions' -} \ No newline at end of file diff --git a/Src/java/cqf-fhir/build.gradle.kts b/Src/java/cqf-fhir/build.gradle.kts new file mode 100644 index 000000000..d5c16b25c --- /dev/null +++ b/Src/java/cqf-fhir/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("cql.fhir-conventions") +} \ No newline at end of file diff --git a/Src/java/cql-to-elm-cli/build.gradle b/Src/java/cql-to-elm-cli/build.gradle deleted file mode 100644 index 24a079c7d..000000000 --- a/Src/java/cql-to-elm-cli/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - id 'cql.java-conventions' - id "application" -} - -application { - mainClass = 'org.cqframework.cql.cql2elm.cli.Main' -} - -dependencies { - implementation project(':cql-to-elm') - implementation project(':quick') - implementation project(':qdm') - implementation project(':model-jaxb') - implementation project(':elm-jaxb') - implementation 'net.sf.jopt-simple:jopt-simple:4.7' - implementation 'org.slf4j:slf4j-simple:2.0.13' - implementation 'org.glassfish.jaxb:jaxb-runtime:4.0.5' - implementation 'org.eclipse.persistence:org.eclipse.persistence.moxy:4.0.2' - testImplementation project(':model-jaxb') - testImplementation project(':elm-jaxb') -} \ No newline at end of file diff --git a/Src/java/cql-to-elm-cli/build.gradle.kts b/Src/java/cql-to-elm-cli/build.gradle.kts new file mode 100644 index 000000000..108ac463e --- /dev/null +++ b/Src/java/cql-to-elm-cli/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("cql.java-conventions") + id("application") +} + +application { + mainClass = "org.cqframework.cql.cql2elm.cli.Main" +} + +dependencies { + implementation(project(":cql-to-elm")) + // Is this needed once JAXB and Jackson are replaced with XmlUtil? + // implementation(project(":cql-to-elm-jackson")) + implementation(project(":quick")) + implementation(project(":qdm")) + implementation(project(":model-xmlutil")) + implementation(project(":elm-xmlutil")) + implementation(project(":ucum")) + implementation("org.jetbrains.kotlinx:kotlinx-io-core-jvm:0.6.0") + implementation("net.sf.jopt-simple:jopt-simple:4.7") + implementation("org.slf4j:slf4j-simple:2.0.13") + implementation("org.glassfish.jaxb:jaxb-runtime:4.0.5") + implementation("org.eclipse.persistence:org.eclipse.persistence.moxy:4.0.2") +} \ No newline at end of file diff --git a/Src/java/cql-to-elm-cli/src/main/java/org/cqframework/cql/cql2elm/cli/Main.java b/Src/java/cql-to-elm-cli/src/main/java/org/cqframework/cql/cql2elm/cli/Main.java index 2ef9749ee..cf1fe80cd 100644 --- a/Src/java/cql-to-elm-cli/src/main/java/org/cqframework/cql/cql2elm/cli/Main.java +++ b/Src/java/cql-to-elm-cli/src/main/java/org/cqframework/cql/cql2elm/cli/Main.java @@ -1,9 +1,12 @@ package org.cqframework.cql.cql2elm.cli; import static java.nio.file.FileVisitResult.CONTINUE; +import static kotlinx.io.CoreKt.buffered; +import static kotlinx.io.JvmCoreKt.asSource; import static org.cqframework.cql.cql2elm.CqlTranslator.fromFile; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.FileVisitResult; @@ -18,7 +21,7 @@ import joptsimple.OptionSpec; import org.cqframework.cql.cql2elm.*; import org.cqframework.cql.cql2elm.quick.FhirLibrarySourceProvider; -import org.cqframework.cql.elm.tracking.TrackBack; +import org.cqframework.cql.cql2elm.tracking.TrackBack; import org.hl7.cql.model.ModelIdentifier; import org.hl7.cql.model.ModelInfoProvider; import org.hl7.elm_modelinfo.r1.ModelInfo; @@ -27,8 +30,9 @@ public class Main { public static ModelInfoProvider getModelInfoProvider(File modelInfoXML) { try { + var source = buffered(asSource(new FileInputStream(modelInfoXML))); final ModelInfo modelInfo = - ModelInfoReaderFactory.getReader("application/xml").read(modelInfoXML); + ModelInfoReaderFactory.INSTANCE.getReader("application/xml").read(source); return (ModelIdentifier modelIdentifier) -> modelInfo; } catch (IOException e) { System.err.printf("Could not load model-info XML: %s%n", modelInfoXML); @@ -265,7 +269,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO options.has(validateUnits), options.has(disableDefaultModelInfoLoad), signatureLevel, - options.has(compatibilityLevel) ? options.valueOf(compatibilityLevel) : null)); + options.has(compatibilityLevel) ? options.valueOf(compatibilityLevel) : "1.5")); } } } diff --git a/Src/java/cql-to-elm-jackson/build.gradle.kts b/Src/java/cql-to-elm-jackson/build.gradle.kts new file mode 100644 index 000000000..64469bcde --- /dev/null +++ b/Src/java/cql-to-elm-jackson/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("cql.library-conventions") +} + +dependencies { + implementation(project(":cql-to-elm")) + implementation("com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:${project.findProperty("jackson.version")}") + testImplementation("com.github.reinert:jjschema:1.16") +} \ No newline at end of file diff --git a/Src/java/cql-to-elm-jackson/src/main/java/org/cqframework/cql/cq2elm/CqlTranslatorOptionsMapper.kt b/Src/java/cql-to-elm-jackson/src/main/java/org/cqframework/cql/cq2elm/CqlTranslatorOptionsMapper.kt new file mode 100644 index 000000000..3952b6673 --- /dev/null +++ b/Src/java/cql-to-elm-jackson/src/main/java/org/cqframework/cql/cq2elm/CqlTranslatorOptionsMapper.kt @@ -0,0 +1,61 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cq2elm + +import com.fasterxml.jackson.databind.ObjectMapper +import java.io.* +import java.util.* +import org.cqframework.cql.cql2elm.CqlTranslatorOptions + +object CqlTranslatorOptionsMapper { + private val om: ObjectMapper = + ObjectMapper() + .setMixIns( + mapOf(CqlTranslatorOptions::class.java to CqlTranslatorOptionsMixin::class.java) + ) + + @JvmStatic + fun fromFile(fileName: String?): CqlTranslatorOptions { + var fr: FileReader? = null + try { + fr = FileReader(fileName) + return fromReader(fr) + } catch (@Suppress("SwallowedException") e: IOException) { + @Suppress("TooGenericExceptionThrown") + throw RuntimeException("Errors occurred reading options: ${e.message}", e) + } + } + + @JvmStatic + fun fromReader(reader: Reader?): CqlTranslatorOptions { + try { + return om.readValue(reader, CqlTranslatorOptions::class.java) + } catch (@Suppress("SwallowedException") e: IOException) { + @Suppress("TooGenericExceptionThrown") + throw RuntimeException("Errors occurred reading options: ${e.message}", e) + } + } + + @JvmStatic + fun toFile(fileName: String?, options: CqlTranslatorOptions?) { + var fw: FileWriter? = null + try { + fw = FileWriter(fileName) + toWriter(fw, options) + } catch (@Suppress("SwallowedException") e: IOException) { + @Suppress("TooGenericExceptionThrown") + throw RuntimeException("Errors occurred writing options: ${e.message}", e) + } + } + + @JvmStatic + fun toWriter(writer: Writer?, options: CqlTranslatorOptions?) { + val om: ObjectMapper = ObjectMapper() + try { + om.writeValue(writer, options) + } catch (@Suppress("SwallowedException") e: IOException) { + @Suppress("TooGenericExceptionThrown") + throw RuntimeException("Errors occurred writing options: ${e.message}", e) + } + } +} diff --git a/Src/java/cql-to-elm-jackson/src/main/java/org/cqframework/cql/cq2elm/CqlTranslatorOptionsMixin.kt b/Src/java/cql-to-elm-jackson/src/main/java/org/cqframework/cql/cq2elm/CqlTranslatorOptionsMixin.kt new file mode 100644 index 000000000..57a0b4102 --- /dev/null +++ b/Src/java/cql-to-elm-jackson/src/main/java/org/cqframework/cql/cq2elm/CqlTranslatorOptionsMixin.kt @@ -0,0 +1,8 @@ +package org.cqframework.cql.cq2elm + +import com.fasterxml.jackson.annotation.JsonUnwrapped +import org.cqframework.cql.cql2elm.CqlCompilerOptions + +abstract class CqlTranslatorOptionsMixin { + @get:JsonUnwrapped @set:JsonUnwrapped abstract var cqlCompilerOptions: CqlCompilerOptions? +} diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/json/CqlTranslatorOptionsToJsonSchema.java b/Src/java/cql-to-elm-jackson/src/test/java/org/cqframework/cql/cql2elm/CqlTranslatorOptionsToJsonSchema.java similarity index 90% rename from Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/json/CqlTranslatorOptionsToJsonSchema.java rename to Src/java/cql-to-elm-jackson/src/test/java/org/cqframework/cql/cql2elm/CqlTranslatorOptionsToJsonSchema.java index eb2fdc41a..87dc1c999 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/json/CqlTranslatorOptionsToJsonSchema.java +++ b/Src/java/cql-to-elm-jackson/src/test/java/org/cqframework/cql/cql2elm/CqlTranslatorOptionsToJsonSchema.java @@ -1,4 +1,4 @@ -package org.cqframework.cql.cql2elm.json; +package org.cqframework.cql.cql2elm; import static org.junit.jupiter.api.Assertions.fail; @@ -8,14 +8,14 @@ import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; -import org.cqframework.cql.cql2elm.CqlCompilerOptions; + import org.junit.jupiter.api.Test; class CqlTranslatorOptionsToJsonSchema { private static final String separator = System.getProperty("file.separator"); private static final String JSON_LOC = "src" + separator + "test" + separator + "resources" + separator + "org" + separator + "cqframework" + separator + "cql" + separator + "cql2elm" + separator - + "json" + separator + "CqlTranslatorOptions.json"; + + "CqlTranslatorOptions.json"; @Test void BuildJsonSchemaFromCqlTranslatorOptions() { diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/OptionsTests.java b/Src/java/cql-to-elm-jackson/src/test/java/org/cqframework/cql/cql2elm/OptionsTests.java similarity index 94% rename from Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/OptionsTests.java rename to Src/java/cql-to-elm-jackson/src/test/java/org/cqframework/cql/cql2elm/OptionsTests.java index 299c2aa7b..b5a4fb4c9 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/OptionsTests.java +++ b/Src/java/cql-to-elm-jackson/src/test/java/org/cqframework/cql/cql2elm/OptionsTests.java @@ -6,6 +6,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; +import org.cqframework.cql.cq2elm.CqlTranslatorOptionsMapper; import org.junit.jupiter.api.Test; class OptionsTests { diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/json/CqlTranslatorOptions.json b/Src/java/cql-to-elm-jackson/src/test/resources/org/cqframework/cql/cql2elm/CqlTranslatorOptions.json similarity index 100% rename from Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/json/CqlTranslatorOptions.json rename to Src/java/cql-to-elm-jackson/src/test/resources/org/cqframework/cql/cql2elm/CqlTranslatorOptions.json diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/options.json b/Src/java/cql-to-elm-jackson/src/test/resources/org/cqframework/cql/cql2elm/options.json similarity index 100% rename from Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/options.json rename to Src/java/cql-to-elm-jackson/src/test/resources/org/cqframework/cql/cql2elm/options.json diff --git a/Src/java/cql-to-elm/build.gradle b/Src/java/cql-to-elm/build.gradle deleted file mode 100644 index ddeff9006..000000000 --- a/Src/java/cql-to-elm/build.gradle +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - id 'cql.library-conventions' - id 'cql.xjc-conventions' -} - -ext { - jacksonVersion = project['jackson.version'] -} - -dependencies { - api project(':cql') - api project(':model') - api project(':elm') - api 'org.fhir:ucum:1.0.8' - api 'org.apache.commons:commons-text:1.10.0' - - // TODO: This dependencies are required due the the fact that the CqlTranslatorOptionsMapper lives - // in the cql-to-elm project. Ideally, we'd factor out all serialization dependencies into common - // libraries such that we could swap out jackson for something else. In the meantime, these are - // "implementation" dependencies so that they are not exported downstream. - implementation "com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:${jacksonVersion}" - testImplementation project(':elm-jaxb') - testImplementation project(':model-jaxb') - testImplementation project(':quick') - testImplementation project(':qdm') - testImplementation 'org.xmlunit:xmlunit-assertj:2.10.0' - testImplementation 'com.github.reinert:jjschema:1.16' - testImplementation 'com.tngtech.archunit:archunit:1.2.1' - testImplementation 'org.skyscreamer:jsonassert:1.5.1' -} \ No newline at end of file diff --git a/Src/java/cql-to-elm/build.gradle.kts b/Src/java/cql-to-elm/build.gradle.kts new file mode 100644 index 000000000..fcf433e91 --- /dev/null +++ b/Src/java/cql-to-elm/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("cql.library-conventions") +} + +dependencies { + api(project(":cql")) + api(project(":model")) + api(project(":elm")) + + implementation("org.jetbrains.kotlinx:kotlinx-io-core-jvm:0.6.0") + + // Temporary until we can get rid of the dependency on wrapping + // the CQL annotations in a JAXBElement for narrative generation + implementation("jakarta.xml.bind:jakarta.xml.bind-api:4.0.1") + + testImplementation(project(":elm-xmlutil")) + testImplementation(project(":model-xmlutil")) + testImplementation(project(":quick")) + testImplementation(project(":qdm")) + testImplementation(project(":ucum")) + testImplementation("com.tngtech.archunit:archunit:1.2.1") +} \ No newline at end of file diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CompilerOptions.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CompilerOptions.java deleted file mode 100644 index 36797a073..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CompilerOptions.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import static java.util.Objects.requireNonNull; - -import java.util.EnumSet; -import java.util.List; -import java.util.Set; -import org.hl7.cql_annotations.r1.CqlToElmBase; -import org.hl7.cql_annotations.r1.CqlToElmInfo; -import org.hl7.elm.r1.Library; - -/** - * This class provides functions for extracting and parsing CQL Compiler - * Options from - * a Library - */ -public class CompilerOptions { - - private CompilerOptions() { - // intentionally empty - } - - /** - * Gets the compiler options used to generate an elm Library. - * - * Returns null if the compiler options could not be determined. - * (for example, the Library was translated without annotations) - * - * @param library The library to extracts the options from. - * @return The set of options used to translate the library. - */ - public static Set getCompilerOptions(Library library) { - requireNonNull(library, "library can not be null"); - if (library.getAnnotation() == null || library.getAnnotation().isEmpty()) { - return null; - } - - String compilerOptions = getCompilerOptions(library.getAnnotation()); - return parseCompilerOptions(compilerOptions); - } - - private static String getCompilerOptions(List annotations) { - for (CqlToElmBase base : annotations) { - if (base instanceof CqlToElmInfo) { - if (((CqlToElmInfo) base).getTranslatorOptions() != null) { - return ((CqlToElmInfo) base).getTranslatorOptions(); - } - } - } - - return null; - } - - /** - * Parses a string representing CQL compiler Options into an EnumSet. The - * string is expected - * to be a comma delimited list of values from the CqlCompiler.Options - * enumeration. - * For example "EnableListPromotion, EnableListDemotion". - * - * @param compilerOptions the string to parse - * @return the set of options - */ - public static Set parseCompilerOptions(String compilerOptions) { - if (compilerOptions == null || compilerOptions.isEmpty()) { - return null; - } - - EnumSet optionSet = EnumSet.noneOf(CqlCompilerOptions.Options.class); - String[] options = compilerOptions.trim().split(","); - - for (String option : options) { - optionSet.add(CqlCompilerOptions.Options.valueOf(option)); - } - - return optionSet; - } - - /** - * Gets the compiler version used to generate an elm Library. - * - * Returns null if the compiled version could not be determined. (for example, - * the Library was - * compiled without annotations) - * - * @param library The library to extracts the compiler version from. - * @return The version of compiler used to compiler the library. - */ - public static String getCompilerVersion(Library library) { - requireNonNull(library, "library can not be null"); - if (library.getAnnotation() == null || library.getAnnotation().isEmpty()) { - return null; - } - - return getCompilerVersion(library.getAnnotation()); - } - - private static String getCompilerVersion(List annotations) { - for (CqlToElmBase o : annotations) { - if (o instanceof CqlToElmInfo) { - CqlToElmInfo c = (CqlToElmInfo) o; - return c.getTranslatorVersion(); - } - } - - return null; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java deleted file mode 100755 index ed3fe9bb7..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java +++ /dev/null @@ -1,4589 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.math.BigDecimal; -import java.util.*; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.cqframework.cql.cql2elm.LibraryBuilder.IdentifierScope; -import org.cqframework.cql.cql2elm.model.*; -import org.cqframework.cql.cql2elm.model.invocation.*; -import org.cqframework.cql.cql2elm.preprocessor.*; -import org.cqframework.cql.elm.tracking.TrackBack; -import org.cqframework.cql.elm.tracking.Trackable; -import org.cqframework.cql.gen.cqlLexer; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.cql.model.*; -import org.hl7.elm.r1.*; -import org.hl7.elm_modelinfo.r1.ModelInfo; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Cql2ElmVisitor extends CqlPreprocessorElmCommonVisitor { - private static final Logger log = LoggerFactory.getLogger(Cql2ElmVisitor.class); - private final SystemMethodResolver systemMethodResolver; - - private final Set definedExpressionDefinitions = new HashSet<>(); - private final Stack forwards = new Stack<>(); - private final Map functionHeaders = new HashMap<>(); - - private final Map functionHeadersByDef = new HashMap<>(); - private final Map functionDefinitions = new HashMap<>(); - private final Stack timingOperators = new Stack<>(); - private final List retrieves = new ArrayList<>(); - private final List expressions = new ArrayList<>(); - private final Map contextDefinitions = new HashMap<>(); - - public Cql2ElmVisitor(LibraryBuilder libraryBuilder, TokenStream tokenStream, LibraryInfo libraryInfo) { - super(libraryBuilder, tokenStream); - this.libraryInfo = Objects.requireNonNull(libraryInfo, "libraryInfo required"); - this.systemMethodResolver = new SystemMethodResolver(this, libraryBuilder); - } - - public List getRetrieves() { - return retrieves; - } - - public List getExpressions() { - return expressions; - } - - @Override - public Object visitLibrary(cqlParser.LibraryContext ctx) { - Object lastResult = null; - - // Loop through and call visit on each child (to ensure they are tracked) - for (int i = 0; i < ctx.getChildCount(); i++) { - ParseTree tree = ctx.getChild(i); - TerminalNode terminalNode = tree instanceof TerminalNode ? (TerminalNode) tree : null; - if (terminalNode != null && terminalNode.getSymbol().getType() == cqlLexer.EOF) { - continue; - } - - Object childResult = visit(tree); - // Only set the last result if we received something useful - if (childResult != null) { - lastResult = childResult; - } - } - - // Return last result (consistent with super implementation and helps w/ - // testing) - return lastResult; - } - - @Override - @SuppressWarnings("unchecked") - public VersionedIdentifier visitLibraryDefinition(cqlParser.LibraryDefinitionContext ctx) { - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - VersionedIdentifier vid = of.createVersionedIdentifier() - .withId(identifiers.remove(identifiers.size() - 1)) - .withVersion(parseString(ctx.versionSpecifier())); - if (!identifiers.isEmpty()) { - vid.setSystem(libraryBuilder.resolveNamespaceUri(String.join(".", identifiers), true)); - } else if (libraryBuilder.getNamespaceInfo() != null) { - vid.setSystem(libraryBuilder.getNamespaceInfo().getUri()); - } - libraryBuilder.setLibraryIdentifier(vid); - - return vid; - } - - @Override - @SuppressWarnings("unchecked") - public UsingDef visitUsingDefinition(cqlParser.UsingDefinitionContext ctx) { - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - String unqualifiedIdentifier = identifiers.remove(identifiers.size() - 1); - String namespaceName = !identifiers.isEmpty() - ? String.join(".", identifiers) - : libraryBuilder.isWellKnownModelName(unqualifiedIdentifier) - ? null - : (libraryBuilder.getNamespaceInfo() != null - ? libraryBuilder.getNamespaceInfo().getName() - : null); - - String path = null; - NamespaceInfo modelNamespace = null; - if (namespaceName != null) { - String namespaceUri = libraryBuilder.resolveNamespaceUri(namespaceName, true); - path = NamespaceManager.getPath(namespaceUri, unqualifiedIdentifier); - modelNamespace = new NamespaceInfo(namespaceName, namespaceUri); - } else { - path = unqualifiedIdentifier; - } - - String localIdentifier = - ctx.localIdentifier() == null ? unqualifiedIdentifier : parseString(ctx.localIdentifier()); - if (!localIdentifier.equals(unqualifiedIdentifier)) { - throw new IllegalArgumentException(String.format( - "Local identifiers for models must be the same as the name of the model in this release of the translator (Model %s, Called %s)", - unqualifiedIdentifier, localIdentifier)); - } - - // The model was already calculated by CqlPreprocessorVisitor - final UsingDef usingDef = libraryBuilder.resolveUsingRef(localIdentifier); - libraryBuilder.pushIdentifier(localIdentifier, usingDef, IdentifierScope.GLOBAL); - return usingDef; - } - - public Model getModel() { - return getModel((String) null); - } - - public Model getModel(String modelName) { - return getModel(null, modelName, null, null); - } - - public Model getModel(NamespaceInfo modelNamespace, String modelName, String version, String localIdentifier) { - if (modelName == null) { - var defaultUsing = libraryInfo.getDefaultUsingDefinition(); - modelName = defaultUsing.getName(); - version = defaultUsing.getVersion(); - } - - var modelIdentifier = new ModelIdentifier().withId(modelName).withVersion(version); - if (modelNamespace != null) { - modelIdentifier.setSystem(modelNamespace.getUri()); - } - return libraryBuilder.getModel(modelIdentifier, localIdentifier); - } - - private String getLibraryPath(String namespaceName, String unqualifiedIdentifier) { - if (namespaceName != null) { - String namespaceUri = libraryBuilder.resolveNamespaceUri(namespaceName, true); - return NamespaceManager.getPath(namespaceUri, unqualifiedIdentifier); - } - - return unqualifiedIdentifier; - } - - @Override - @SuppressWarnings("unchecked") - public Object visitIncludeDefinition(cqlParser.IncludeDefinitionContext ctx) { - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - String unqualifiedIdentifier = identifiers.remove(identifiers.size() - 1); - String namespaceName = !identifiers.isEmpty() - ? String.join(".", identifiers) - : (libraryBuilder.getNamespaceInfo() != null - ? libraryBuilder.getNamespaceInfo().getName() - : null); - String path = getLibraryPath(namespaceName, unqualifiedIdentifier); - IncludeDef library = of.createIncludeDef() - .withLocalIdentifier( - ctx.localIdentifier() == null ? unqualifiedIdentifier : parseString(ctx.localIdentifier())) - .withPath(path) - .withVersion(parseString(ctx.versionSpecifier())); - - // TODO: This isn't great because it complicates the loading process (and - // results in the source being loaded - // twice in the general case) - // But the full fix is to introduce source resolution/caching to enable this - // layer to determine whether the - // library identifier resolved - // with the namespace - if (!libraryBuilder.canResolveLibrary(library)) { - namespaceName = identifiers.size() > 0 - ? String.join(".", identifiers) - : libraryBuilder.isWellKnownLibraryName(unqualifiedIdentifier) - ? null - : (libraryBuilder.getNamespaceInfo() != null - ? libraryBuilder.getNamespaceInfo().getName() - : null); - path = getLibraryPath(namespaceName, unqualifiedIdentifier); - library = of.createIncludeDef() - .withLocalIdentifier( - ctx.localIdentifier() == null ? unqualifiedIdentifier : parseString(ctx.localIdentifier())) - .withPath(path) - .withVersion(parseString(ctx.versionSpecifier())); - } - - libraryBuilder.addInclude(library); - libraryBuilder.pushIdentifier(library.getLocalIdentifier(), library, IdentifierScope.GLOBAL); - - return library; - } - - @Override - public ParameterDef visitParameterDefinition(cqlParser.ParameterDefinitionContext ctx) { - ParameterDef param = of.createParameterDef() - .withAccessLevel(parseAccessModifier(ctx.accessModifier())) - .withName(parseString(ctx.identifier())) - .withDefault(parseLiteralExpression(ctx.expression())) - .withParameterTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())); - - DataType paramType = null; - if (param.getParameterTypeSpecifier() != null) { - paramType = param.getParameterTypeSpecifier().getResultType(); - } - - if (param.getDefault() != null) { - if (paramType != null) { - libraryBuilder.verifyType(param.getDefault().getResultType(), paramType); - } else { - paramType = param.getDefault().getResultType(); - } - } - - if (paramType == null) { - throw new IllegalArgumentException( - String.format("Could not determine parameter type for parameter %s.", param.getName())); - } - - param.setResultType(paramType); - if (param.getDefault() != null) { - param.setDefault(libraryBuilder.ensureCompatible(param.getDefault(), paramType)); - } - - libraryBuilder.addParameter(param); - libraryBuilder.pushIdentifier(param.getName(), param, IdentifierScope.GLOBAL); - - return param; - } - - @Override - public TupleElementDefinition visitTupleElementDefinition(cqlParser.TupleElementDefinitionContext ctx) { - TupleElementDefinition result = of.createTupleElementDefinition() - .withName(parseString(ctx.referentialIdentifier())) - .withElementType(parseTypeSpecifier(ctx.typeSpecifier())); - - return result; - } - - @Override - public AccessModifier visitAccessModifier(cqlParser.AccessModifierContext ctx) { - switch (ctx.getText().toLowerCase()) { - case "public": - return AccessModifier.PUBLIC; - case "private": - return AccessModifier.PRIVATE; - default: - throw new IllegalArgumentException(String.format( - "Unknown access modifier %s.", ctx.getText().toLowerCase())); - } - } - - @Override - public CodeSystemDef visitCodesystemDefinition(cqlParser.CodesystemDefinitionContext ctx) { - CodeSystemDef cs = (CodeSystemDef) of.createCodeSystemDef() - .withAccessLevel(parseAccessModifier(ctx.accessModifier())) - .withName(parseString(ctx.identifier())) - .withId(parseString(ctx.codesystemId())) - .withVersion(parseString(ctx.versionSpecifier())); - - if (libraryBuilder.isCompatibleWith("1.5")) { - cs.setResultType(libraryBuilder.resolveTypeName("System", "CodeSystem")); - } else { - cs.setResultType(new ListType(libraryBuilder.resolveTypeName("System", "Code"))); - } - - libraryBuilder.addCodeSystem(cs); - libraryBuilder.pushIdentifier(cs.getName(), cs, IdentifierScope.GLOBAL); - return cs; - } - - @Override - public CodeSystemRef visitCodesystemIdentifier(cqlParser.CodesystemIdentifierContext ctx) { - String libraryName = parseString(ctx.libraryIdentifier()); - String name = parseString(ctx.identifier()); - CodeSystemDef def; - if (libraryName != null) { - def = libraryBuilder.resolveLibrary(libraryName).resolveCodeSystemRef(name); - libraryBuilder.checkAccessLevel(libraryName, name, def.getAccessLevel()); - } else { - def = libraryBuilder.resolveCodeSystemRef(name); - } - - if (def == null) { - // ERROR: - throw new IllegalArgumentException(String.format("Could not resolve reference to code system %s.", name)); - } - - return (CodeSystemRef) of.createCodeSystemRef() - .withLibraryName(libraryName) - .withName(name) - .withResultType(def.getResultType()); - } - - @Override - public CodeRef visitCodeIdentifier(cqlParser.CodeIdentifierContext ctx) { - String libraryName = parseString(ctx.libraryIdentifier()); - String name = parseString(ctx.identifier()); - CodeDef def; - if (libraryName != null) { - def = libraryBuilder.resolveLibrary(libraryName).resolveCodeRef(name); - libraryBuilder.checkAccessLevel(libraryName, name, def.getAccessLevel()); - } else { - def = libraryBuilder.resolveCodeRef(name); - } - - if (def == null) { - // ERROR: - throw new IllegalArgumentException(String.format("Could not resolve reference to code %s.", name)); - } - - return (CodeRef) - of.createCodeRef().withLibraryName(libraryName).withName(name).withResultType(def.getResultType()); - } - - @Override - public ValueSetDef visitValuesetDefinition(cqlParser.ValuesetDefinitionContext ctx) { - ValueSetDef vs = of.createValueSetDef() - .withAccessLevel(parseAccessModifier(ctx.accessModifier())) - .withName(parseString(ctx.identifier())) - .withId(parseString(ctx.valuesetId())) - .withVersion(parseString(ctx.versionSpecifier())); - - if (ctx.codesystems() != null) { - for (cqlParser.CodesystemIdentifierContext codesystem : - ctx.codesystems().codesystemIdentifier()) { - var cs = (CodeSystemRef) visit(codesystem); - if (cs == null) { - throw new IllegalArgumentException( - String.format("Could not resolve reference to code system %s.", codesystem.getText())); - } - - vs.getCodeSystem().add(cs); - } - } - if (libraryBuilder.isCompatibleWith("1.5")) { - vs.setResultType(libraryBuilder.resolveTypeName("System", "ValueSet")); - } else { - vs.setResultType(new ListType(libraryBuilder.resolveTypeName("System", "Code"))); - } - libraryBuilder.addValueSet(vs); - libraryBuilder.pushIdentifier(vs.getName(), vs, IdentifierScope.GLOBAL); - - return vs; - } - - @Override - public CodeDef visitCodeDefinition(cqlParser.CodeDefinitionContext ctx) { - CodeDef cd = of.createCodeDef() - .withAccessLevel(parseAccessModifier(ctx.accessModifier())) - .withName(parseString(ctx.identifier())) - .withId(parseString(ctx.codeId())); - - if (ctx.codesystemIdentifier() != null) { - cd.setCodeSystem((CodeSystemRef) visit(ctx.codesystemIdentifier())); - } - - if (ctx.displayClause() != null) { - cd.setDisplay(parseString(ctx.displayClause().STRING())); - } - - cd.setResultType(libraryBuilder.resolveTypeName("Code")); - libraryBuilder.addCode(cd); - libraryBuilder.pushIdentifier(cd.getName(), cd, IdentifierScope.GLOBAL); - - return cd; - } - - @Override - public ConceptDef visitConceptDefinition(cqlParser.ConceptDefinitionContext ctx) { - ConceptDef cd = of.createConceptDef() - .withAccessLevel(parseAccessModifier(ctx.accessModifier())) - .withName(parseString(ctx.identifier())); - - if (ctx.codeIdentifier() != null) { - for (cqlParser.CodeIdentifierContext ci : ctx.codeIdentifier()) { - cd.getCode().add((CodeRef) visit(ci)); - } - } - - if (ctx.displayClause() != null) { - cd.setDisplay(parseString(ctx.displayClause().STRING())); - } - - cd.setResultType(libraryBuilder.resolveTypeName("Concept")); - libraryBuilder.addConcept(cd); - - return cd; - } - - @Override - public NamedTypeSpecifier visitNamedTypeSpecifier(cqlParser.NamedTypeSpecifierContext ctx) { - List qualifiers = parseQualifiers(ctx); - String modelIdentifier = getModelIdentifier(qualifiers); - String identifier = getTypeIdentifier(qualifiers, parseString(ctx.referentialOrTypeNameIdentifier())); - - final ResultWithPossibleError retrievedResult = - libraryBuilder.getNamedTypeSpecifierResult(String.format("%s:%s", modelIdentifier, identifier)); - - if (retrievedResult != null) { - if (retrievedResult.hasError()) { - return null; - } - return retrievedResult.getUnderlyingResultIfExists(); - } - - DataType resultType = libraryBuilder.resolveTypeName(modelIdentifier, identifier); - if (null == resultType) { - throw new CqlCompilerException( - String.format("Could not find type for model: %s and name: %s", modelIdentifier, identifier), - getTrackBack(ctx)); - } - NamedTypeSpecifier result = of.createNamedTypeSpecifier().withName(libraryBuilder.dataTypeToQName(resultType)); - - // Fluent API would be nice here, but resultType isn't part of the model so... - result.setResultType(resultType); - - return result; - } - - private boolean isUnfilteredContext(String contextName) { - return contextName.equals("Unfiltered") - || (libraryBuilder.isCompatibilityLevel3() && contextName.equals("Population")); - } - - @Override - public Object visitContextDefinition(cqlParser.ContextDefinitionContext ctx) { - String modelIdentifier = parseString(ctx.modelIdentifier()); - String unqualifiedIdentifier = parseString(ctx.identifier()); - - setCurrentContext( - modelIdentifier != null ? modelIdentifier + "." + unqualifiedIdentifier : unqualifiedIdentifier); - - if (!isUnfilteredContext(unqualifiedIdentifier)) { - ModelContext modelContext = libraryBuilder.resolveContextName(modelIdentifier, unqualifiedIdentifier); - - // If this is the first time a context definition is encountered, construct a - // context definition: - // define = element of [] - Element modelContextDefinition = contextDefinitions.get(modelContext.getName()); - if (modelContextDefinition == null) { - if (libraryBuilder.hasUsings()) { - ModelInfo modelInfo = modelIdentifier == null - ? libraryBuilder - .getModel(libraryInfo.getDefaultModelName()) - .getModelInfo() - : libraryBuilder.getModel(modelIdentifier).getModelInfo(); - // String contextTypeName = modelContext.getName(); - // DataType contextType = libraryBuilder.resolveTypeName(modelInfo.getName(), - // contextTypeName); - DataType contextType = modelContext.getType(); - modelContextDefinition = libraryBuilder.resolveParameterRef(modelContext.getName()); - if (modelContextDefinition != null) { - contextDefinitions.put(modelContext.getName(), modelContextDefinition); - } else { - Retrieve contextRetrieve = - of.createRetrieve().withDataType(libraryBuilder.dataTypeToQName(contextType)); - track(contextRetrieve, ctx); - contextRetrieve.setResultType(new ListType(contextType)); - String contextClassIdentifier = ((ClassType) contextType).getIdentifier(); - if (contextClassIdentifier != null) { - contextRetrieve.setTemplateId(contextClassIdentifier); - } - - modelContextDefinition = of.createExpressionDef() - .withName(unqualifiedIdentifier) - .withContext(getCurrentContext()) - .withExpression(of.createSingletonFrom().withOperand(contextRetrieve)); - track(modelContextDefinition, ctx); - ((ExpressionDef) modelContextDefinition).getExpression().setResultType(contextType); - modelContextDefinition.setResultType(contextType); - libraryBuilder.addExpression((ExpressionDef) modelContextDefinition); - contextDefinitions.put(modelContext.getName(), modelContextDefinition); - } - } else { - modelContextDefinition = of.createExpressionDef() - .withName(unqualifiedIdentifier) - .withContext(getCurrentContext()) - .withExpression(of.createNull()); - track(modelContextDefinition, ctx); - ((ExpressionDef) modelContextDefinition) - .getExpression() - .setResultType(libraryBuilder.resolveTypeName("System", "Any")); - modelContextDefinition.setResultType(((ExpressionDef) modelContextDefinition) - .getExpression() - .getResultType()); - libraryBuilder.addExpression((ExpressionDef) modelContextDefinition); - contextDefinitions.put(modelContext.getName(), modelContextDefinition); - } - } - } - - ContextDef contextDef = of.createContextDef().withName(getCurrentContext()); - track(contextDef, ctx); - if (libraryBuilder.isCompatibleWith("1.5")) { - libraryBuilder.addContext(contextDef); - } - - return getCurrentContext(); - } - - private boolean isImplicitContextExpressionDef(ExpressionDef def) { - for (Element e : contextDefinitions.values()) { - if (def == e) { - return true; - } - } - - return false; - } - - private void removeImplicitContextExpressionDef(ExpressionDef def) { - for (Map.Entry e : contextDefinitions.entrySet()) { - if (def == e.getValue()) { - contextDefinitions.remove(e.getKey()); - break; - } - } - } - - public ExpressionDef internalVisitExpressionDefinition(cqlParser.ExpressionDefinitionContext ctx) { - String identifier = parseString(ctx.identifier()); - ExpressionDef def = libraryBuilder.resolveExpressionRef(identifier); - - // First time visiting this expression definition, create a lightweight ExpressionDef to be used to output a - // hiding warning message - // If it's the second time around, we'll be able to resolve it and we can assume it's already on the - // hiding stack. - if (def == null) { - final ExpressionDef hollowExpressionDef = - of.createExpressionDef().withName(identifier).withContext(getCurrentContext()); - libraryBuilder.pushIdentifier(identifier, hollowExpressionDef, IdentifierScope.GLOBAL); - } - - if (def == null || isImplicitContextExpressionDef(def)) { - if (def != null && isImplicitContextExpressionDef(def)) { - libraryBuilder.removeExpression(def); - removeImplicitContextExpressionDef(def); - def = null; - } - libraryBuilder.pushExpressionContext(getCurrentContext()); - try { - libraryBuilder.pushExpressionDefinition(identifier); - try { - def = of.createExpressionDef() - .withAccessLevel(parseAccessModifier(ctx.accessModifier())) - .withName(identifier) - .withContext(getCurrentContext()) - .withExpression((Expression) visit(ctx.expression())); - if (def.getExpression() != null) { - def.setResultType(def.getExpression().getResultType()); - } - libraryBuilder.addExpression(def); - } finally { - libraryBuilder.popExpressionDefinition(); - } - } finally { - libraryBuilder.popExpressionContext(); - } - } - - return def; - } - - @Override - public ExpressionDef visitExpressionDefinition(cqlParser.ExpressionDefinitionContext ctx) { - this.libraryBuilder.pushIdentifierScope(); - try { - ExpressionDef expressionDef = internalVisitExpressionDefinition(ctx); - if (forwards.isEmpty() || !forwards.peek().getName().equals(expressionDef.getName())) { - if (definedExpressionDefinitions.contains(expressionDef.getName())) { - // ERROR: - throw new IllegalArgumentException( - String.format("Identifier %s is already in use in this library.", expressionDef.getName())); - } - - // Track defined expression definitions locally, otherwise duplicate expression - // definitions will be missed - // because they are - // overwritten by name when they are encountered by the preprocessor. - definedExpressionDefinitions.add(expressionDef.getName()); - } - return expressionDef; - - } finally { - this.libraryBuilder.popIdentifierScope(); - } - } - - @Override - public Literal visitStringLiteral(cqlParser.StringLiteralContext ctx) { - final Literal stringLiteral = libraryBuilder.createLiteral(parseString(ctx.STRING())); - // Literals are never actually pushed to the stack. This just emits a warning if - // the literal is hiding something - libraryBuilder.pushIdentifier(stringLiteral.getValue(), stringLiteral); - return stringLiteral; - } - - @Override - public Literal visitSimpleStringLiteral(cqlParser.SimpleStringLiteralContext ctx) { - return libraryBuilder.createLiteral(parseString(ctx.STRING())); - } - - @Override - public Literal visitBooleanLiteral(cqlParser.BooleanLiteralContext ctx) { - return libraryBuilder.createLiteral(Boolean.valueOf(ctx.getText())); - } - - @Override - public Object visitIntervalSelector(cqlParser.IntervalSelectorContext ctx) { - return libraryBuilder.createInterval( - parseExpression(ctx.expression(0)), - ctx.getChild(1).getText().equals("["), - parseExpression(ctx.expression(1)), - ctx.getChild(5).getText().equals("]")); - } - - @Override - public Object visitTupleElementSelector(cqlParser.TupleElementSelectorContext ctx) { - TupleElement result = of.createTupleElement() - .withName(parseString(ctx.referentialIdentifier())) - .withValue(parseExpression(ctx.expression())); - result.setResultType(result.getValue().getResultType()); - return result; - } - - @Override - public Object visitTupleSelector(cqlParser.TupleSelectorContext ctx) { - Tuple tuple = of.createTuple(); - TupleType tupleType = new TupleType(); - for (cqlParser.TupleElementSelectorContext elementContext : ctx.tupleElementSelector()) { - TupleElement element = (TupleElement) visit(elementContext); - tupleType.addElement(new TupleTypeElement(element.getName(), element.getResultType())); - tuple.getElement().add(element); - } - tuple.setResultType(tupleType); - return tuple; - } - - @Override - public Object visitInstanceElementSelector(cqlParser.InstanceElementSelectorContext ctx) { - InstanceElement result = of.createInstanceElement() - .withName(parseString(ctx.referentialIdentifier())) - .withValue(parseExpression(ctx.expression())); - result.setResultType(result.getValue().getResultType()); - return result; - } - - @Override - public Object visitInstanceSelector(cqlParser.InstanceSelectorContext ctx) { - Instance instance = of.createInstance(); - NamedTypeSpecifier classTypeSpecifier = (NamedTypeSpecifier) visitNamedTypeSpecifier(ctx.namedTypeSpecifier()); - instance.setClassType(classTypeSpecifier.getName()); - instance.setResultType(classTypeSpecifier.getResultType()); - - for (cqlParser.InstanceElementSelectorContext elementContext : ctx.instanceElementSelector()) { - InstanceElement element = (InstanceElement) visit(elementContext); - PropertyResolution resolution = - libraryBuilder.resolveProperty(classTypeSpecifier.getResultType(), element.getName()); - element.setValue(libraryBuilder.ensureCompatible(element.getValue(), resolution.getType())); - element.setName(resolution.getName()); - if (resolution.getTargetMap() != null) { - // TODO: Target mapping in instance selectors - throw new IllegalArgumentException("Target Mapping in instance selectors not yet supported"); - } - instance.getElement().add(element); - } - - return instance; - } - - @Override - public Object visitCodeSelector(cqlParser.CodeSelectorContext ctx) { - Code code = of.createCode(); - code.setCode(parseString(ctx.STRING())); - code.setSystem((CodeSystemRef) visit(ctx.codesystemIdentifier())); - if (ctx.displayClause() != null) { - code.setDisplay(parseString(ctx.displayClause().STRING())); - } - - code.setResultType(libraryBuilder.resolveTypeName("System", "Code")); - return code; - } - - @Override - public Object visitConceptSelector(cqlParser.ConceptSelectorContext ctx) { - Concept concept = of.createConcept(); - if (ctx.displayClause() != null) { - concept.setDisplay(parseString(ctx.displayClause().STRING())); - } - - for (cqlParser.CodeSelectorContext codeContext : ctx.codeSelector()) { - concept.getCode().add((Code) visit(codeContext)); - } - - concept.setResultType(libraryBuilder.resolveTypeName("System", "Concept")); - return concept; - } - - @Override - public Object visitListSelector(cqlParser.ListSelectorContext ctx) { - TypeSpecifier elementTypeSpecifier = parseTypeSpecifier(ctx.typeSpecifier()); - org.hl7.elm.r1.List list = of.createList(); - ListType listType = null; - if (elementTypeSpecifier != null) { - ListTypeSpecifier listTypeSpecifier = of.createListTypeSpecifier().withElementType(elementTypeSpecifier); - track(listTypeSpecifier, ctx.typeSpecifier()); - listType = new ListType(elementTypeSpecifier.getResultType()); - listTypeSpecifier.setResultType(listType); - } - - DataType elementType = elementTypeSpecifier != null ? elementTypeSpecifier.getResultType() : null; - DataType inferredElementType = null; - DataType initialInferredElementType = null; - - List elements = new ArrayList<>(); - for (cqlParser.ExpressionContext elementContext : ctx.expression()) { - Expression element = parseExpression(elementContext); - - if (element == null) { - throw new RuntimeException("Element failed to parse"); - } - - if (elementType != null) { - libraryBuilder.verifyType(element.getResultType(), elementType); - } else { - if (initialInferredElementType == null) { - initialInferredElementType = element.getResultType(); - inferredElementType = initialInferredElementType; - } else { - // Once a list type is inferred as Any, keep it that way - // The only potential exception to this is if the element responsible for the inferred type of Any - // is a null - DataType compatibleType = - libraryBuilder.findCompatibleType(inferredElementType, element.getResultType()); - if (compatibleType != null - && (!inferredElementType.equals(libraryBuilder.resolveTypeName("System", "Any")) - || initialInferredElementType.equals( - libraryBuilder.resolveTypeName("System", "Any")))) { - inferredElementType = compatibleType; - } else { - inferredElementType = libraryBuilder.resolveTypeName("System", "Any"); - } - } - } - - elements.add(element); - } - - if (elementType == null) { - elementType = - inferredElementType == null ? libraryBuilder.resolveTypeName("System", "Any") : inferredElementType; - } - - for (Expression element : elements) { - if (!elementType.isSuperTypeOf(element.getResultType())) { - Conversion conversion = - libraryBuilder.findConversion(element.getResultType(), elementType, true, false); - if (conversion != null) { - list.getElement().add(libraryBuilder.convertExpression(element, conversion)); - } else { - list.getElement().add(element); - } - } else { - list.getElement().add(element); - } - } - - if (listType == null) { - listType = new ListType(elementType); - } - - list.setResultType(listType); - return list; - } - - @Override - public Object visitTimeLiteral(cqlParser.TimeLiteralContext ctx) { - String input = ctx.getText(); - if (input.startsWith("@")) { - input = input.substring(1); - } - - Pattern timePattern = Pattern.compile("T(\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?"); - // -1-------2---3-------4---5-------6---7----------- - - Matcher matcher = timePattern.matcher(input); - if (matcher.matches()) { - try { - Time result = of.createTime(); - int hour = Integer.parseInt(matcher.group(1)); - int minute = -1; - int second = -1; - int millisecond = -1; - if (hour < 0 || hour > 24) { - throw new IllegalArgumentException(String.format("Invalid hour in time literal (%s).", input)); - } - result.setHour(libraryBuilder.createLiteral(hour)); - - if (matcher.group(3) != null) { - minute = Integer.parseInt(matcher.group(3)); - if (minute < 0 || minute >= 60 || (hour == 24 && minute > 0)) { - throw new IllegalArgumentException( - String.format("Invalid minute in time literal (%s).", input)); - } - result.setMinute(libraryBuilder.createLiteral(minute)); - } - - if (matcher.group(5) != null) { - second = Integer.parseInt(matcher.group(5)); - if (second < 0 || second >= 60 || (hour == 24 && second > 0)) { - throw new IllegalArgumentException( - String.format("Invalid second in time literal (%s).", input)); - } - result.setSecond(libraryBuilder.createLiteral(second)); - } - - if (matcher.group(7) != null) { - millisecond = Integer.parseInt(matcher.group(7)); - if (millisecond < 0 || (hour == 24 && millisecond > 0)) { - throw new IllegalArgumentException( - String.format("Invalid millisecond in time literal (%s).", input)); - } - result.setMillisecond(libraryBuilder.createLiteral(millisecond)); - } - - result.setResultType(libraryBuilder.resolveTypeName("System", "Time")); - return result; - } catch (RuntimeException e) { - throw new IllegalArgumentException( - String.format( - "Invalid time input (%s). Use ISO 8601 time representation (hh:mm:ss.fff).", input), - e); - } - } else { - throw new IllegalArgumentException( - String.format("Invalid time input (%s). Use ISO 8601 time representation (hh:mm:ss.fff).", input)); - } - } - - private Expression parseDateTimeLiteral(String input) { - /* - * DATETIME - * : '@' - * [0-9][0-9][0-9][0-9] // year - * ( - * ( - * '-'[0-9][0-9] // month - * ( - * ( - * '-'[0-9][0-9] // day - * ('T' TIMEFORMAT?)? - * ) - * | 'T' - * )? - * ) - * | 'T' - * )? - * ('Z' | ('+' | '-') [0-9][0-9]':'[0-9][0-9])? // timezone offset - * ; - */ - - Pattern dateTimePattern = Pattern.compile( - "(\\d{4})(((-(\\d{2}))(((-(\\d{2}))((T)((\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?)?)?)|(T))?)|(T))?((Z)|(([+-])(\\d{2})(\\:(\\d{2}))))?"); - // 1-------234-5--------678-9--------11--11-------1---1-------1---1-------1---1-----------------2------2----22---22-----2-------2---2----------- - // ----------------------------------01--23-------4---5-------6---7-------8---9-----------------0------1----23---45-----6-------7---8----------- - - /* - * year - group 1 - * month - group 5 - * day - group 9 - * day dateTime indicator - group 11 - * hour - group 13 - * minute - group 15 - * second - group 17 - * millisecond - group 19 - * month dateTime indicator - group 20 - * year dateTime indicator - group 21 - * utc indicator - group 23 - * timezone offset polarity - group 25 - * timezone offset hour - group 26 - * timezone offset minute - group 28 - */ - - /* - * Pattern dateTimePattern = - * Pattern.compile( - * "(\\d{4})(-(\\d{2}))?(-(\\d{2}))?((Z)|(T((\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?)?((Z)|(([+-])(\\d{2})(\\:?(\\d{2}))?))?))?" - * ); - * //1-------2-3---------4-5---------67---8-91-------1---1-------1---1-------1-- - * -1-------------11---12-----2-------2----2--------------- - * //----------------------------------------0-------1---2-------3---4-------5-- - * -6-------------78---90-----1-------2----3--------------- - */ - - Matcher matcher = dateTimePattern.matcher(input); - if (matcher.matches()) { - try { - GregorianCalendar calendar = (GregorianCalendar) GregorianCalendar.getInstance(); - DateTime result = of.createDateTime(); - int year = Integer.parseInt(matcher.group(1)); - int month = -1; - int day = -1; - int hour = -1; - int minute = -1; - int second = -1; - int millisecond = -1; - result.setYear(libraryBuilder.createLiteral(year)); - if (matcher.group(5) != null) { - month = Integer.parseInt(matcher.group(5)); - if (month < 0 || month > 12) { - throw new IllegalArgumentException( - String.format("Invalid month in date/time literal (%s).", input)); - } - result.setMonth(libraryBuilder.createLiteral(month)); - } - - if (matcher.group(9) != null) { - day = Integer.parseInt(matcher.group(9)); - int maxDay = 31; - switch (month) { - case 2: - maxDay = calendar.isLeapYear(year) ? 29 : 28; - break; - case 4: - case 6: - case 9: - case 11: - maxDay = 30; - break; - default: - break; - } - - if (day < 0 || day > maxDay) { - throw new IllegalArgumentException( - String.format("Invalid day in date/time literal (%s).", input)); - } - - result.setDay(libraryBuilder.createLiteral(day)); - } - - if (matcher.group(13) != null) { - hour = Integer.parseInt(matcher.group(13)); - if (hour < 0 || hour > 24) { - throw new IllegalArgumentException( - String.format("Invalid hour in date/time literal (%s).", input)); - } - result.setHour(libraryBuilder.createLiteral(hour)); - } - - if (matcher.group(15) != null) { - minute = Integer.parseInt(matcher.group(15)); - if (minute < 0 || minute >= 60 || (hour == 24 && minute > 0)) { - throw new IllegalArgumentException( - String.format("Invalid minute in date/time literal (%s).", input)); - } - result.setMinute(libraryBuilder.createLiteral(minute)); - } - - if (matcher.group(17) != null) { - second = Integer.parseInt(matcher.group(17)); - if (second < 0 || second >= 60 || (hour == 24 && second > 0)) { - throw new IllegalArgumentException( - String.format("Invalid second in date/time literal (%s).", input)); - } - result.setSecond(libraryBuilder.createLiteral(second)); - } - - if (matcher.group(19) != null) { - millisecond = Integer.parseInt(matcher.group(19)); - if (millisecond < 0 || (hour == 24 && millisecond > 0)) { - throw new IllegalArgumentException( - String.format("Invalid millisecond in date/time literal (%s).", input)); - } - result.setMillisecond(libraryBuilder.createLiteral(millisecond)); - } - - if (matcher.group(23) != null && matcher.group(23).equals("Z")) { - result.setTimezoneOffset(libraryBuilder.createLiteral(0.0)); - } - - if (matcher.group(25) != null) { - int offsetPolarity = matcher.group(25).equals("+") ? 1 : -1; - - if (matcher.group(28) != null) { - int hourOffset = Integer.parseInt(matcher.group(26)); - if (hourOffset < 0 || hourOffset > 14) { - throw new IllegalArgumentException(String.format( - "Timezone hour offset is out of range in date/time literal (%s).", input)); - } - - int minuteOffset = Integer.parseInt(matcher.group(28)); - if (minuteOffset < 0 || minuteOffset >= 60 || (hourOffset == 14 && minuteOffset > 0)) { - throw new IllegalArgumentException(String.format( - "Timezone minute offset is out of range in date/time literal (%s).", input)); - } - - result.setTimezoneOffset(libraryBuilder.createLiteral( - (double) (hourOffset + ((double) minuteOffset / 60)) * offsetPolarity)); - } else { - if (matcher.group(26) != null) { - int hourOffset = Integer.parseInt(matcher.group(26)); - if (hourOffset < 0 || hourOffset > 14) { - throw new IllegalArgumentException(String.format( - "Timezone hour offset is out of range in date/time literal (%s).", input)); - } - - result.setTimezoneOffset( - libraryBuilder.createLiteral((double) (hourOffset * offsetPolarity))); - } - } - } - - if (result.getHour() == null - && matcher.group(11) == null - && matcher.group(20) == null - && matcher.group(21) == null) { - org.hl7.elm.r1.Date date = of.createDate(); - date.setYear(result.getYear()); - date.setMonth(result.getMonth()); - date.setDay(result.getDay()); - date.setResultType(libraryBuilder.resolveTypeName("System", "Date")); - return date; - } - - result.setResultType(libraryBuilder.resolveTypeName("System", "DateTime")); - return result; - } catch (RuntimeException e) { - throw new IllegalArgumentException( - String.format( - "Invalid date-time input (%s). Use ISO 8601 date time representation (yyyy-MM-ddThh:mm:ss.fff(Z|(+/-hh:mm)).", - input), - e); - } - } else { - throw new IllegalArgumentException(String.format( - "Invalid date-time input (%s). Use ISO 8601 date time representation (yyyy-MM-ddThh:mm:ss.fff(Z|+/-hh:mm)).", - input)); - } - } - - @Override - public Object visitDateLiteral(cqlParser.DateLiteralContext ctx) { - String input = ctx.getText(); - if (input.startsWith("@")) { - input = input.substring(1); - } - - return parseDateTimeLiteral(input); - } - - @Override - public Object visitDateTimeLiteral(cqlParser.DateTimeLiteralContext ctx) { - String input = ctx.getText(); - if (input.startsWith("@")) { - input = input.substring(1); - } - - return parseDateTimeLiteral(input); - } - - @Override - public Null visitNullLiteral(cqlParser.NullLiteralContext ctx) { - Null result = of.createNull(); - result.setResultType(libraryBuilder.resolveTypeName("System", "Any")); - return result; - } - - @Override - public Expression visitNumberLiteral(cqlParser.NumberLiteralContext ctx) { - return libraryBuilder.createNumberLiteral(ctx.NUMBER().getText()); - } - - @Override - public Expression visitSimpleNumberLiteral(cqlParser.SimpleNumberLiteralContext ctx) { - return libraryBuilder.createNumberLiteral(ctx.NUMBER().getText()); - } - - @Override - public Literal visitLongNumberLiteral(cqlParser.LongNumberLiteralContext ctx) { - String input = ctx.LONGNUMBER().getText(); - if (input.endsWith("L")) { - input = input.substring(0, input.length() - 1); - } - return libraryBuilder.createLongNumberLiteral(input); - } - - private BigDecimal parseDecimal(String value) { - try { - return new BigDecimal(value); - } catch (Exception e) { - throw new IllegalArgumentException(String.format("Could not parse number literal: %s", value)); - } - } - - @Override - public Expression visitQuantity(cqlParser.QuantityContext ctx) { - if (ctx.unit() != null) { - Quantity result = - libraryBuilder.createQuantity(parseDecimal(ctx.NUMBER().getText()), parseString(ctx.unit())); - return result; - } else { - return libraryBuilder.createNumberLiteral(ctx.NUMBER().getText()); - } - } - - private Quantity getQuantity(Expression source) { - if (source instanceof Literal) { - return libraryBuilder.createQuantity(parseDecimal(((Literal) source).getValue()), "1"); - } else if (source instanceof Quantity) { - return (Quantity) source; - } - - throw new IllegalArgumentException("Could not create quantity from source expression."); - } - - @Override - public Expression visitRatio(cqlParser.RatioContext ctx) { - Quantity numerator = getQuantity((Expression) visit(ctx.quantity(0))); - Quantity denominator = getQuantity((Expression) visit(ctx.quantity(1))); - return libraryBuilder.createRatio(numerator, denominator); - } - - @Override - public Not visitNotExpression(cqlParser.NotExpressionContext ctx) { - Not result = of.createNot().withOperand(parseExpression(ctx.expression())); - libraryBuilder.resolveUnaryCall("System", "Not", result); - return result; - } - - @Override - public Exists visitExistenceExpression(cqlParser.ExistenceExpressionContext ctx) { - Exists result = of.createExists().withOperand(parseExpression(ctx.expression())); - libraryBuilder.resolveUnaryCall("System", "Exists", result); - return result; - } - - @Override - public BinaryExpression visitMultiplicationExpressionTerm(cqlParser.MultiplicationExpressionTermContext ctx) { - BinaryExpression exp = null; - String operatorName = null; - switch (ctx.getChild(1).getText()) { - case "*": - exp = of.createMultiply(); - operatorName = "Multiply"; - break; - case "/": - exp = of.createDivide(); - operatorName = "Divide"; - break; - case "div": - exp = of.createTruncatedDivide(); - operatorName = "TruncatedDivide"; - break; - case "mod": - exp = of.createModulo(); - operatorName = "Modulo"; - break; - default: - throw new IllegalArgumentException(String.format( - "Unsupported operator: %s.", ctx.getChild(1).getText())); - } - - exp.withOperand(parseExpression(ctx.expressionTerm(0)), parseExpression(ctx.expressionTerm(1))); - - libraryBuilder.resolveBinaryCall("System", operatorName, exp); - - return exp; - } - - @Override - public Power visitPowerExpressionTerm(cqlParser.PowerExpressionTermContext ctx) { - Power power = of.createPower() - .withOperand(parseExpression(ctx.expressionTerm(0)), parseExpression(ctx.expressionTerm(1))); - - libraryBuilder.resolveBinaryCall("System", "Power", power); - - return power; - } - - @Override - public Object visitPolarityExpressionTerm(cqlParser.PolarityExpressionTermContext ctx) { - if (ctx.getChild(0).getText().equals("+")) { - return visit(ctx.expressionTerm()); - } - - Negate result = of.createNegate().withOperand(parseExpression(ctx.expressionTerm())); - libraryBuilder.resolveUnaryCall("System", "Negate", result); - return result; - } - - @Override - public Expression visitAdditionExpressionTerm(cqlParser.AdditionExpressionTermContext ctx) { - Expression exp = null; - String operatorName = null; - switch (ctx.getChild(1).getText()) { - case "+": - exp = of.createAdd(); - operatorName = "Add"; - break; - case "-": - exp = of.createSubtract(); - operatorName = "Subtract"; - break; - case "&": - exp = of.createConcatenate(); - operatorName = "Concatenate"; - break; - default: - throw new IllegalArgumentException(String.format( - "Unsupported operator: %s.", ctx.getChild(1).getText())); - } - - if (exp instanceof BinaryExpression) { - ((BinaryExpression) exp) - .withOperand(parseExpression(ctx.expressionTerm(0)), parseExpression(ctx.expressionTerm(1))); - - libraryBuilder.resolveBinaryCall("System", operatorName, (BinaryExpression) exp); - - if (exp.getResultType() == libraryBuilder.resolveTypeName("System", "String")) { - Concatenate concatenate = of.createConcatenate(); - concatenate.getOperand().addAll(((BinaryExpression) exp).getOperand()); - concatenate.setResultType(exp.getResultType()); - exp = concatenate; - } - } else { - Concatenate concatenate = (Concatenate) exp; - concatenate.withOperand(parseExpression(ctx.expressionTerm(0)), parseExpression(ctx.expressionTerm(1))); - - for (int i = 0; i < concatenate.getOperand().size(); i++) { - Expression operand = concatenate.getOperand().get(i); - Literal empty = libraryBuilder.createLiteral(""); - ArrayList params = new ArrayList(); - params.add(operand); - params.add(empty); - Expression coalesce = libraryBuilder.resolveFunction("System", "Coalesce", params); - concatenate.getOperand().set(i, coalesce); - } - libraryBuilder.resolveNaryCall("System", operatorName, concatenate); - } - return exp; - } - - @Override - public Object visitPredecessorExpressionTerm(cqlParser.PredecessorExpressionTermContext ctx) { - return libraryBuilder.buildPredecessor(parseExpression(ctx.expressionTerm())); - } - - @Override - public Object visitSuccessorExpressionTerm(cqlParser.SuccessorExpressionTermContext ctx) { - return libraryBuilder.buildSuccessor(parseExpression(ctx.expressionTerm())); - } - - @Override - public Object visitElementExtractorExpressionTerm(cqlParser.ElementExtractorExpressionTermContext ctx) { - SingletonFrom result = of.createSingletonFrom().withOperand(parseExpression(ctx.expressionTerm())); - - libraryBuilder.resolveUnaryCall("System", "SingletonFrom", result); - return result; - } - - @Override - public Object visitPointExtractorExpressionTerm(cqlParser.PointExtractorExpressionTermContext ctx) { - PointFrom result = of.createPointFrom().withOperand(parseExpression(ctx.expressionTerm())); - - libraryBuilder.resolveUnaryCall("System", "PointFrom", result); - return result; - } - - @Override - public Object visitTypeExtentExpressionTerm(cqlParser.TypeExtentExpressionTermContext ctx) { - String extent = parseString(ctx.getChild(0)); - TypeSpecifier targetType = parseTypeSpecifier(ctx.namedTypeSpecifier()); - switch (extent) { - case "minimum": { - return libraryBuilder.buildMinimum(targetType.getResultType()); - } - - case "maximum": { - return libraryBuilder.buildMaximum(targetType.getResultType()); - } - - default: - throw new IllegalArgumentException(String.format("Unknown extent: %s", extent)); - } - } - - @Override - public Object visitTimeBoundaryExpressionTerm(cqlParser.TimeBoundaryExpressionTermContext ctx) { - UnaryExpression result = null; - String operatorName = null; - - if (ctx.getChild(0).getText().equals("start")) { - result = of.createStart().withOperand(parseExpression(ctx.expressionTerm())); - operatorName = "Start"; - } else { - result = of.createEnd().withOperand(parseExpression(ctx.expressionTerm())); - operatorName = "End"; - } - - libraryBuilder.resolveUnaryCall("System", operatorName, result); - return result; - } - - private DateTimePrecision parseDateTimePrecision(String dateTimePrecision) { - return parseDateTimePrecision(dateTimePrecision, true, true); - } - - private DateTimePrecision parseComparableDateTimePrecision(String dateTimePrecision) { - return parseDateTimePrecision(dateTimePrecision, true, false); - } - - private DateTimePrecision parseComparableDateTimePrecision(String dateTimePrecision, boolean precisionRequired) { - return parseDateTimePrecision(dateTimePrecision, precisionRequired, false); - } - - private DateTimePrecision parseDateTimePrecision( - String dateTimePrecision, boolean precisionRequired, boolean allowWeeks) { - if (dateTimePrecision == null) { - if (precisionRequired) { - throw new IllegalArgumentException("dateTimePrecision is null"); - } - - return null; - } - - switch (dateTimePrecision) { - case "a": - case "year": - case "years": - return DateTimePrecision.YEAR; - case "mo": - case "month": - case "months": - return DateTimePrecision.MONTH; - case "wk": - case "week": - case "weeks": - if (!allowWeeks) { - throw new IllegalArgumentException("Week precision cannot be used for comparisons."); - } - return DateTimePrecision.WEEK; - case "d": - case "day": - case "days": - return DateTimePrecision.DAY; - case "h": - case "hour": - case "hours": - return DateTimePrecision.HOUR; - case "min": - case "minute": - case "minutes": - return DateTimePrecision.MINUTE; - case "s": - case "second": - case "seconds": - return DateTimePrecision.SECOND; - case "ms": - case "millisecond": - case "milliseconds": - return DateTimePrecision.MILLISECOND; - default: - throw new IllegalArgumentException(String.format("Unknown precision '%s'.", dateTimePrecision)); - } - } - - @Override - public Object visitTimeUnitExpressionTerm(cqlParser.TimeUnitExpressionTermContext ctx) { - String component = ctx.dateTimeComponent().getText(); - - UnaryExpression result = null; - String operatorName = null; - switch (component) { - case "date": - result = of.createDateFrom().withOperand(parseExpression(ctx.expressionTerm())); - operatorName = "DateFrom"; - break; - case "time": - result = of.createTimeFrom().withOperand(parseExpression(ctx.expressionTerm())); - operatorName = "TimeFrom"; - break; - case "timezone": - if (!libraryBuilder.isCompatibilityLevel3()) { - // ERROR: - throw new IllegalArgumentException("Timezone keyword is only valid in 1.3 or lower"); - } - result = of.createTimezoneFrom().withOperand(parseExpression(ctx.expressionTerm())); - operatorName = "TimezoneFrom"; - break; - case "timezoneoffset": - result = of.createTimezoneOffsetFrom().withOperand(parseExpression(ctx.expressionTerm())); - operatorName = "TimezoneOffsetFrom"; - break; - case "year": - case "month": - case "day": - case "hour": - case "minute": - case "second": - case "millisecond": - result = of.createDateTimeComponentFrom() - .withOperand(parseExpression(ctx.expressionTerm())) - .withPrecision(parseDateTimePrecision(component)); - operatorName = "DateTimeComponentFrom"; - break; - case "week": - throw new IllegalArgumentException("Date/time values do not have a week component."); - default: - throw new IllegalArgumentException(String.format("Unknown precision '%s'.", component)); - } - - libraryBuilder.resolveUnaryCall("System", operatorName, result); - return result; - } - - @Override - public Object visitDurationExpressionTerm(cqlParser.DurationExpressionTermContext ctx) { - // duration in days of X <=> days between start of X and end of X - Expression operand = parseExpression(ctx.expressionTerm()); - - Start start = of.createStart().withOperand(operand); - libraryBuilder.resolveUnaryCall("System", "Start", start); - - End end = of.createEnd().withOperand(operand); - libraryBuilder.resolveUnaryCall("System", "End", end); - - DurationBetween result = of.createDurationBetween() - .withPrecision( - parseDateTimePrecision(ctx.pluralDateTimePrecision().getText())) - .withOperand(start, end); - - libraryBuilder.resolveBinaryCall("System", "DurationBetween", result); - return result; - } - - @Override - public Object visitDifferenceExpressionTerm(cqlParser.DifferenceExpressionTermContext ctx) { - // difference in days of X <=> difference in days between start of X and end of - // X - Expression operand = parseExpression(ctx.expressionTerm()); - - Start start = of.createStart().withOperand(operand); - libraryBuilder.resolveUnaryCall("System", "Start", start); - - End end = of.createEnd().withOperand(operand); - libraryBuilder.resolveUnaryCall("System", "End", end); - - DifferenceBetween result = of.createDifferenceBetween() - .withPrecision( - parseDateTimePrecision(ctx.pluralDateTimePrecision().getText())) - .withOperand(start, end); - - libraryBuilder.resolveBinaryCall("System", "DifferenceBetween", result); - return result; - } - - @Override - public Object visitBetweenExpression(cqlParser.BetweenExpressionContext ctx) { - // X properly? between Y and Z - Expression first = parseExpression(ctx.expression()); - Expression second = parseExpression(ctx.expressionTerm(0)); - Expression third = parseExpression(ctx.expressionTerm(1)); - boolean isProper = ctx.getChild(0).getText().equals("properly"); - - if (first.getResultType() instanceof IntervalType) { - BinaryExpression result = isProper - ? of.createProperIncludedIn() - : of.createIncludedIn() - .withOperand(first, libraryBuilder.createInterval(second, true, third, true)); - - libraryBuilder.resolveBinaryCall("System", isProper ? "ProperIncludedIn" : "IncludedIn", result); - return result; - } else { - BinaryExpression result = of.createAnd() - .withOperand( - (isProper ? of.createGreater() : of.createGreaterOrEqual()).withOperand(first, second), - (isProper ? of.createLess() : of.createLessOrEqual()).withOperand(first, third)); - - libraryBuilder.resolveBinaryCall("System", isProper ? "Greater" : "GreaterOrEqual", (BinaryExpression) - result.getOperand().get(0)); - libraryBuilder.resolveBinaryCall("System", isProper ? "Less" : "LessOrEqual", (BinaryExpression) - result.getOperand().get(1)); - libraryBuilder.resolveBinaryCall("System", "And", result); - return result; - } - } - - @Override - public Object visitDurationBetweenExpression(cqlParser.DurationBetweenExpressionContext ctx) { - BinaryExpression result = of.createDurationBetween() - .withPrecision( - parseDateTimePrecision(ctx.pluralDateTimePrecision().getText())) - .withOperand(parseExpression(ctx.expressionTerm(0)), parseExpression(ctx.expressionTerm(1))); - - libraryBuilder.resolveBinaryCall("System", "DurationBetween", result); - return result; - } - - @Override - public Object visitDifferenceBetweenExpression(cqlParser.DifferenceBetweenExpressionContext ctx) { - BinaryExpression result = of.createDifferenceBetween() - .withPrecision( - parseDateTimePrecision(ctx.pluralDateTimePrecision().getText())) - .withOperand(parseExpression(ctx.expressionTerm(0)), parseExpression(ctx.expressionTerm(1))); - - libraryBuilder.resolveBinaryCall("System", "DifferenceBetween", result); - return result; - } - - @Override - public Object visitWidthExpressionTerm(cqlParser.WidthExpressionTermContext ctx) { - UnaryExpression result = of.createWidth().withOperand(parseExpression(ctx.expressionTerm())); - libraryBuilder.resolveUnaryCall("System", "Width", result); - return result; - } - - @Override - public Expression visitParenthesizedTerm(cqlParser.ParenthesizedTermContext ctx) { - return parseExpression(ctx.expression()); - } - - @Override - public Object visitMembershipExpression(cqlParser.MembershipExpressionContext ctx) { - String operator = ctx.getChild(1).getText(); - - switch (operator) { - case "in": - if (ctx.dateTimePrecisionSpecifier() != null) { - In in = of.createIn() - .withPrecision(parseComparableDateTimePrecision(ctx.dateTimePrecisionSpecifier() - .dateTimePrecision() - .getText())) - .withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - - libraryBuilder.resolveBinaryCall("System", "In", in); - return in; - } else { - Expression left = parseExpression(ctx.expression(0)); - Expression right = parseExpression(ctx.expression(1)); - return libraryBuilder.resolveIn(left, right); - } - case "contains": - if (ctx.dateTimePrecisionSpecifier() != null) { - Contains contains = of.createContains() - .withPrecision(parseComparableDateTimePrecision(ctx.dateTimePrecisionSpecifier() - .dateTimePrecision() - .getText())) - .withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - - libraryBuilder.resolveBinaryCall("System", "Contains", contains); - return contains; - } else { - Expression left = parseExpression(ctx.expression(0)); - Expression right = parseExpression(ctx.expression(1)); - if (left instanceof ValueSetRef) { - InValueSet in = of.createInValueSet() - .withCode(right) - .withValueset((ValueSetRef) left) - .withValuesetExpression(left); - libraryBuilder.resolveCall("System", "InValueSet", new InValueSetInvocation(in)); - return in; - } - - if (left instanceof CodeSystemRef) { - InCodeSystem in = of.createInCodeSystem() - .withCode(right) - .withCodesystem((CodeSystemRef) left) - .withCodesystemExpression(left); - libraryBuilder.resolveCall("System", "InCodeSystem", new InCodeSystemInvocation(in)); - return in; - } - - Contains contains = of.createContains().withOperand(left, right); - libraryBuilder.resolveBinaryCall("System", "Contains", contains); - return contains; - } - } - - throw new IllegalArgumentException(String.format("Unknown operator: %s", operator)); - } - - @Override - public And visitAndExpression(cqlParser.AndExpressionContext ctx) { - And and = of.createAnd().withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - - libraryBuilder.resolveBinaryCall("System", "And", and); - return and; - } - - @Override - public Expression visitOrExpression(cqlParser.OrExpressionContext ctx) { - if (ctx.getChild(1).getText().equals("xor")) { - Xor xor = - of.createXor().withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - libraryBuilder.resolveBinaryCall("System", "Xor", xor); - return xor; - } else { - Or or = of.createOr().withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - libraryBuilder.resolveBinaryCall("System", "Or", or); - return or; - } - } - - @Override - public Expression visitImpliesExpression(cqlParser.ImpliesExpressionContext ctx) { - Implies implies = - of.createImplies().withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - - libraryBuilder.resolveBinaryCall("System", "Implies", implies); - return implies; - } - - @Override - public Object visitInFixSetExpression(cqlParser.InFixSetExpressionContext ctx) { - String operator = ctx.getChild(1).getText(); - - Expression left = parseExpression(ctx.expression(0)); - Expression right = parseExpression(ctx.expression(1)); - - switch (operator) { - case "|": - case "union": - return libraryBuilder.resolveUnion(left, right); - case "intersect": - return libraryBuilder.resolveIntersect(left, right); - case "except": - return libraryBuilder.resolveExcept(left, right); - } - - return of.createNull(); - } - - @Override - public Expression visitEqualityExpression(cqlParser.EqualityExpressionContext ctx) { - String operator = parseString(ctx.getChild(1)); - if (operator.equals("~") || operator.equals("!~")) { - BinaryExpression equivalent = of.createEquivalent() - .withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - - libraryBuilder.resolveBinaryCall("System", "Equivalent", equivalent); - - if (!"~".equals(parseString(ctx.getChild(1)))) { - track(equivalent, ctx); - Not not = of.createNot().withOperand(equivalent); - libraryBuilder.resolveUnaryCall("System", "Not", not); - return not; - } - - return equivalent; - } else { - BinaryExpression equal = of.createEqual() - .withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - - libraryBuilder.resolveBinaryCall("System", "Equal", equal); - if (!"=".equals(parseString(ctx.getChild(1)))) { - track(equal, ctx); - Not not = of.createNot().withOperand(equal); - libraryBuilder.resolveUnaryCall("System", "Not", not); - return not; - } - - return equal; - } - } - - @Override - public BinaryExpression visitInequalityExpression(cqlParser.InequalityExpressionContext ctx) { - BinaryExpression exp; - String operatorName; - switch (parseString(ctx.getChild(1))) { - case "<=": - operatorName = "LessOrEqual"; - exp = of.createLessOrEqual(); - break; - case "<": - operatorName = "Less"; - exp = of.createLess(); - break; - case ">": - operatorName = "Greater"; - exp = of.createGreater(); - break; - case ">=": - operatorName = "GreaterOrEqual"; - exp = of.createGreaterOrEqual(); - break; - default: - throw new IllegalArgumentException( - String.format("Unknown operator: %s", ctx.getChild(1).getText())); - } - exp.withOperand(parseExpression(ctx.expression(0)), parseExpression(ctx.expression(1))); - - libraryBuilder.resolveBinaryCall("System", operatorName, exp); - return exp; - } - - @Override - public List visitQualifiedIdentifier(cqlParser.QualifiedIdentifierContext ctx) { - // Return the list of qualified identifiers for resolution by the containing - // element - List identifiers = new ArrayList<>(); - for (cqlParser.QualifierContext qualifierContext : ctx.qualifier()) { - String qualifier = parseString(qualifierContext); - identifiers.add(qualifier); - } - - String identifier = parseString(ctx.identifier()); - identifiers.add(identifier); - return identifiers; - } - - @Override - public List visitQualifiedIdentifierExpression(cqlParser.QualifiedIdentifierExpressionContext ctx) { - // Return the list of qualified identifiers for resolution by the containing - // element - List identifiers = new ArrayList<>(); - for (cqlParser.QualifierExpressionContext qualifierContext : ctx.qualifierExpression()) { - String qualifier = parseString(qualifierContext); - identifiers.add(qualifier); - } - - String identifier = parseString(ctx.referentialIdentifier()); - identifiers.add(identifier); - return identifiers; - } - - @Override - public String visitSimplePathReferentialIdentifier(cqlParser.SimplePathReferentialIdentifierContext ctx) { - return (String) visit(ctx.referentialIdentifier()); - } - - @Override - public String visitSimplePathQualifiedIdentifier(cqlParser.SimplePathQualifiedIdentifierContext ctx) { - return visit(ctx.simplePath()) + "." + visit(ctx.referentialIdentifier()); - } - - @Override - public String visitSimplePathIndexer(cqlParser.SimplePathIndexerContext ctx) { - return visit(ctx.simplePath()) + "[" + visit(ctx.simpleLiteral()) + "]"; - } - - @Override - public Object visitTermExpression(cqlParser.TermExpressionContext ctx) { - Object result = super.visitTermExpression(ctx); - - if (result instanceof LibraryRef) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Identifier %s is a library and cannot be used as an expression.", - ((LibraryRef) result).getLibraryName())); - } - - return result; - } - - @Override - public Object visitTerminal(TerminalNode node) { - String text = node.getText(); - int tokenType = node.getSymbol().getType(); - - if (cqlLexer.EOF == tokenType) { - return null; - } - - if (cqlLexer.STRING == tokenType - || cqlLexer.QUOTEDIDENTIFIER == tokenType - || cqlLexer.DELIMITEDIDENTIFIER == tokenType) { - // chop off leading and trailing ', ", or ` - text = text.substring(1, text.length() - 1); - - // This is an alternate style of escaping that was removed when we switched to - // industry-standard escape - // sequences - // if (cqlLexer.STRING == tokenType) { - // text = text.replace("''", "'"); - // } - // else { - // text = text.replace("\"\"", "\""); - // } - } - - return text; - } - - @Override - public Object visitConversionExpressionTerm(cqlParser.ConversionExpressionTermContext ctx) { - if (ctx.typeSpecifier() != null) { - TypeSpecifier targetType = parseTypeSpecifier(ctx.typeSpecifier()); - Expression operand = parseExpression(ctx.expression()); - if (!DataTypes.equal(operand.getResultType(), targetType.getResultType())) { - Conversion conversion = - libraryBuilder.findConversion(operand.getResultType(), targetType.getResultType(), false, true); - if (conversion == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not resolve conversion from type %s to type %s.", - operand.getResultType(), targetType.getResultType())); - } - - return libraryBuilder.convertExpression(operand, conversion); - } - - return operand; - } else { - String targetUnit = parseString(ctx.unit()); - targetUnit = libraryBuilder.ensureUcumUnit(targetUnit); - Expression operand = parseExpression(ctx.expression()); - Expression unitOperand = libraryBuilder.createLiteral(targetUnit); - track(unitOperand, ctx.unit()); - ConvertQuantity convertQuantity = of.createConvertQuantity().withOperand(operand, unitOperand); - track(convertQuantity, ctx); - return libraryBuilder.resolveBinaryCall("System", "ConvertQuantity", convertQuantity); - } - } - - @Override - public Object visitTypeExpression(cqlParser.TypeExpressionContext ctx) { - // NOTE: These don't use the buildIs or buildAs because those start with a - // DataType, rather than a TypeSpecifier - if (ctx.getChild(1).getText().equals("is")) { - Is is = of.createIs() - .withOperand(parseExpression(ctx.expression())) - .withIsTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())); - is.setResultType(libraryBuilder.resolveTypeName("System", "Boolean")); - return is; - } - - As as = of.createAs() - .withOperand(parseExpression(ctx.expression())) - .withAsTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())) - .withStrict(false); - DataType targetType = as.getAsTypeSpecifier().getResultType(); - DataTypes.verifyCast(targetType, as.getOperand().getResultType()); - as.setResultType(targetType); - return as; - } - - @Override - public Object visitCastExpression(cqlParser.CastExpressionContext ctx) { - // NOTE: This doesn't use buildAs because it starts with a DataType, rather than - // a TypeSpecifier - As as = of.createAs() - .withOperand(parseExpression(ctx.expression())) - .withAsTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())) - .withStrict(true); - DataType targetType = as.getAsTypeSpecifier().getResultType(); - DataTypes.verifyCast(targetType, as.getOperand().getResultType()); - as.setResultType(targetType); - return as; - } - - @Override - public Expression visitBooleanExpression(cqlParser.BooleanExpressionContext ctx) { - UnaryExpression exp = null; - Expression left = (Expression) visit(ctx.expression()); - String lastChild = ctx.getChild(ctx.getChildCount() - 1).getText(); - String nextToLast = ctx.getChild(ctx.getChildCount() - 2).getText(); - switch (lastChild) { - case "null": - exp = of.createIsNull().withOperand(left); - libraryBuilder.resolveUnaryCall("System", "IsNull", exp); - break; - - case "true": - exp = of.createIsTrue().withOperand(left); - libraryBuilder.resolveUnaryCall("System", "IsTrue", exp); - break; - - case "false": - exp = of.createIsFalse().withOperand(left); - libraryBuilder.resolveUnaryCall("System", "IsFalse", exp); - break; - - default: - throw new IllegalArgumentException(String.format("Unknown boolean test predicate %s.", lastChild)); - } - - if ("not".equals(nextToLast)) { - track(exp, ctx); - exp = of.createNot().withOperand(exp); - libraryBuilder.resolveUnaryCall("System", "Not", exp); - } - - return exp; - } - - @Override - public Object visitTimingExpression(cqlParser.TimingExpressionContext ctx) { - Expression left = parseExpression(ctx.expression(0)); - Expression right = parseExpression(ctx.expression(1)); - TimingOperatorContext timingOperatorContext = new TimingOperatorContext(left, right); - timingOperators.push(timingOperatorContext); - try { - return visit(ctx.intervalOperatorPhrase()); - } finally { - timingOperators.pop(); - } - } - - @Override - public Object visitConcurrentWithIntervalOperatorPhrase(cqlParser.ConcurrentWithIntervalOperatorPhraseContext ctx) { - // ('starts' | 'ends' | 'occurs')? 'same' dateTimePrecision? (relativeQualifier - // | 'as') ('start' | 'end')? - TimingOperatorContext timingOperator = timingOperators.peek(); - ParseTree firstChild = ctx.getChild(0); - if ("starts".equals(firstChild.getText())) { - Start start = of.createStart().withOperand(timingOperator.getLeft()); - track(start, firstChild); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setLeft(start); - } - - if ("ends".equals(firstChild.getText())) { - End end = of.createEnd().withOperand(timingOperator.getLeft()); - track(end, firstChild); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setLeft(end); - } - - ParseTree lastChild = ctx.getChild(ctx.getChildCount() - 1); - if ("start".equals(lastChild.getText())) { - Start start = of.createStart().withOperand(timingOperator.getRight()); - track(start, lastChild); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setRight(start); - } - - if ("end".equals(lastChild.getText())) { - End end = of.createEnd().withOperand(timingOperator.getRight()); - track(end, lastChild); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setRight(end); - } - - String operatorName = null; - BinaryExpression operator = null; - boolean allowPromotionAndDemotion = false; - if (ctx.relativeQualifier() == null) { - if (ctx.dateTimePrecision() != null) { - operator = of.createSameAs() - .withPrecision(parseComparableDateTimePrecision( - ctx.dateTimePrecision().getText())); - } else { - operator = of.createSameAs(); - } - operatorName = "SameAs"; - } else { - switch (ctx.relativeQualifier().getText()) { - case "or after": - { - if (ctx.dateTimePrecision() != null) { - operator = of.createSameOrAfter() - .withPrecision(parseComparableDateTimePrecision( - ctx.dateTimePrecision().getText())); - } else { - operator = of.createSameOrAfter(); - } - operatorName = "SameOrAfter"; - allowPromotionAndDemotion = true; - } - break; - case "or before": - { - if (ctx.dateTimePrecision() != null) { - operator = of.createSameOrBefore() - .withPrecision(parseComparableDateTimePrecision( - ctx.dateTimePrecision().getText())); - } else { - operator = of.createSameOrBefore(); - } - operatorName = "SameOrBefore"; - allowPromotionAndDemotion = true; - } - break; - default: - throw new IllegalArgumentException(String.format( - "Unknown relative qualifier: '%s'.", - ctx.relativeQualifier().getText())); - } - } - - operator = operator.withOperand(timingOperator.getLeft(), timingOperator.getRight()); - libraryBuilder.resolveBinaryCall("System", operatorName, operator, true, allowPromotionAndDemotion); - - return operator; - } - - @Override - public Object visitIncludesIntervalOperatorPhrase(cqlParser.IncludesIntervalOperatorPhraseContext ctx) { - // 'properly'? 'includes' dateTimePrecisionSpecifier? ('start' | 'end')? - boolean isProper = false; - boolean isRightPoint = false; - TimingOperatorContext timingOperator = timingOperators.peek(); - for (ParseTree pt : ctx.children) { - if ("properly".equals(pt.getText())) { - isProper = true; - continue; - } - - if ("start".equals(pt.getText())) { - Start start = of.createStart().withOperand(timingOperator.getRight()); - track(start, pt); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setRight(start); - isRightPoint = true; - continue; - } - - if ("end".equals(pt.getText())) { - End end = of.createEnd().withOperand(timingOperator.getRight()); - track(end, pt); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setRight(end); - isRightPoint = true; - continue; - } - } - - String dateTimePrecision = ctx.dateTimePrecisionSpecifier() != null - ? ctx.dateTimePrecisionSpecifier().dateTimePrecision().getText() - : null; - - // If the right is not convertible to an interval or list - // if (!isRightPoint && - // !(timingOperator.getRight().getResultType() instanceof IntervalType - // || timingOperator.getRight().getResultType() instanceof ListType)) { - // isRightPoint = true; - // } - - if (isRightPoint) { - if (isProper) { - return libraryBuilder.resolveProperContains( - timingOperator.getLeft(), - timingOperator.getRight(), - parseComparableDateTimePrecision(dateTimePrecision, false)); - } - - return libraryBuilder.resolveContains( - timingOperator.getLeft(), - timingOperator.getRight(), - parseComparableDateTimePrecision(dateTimePrecision, false)); - } - - if (isProper) { - return libraryBuilder.resolveProperIncludes( - timingOperator.getLeft(), - timingOperator.getRight(), - parseComparableDateTimePrecision(dateTimePrecision, false)); - } - - return libraryBuilder.resolveIncludes( - timingOperator.getLeft(), - timingOperator.getRight(), - parseComparableDateTimePrecision(dateTimePrecision, false)); - } - - @Override - public Object visitIncludedInIntervalOperatorPhrase(cqlParser.IncludedInIntervalOperatorPhraseContext ctx) { - // ('starts' | 'ends' | 'occurs')? 'properly'? ('during' | 'included in') - // dateTimePrecisionSpecifier? - boolean isProper = false; - boolean isLeftPoint = false; - TimingOperatorContext timingOperator = timingOperators.peek(); - for (ParseTree pt : ctx.children) { - if ("starts".equals(pt.getText())) { - Start start = of.createStart().withOperand(timingOperator.getLeft()); - track(start, pt); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setLeft(start); - isLeftPoint = true; - continue; - } - - if ("ends".equals(pt.getText())) { - End end = of.createEnd().withOperand(timingOperator.getLeft()); - track(end, pt); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setLeft(end); - isLeftPoint = true; - continue; - } - - if ("properly".equals(pt.getText())) { - isProper = true; - continue; - } - } - - String dateTimePrecision = ctx.dateTimePrecisionSpecifier() != null - ? ctx.dateTimePrecisionSpecifier().dateTimePrecision().getText() - : null; - - // If the left is not convertible to an interval or list - // if (!isLeftPoint && - // !(timingOperator.getLeft().getResultType() instanceof IntervalType - // || timingOperator.getLeft().getResultType() instanceof ListType)) { - // isLeftPoint = true; - // } - - if (isLeftPoint) { - if (isProper) { - return libraryBuilder.resolveProperIn( - timingOperator.getLeft(), - timingOperator.getRight(), - parseComparableDateTimePrecision(dateTimePrecision, false)); - } - - return libraryBuilder.resolveIn( - timingOperator.getLeft(), - timingOperator.getRight(), - parseComparableDateTimePrecision(dateTimePrecision, false)); - } - - if (isProper) { - return libraryBuilder.resolveProperIncludedIn( - timingOperator.getLeft(), - timingOperator.getRight(), - parseComparableDateTimePrecision(dateTimePrecision, false)); - } - - return libraryBuilder.resolveIncludedIn( - timingOperator.getLeft(), - timingOperator.getRight(), - parseComparableDateTimePrecision(dateTimePrecision, false)); - } - - @Override - public Object visitBeforeOrAfterIntervalOperatorPhrase(cqlParser.BeforeOrAfterIntervalOperatorPhraseContext ctx) { - // ('starts' | 'ends' | 'occurs')? quantityOffset? ('before' | 'after') - // dateTimePrecisionSpecifier? ('start' | - // 'end')? - - // duration before/after - // A starts 3 days before start B - // * start of A same day as start of B - 3 days - // A starts 3 days after start B - // * start of A same day as start of B + 3 days - - // or more/less duration before/after - // A starts 3 days or more before start B - // * start of A <= start of B - 3 days - // A starts 3 days or more after start B - // * start of A >= start of B + 3 days - // A starts 3 days or less before start B - // * start of A in [start of B - 3 days, start of B) and B is not null - // A starts 3 days or less after start B - // * start of A in (start of B, start of B + 3 days] and B is not null - - // less/more than duration before/after - // A starts more than 3 days before start B - // * start of A < start of B - 3 days - // A starts more than 3 days after start B - // * start of A > start of B + 3 days - // A starts less than 3 days before start B - // * start of A in (start of B - 3 days, start of B) - // A starts less than 3 days after start B - // * start of A in (start of B, start of B + 3 days) - - TimingOperatorContext timingOperator = timingOperators.peek(); - boolean isBefore = false; - boolean isInclusive = false; - for (ParseTree child : ctx.children) { - if ("starts".equals(child.getText())) { - Start start = of.createStart().withOperand(timingOperator.getLeft()); - track(start, child); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setLeft(start); - continue; - } - - if ("ends".equals(child.getText())) { - End end = of.createEnd().withOperand(timingOperator.getLeft()); - track(end, child); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setLeft(end); - continue; - } - - if ("start".equals(child.getText())) { - Start start = of.createStart().withOperand(timingOperator.getRight()); - track(start, child); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setRight(start); - continue; - } - - if ("end".equals(child.getText())) { - End end = of.createEnd().withOperand(timingOperator.getRight()); - track(end, child); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setRight(end); - continue; - } - } - - for (ParseTree child : ctx.temporalRelationship().children) { - if ("before".equals(child.getText())) { - isBefore = true; - continue; - } - - if ("on or".equals(child.getText()) || "or on".equals(child.getText())) { - isInclusive = true; - continue; - } - } - - String dateTimePrecision = ctx.dateTimePrecisionSpecifier() != null - ? ctx.dateTimePrecisionSpecifier().dateTimePrecision().getText() - : null; - - if (ctx.quantityOffset() == null) { - if (isInclusive) { - if (isBefore) { - SameOrBefore sameOrBefore = - of.createSameOrBefore().withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - sameOrBefore.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "SameOrBefore", sameOrBefore, true, true); - return sameOrBefore; - - } else { - SameOrAfter sameOrAfter = - of.createSameOrAfter().withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - sameOrAfter.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "SameOrAfter", sameOrAfter, true, true); - return sameOrAfter; - } - } else { - if (isBefore) { - Before before = of.createBefore().withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - before.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "Before", before, true, true); - return before; - - } else { - After after = of.createAfter().withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - after.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "After", after, true, true); - return after; - } - } - } else { - Quantity quantity = (Quantity) visit(ctx.quantityOffset().quantity()); - - if (timingOperator.getLeft().getResultType() instanceof IntervalType) { - if (isBefore) { - End end = of.createEnd().withOperand(timingOperator.getLeft()); - track(end, timingOperator.getLeft()); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setLeft(end); - } else { - Start start = of.createStart().withOperand(timingOperator.getLeft()); - track(start, timingOperator.getLeft()); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setLeft(start); - } - } - - if (timingOperator.getRight().getResultType() instanceof IntervalType) { - if (isBefore) { - Start start = of.createStart().withOperand(timingOperator.getRight()); - track(start, timingOperator.getRight()); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setRight(start); - } else { - End end = of.createEnd().withOperand(timingOperator.getRight()); - track(end, timingOperator.getRight()); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setRight(end); - } - } - - if (ctx.quantityOffset().offsetRelativeQualifier() == null - && ctx.quantityOffset().exclusiveRelativeQualifier() == null) { - // Use a SameAs - // For a Before, subtract the quantity from the right operand - // For an After, add the quantity to the right operand - if (isBefore) { - Subtract subtract = of.createSubtract().withOperand(timingOperator.getRight(), quantity); - track(subtract, timingOperator.getRight()); - libraryBuilder.resolveBinaryCall("System", "Subtract", subtract); - timingOperator.setRight(subtract); - } else { - Add add = of.createAdd().withOperand(timingOperator.getRight(), quantity); - track(add, timingOperator.getRight()); - libraryBuilder.resolveBinaryCall("System", "Add", add); - timingOperator.setRight(add); - } - - SameAs sameAs = of.createSameAs().withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - sameAs.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "SameAs", sameAs); - return sameAs; - } else { - boolean isOffsetInclusive = ctx.quantityOffset().offsetRelativeQualifier() != null; - String qualifier = ctx.quantityOffset().offsetRelativeQualifier() != null - ? ctx.quantityOffset().offsetRelativeQualifier().getText() - : ctx.quantityOffset().exclusiveRelativeQualifier().getText(); - - switch (qualifier) { - case "more than": - case "or more": - // For More Than/Or More, Use a Before/After/SameOrBefore/SameOrAfter - // For a Before, subtract the quantity from the right operand - // For an After, add the quantity to the right operand - if (isBefore) { - Subtract subtract = of.createSubtract().withOperand(timingOperator.getRight(), quantity); - track(subtract, timingOperator.getRight()); - libraryBuilder.resolveBinaryCall("System", "Subtract", subtract); - timingOperator.setRight(subtract); - - if (!isOffsetInclusive) { - Before before = of.createBefore() - .withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - before.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "Before", before, true, true); - return before; - } else { - SameOrBefore sameOrBefore = of.createSameOrBefore() - .withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - sameOrBefore.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "SameOrBefore", sameOrBefore, true, true); - return sameOrBefore; - } - } else { - Add add = of.createAdd().withOperand(timingOperator.getRight(), quantity); - track(add, timingOperator.getRight()); - libraryBuilder.resolveBinaryCall("System", "Add", add); - timingOperator.setRight(add); - - if (!isOffsetInclusive) { - After after = of.createAfter() - .withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - after.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "After", after, true, true); - return after; - } else { - SameOrAfter sameOrAfter = of.createSameOrAfter() - .withOperand(timingOperator.getLeft(), timingOperator.getRight()); - if (dateTimePrecision != null) { - sameOrAfter.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - libraryBuilder.resolveBinaryCall("System", "SameOrAfter", sameOrAfter, true, true); - return sameOrAfter; - } - } - - case "less than": - case "or less": - // For Less Than/Or Less, Use an In - // For Before, construct an interval from right - quantity to right - // For After, construct an interval from right to right + quantity - Expression lowerBound = null; - Expression upperBound = null; - Expression right = timingOperator.getRight(); - if (isBefore) { - lowerBound = of.createSubtract().withOperand(right, quantity); - track(lowerBound, right); - libraryBuilder.resolveBinaryCall("System", "Subtract", (BinaryExpression) lowerBound); - upperBound = right; - } else { - lowerBound = right; - upperBound = of.createAdd().withOperand(right, quantity); - track(upperBound, right); - libraryBuilder.resolveBinaryCall("System", "Add", (BinaryExpression) upperBound); - } - - // 3 days or less before -> [B - 3 days, B) - // less than 3 days before -> (B - 3 days, B) - // 3 days or less after -> (B, B + 3 days] - // less than 3 days after -> (B, B + 3 days) - Interval interval = isBefore - ? libraryBuilder.createInterval(lowerBound, isOffsetInclusive, upperBound, isInclusive) - : libraryBuilder.createInterval(lowerBound, isInclusive, upperBound, isOffsetInclusive); - - track(interval, ctx.quantityOffset()); - In in = of.createIn().withOperand(timingOperator.getLeft(), interval); - if (dateTimePrecision != null) { - in.setPrecision(parseComparableDateTimePrecision(dateTimePrecision)); - } - track(in, ctx.quantityOffset()); - libraryBuilder.resolveBinaryCall("System", "In", in); - - // if the offset or comparison is inclusive, add a null check for B to ensure - // correct - // interpretation - if (isOffsetInclusive || isInclusive) { - IsNull nullTest = of.createIsNull().withOperand(right); - track(nullTest, ctx.quantityOffset()); - libraryBuilder.resolveUnaryCall("System", "IsNull", nullTest); - Not notNullTest = of.createNot().withOperand(nullTest); - track(notNullTest, ctx.quantityOffset()); - libraryBuilder.resolveUnaryCall("System", "Not", notNullTest); - And and = of.createAnd().withOperand(in, notNullTest); - track(and, ctx.quantityOffset()); - libraryBuilder.resolveBinaryCall("System", "And", and); - return and; - } - - // Otherwise, return the constructed in - return in; - } - } - } - - throw new IllegalArgumentException("Unable to resolve interval operator phrase."); - } - - private BinaryExpression resolveBetweenOperator(String unit, Expression left, Expression right) { - if (unit != null) { - DurationBetween between = of.createDurationBetween() - .withPrecision(parseDateTimePrecision(unit)) - .withOperand(left, right); - libraryBuilder.resolveBinaryCall("System", "DurationBetween", between); - return between; - } - - return null; - } - - @Override - public Object visitWithinIntervalOperatorPhrase(cqlParser.WithinIntervalOperatorPhraseContext ctx) { - // ('starts' | 'ends' | 'occurs')? 'properly'? 'within' quantityLiteral 'of' - // ('start' | 'end')? - // A starts within 3 days of start B - // * start of A in [start of B - 3 days, start of B + 3 days] and start B is not - // null - // A starts within 3 days of B - // * start of A in [start of B - 3 days, end of B + 3 days] - - TimingOperatorContext timingOperator = timingOperators.peek(); - boolean isProper = false; - for (ParseTree child : ctx.children) { - if ("starts".equals(child.getText())) { - Start start = of.createStart().withOperand(timingOperator.getLeft()); - track(start, child); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setLeft(start); - continue; - } - - if ("ends".equals(child.getText())) { - End end = of.createEnd().withOperand(timingOperator.getLeft()); - track(end, child); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setLeft(end); - continue; - } - - if ("start".equals(child.getText())) { - Start start = of.createStart().withOperand(timingOperator.getRight()); - track(start, child); - libraryBuilder.resolveUnaryCall("System", "Start", start); - timingOperator.setRight(start); - continue; - } - - if ("end".equals(child.getText())) { - End end = of.createEnd().withOperand(timingOperator.getRight()); - track(end, child); - libraryBuilder.resolveUnaryCall("System", "End", end); - timingOperator.setRight(end); - continue; - } - - if ("properly".equals(child.getText())) { - isProper = true; - continue; - } - } - - Quantity quantity = (Quantity) visit(ctx.quantity()); - Expression lowerBound = null; - Expression upperBound = null; - Expression initialBound = null; - if (timingOperator.getRight().getResultType() instanceof IntervalType) { - lowerBound = of.createStart().withOperand(timingOperator.getRight()); - track(lowerBound, ctx.quantity()); - libraryBuilder.resolveUnaryCall("System", "Start", (Start) lowerBound); - upperBound = of.createEnd().withOperand(timingOperator.getRight()); - track(upperBound, ctx.quantity()); - libraryBuilder.resolveUnaryCall("System", "End", (End) upperBound); - } else { - lowerBound = timingOperator.getRight(); - upperBound = timingOperator.getRight(); - initialBound = lowerBound; - } - - lowerBound = of.createSubtract().withOperand(lowerBound, quantity); - track(lowerBound, ctx.quantity()); - libraryBuilder.resolveBinaryCall("System", "Subtract", (BinaryExpression) lowerBound); - - upperBound = of.createAdd().withOperand(upperBound, quantity); - track(upperBound, ctx.quantity()); - libraryBuilder.resolveBinaryCall("System", "Add", (BinaryExpression) upperBound); - - Interval interval = libraryBuilder.createInterval(lowerBound, !isProper, upperBound, !isProper); - track(interval, ctx.quantity()); - - In in = of.createIn().withOperand(timingOperator.getLeft(), interval); - libraryBuilder.resolveBinaryCall("System", "In", in); - - // if the within is not proper and the interval is being constructed from a - // single point, add a null check for - // that point to ensure correct interpretation - if (!isProper && (initialBound != null)) { - IsNull nullTest = of.createIsNull().withOperand(initialBound); - track(nullTest, ctx.quantity()); - libraryBuilder.resolveUnaryCall("System", "IsNull", nullTest); - Not notNullTest = of.createNot().withOperand(nullTest); - track(notNullTest, ctx.quantity()); - libraryBuilder.resolveUnaryCall("System", "Not", notNullTest); - And and = of.createAnd().withOperand(in, notNullTest); - track(and, ctx.quantity()); - libraryBuilder.resolveBinaryCall("System", "And", and); - return and; - } - - // Otherwise, return the constructed in - return in; - } - - @Override - public Object visitMeetsIntervalOperatorPhrase(cqlParser.MeetsIntervalOperatorPhraseContext ctx) { - String operatorName = null; - BinaryExpression operator; - String dateTimePrecision = ctx.dateTimePrecisionSpecifier() != null - ? ctx.dateTimePrecisionSpecifier().dateTimePrecision().getText() - : null; - - if (ctx.getChildCount() == (1 + (dateTimePrecision == null ? 0 : 1))) { - operator = dateTimePrecision != null - ? of.createMeets().withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) - : of.createMeets(); - operatorName = "Meets"; - } else { - if ("before".equals(ctx.getChild(1).getText())) { - operator = dateTimePrecision != null - ? of.createMeetsBefore().withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) - : of.createMeetsBefore(); - operatorName = "MeetsBefore"; - } else { - operator = dateTimePrecision != null - ? of.createMeetsAfter().withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) - : of.createMeetsAfter(); - operatorName = "MeetsAfter"; - } - } - - operator.withOperand( - timingOperators.peek().getLeft(), timingOperators.peek().getRight()); - libraryBuilder.resolveBinaryCall("System", operatorName, operator); - return operator; - } - - @Override - public Object visitOverlapsIntervalOperatorPhrase(cqlParser.OverlapsIntervalOperatorPhraseContext ctx) { - String operatorName = null; - BinaryExpression operator; - String dateTimePrecision = ctx.dateTimePrecisionSpecifier() != null - ? ctx.dateTimePrecisionSpecifier().dateTimePrecision().getText() - : null; - - if (ctx.getChildCount() == (1 + (dateTimePrecision == null ? 0 : 1))) { - operator = dateTimePrecision != null - ? of.createOverlaps().withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) - : of.createOverlaps(); - operatorName = "Overlaps"; - } else { - if ("before".equals(ctx.getChild(1).getText())) { - operator = dateTimePrecision != null - ? of.createOverlapsBefore().withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) - : of.createOverlapsBefore(); - operatorName = "OverlapsBefore"; - } else { - operator = dateTimePrecision != null - ? of.createOverlapsAfter().withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) - : of.createOverlapsAfter(); - operatorName = "OverlapsAfter"; - } - } - - operator.withOperand( - timingOperators.peek().getLeft(), timingOperators.peek().getRight()); - libraryBuilder.resolveBinaryCall("System", operatorName, operator); - return operator; - } - - @Override - public Object visitStartsIntervalOperatorPhrase(cqlParser.StartsIntervalOperatorPhraseContext ctx) { - String dateTimePrecision = ctx.dateTimePrecisionSpecifier() != null - ? ctx.dateTimePrecisionSpecifier().dateTimePrecision().getText() - : null; - - Starts starts = (dateTimePrecision != null - ? of.createStarts().withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) - : of.createStarts()) - .withOperand( - timingOperators.peek().getLeft(), timingOperators.peek().getRight()); - - libraryBuilder.resolveBinaryCall("System", "Starts", starts); - return starts; - } - - @Override - public Object visitEndsIntervalOperatorPhrase(cqlParser.EndsIntervalOperatorPhraseContext ctx) { - String dateTimePrecision = ctx.dateTimePrecisionSpecifier() != null - ? ctx.dateTimePrecisionSpecifier().dateTimePrecision().getText() - : null; - - Ends ends = (dateTimePrecision != null - ? of.createEnds().withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) - : of.createEnds()) - .withOperand( - timingOperators.peek().getLeft(), timingOperators.peek().getRight()); - - libraryBuilder.resolveBinaryCall("System", "Ends", ends); - return ends; - } - - public Expression resolveIfThenElse(If ifObject) { - ifObject.setCondition(libraryBuilder.ensureCompatible( - ifObject.getCondition(), libraryBuilder.resolveTypeName("System", "Boolean"))); - DataType resultType = libraryBuilder.ensureCompatibleTypes( - ifObject.getThen().getResultType(), ifObject.getElse().getResultType()); - ifObject.setResultType(resultType); - ifObject.setThen(libraryBuilder.ensureCompatible(ifObject.getThen(), resultType)); - ifObject.setElse(libraryBuilder.ensureCompatible(ifObject.getElse(), resultType)); - return ifObject; - } - - @Override - public Object visitIfThenElseExpressionTerm(cqlParser.IfThenElseExpressionTermContext ctx) { - If ifObject = of.createIf() - .withCondition(parseExpression(ctx.expression(0))) - .withThen(parseExpression(ctx.expression(1))) - .withElse(parseExpression(ctx.expression(2))); - - return resolveIfThenElse(ifObject); - } - - @Override - public Object visitCaseExpressionTerm(cqlParser.CaseExpressionTermContext ctx) { - Case result = of.createCase(); - Boolean hitElse = false; - DataType resultType = null; - for (ParseTree pt : ctx.children) { - if ("else".equals(pt.getText())) { - hitElse = true; - continue; - } - - if (pt instanceof cqlParser.ExpressionContext) { - if (hitElse) { - result.setElse(parseExpression(pt)); - resultType = libraryBuilder.ensureCompatibleTypes( - resultType, result.getElse().getResultType()); - } else { - result.setComparand(parseExpression(pt)); - } - } - - if (pt instanceof cqlParser.CaseExpressionItemContext) { - CaseItem caseItem = (CaseItem) visit(pt); - if (result.getComparand() != null) { - libraryBuilder.verifyType( - caseItem.getWhen().getResultType(), - result.getComparand().getResultType()); - } else { - DataTypes.verifyType( - caseItem.getWhen().getResultType(), libraryBuilder.resolveTypeName("System", "Boolean")); - } - - if (resultType == null) { - resultType = caseItem.getThen().getResultType(); - } else { - resultType = libraryBuilder.ensureCompatibleTypes( - resultType, caseItem.getThen().getResultType()); - } - - result.getCaseItem().add(caseItem); - } - } - - for (CaseItem caseItem : result.getCaseItem()) { - if (result.getComparand() != null) { - caseItem.setWhen(libraryBuilder.ensureCompatible( - caseItem.getWhen(), result.getComparand().getResultType())); - } - - caseItem.setThen(libraryBuilder.ensureCompatible(caseItem.getThen(), resultType)); - } - - result.setElse(libraryBuilder.ensureCompatible(result.getElse(), resultType)); - result.setResultType(resultType); - return result; - } - - @Override - public Object visitCaseExpressionItem(cqlParser.CaseExpressionItemContext ctx) { - return of.createCaseItem() - .withWhen(parseExpression(ctx.expression(0))) - .withThen(parseExpression(ctx.expression(1))); - } - - @Override - public Object visitAggregateExpressionTerm(cqlParser.AggregateExpressionTermContext ctx) { - switch (ctx.getChild(0).getText()) { - case "distinct": - Distinct distinct = of.createDistinct().withOperand(parseExpression(ctx.expression())); - libraryBuilder.resolveUnaryCall("System", "Distinct", distinct); - return distinct; - case "flatten": - Flatten flatten = of.createFlatten().withOperand(parseExpression(ctx.expression())); - libraryBuilder.resolveUnaryCall("System", "Flatten", flatten); - return flatten; - } - - throw new IllegalArgumentException( - String.format("Unknown aggregate operator %s.", ctx.getChild(0).getText())); - } - - @Override - public Object visitSetAggregateExpressionTerm(cqlParser.SetAggregateExpressionTermContext ctx) { - Expression source = parseExpression(ctx.expression(0)); - - // If `per` is not set, it will remain `null as System.Quantity`. - Expression per = libraryBuilder.buildNull(libraryBuilder.resolveTypeName("System", "Quantity")); - if (ctx.dateTimePrecision() != null) { - per = libraryBuilder.createQuantity(BigDecimal.valueOf(1.0), parseString(ctx.dateTimePrecision())); - } else if (ctx.expression().size() > 1) { - per = parseExpression(ctx.expression(1)); - } else { - // Determine per quantity based on point type of the intervals involved - if (source.getResultType() instanceof ListType) { - ListType listType = (ListType) source.getResultType(); - if (listType.getElementType() instanceof IntervalType) { - IntervalType intervalType = (IntervalType) listType.getElementType(); - DataType pointType = intervalType.getPointType(); - - // TODO: Test this... - // // Successor(MinValue) - MinValue - // MinValue minimum = libraryBuilder.buildMinimum(pointType); - // track(minimum, ctx); - // - // Expression successor = libraryBuilder.buildSuccessor(minimum); - // track(successor, ctx); - // - // minimum = libraryBuilder.buildMinimum(pointType); - // track(minimum, ctx); - // - // Subtract subtract = of.createSubtract().withOperand(successor, minimum); - // libraryBuilder.resolveBinaryCall("System", "Subtract", subtract); - // per = subtract; - } - } - } - - switch (ctx.getChild(0).getText()) { - case "expand": - Expand expand = of.createExpand().withOperand(source, per); - libraryBuilder.resolveBinaryCall("System", "Expand", expand); - return expand; - - case "collapse": - Collapse collapse = of.createCollapse().withOperand(source, per); - libraryBuilder.resolveBinaryCall("System", "Collapse", collapse); - return collapse; - } - - throw new IllegalArgumentException(String.format( - "Unknown aggregate set operator %s.", ctx.getChild(0).getText())); - } - - @Override - @SuppressWarnings("unchecked") - public Expression visitRetrieve(cqlParser.RetrieveContext ctx) { - libraryBuilder.checkLiteralContext(); - List qualifiers = parseQualifiers(ctx.namedTypeSpecifier()); - String model = getModelIdentifier(qualifiers); - String label = getTypeIdentifier( - qualifiers, parseString(ctx.namedTypeSpecifier().referentialOrTypeNameIdentifier())); - DataType dataType = libraryBuilder.resolveTypeName(model, label); - if (dataType == null) { - // ERROR: - throw new IllegalArgumentException(String.format("Could not resolve type name %s.", label)); - } - - if (!(dataType instanceof ClassType) || !((ClassType) dataType).isRetrievable()) { - // ERROR: - throw new IllegalArgumentException( - String.format("Specified data type %s does not support retrieval.", label)); - } - - ClassType classType = (ClassType) dataType; - // BTR -> The original intent of this code was to have the retrieve return the - // base type, and use the - // "templateId" - // element of the retrieve to communicate the "positive" or "negative" profile - // to the data access layer. - // However, because this notion of carrying the "profile" through a type is not - // general, it causes - // inconsistencies - // when using retrieve results with functions defined in terms of the same type - // (see GitHub Issue #131). - // Based on the discussion there, the retrieve will now return the declared - // type, whether it is a profile or - // not. - // ProfileType profileType = dataType instanceof ProfileType ? - // (ProfileType)dataType : null; - // NamedType namedType = profileType == null ? classType : - // (NamedType)classType.getBaseType(); - NamedType namedType = classType; - - ModelInfo modelInfo = libraryBuilder.getModel(namedType.getNamespace()).getModelInfo(); - boolean useStrictRetrieveTyping = - modelInfo.isStrictRetrieveTyping() != null && modelInfo.isStrictRetrieveTyping(); - - String codePath = null; - Property property = null; - CqlCompilerException propertyException = null; - Expression terminology = null; - String codeComparator = null; - if (ctx.terminology() != null) { - if (ctx.codePath() != null) { - String identifiers = (String) visit(ctx.codePath()); - codePath = identifiers; - } else if (classType.getPrimaryCodePath() != null) { - codePath = classType.getPrimaryCodePath(); - } - - if (codePath == null) { - // ERROR: - // WARNING: - propertyException = new CqlSemanticException( - "Retrieve has a terminology target but does not specify a code path and the type of the retrieve does not have a primary code path defined.", - useStrictRetrieveTyping - ? CqlCompilerException.ErrorSeverity.Error - : CqlCompilerException.ErrorSeverity.Warning, - getTrackBack(ctx)); - libraryBuilder.recordParsingException(propertyException); - } else { - try { - DataType codeType = libraryBuilder.resolvePath((DataType) namedType, codePath); - property = of.createProperty().withPath(codePath); - property.setResultType(codeType); - } catch (Exception e) { - // ERROR: - // WARNING: - propertyException = new CqlSemanticException( - String.format( - "Could not resolve code path %s for the type of the retrieve %s.", - codePath, namedType.getName()), - useStrictRetrieveTyping - ? CqlCompilerException.ErrorSeverity.Error - : CqlCompilerException.ErrorSeverity.Warning, - getTrackBack(ctx), - e); - libraryBuilder.recordParsingException(propertyException); - } - } - - if (ctx.terminology().qualifiedIdentifierExpression() != null) { - List identifiers = (List) visit(ctx.terminology()); - terminology = resolveQualifiedIdentifier(identifiers); - track(terminology, ctx.terminology().qualifiedIdentifierExpression()); - } else { - terminology = parseExpression(ctx.terminology().expression()); - } - - codeComparator = ctx.codeComparator() != null ? (String) visit(ctx.codeComparator()) : null; - } - - Expression result = null; - - // Only expand a choice-valued code path if no comparator is specified - // Otherwise, a code comparator will always choose a specific representation - boolean hasFHIRHelpers = libraryInfo.resolveLibraryName("FHIRHelpers") != null; - if (property != null && property.getResultType() instanceof ChoiceType && codeComparator == null) { - for (DataType propertyType : ((ChoiceType) property.getResultType()).getTypes()) { - if (hasFHIRHelpers - && propertyType instanceof NamedType - && ((NamedType) propertyType).getSimpleName().equals("Reference") - && (namedType.getSimpleName().equals("MedicationRequest") - || namedType.getSimpleName().equals("MedicationAdministration") - || namedType.getSimpleName().equals("MedicationDispense") - || namedType.getSimpleName().equals("MedicationStatement"))) { - // TODO: This is a model-specific special case to support QICore - // This functionality needs to be generalized to a retrieve mapping in the model - // info. But that requires a model info change (to represent references, right - // now the model info only includes context relationships) - // The reference expands to - // [MedicationRequest] MR - // with [Medication] M - // such that M.id = Last(Split(MR.medication.reference, '/')) - // and M.code in - Retrieve mrRetrieve = buildRetrieve( - ctx, useStrictRetrieveTyping, namedType, classType, null, null, null, null, null, null); - retrieves.add(mrRetrieve); - mrRetrieve.setResultType(new ListType((DataType) namedType)); - DataType mDataType = libraryBuilder.resolveTypeName(model, "Medication"); - ClassType mClassType = (ClassType) mDataType; - NamedType mNamedType = mClassType; - Retrieve mRetrieve = buildRetrieve( - ctx, useStrictRetrieveTyping, mNamedType, mClassType, null, null, null, null, null, null); - retrieves.add(mRetrieve); - mRetrieve.setResultType(new ListType(mDataType)); - Query q = of.createQuery(); - AliasedQuerySource aqs = of.createAliasedQuerySource() - .withExpression(mrRetrieve) - .withAlias("MR"); - track(aqs, ctx); - aqs.setResultType(aqs.getExpression().getResultType()); - q.getSource().add(aqs); - track(q, ctx); - q.setResultType(aqs.getResultType()); - With w = of.createWith().withExpression(mRetrieve).withAlias("M"); - track(w, ctx); - w.setResultType(w.getExpression().getResultType()); - q.getRelationship().add(w); - String idPath = "id"; - DataType idType = libraryBuilder.resolvePath(mDataType, idPath); - Property idProperty = libraryBuilder.buildProperty("M", idPath, false, idType); - String refPath = "medication.reference"; - DataType refType = libraryBuilder.resolvePath(dataType, refPath); - Property refProperty = libraryBuilder.buildProperty("MR", refPath, false, refType); - Split split = of.createSplit() - .withStringToSplit(refProperty) - .withSeparator(libraryBuilder.createLiteral("/")); - libraryBuilder.resolveCall("System", "Split", new SplitInvocation(split)); - Last last = of.createLast().withSource(split); - libraryBuilder.resolveCall("System", "Last", new LastInvocation(last)); - Equal e = of.createEqual().withOperand(idProperty, last); - libraryBuilder.resolveBinaryCall("System", "Equal", e); - - // Apply target mapping if this is a profile-informed model info - if (DataTypes.equal(idType, libraryBuilder.resolveTypeName("System", "String"))) { - idProperty.setPath("id.value"); - } - if (DataTypes.equal(refType, libraryBuilder.resolveTypeName("System", "String"))) { - refProperty.setPath("medication.reference.value"); - } - - DataType mCodeType = libraryBuilder.resolvePath((DataType) mNamedType, "code"); - Property mProperty = libraryBuilder.buildProperty("M", "code", false, mCodeType); - Expression mCodeProperty = mProperty; - - // Apply target mapping if this is a profile-informed model info - if (DataTypes.equal(mCodeType, libraryBuilder.resolveTypeName("System", "Concept"))) { - FunctionRef toConcept = of.createFunctionRef() - .withLibraryName("FHIRHelpers") - .withName("ToConcept") - .withOperand(mCodeProperty); - toConcept.setResultType(mCodeType); - mCodeProperty = toConcept; - } - String mCodeComparator = "~"; - if (terminology.getResultType() instanceof ListType) { - mCodeComparator = "in"; - - } else if (libraryBuilder.isCompatibleWith("1.5")) { - mCodeComparator = terminology - .getResultType() - .isSubTypeOf(libraryBuilder.resolveTypeName("System", "Vocabulary")) - ? "in" - : "~"; - } - - Expression terminologyComparison = null; - if (mCodeComparator.equals("in")) { - terminologyComparison = libraryBuilder.resolveIn(mCodeProperty, terminology); - } else { - BinaryExpression equivalent = of.createEquivalent().withOperand(mCodeProperty, terminology); - libraryBuilder.resolveBinaryCall("System", "Equivalent", equivalent); - terminologyComparison = equivalent; - } - And a = of.createAnd().withOperand(e, terminologyComparison); - libraryBuilder.resolveBinaryCall("System", "And", a); - w.withSuchThat(a); - - if (result == null) { - result = q; - } else { - track(q, ctx); - result = libraryBuilder.resolveUnion(result, q); - } - } else { - Retrieve retrieve = buildRetrieve( - ctx, - useStrictRetrieveTyping, - namedType, - classType, - codePath, - codeComparator, - property, - propertyType, - propertyException, - terminology); - retrieves.add(retrieve); - retrieve.setResultType(new ListType((DataType) namedType)); - - if (result == null) { - result = retrieve; - } else { - // Should only include the result if it resolved appropriately with the - // codeComparator - // Allowing it to go through for now - // if (retrieve.getCodeProperty() != null && retrieve.getCodeComparator() != - // null && - // retrieve.getCodes() != null) { - track(retrieve, ctx); - result = libraryBuilder.resolveUnion(result, retrieve); - // } - } - } - } - } else { - Retrieve retrieve = buildRetrieve( - ctx, - useStrictRetrieveTyping, - namedType, - classType, - codePath, - codeComparator, - property, - property != null ? property.getResultType() : null, - propertyException, - terminology); - retrieves.add(retrieve); - retrieve.setResultType(new ListType((DataType) namedType)); - result = retrieve; - } - - return result; - } - - private Retrieve buildRetrieve( - cqlParser.RetrieveContext ctx, - boolean useStrictRetrieveTyping, - NamedType namedType, - ClassType classType, - String codePath, - String codeComparator, - Property property, - DataType propertyType, - Exception propertyException, - Expression terminology) { - - Retrieve retrieve = of.createRetrieve() - .withDataType(libraryBuilder.dataTypeToQName((DataType) namedType)) - .withTemplateId(classType.getIdentifier()) - .withCodeProperty(codePath); - - if (ctx.contextIdentifier() != null) { - @SuppressWarnings("unchecked") - List identifiers = (List) visit(ctx.contextIdentifier()); - Expression contextExpression = resolveQualifiedIdentifier(identifiers); - retrieve.setContext(contextExpression); - } - - if (terminology != null) { - // Resolve the terminology target using an in or ~ operator - try { - if (codeComparator == null) { - codeComparator = "~"; - if (terminology.getResultType() instanceof ListType) { - codeComparator = "in"; - } else if (libraryBuilder.isCompatibleWith("1.5")) { - if (propertyType != null - && propertyType.isSubTypeOf(libraryBuilder.resolveTypeName("System", "Vocabulary"))) { - codeComparator = terminology - .getResultType() - .isSubTypeOf(libraryBuilder.resolveTypeName("System", "Vocabulary")) - ? "~" - : "contains"; - } else { - codeComparator = terminology - .getResultType() - .isSubTypeOf(libraryBuilder.resolveTypeName("System", "Vocabulary")) - ? "in" - : "~"; - } - } - } - - if (property == null) { - throw propertyException; - } - - switch (codeComparator) { - case "in": - { - Expression in = libraryBuilder.resolveIn(property, terminology); - if (in instanceof In) { - retrieve.setCodes(((In) in).getOperand().get(1)); - } else if (in instanceof InValueSet) { - retrieve.setCodes(((InValueSet) in).getValueset()); - } else if (in instanceof InCodeSystem) { - retrieve.setCodes(((InCodeSystem) in).getCodesystem()); - } else if (in instanceof AnyInValueSet) { - retrieve.setCodes(((AnyInValueSet) in).getValueset()); - } else if (in instanceof AnyInCodeSystem) { - retrieve.setCodes(((AnyInCodeSystem) in).getCodesystem()); - } else { - // ERROR: - // WARNING: - libraryBuilder.recordParsingException(new CqlSemanticException( - String.format( - "Unexpected membership operator %s in retrieve", - in.getClass().getSimpleName()), - useStrictRetrieveTyping - ? CqlCompilerException.ErrorSeverity.Error - : CqlCompilerException.ErrorSeverity.Warning, - getTrackBack(ctx))); - } - } - break; - - case "contains": - { - Expression contains = libraryBuilder.resolveContains(property, terminology); - if (contains instanceof Contains) { - retrieve.setCodes( - ((Contains) contains).getOperand().get(1)); - } - // TODO: Introduce support for the contains operator to make this possible to - // support with a - // retrieve (direct-reference code negation) - // ERROR: - libraryBuilder.recordParsingException(new CqlSemanticException( - "Terminology resolution using contains is not supported at this time. Use a where clause with an in operator instead.", - useStrictRetrieveTyping - ? CqlCompilerException.ErrorSeverity.Error - : CqlCompilerException.ErrorSeverity.Warning, - getTrackBack(ctx))); - } - break; - - case "~": - { - // Resolve with equivalent to verify the type of the target - BinaryExpression equivalent = of.createEquivalent().withOperand(property, terminology); - libraryBuilder.resolveBinaryCall("System", "Equivalent", equivalent); - - // Automatically promote to a list for use in the retrieve target - if (!(equivalent.getOperand().get(1).getResultType() instanceof ListType - || (libraryBuilder.isCompatibleWith("1.5") - && equivalent - .getOperand() - .get(1) - .getResultType() - .isSubTypeOf( - libraryBuilder.resolveTypeName("System", "Vocabulary"))))) { - retrieve.setCodes(libraryBuilder.resolveToList( - equivalent.getOperand().get(1))); - } else { - retrieve.setCodes(equivalent.getOperand().get(1)); - } - } - break; - - case "=": - { - // Resolve with equality to verify the type of the source and target - BinaryExpression equal = of.createEqual().withOperand(property, terminology); - libraryBuilder.resolveBinaryCall("System", "Equal", equal); - - // Automatically promote to a list for use in the retrieve target - if (!(equal.getOperand().get(1).getResultType() instanceof ListType - || (libraryBuilder.isCompatibleWith("1.5") - && equal.getOperand() - .get(1) - .getResultType() - .isSubTypeOf( - libraryBuilder.resolveTypeName("System", "Vocabulary"))))) { - retrieve.setCodes(libraryBuilder.resolveToList( - equal.getOperand().get(1))); - } else { - retrieve.setCodes(equal.getOperand().get(1)); - } - } - break; - - default: - // ERROR: - // WARNING: - libraryBuilder.recordParsingException(new CqlSemanticException( - String.format("Unknown code comparator %s in retrieve", codeComparator), - useStrictRetrieveTyping - ? CqlCompilerException.ErrorSeverity.Error - : CqlCompilerException.ErrorSeverity.Warning, - getTrackBack(ctx.codeComparator()))); - } - - retrieve.setCodeComparator(codeComparator); - - // Verify that the type of the terminology target is a List - // Due to implicit conversion defined by specific models, the resolution path - // above may result in a - // List - // In that case, convert to a list of code (Union the Code elements of the - // Concepts in the list) - if (retrieve.getCodes() != null - && retrieve.getCodes().getResultType() != null - && retrieve.getCodes().getResultType() instanceof ListType - && ((ListType) retrieve.getCodes().getResultType()) - .getElementType() - .equals(libraryBuilder.resolveTypeName("System", "Concept"))) { - if (retrieve.getCodes() instanceof ToList) { - // ToList will always have a single argument - ToList toList = (ToList) retrieve.getCodes(); - // If that argument is a ToConcept, replace the ToList argument with the code - // (skip the implicit - // conversion, the data access layer is responsible for it) - if (toList.getOperand() instanceof ToConcept) { - toList.setOperand(((ToConcept) toList.getOperand()).getOperand()); - } else { - // Otherwise, access the codes property of the resulting Concept - Expression codesAccessor = libraryBuilder.buildProperty( - toList.getOperand(), - "codes", - false, - toList.getOperand().getResultType()); - retrieve.setCodes(codesAccessor); - } - } else { - // WARNING: - libraryBuilder.recordParsingException(new CqlSemanticException( - "Terminology target is a list of concepts, but expects a list of codes", - CqlCompilerException.ErrorSeverity.Warning, - getTrackBack(ctx))); - } - } - } catch (Exception e) { - // If something goes wrong attempting to resolve, just set to the expression and - // report it as a warning, - // it shouldn't prevent translation unless the modelinfo indicates strict - // retrieve typing - if ((libraryBuilder.isCompatibleWith("1.5") - && !(terminology - .getResultType() - .isSubTypeOf(libraryBuilder.resolveTypeName("System", "Vocabulary")))) - || (!libraryBuilder.isCompatibleWith("1.5") - && !(terminology.getResultType() instanceof ListType))) { - retrieve.setCodes(libraryBuilder.resolveToList(terminology)); - } else { - retrieve.setCodes(terminology); - } - retrieve.setCodeComparator(codeComparator); - // ERROR: - // WARNING: - libraryBuilder.recordParsingException(new CqlSemanticException( - "Could not resolve membership operator for terminology target of the retrieve.", - useStrictRetrieveTyping - ? CqlCompilerException.ErrorSeverity.Error - : CqlCompilerException.ErrorSeverity.Warning, - getTrackBack(ctx), - e)); - } - } - - return retrieve; - } - - @Override - public Object visitSourceClause(cqlParser.SourceClauseContext ctx) { - boolean hasFrom = "from".equals(ctx.getChild(0).getText()); - if (!hasFrom && isFromKeywordRequired()) { - throw new IllegalArgumentException("The from keyword is required for queries."); - } - - List sources = new ArrayList<>(); - for (cqlParser.AliasedQuerySourceContext source : ctx.aliasedQuerySource()) { - if (sources.size() > 0 && !hasFrom) { - throw new IllegalArgumentException("The from keyword is required for multi-source queries."); - } - sources.add((AliasedQuerySource) visit(source)); - } - return sources; - } - - @Override - @SuppressWarnings("unchecked") - public Object visitQuery(cqlParser.QueryContext ctx) { - QueryContext queryContext = new QueryContext(); - libraryBuilder.pushQueryContext(queryContext); - List sources = null; - try { - - queryContext.enterSourceClause(); - try { - sources = (List) visit(ctx.sourceClause()); - } finally { - queryContext.exitSourceClause(); - } - - queryContext.addPrimaryQuerySources(sources); - - for (AliasedQuerySource source : sources) { - libraryBuilder.pushIdentifier(source.getAlias(), source); - } - - // If we are evaluating a population-level query whose source ranges over any - // patient-context expressions, - // then references to patient context expressions within the iteration clauses - // of the query can be accessed - // at the patient, rather than the population, context. - boolean expressionContextPushed = false; - /* - * TODO: Address the issue of referencing multiple context expressions within a - * query (or even expression in general) - * if (libraryBuilder.inUnfilteredContext() && - * queryContext.referencesSpecificContext()) { - * libraryBuilder.pushExpressionContext("Patient"); - * expressionContextPushed = true; - * } - */ - List dfcx = null; - try { - dfcx = ctx.letClause() != null ? (List) visit(ctx.letClause()) : null; - - if (dfcx != null) { - for (LetClause letClause : dfcx) { - libraryBuilder.pushIdentifier(letClause.getIdentifier(), letClause); - } - } - - List qicx = new ArrayList<>(); - if (ctx.queryInclusionClause() != null) { - for (cqlParser.QueryInclusionClauseContext queryInclusionClauseContext : - ctx.queryInclusionClause()) { - qicx.add((RelationshipClause) visit(queryInclusionClauseContext)); - } - } - - Expression where = ctx.whereClause() != null ? (Expression) visit(ctx.whereClause()) : null; - if (getDateRangeOptimization() && where != null) { - for (AliasedQuerySource aqs : sources) { - where = optimizeDateRangeInQuery(where, aqs); - } - } - - ReturnClause ret = ctx.returnClause() != null ? (ReturnClause) visit(ctx.returnClause()) : null; - AggregateClause agg = - ctx.aggregateClause() != null ? (AggregateClause) visit(ctx.aggregateClause()) : null; - - if ((agg == null) && (ret == null) && (sources.size() > 1)) { - ret = of.createReturnClause().withDistinct(true); - - Tuple returnExpression = of.createTuple(); - TupleType returnType = new TupleType(); - for (AliasedQuerySource aqs : sources) { - TupleElement element = of.createTupleElement() - .withName(aqs.getAlias()) - .withValue(of.createAliasRef().withName(aqs.getAlias())); - DataType sourceType = aqs.getResultType() instanceof ListType - ? ((ListType) aqs.getResultType()).getElementType() - : aqs.getResultType(); - element.getValue().setResultType(sourceType); // Doesn't use the fluent API to avoid casting - element.setResultType(element.getValue().getResultType()); - returnType.addElement(new TupleTypeElement(element.getName(), element.getResultType())); - returnExpression.getElement().add(element); - } - - returnExpression.setResultType(queryContext.isSingular() ? returnType : new ListType(returnType)); - ret.setExpression(returnExpression); - ret.setResultType(returnExpression.getResultType()); - } - - queryContext.removeQuerySources(sources); - if (dfcx != null) { - queryContext.removeLetClauses(dfcx); - } - - DataType queryResultType = null; - if (agg != null) { - queryResultType = agg.getResultType(); - } else if (ret != null) { - queryResultType = ret.getResultType(); - } else { - queryResultType = sources.get(0).getResultType(); - } - - SortClause sort = null; - if (agg == null) { - queryContext.setResultElementType( - queryContext.isSingular() ? null : ((ListType) queryResultType).getElementType()); - if (ctx.sortClause() != null) { - if (queryContext.isSingular()) { - // ERROR: - throw new IllegalArgumentException("Sort clause cannot be used in a singular query."); - } - queryContext.enterSortClause(); - try { - sort = (SortClause) visit(ctx.sortClause()); - // Validate that the sort can be performed based on the existence of comparison - // operators - // for all types involved - for (SortByItem sortByItem : sort.getBy()) { - if (sortByItem instanceof ByDirection) { - // validate that there is a comparison operator defined for the result element - // type - // of the query context - libraryBuilder.verifyComparable(queryContext.getResultElementType()); - } else { - libraryBuilder.verifyComparable(sortByItem.getResultType()); - } - } - } finally { - queryContext.exitSortClause(); - } - } - } else { - if (ctx.sortClause() != null) { - // ERROR: - throw new IllegalArgumentException("Sort clause cannot be used in an aggregate query."); - } - } - - Query query = of.createQuery() - .withSource(sources) - .withLet(dfcx) - .withRelationship(qicx) - .withWhere(where) - .withReturn(ret) - .withAggregate(agg) - .withSort(sort); - - query.setResultType(queryResultType); - return query; - } finally { - if (expressionContextPushed) { - libraryBuilder.popExpressionContext(); - } - if (dfcx != null) { - for (LetClause letClause : dfcx) { - libraryBuilder.popIdentifier(); - } - } - } - - } finally { - libraryBuilder.popQueryContext(); - if (sources != null) { - for (AliasedQuerySource source : sources) { - libraryBuilder.popIdentifier(); - } - } - } - } - - // TODO: Expand this optimization to work the DateLow/DateHigh property - // attributes - - /** - * Some systems may wish to optimize performance by restricting retrieves with - * available date ranges. Specifying - * date ranges in a retrieve was removed from the CQL grammar, but it is still - * possible to extract date ranges from - * the where clause and put them in the Retrieve in ELM. The - * optimizeDateRangeInQuery method - * attempts to do this automatically. If optimization is possible, it will - * remove the corresponding "during" from - * the where clause and insert the date range into the Retrieve. - * - * @param aqs the AliasedQuerySource containing the ClinicalRequest to - * possibly refactor a date range into. - * @param where the Where clause to search for potential date range - * optimizations - * @return the where clause with optimized "durings" removed, or - * null if there is no longer a Where - * clause after optimization. - */ - public Expression optimizeDateRangeInQuery(Expression where, AliasedQuerySource aqs) { - if (aqs.getExpression() instanceof Retrieve) { - Retrieve retrieve = (Retrieve) aqs.getExpression(); - String alias = aqs.getAlias(); - if ((where instanceof IncludedIn || where instanceof In) - && attemptDateRangeOptimization((BinaryExpression) where, retrieve, alias)) { - where = null; - } else if (where instanceof And && attemptDateRangeOptimization((And) where, retrieve, alias)) { - // Now optimize out the trues from the Ands - where = consolidateAnd((And) where); - } - } - return where; - } - - /** - * Test a BinaryExpression expression and determine if it is - * suitable to be refactored into the - * Retrieve as a date range restriction. If so, adjust the - * Retrieve - * accordingly and return true. - * - * @param during the BinaryExpression expression to potentially - * refactor into the Retrieve - * @param retrieve the Retrieve to add qualifying date ranges to - * (if applicable) - * @param alias the alias of the Retrieve in the query. - * @return true if the date range was set in the - * Retrieve; false - * otherwise. - */ - private boolean attemptDateRangeOptimization(BinaryExpression during, Retrieve retrieve, String alias) { - if (retrieve.getDateProperty() != null || retrieve.getDateRange() != null) { - return false; - } - - Expression left = during.getOperand().get(0); - Expression right = during.getOperand().get(1); - - String propertyPath = getPropertyPath(left, alias); - if (propertyPath != null && isRHSEligibleForDateRangeOptimization(right)) { - retrieve.setDateProperty(propertyPath); - retrieve.setDateRange(right); - return true; - } - - return false; - } - - /** - * Collapse a property path expression back to it's qualified form for use as - * the path attribute of the retrieve. - * - * @param reference the Expression to collapse - * @param alias the alias of the Retrieve in the query. - * @return The collapsed path - * operands (or sub-operands) were modified; false - * otherwise. - */ - private String getPropertyPath(Expression reference, String alias) { - reference = getConversionReference(reference); - reference = getChoiceSelection(reference); - if (reference instanceof Property) { - Property property = (Property) reference; - if (alias.equals(property.getScope())) { - return property.getPath(); - } else if (property.getSource() != null) { - String subPath = getPropertyPath(property.getSource(), alias); - if (subPath != null) { - return String.format("%s.%s", subPath, property.getPath()); - } - } - } - - return null; - } - - /** - * If this is a conversion operator, return the argument of the conversion, on - * the grounds that the date range optimization - * should apply through a conversion (i.e. it is an order-preserving conversion) - * - * @param reference the Expression to examine - * @return The argument to the conversion operator if there was one, otherwise, - * the given reference - */ - private Expression getConversionReference(Expression reference) { - if (reference instanceof FunctionRef) { - FunctionRef functionRef = (FunctionRef) reference; - if (functionRef.getOperand().size() == 1 - && functionRef.getResultType() != null - && functionRef.getOperand().get(0).getResultType() != null) { - Operator o = this.libraryBuilder - .getConversionMap() - .getConversionOperator( - functionRef.getOperand().get(0).getResultType(), functionRef.getResultType()); - if (o != null - && o.getLibraryName() != null - && o.getLibraryName().equals(functionRef.getLibraryName()) - && o.getName() != null - && o.getName().equals(functionRef.getName())) { - return functionRef.getOperand().get(0); - } - } - } - - return reference; - } - - /** - * If this is a choice selection, return the argument of the choice selection, - * on the grounds that the date range optimization - * should apply through the cast (i.e. it is an order-preserving cast) - * - * @param reference the Expression to examine - * @return The argument to the choice selection (i.e. As) if there was one, - * otherwise, the given reference - */ - private Expression getChoiceSelection(Expression reference) { - if (reference instanceof As) { - As as = (As) reference; - if (as.getOperand() != null && as.getOperand().getResultType() instanceof ChoiceType) { - return as.getOperand(); - } - } - - return reference; - } - - /** - * Test an And expression and determine if it contains any operands - * (first-level or nested deeper) - * than are IncludedIn expressions that can be refactored into a - * Retrieve. If so, - * adjust the Retrieve accordingly and reset the corresponding - * operand to a literal - * true. This and branch containing a - * true can be further consolidated - * later. - * - * @param and the And expression containing operands to - * potentially refactor into the - * Retrieve - * @param retrieve the Retrieve to add qualifying date ranges to - * (if applicable) - * @param alias the alias of the Retrieve in the query. - * @return true if the date range was set in the - * Retrieve and the And - * operands (or sub-operands) were modified; false - * otherwise. - */ - private boolean attemptDateRangeOptimization(And and, Retrieve retrieve, String alias) { - if (retrieve.getDateProperty() != null || retrieve.getDateRange() != null) { - return false; - } - - for (int i = 0; i < and.getOperand().size(); i++) { - Expression operand = and.getOperand().get(i); - if ((operand instanceof IncludedIn || operand instanceof In) - && attemptDateRangeOptimization((BinaryExpression) operand, retrieve, alias)) { - // Replace optimized part in And with true -- to be optimized out later - and.getOperand().set(i, libraryBuilder.createLiteral(true)); - return true; - } else if (operand instanceof And && attemptDateRangeOptimization((And) operand, retrieve, alias)) { - return true; - } - } - - return false; - } - - /** - * If any branches in the And tree contain a true, - * refactor it out. - * - * @param and the And tree to attempt to consolidate - * @return the potentially consolidated And - */ - private Expression consolidateAnd(And and) { - Expression result = and; - Expression lhs = and.getOperand().get(0); - Expression rhs = and.getOperand().get(1); - if (isBooleanLiteral(lhs, true)) { - result = rhs; - } else if (isBooleanLiteral(rhs, true)) { - result = lhs; - } else if (lhs instanceof And) { - and.getOperand().set(0, consolidateAnd((And) lhs)); - } else if (rhs instanceof And) { - and.getOperand().set(1, consolidateAnd((And) rhs)); - } - - return result; - } - - /** - * Determine if the right-hand side of an IncludedIn expression can - * be refactored into the date range - * of a Retrieve. Currently, refactoring is only supported when the - * RHS is a literal - * DateTime interval, a literal DateTime, a parameter representing a DateTime - * interval or a DateTime, or an - * expression reference representing a DateTime interval or a DateTime. - * - * @param rhs the right-hand side of the IncludedIn to test for - * potential optimization - * @return true if the RHS supports refactoring to a - * Retrieve, false - * otherwise. - */ - private boolean isRHSEligibleForDateRangeOptimization(Expression rhs) { - return rhs.getResultType().isSubTypeOf(libraryBuilder.resolveTypeName("System", "DateTime")) - || rhs.getResultType() - .isSubTypeOf(new IntervalType(libraryBuilder.resolveTypeName("System", "DateTime"))); - - // BTR: The only requirement for the optimization is that the expression be of - // type DateTime or - // Interval - // Whether or not the expression can be statically evaluated (literal, in the - // loose sense of the word) is really - // a function of the engine in determining the "initial" data requirements, - // versus subsequent data requirements - // Element targetElement = rhs; - // if (rhs instanceof ParameterRef) { - // String paramName = ((ParameterRef) rhs).getName(); - // for (ParameterDef def : getLibrary().getParameters().getDef()) { - // if (paramName.equals(def.getName())) { - // targetElement = def.getParameterTypeSpecifier(); - // if (targetElement == null) { - // targetElement = def.getDefault(); - // } - // break; - // } - // } - // } else if (rhs instanceof ExpressionRef && !(rhs instanceof FunctionRef)) { - // // TODO: Support forward declaration, if necessary - // String expName = ((ExpressionRef) rhs).getName(); - // for (ExpressionDef def : getLibrary().getStatements().getDef()) { - // if (expName.equals(def.getName())) { - // targetElement = def.getExpression(); - // } - // } - // } - // - // boolean isEligible = false; - // if (targetElement instanceof DateTime) { - // isEligible = true; - // } else if (targetElement instanceof Interval) { - // Interval ivl = (Interval) targetElement; - // isEligible = (ivl.getLow() != null && ivl.getLow() instanceof DateTime) || - // (ivl.getHigh() != null - // && ivl.getHigh() instanceof DateTime); - // } else if (targetElement instanceof IntervalTypeSpecifier) { - // IntervalTypeSpecifier spec = (IntervalTypeSpecifier) targetElement; - // isEligible = isDateTimeTypeSpecifier(spec.getPointType()); - // } else if (targetElement instanceof NamedTypeSpecifier) { - // isEligible = isDateTimeTypeSpecifier(targetElement); - // } - // return isEligible; - } - - private boolean isDateTimeTypeSpecifier(Element e) { - return e.getResultType().equals(libraryBuilder.resolveTypeName("System", "DateTime")); - } - - @Override - public Object visitLetClause(cqlParser.LetClauseContext ctx) { - List letClauseItems = new ArrayList<>(); - for (cqlParser.LetClauseItemContext letClauseItem : ctx.letClauseItem()) { - letClauseItems.add((LetClause) visit(letClauseItem)); - } - return letClauseItems; - } - - @Override - public Object visitLetClauseItem(cqlParser.LetClauseItemContext ctx) { - LetClause letClause = of.createLetClause() - .withExpression(parseExpression(ctx.expression())) - .withIdentifier(parseString(ctx.identifier())); - letClause.setResultType(letClause.getExpression().getResultType()); - libraryBuilder.peekQueryContext().addLetClause(letClause); - return letClause; - } - - @Override - public Object visitAliasedQuerySource(cqlParser.AliasedQuerySourceContext ctx) { - AliasedQuerySource source = of.createAliasedQuerySource() - .withExpression(parseExpression(ctx.querySource())) - .withAlias(parseString(ctx.alias())); - source.setResultType(source.getExpression().getResultType()); - return source; - } - - @Override - public Object visitWithClause(cqlParser.WithClauseContext ctx) { - AliasedQuerySource aqs = (AliasedQuerySource) visit(ctx.aliasedQuerySource()); - libraryBuilder.peekQueryContext().addRelatedQuerySource(aqs); - try { - Expression expression = (Expression) visit(ctx.expression()); - DataTypes.verifyType(expression.getResultType(), libraryBuilder.resolveTypeName("System", "Boolean")); - RelationshipClause result = of.createWith(); - result.withExpression(aqs.getExpression()).withAlias(aqs.getAlias()).withSuchThat(expression); - result.setResultType(aqs.getResultType()); - return result; - } finally { - libraryBuilder.peekQueryContext().removeQuerySource(aqs); - } - } - - @Override - public Object visitWithoutClause(cqlParser.WithoutClauseContext ctx) { - AliasedQuerySource aqs = (AliasedQuerySource) visit(ctx.aliasedQuerySource()); - libraryBuilder.peekQueryContext().addRelatedQuerySource(aqs); - try { - Expression expression = (Expression) visit(ctx.expression()); - DataTypes.verifyType(expression.getResultType(), libraryBuilder.resolveTypeName("System", "Boolean")); - RelationshipClause result = of.createWithout(); - result.withExpression(aqs.getExpression()).withAlias(aqs.getAlias()).withSuchThat(expression); - result.setResultType(aqs.getResultType()); - return result; - } finally { - libraryBuilder.peekQueryContext().removeQuerySource(aqs); - } - } - - @Override - public Object visitWhereClause(cqlParser.WhereClauseContext ctx) { - Expression result = (Expression) visit(ctx.expression()); - DataTypes.verifyType(result.getResultType(), libraryBuilder.resolveTypeName("System", "Boolean")); - return result; - } - - @Override - public Object visitReturnClause(cqlParser.ReturnClauseContext ctx) { - ReturnClause returnClause = of.createReturnClause(); - if (ctx.getChild(1) instanceof TerminalNode) { - switch (ctx.getChild(1).getText()) { - case "all": - returnClause.setDistinct(false); - break; - case "distinct": - returnClause.setDistinct(true); - break; - default: - break; - } - } - - returnClause.setExpression(parseExpression(ctx.expression())); - returnClause.setResultType( - libraryBuilder.peekQueryContext().isSingular() - ? returnClause.getExpression().getResultType() - : new ListType(returnClause.getExpression().getResultType())); - - return returnClause; - } - - @Override - public Object visitStartingClause(cqlParser.StartingClauseContext ctx) { - if (ctx.simpleLiteral() != null) { - return visit(ctx.simpleLiteral()); - } - - if (ctx.quantity() != null) { - return visit(ctx.quantity()); - } - - if (ctx.expression() != null) { - return visit(ctx.expression()); - } - - return null; - } - - @Override - public Object visitAggregateClause(cqlParser.AggregateClauseContext ctx) { - libraryBuilder.checkCompatibilityLevel("Aggregate query clause", "1.5"); - AggregateClause aggregateClause = of.createAggregateClause(); - if (ctx.getChild(1) instanceof TerminalNode) { - switch (ctx.getChild(1).getText()) { - case "all": - aggregateClause.setDistinct(false); - break; - case "distinct": - aggregateClause.setDistinct(true); - break; - default: - break; - } - } - - if (ctx.startingClause() != null) { - aggregateClause.setStarting(parseExpression(ctx.startingClause())); - } - - // If there is a starting, that's the type of the var - // If there's not a starting, push an Any and then attempt to evaluate (might - // need a type hint here) - aggregateClause.setIdentifier(parseString(ctx.identifier())); - - Expression accumulator = null; - if (aggregateClause.getStarting() != null) { - accumulator = libraryBuilder.buildNull(aggregateClause.getStarting().getResultType()); - } else { - accumulator = libraryBuilder.buildNull(libraryBuilder.resolveTypeName("System", "Any")); - } - - LetClause letClause = - of.createLetClause().withExpression(accumulator).withIdentifier(aggregateClause.getIdentifier()); - letClause.setResultType(letClause.getExpression().getResultType()); - libraryBuilder.peekQueryContext().addLetClause(letClause); - - aggregateClause.setExpression(parseExpression(ctx.expression())); - aggregateClause.setResultType(aggregateClause.getExpression().getResultType()); - - if (aggregateClause.getStarting() == null) { - accumulator.setResultType(aggregateClause.getResultType()); - aggregateClause.setStarting(accumulator); - } - - return aggregateClause; - } - - @Override - public SortDirection visitSortDirection(cqlParser.SortDirectionContext ctx) { - return SortDirection.fromValue(ctx.getText()); - } - - private SortDirection parseSortDirection(cqlParser.SortDirectionContext ctx) { - if (ctx != null) { - return visitSortDirection(ctx); - } - - return SortDirection.ASC; - } - - @Override - public SortByItem visitSortByItem(cqlParser.SortByItemContext ctx) { - Expression sortExpression = parseExpression(ctx.expressionTerm()); - if (sortExpression instanceof IdentifierRef) { - return (SortByItem) of.createByColumn() - .withPath(((IdentifierRef) sortExpression).getName()) - .withDirection(parseSortDirection(ctx.sortDirection())) - .withResultType(sortExpression.getResultType()); - } - - return (SortByItem) of.createByExpression() - .withExpression(sortExpression) - .withDirection(parseSortDirection(ctx.sortDirection())) - .withResultType(sortExpression.getResultType()); - } - - @Override - public Object visitSortClause(cqlParser.SortClauseContext ctx) { - if (ctx.sortDirection() != null) { - return of.createSortClause() - .withBy(of.createByDirection().withDirection(parseSortDirection(ctx.sortDirection()))); - } - - List sortItems = new ArrayList<>(); - if (ctx.sortByItem() != null) { - for (cqlParser.SortByItemContext sortByItemContext : ctx.sortByItem()) { - sortItems.add((SortByItem) visit(sortByItemContext)); - } - } - - return of.createSortClause().withBy(sortItems); - } - - @Override - @SuppressWarnings("unchecked") - public Object visitQuerySource(cqlParser.QuerySourceContext ctx) { - if (ctx.expression() != null) { - return visit(ctx.expression()); - } else if (ctx.retrieve() != null) { - return visit(ctx.retrieve()); - } else { - List identifiers = (List) visit(ctx.qualifiedIdentifierExpression()); - return resolveQualifiedIdentifier(identifiers); - } - } - - @Override - public Object visitIndexedExpressionTerm(cqlParser.IndexedExpressionTermContext ctx) { - Indexer indexer = of.createIndexer() - .withOperand(parseExpression(ctx.expressionTerm())) - .withOperand(parseExpression(ctx.expression())); - - // TODO: Support zero-based indexers as defined by the isZeroBased attribute - libraryBuilder.resolveBinaryCall("System", "Indexer", indexer); - return indexer; - } - - @Override - public Expression visitInvocationExpressionTerm(cqlParser.InvocationExpressionTermContext ctx) { - Expression left = parseExpression(ctx.expressionTerm()); - libraryBuilder.pushExpressionTarget(left); - try { - return (Expression) visit(ctx.qualifiedInvocation()); - } finally { - libraryBuilder.popExpressionTarget(); - } - } - - @Override - public Expression visitExternalConstant(cqlParser.ExternalConstantContext ctx) { - return libraryBuilder.resolveIdentifier(ctx.getText(), true); - } - - @Override - public Expression visitThisInvocation(cqlParser.ThisInvocationContext ctx) { - return libraryBuilder.resolveIdentifier(ctx.getText(), true); - } - - @Override - public Expression visitMemberInvocation(cqlParser.MemberInvocationContext ctx) { - String identifier = parseString(ctx.referentialIdentifier()); - return resolveMemberIdentifier(identifier); - } - - @Override - public Expression visitQualifiedMemberInvocation(cqlParser.QualifiedMemberInvocationContext ctx) { - String identifier = parseString(ctx.referentialIdentifier()); - return resolveMemberIdentifier(identifier); - } - - public Expression resolveQualifiedIdentifier(List identifiers) { - Expression current = null; - for (String identifier : identifiers) { - if (current == null) { - current = resolveIdentifier(identifier); - } else { - current = libraryBuilder.resolveAccessor(current, identifier); - } - } - - return current; - } - - public Expression resolveMemberIdentifier(String identifier) { - if (libraryBuilder.hasExpressionTarget()) { - Expression target = libraryBuilder.popExpressionTarget(); - try { - return libraryBuilder.resolveAccessor(target, identifier); - } finally { - libraryBuilder.pushExpressionTarget(target); - } - } - - return resolveIdentifier(identifier); - } - - private Expression resolveIdentifier(String identifier) { - // If the identifier cannot be resolved in the library builder, check for - // forward declarations for expressions - // and parameters - Expression result = libraryBuilder.resolveIdentifier(identifier, false); - if (result == null) { - ExpressionDefinitionInfo expressionInfo = libraryInfo.resolveExpressionReference(identifier); - if (expressionInfo != null) { - String saveContext = saveCurrentContext(expressionInfo.getContext()); - try { - Stack saveChunks = chunks; - chunks = new Stack(); - forwards.push(expressionInfo); - try { - if (expressionInfo.getDefinition() == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not validate reference to expression %s because its definition contains errors.", - expressionInfo.getName())); - } - - // Have to call the visit to get the outer processing to occur - visit(expressionInfo.getDefinition()); - } finally { - chunks = saveChunks; - forwards.pop(); - } - } finally { - setCurrentContext(saveContext); - } - } - - ParameterDefinitionInfo parameterInfo = libraryInfo.resolveParameterReference(identifier); - if (parameterInfo != null) { - visitParameterDefinition(parameterInfo.getDefinition()); - } - result = libraryBuilder.resolveIdentifier(identifier, true); - } - - return result; - } - - private String ensureSystemFunctionName(String libraryName, String functionName) { - if (libraryName == null || libraryName.equals("System")) { - // Because these functions can be both a keyword and the name of a method, they - // can be resolved by the - // parser as a function, instead of as the keyword-based parser rule. In this - // case, the function - // name needs to be translated to the System function name in order to resolve. - switch (functionName) { - case "contains": - functionName = "Contains"; - break; - case "distinct": - functionName = "Distinct"; - break; - case "exists": - functionName = "Exists"; - break; - case "in": - functionName = "In"; - break; - case "not": - functionName = "Not"; - break; - } - } - - return functionName; - } - - private Expression resolveFunction(String libraryName, String functionName, cqlParser.ParamListContext paramList) { - List expressions = new ArrayList(); - if (paramList != null && paramList.expression() != null) { - for (cqlParser.ExpressionContext expressionContext : paramList.expression()) { - expressions.add((Expression) visit(expressionContext)); - } - } - return resolveFunction(libraryName, functionName, expressions, true, false, false); - } - - public Expression resolveFunction( - String libraryName, - String functionName, - List expressions, - boolean mustResolve, - boolean allowPromotionAndDemotion, - boolean allowFluent) { - if (allowFluent) { - libraryBuilder.checkCompatibilityLevel("Fluent functions", "1.5"); - } - - functionName = ensureSystemFunctionName(libraryName, functionName); - - // 1. Ensure all overloads of the function are registered with the operator map - // 2. Resolve the function, allowing for the case that operator map is a - // skeleton - // 3. If the resolution from the operator map is a skeleton, compile the - // function body to determine the result - // type - - // Find all functionDefinitionInfo instances with the given name - // register each functionDefinitionInfo - if (libraryName == null || libraryName.equals("") || libraryName.equals(this.libraryInfo.getLibraryName())) { - Iterable fdis = libraryInfo.resolveFunctionReference(functionName); - if (fdis != null) { - for (FunctionDefinitionInfo fdi : fdis) { - String saveContext = saveCurrentContext(fdi.getContext()); - try { - registerFunctionDefinition(fdi.getDefinition()); - } finally { - this.setCurrentContext(saveContext); - } - } - } - } - - Invocation result = libraryBuilder.resolveFunction( - libraryName, functionName, expressions, mustResolve, allowPromotionAndDemotion, allowFluent); - - if (result instanceof FunctionRefInvocation) { - FunctionRefInvocation invocation = (FunctionRefInvocation) result; - if (invocation.getResolution() != null - && invocation.getResolution().getOperator() != null - && (invocation.getResolution().getOperator().getLibraryName() == null - || invocation - .getResolution() - .getOperator() - .getLibraryName() - .equals(libraryBuilder - .getCompiledLibrary() - .getIdentifier() - .getId()))) { - Operator op = invocation.getResolution().getOperator(); - FunctionHeader fh = getFunctionHeader(op); - if (!fh.getIsCompiled()) { - cqlParser.FunctionDefinitionContext ctx = getFunctionDefinitionContext(fh); - String saveContext = saveCurrentContext(fh.getFunctionDef().getContext()); - Stack saveChunks = chunks; - chunks = new Stack(); - try { - FunctionDef fd = compileFunctionDefinition(ctx); - op.setResultType(fd.getResultType()); - invocation.setResultType(op.getResultType()); - } finally { - setCurrentContext(saveContext); - this.chunks = saveChunks; - } - } - } - } - - if (mustResolve) { - // Extra internal error handling, these should never be hit if the two-phase - // operator compile is working as - // expected - if (result == null) { - throw new IllegalArgumentException("Internal error: could not resolve function"); - } - - if (result.getExpression() == null) { - throw new IllegalArgumentException("Internal error: could not resolve invocation expression"); - } - - if (result.getExpression().getResultType() == null) { - throw new IllegalArgumentException("Internal error: could not determine result type"); - } - } - - if (result == null) { - return null; - } - return result.getExpression(); - } - - public Expression resolveFunctionOrQualifiedFunction(String identifier, cqlParser.ParamListContext paramListCtx) { - if (libraryBuilder.hasExpressionTarget()) { - Expression target = libraryBuilder.popExpressionTarget(); - try { - // If the target is a library reference, resolve as a standard qualified call - if (target instanceof LibraryRef) { - return resolveFunction(((LibraryRef) target).getLibraryName(), identifier, paramListCtx); - } - - // NOTE: FHIRPath method invocation - // If the target is an expression, resolve as a method invocation - if (target instanceof Expression && isMethodInvocationEnabled()) { - return systemMethodResolver.resolveMethod((Expression) target, identifier, paramListCtx, true); - } - - if (!isMethodInvocationEnabled()) { - throw new CqlCompilerException( - String.format( - "The identifier %s could not be resolved as an invocation because method-style invocation is disabled.", - identifier), - CqlCompilerException.ErrorSeverity.Error); - } - throw new IllegalArgumentException(String.format( - "Invalid invocation target: %s", target.getClass().getName())); - } finally { - libraryBuilder.pushExpressionTarget(target); - } - } - - // If we are in an implicit $this context, the function may be resolved as a - // method invocation - Expression thisRef = libraryBuilder.resolveIdentifier("$this", false); - if (thisRef != null) { - Expression result = systemMethodResolver.resolveMethod(thisRef, identifier, paramListCtx, false); - if (result != null) { - return result; - } - } - - // If we are in an implicit context (i.e. a context named the same as a - // parameter), the function may be resolved - // as a method invocation - ParameterRef parameterRef = libraryBuilder.resolveImplicitContext(); - if (parameterRef != null) { - Expression result = systemMethodResolver.resolveMethod(parameterRef, identifier, paramListCtx, false); - if (result != null) { - return result; - } - } - - // If there is no target, resolve as a system function - return resolveFunction(null, identifier, paramListCtx); - } - - @Override - public Expression visitFunction(cqlParser.FunctionContext ctx) { - return resolveFunctionOrQualifiedFunction(parseString(ctx.referentialIdentifier()), ctx.paramList()); - } - - @Override - public Expression visitQualifiedFunction(cqlParser.QualifiedFunctionContext ctx) { - return resolveFunctionOrQualifiedFunction(parseString(ctx.identifierOrFunctionIdentifier()), ctx.paramList()); - } - - @Override - public Object visitFunctionBody(cqlParser.FunctionBodyContext ctx) { - return visit(ctx.expression()); - } - - private FunctionHeader getFunctionHeader(cqlParser.FunctionDefinitionContext ctx) { - FunctionHeader fh = functionHeaders.get(ctx); - if (fh == null) { - - final Stack saveChunks = chunks; - chunks = new Stack<>(); - try { - // Have to call the visit to allow the outer processing to occur - fh = parseFunctionHeader(ctx); - } finally { - chunks = saveChunks; - } - - functionHeaders.put(ctx, fh); - functionDefinitions.put(fh, ctx); - functionHeadersByDef.put(fh.getFunctionDef(), fh); - } - return fh; - } - - private FunctionDef getFunctionDef(Operator op) { - FunctionDef target = null; - List st = new ArrayList<>(); - for (DataType dt : op.getSignature().getOperandTypes()) { - st.add(dt); - } - Iterable fds = libraryBuilder.getCompiledLibrary().resolveFunctionRef(op.getName(), st); - for (FunctionDef fd : fds) { - if (fd.getOperand().size() == op.getSignature().getSize()) { - Iterator signatureTypes = - op.getSignature().getOperandTypes().iterator(); - boolean signaturesMatch = true; - for (int i = 0; i < fd.getOperand().size(); i++) { - if (!DataTypes.equal(fd.getOperand().get(i).getResultType(), signatureTypes.next())) { - signaturesMatch = false; - } - } - if (signaturesMatch) { - if (target == null) { - target = fd; - } else { - throw new IllegalArgumentException(String.format( - "Internal error attempting to resolve function header for %s", op.getName())); - } - } - } - } - - return target; - } - - private FunctionHeader getFunctionHeaderByDef(FunctionDef fd) { - // Shouldn't need to do this, something about the hashCode implementation of - // FunctionDef is throwing this off, - // Don't have time to investigate right now, this should work fine, could - // potentially be improved - for (Map.Entry entry : functionHeadersByDef.entrySet()) { - if (entry.getKey() == fd) { - return entry.getValue(); - } - } - - return null; - } - - private FunctionHeader getFunctionHeader(Operator op) { - FunctionDef fd = getFunctionDef(op); - if (fd == null) { - throw new IllegalArgumentException( - String.format("Could not resolve function header for operator %s", op.getName())); - } - FunctionHeader result = getFunctionHeaderByDef(fd); - // FunctionHeader result = functionHeadersByDef.get(fd); - if (result == null) { - throw new IllegalArgumentException( - String.format("Could not resolve function header for operator %s", op.getName())); - } - return result; - } - - private cqlParser.FunctionDefinitionContext getFunctionDefinitionContext(FunctionHeader fh) { - cqlParser.FunctionDefinitionContext ctx = functionDefinitions.get(fh); - if (ctx == null) { - throw new IllegalArgumentException(String.format( - "Could not resolve function definition context for function header %s", - fh.getFunctionDef().getName())); - } - return ctx; - } - - public void registerFunctionDefinition(cqlParser.FunctionDefinitionContext ctx) { - FunctionHeader fh = getFunctionHeader(ctx); - if (!libraryBuilder.getCompiledLibrary().contains(fh.getFunctionDef())) { - libraryBuilder.addExpression(fh.getFunctionDef()); - } - } - - public FunctionDef compileFunctionDefinition(cqlParser.FunctionDefinitionContext ctx) { - FunctionHeader fh = getFunctionHeader(ctx); - - final FunctionDef fun = fh.getFunctionDef(); - final TypeSpecifier resultType = fh.getResultType(); - final Operator op = libraryBuilder.resolveFunctionDefinition(fh.getFunctionDef()); - if (op == null) { - throw new IllegalArgumentException(String.format( - "Internal error: Could not resolve operator map entry for function header %s", - fh.getMangledName())); - } - libraryBuilder.pushIdentifier(fun.getName(), fun, IdentifierScope.GLOBAL); - final List operand = op.getFunctionDef().getOperand(); - for (OperandDef operandDef : operand) { - libraryBuilder.pushIdentifier(operandDef.getName(), operandDef); - } - - try { - if (ctx.functionBody() != null) { - libraryBuilder.beginFunctionDef(fun); - try { - libraryBuilder.pushExpressionContext(getCurrentContext()); - try { - libraryBuilder.pushExpressionDefinition(fh.getMangledName()); - try { - fun.setExpression(parseExpression(ctx.functionBody())); - } finally { - libraryBuilder.popExpressionDefinition(); - } - } finally { - libraryBuilder.popExpressionContext(); - } - } finally { - libraryBuilder.endFunctionDef(); - } - - if (resultType != null - && fun.getExpression() != null - && fun.getExpression().getResultType() != null) { - if (!DataTypes.subTypeOf(fun.getExpression().getResultType(), resultType.getResultType())) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Function %s has declared return type %s but the function body returns incompatible type %s.", - fun.getName(), - resultType.getResultType(), - fun.getExpression().getResultType())); - } - } - - fun.setResultType(fun.getExpression().getResultType()); - op.setResultType(fun.getResultType()); - } else { - fun.setExternal(true); - if (resultType == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Function %s is marked external but does not declare a return type.", fun.getName())); - } - fun.setResultType(resultType.getResultType()); - op.setResultType(fun.getResultType()); - } - - fun.setContext(getCurrentContext()); - fh.setIsCompiled(); - - return fun; - } finally { - for (OperandDef operandDef : operand) { - try { - libraryBuilder.popIdentifier(); - } catch (Exception e) { - log.warn("error popping identifier", e); - } - } - // Intentionally do _not_ pop the function name, it needs to remain in global scope! - } - } - - @Override - public Object visitFunctionDefinition(cqlParser.FunctionDefinitionContext ctx) { - libraryBuilder.pushIdentifierScope(); - try { - registerFunctionDefinition(ctx); - return compileFunctionDefinition(ctx); - } finally { - libraryBuilder.popIdentifierScope(); - } - } - - private Expression parseLiteralExpression(ParseTree pt) { - libraryBuilder.pushLiteralContext(); - try { - return parseExpression(pt); - } finally { - libraryBuilder.popLiteralContext(); - } - } - - private Expression parseExpression(ParseTree pt) { - return pt == null ? null : (Expression) visit(pt); - } - - private boolean isBooleanLiteral(Expression expression, Boolean bool) { - boolean ret = false; - if (expression instanceof Literal) { - Literal lit = (Literal) expression; - ret = lit.getValueType() - .equals(libraryBuilder.dataTypeToQName(libraryBuilder.resolveTypeName("System", "Boolean"))); - if (ret && bool != null) { - ret = bool.equals(Boolean.valueOf(lit.getValue())); - } - } - return ret; - } - - private TrackBack getTrackBack(ParseTree tree) { - if (tree instanceof ParserRuleContext) { - return getTrackBack((ParserRuleContext) tree); - } - if (tree instanceof TerminalNode) { - return getTrackBack((TerminalNode) tree); - } - return null; - } - - private TrackBack getTrackBack(TerminalNode node) { - TrackBack tb = new TrackBack( - libraryBuilder.getLibraryIdentifier(), - node.getSymbol().getLine(), - node.getSymbol().getCharPositionInLine() + 1, // 1-based instead of 0-based - node.getSymbol().getLine(), - node.getSymbol().getCharPositionInLine() - + node.getSymbol().getText().length()); - return tb; - } - - private TrackBack getTrackBack(ParserRuleContext ctx) { - TrackBack tb = new TrackBack( - libraryBuilder.getLibraryIdentifier(), - ctx.getStart().getLine(), - ctx.getStart().getCharPositionInLine() + 1, // 1-based instead of 0-based - ctx.getStop().getLine(), - ctx.getStop().getCharPositionInLine() + ctx.getStop().getText().length() // 1-based instead of 0-based - ); - return tb; - } - - private void decorate(Element element, TrackBack tb) { - if (locatorsEnabled() && tb != null) { - element.setLocator(tb.toLocator()); - } - - if (resultTypesEnabled() && element.getResultType() != null) { - if (element.getResultType() instanceof NamedType) { - element.setResultTypeName(libraryBuilder.dataTypeToQName(element.getResultType())); - } else { - element.setResultTypeSpecifier(libraryBuilder.dataTypeToTypeSpecifier(element.getResultType())); - } - } - } - - private TrackBack track(Trackable trackable, ParseTree pt) { - TrackBack tb = getTrackBack(pt); - - if (tb != null) { - trackable.getTrackbacks().add(tb); - } - - if (trackable instanceof Element) { - decorate((Element) trackable, tb); - } - - return tb; - } - - private TrackBack track(Trackable trackable, Element from) { - TrackBack tb = from.getTrackbacks().size() > 0 ? from.getTrackbacks().get(0) : null; - - if (tb != null) { - trackable.getTrackbacks().add(tb); - } - - if (trackable instanceof Element) { - decorate((Element) trackable, tb); - } - - return tb; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCapability.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCapability.java deleted file mode 100644 index 12fcc36b0..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCapability.java +++ /dev/null @@ -1,154 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -/* -Defines a language capability - */ -public class CqlCapability { - public CqlCapability(String code, String display, String definition) { - this(code, display, definition, "1.0", null); - } - - public CqlCapability(String code, String display, String definition, String sinceVersion) { - this(code, display, definition, sinceVersion, null); - } - - public CqlCapability(String code, String display, String definition, String sinceVersion, String upToVersion) { - this.code = code; - this.display = display; - this.definition = definition; - this.sinceVersion = sinceVersion; - this.upToVersion = upToVersion; - } - - // A unique code identifying the capability - private String code; - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - // A short string providing a user-friendly display name for the capability - private String display; - - public String getDisplay() { - return display; - } - - public void setDisplay(String display) { - this.display = display; - } - - // A definition of the capability, including description of possible values for the capability - private String definition; - - public String getDefinition() { - return definition; - } - - public void setDefinition(String definition) { - this.definition = definition; - } - - // The version in which the capability was introduced, drawn from release versions, specifying as . - private String sinceVersion; - - public String getSinceVersion() { - return sinceVersion; - } - - public void setSinceVersion(String sinceVersion) { - this.sinceVersion = sinceVersion; - } - - // The version in which the capability was removed, drawn from release versions, specifying as . - private String upToVersion; - - public String getUpToVersion() { - return upToVersion; - } - - public void setUpToVersion(String upToVersion) { - this.upToVersion = upToVersion; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - CqlCapability that = (CqlCapability) o; - return code.equals(that.code); - } - - @Override - public int hashCode() { - return Objects.hash(code); - } - - @Override - public String toString() { - return "CqlCapability{" + "code='" - + code + '\'' + ", display='" - + display + '\'' + ", definition='" - + definition + '\'' + '}'; - } - - public static Set capabilities = new HashSet() { - { - add( - new CqlCapability( - "decimal-precision", - "Decimal Precision", - "Maximum number of digits of precision that can be represented in decimal values. Conformant implementations SHALL support at least 28 digits of precision for decimal values.")); - add( - new CqlCapability( - "decimal-scale", - "Decimal Scale", - "Maximum number of digits of scale that can be represented in decimal values (i.e. the maximum number of digits after the decimal point). Conformant implementations SHALL support at least 8 digits of scale for decimal values.")); - add( - new CqlCapability( - "datetime-precision", - "DateTime Precision", - "The maximum number of digits of precision that can be represented for DateTime values, where each numeric place, beginning with years, is counted as a single digit. Conformant implementations SHALL support at least 17 digits of precision for datetime values (YYYYMMDDHHmmss.fff).")); - add( - new CqlCapability( - "datetime-scale", - "DateTime Scale", - "The maximum number of digits of scale that can be represented in datetime values (i.e. the maximum number of digits after the decimal point in the seconds component). Conformant implementations SHALL support at least 3 digits of scale for datetime values.")); - add( - new CqlCapability( - "ucum-unit-conversion", - "UCUM Unit Conversion", - "Whether or not the implementation supports conversion of Unified Code for Units of Measure (UCUM) units. Conformant implementations SHOULD support UCUM unit conversion.")); - add( - new CqlCapability( - "regex-dialect", - "Regular Expression Dialect", - "The dialect of regular expressions used by the implementation. Conformant implementations SHOULD use the Perl Compatible Regular Expression (PCRE) dialect. Values for this feature should be drawn from the Name of the regular expression language list here: https://en.wikipedia.org/wiki/Comparison_of_regular-expression_engines")); - add(new CqlCapability( - "supported-data-model", - "Supported Data Model", - "A supported data model, specified as the URI of the model information.")); - add(new CqlCapability( - "supported-function", - "Supported Function", - "A supported function, specified as the fully qualified name of the function.")); - add(new CqlCapability( - "unfiltered-context-retrieve", - "Unfiltered Context Retrieve", - "Whether or not the implementation supports evaluating retrieves in the unfiltered context.")); - add(new CqlCapability( - "related-context-retrieve", - "Related Context Retrieve", - "Whether or not the implementation supports related-context retrieves.", - "1.4")); - } - }; -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompiler.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompiler.java deleted file mode 100644 index 32624d4a9..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompiler.java +++ /dev/null @@ -1,255 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import static org.cqframework.cql.cql2elm.CqlCompilerOptions.Options.EnableAnnotations; -import static org.cqframework.cql.cql2elm.CqlCompilerOptions.Options.EnableLocators; -import static org.cqframework.cql.cql2elm.CqlCompilerOptions.Options.EnableResultTypes; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.tree.ParseTree; -import org.cqframework.cql.cql2elm.elm.ElmEdit; -import org.cqframework.cql.cql2elm.elm.ElmEditor; -import org.cqframework.cql.cql2elm.elm.IElmEdit; -import org.cqframework.cql.cql2elm.model.CompiledLibrary; -import org.cqframework.cql.cql2elm.preprocessor.CqlPreprocessor; -import org.cqframework.cql.elm.IdObjectFactory; -import org.cqframework.cql.elm.tracking.TrackBack; -import org.cqframework.cql.gen.cqlLexer; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.cql.model.NamespaceAware; -import org.hl7.cql.model.NamespaceInfo; -import org.hl7.elm.r1.Library; -import org.hl7.elm.r1.Retrieve; -import org.hl7.elm.r1.VersionedIdentifier; - -public class CqlCompiler { - private Library library = null; - private CompiledLibrary compiledLibrary = null; - private Object visitResult = null; - private List retrieves = null; - private List exceptions = null; - private List errors = null; - private List warnings = null; - private List messages = null; - private final VersionedIdentifier sourceInfo; - private final NamespaceInfo namespaceInfo; - private final LibraryManager libraryManager; - - public CqlCompiler(LibraryManager libraryManager) { - this(null, null, libraryManager); - } - - public CqlCompiler(NamespaceInfo namespaceInfo, LibraryManager libraryManager) { - this(namespaceInfo, null, libraryManager); - } - - public CqlCompiler(NamespaceInfo namespaceInfo, VersionedIdentifier sourceInfo, LibraryManager libraryManager) { - this.namespaceInfo = namespaceInfo; - this.libraryManager = libraryManager; - - if (sourceInfo == null) { - this.sourceInfo = new VersionedIdentifier().withId("Anonymous").withSystem("text/cql"); - } else { - this.sourceInfo = sourceInfo; - } - - if (this.namespaceInfo != null) { - libraryManager.getNamespaceManager().ensureNamespaceRegistered(this.namespaceInfo); - } - - if (libraryManager.getNamespaceManager().hasNamespaces() - && libraryManager.getLibrarySourceLoader() instanceof NamespaceAware) { - ((NamespaceAware) libraryManager.getLibrarySourceLoader()) - .setNamespaceManager(libraryManager.getNamespaceManager()); - } - } - - public Library getLibrary() { - return library; - } - - public CompiledLibrary getCompiledLibrary() { - return compiledLibrary; - } - - public Object toObject() { - return visitResult; - } - - public List toRetrieves() { - return retrieves; - } - - public Map getCompiledLibraries() { - return libraryManager.getCompiledLibraries(); - } - - public Map getLibraries() { - var result = new HashMap(); - for (var id : libraryManager.getCompiledLibraries().keySet()) { - result.put(id, libraryManager.getCompiledLibraries().get(id).getLibrary()); - } - return result; - } - - public List getExceptions() { - return exceptions; - } - - public List getErrors() { - return errors; - } - - public List getWarnings() { - return warnings; - } - - public List getMessages() { - return messages; - } - - private class CqlErrorListener extends BaseErrorListener { - - private LibraryBuilder builder; - private boolean detailedErrors; - - public CqlErrorListener(LibraryBuilder builder, boolean detailedErrors) { - this.builder = builder; - this.detailedErrors = detailedErrors; - } - - private VersionedIdentifier extractLibraryIdentifier(cqlParser parser) { - RuleContext context = parser.getContext(); - while (context != null && !(context instanceof cqlParser.LibraryContext)) { - context = context.parent; - } - - if (context instanceof cqlParser.LibraryContext) { - cqlParser.LibraryDefinitionContext ldc = ((cqlParser.LibraryContext) context).libraryDefinition(); - if (ldc != null - && ldc.qualifiedIdentifier() != null - && ldc.qualifiedIdentifier().identifier() != null) { - return new VersionedIdentifier() - .withId(StringEscapeUtils.unescapeCql( - ldc.qualifiedIdentifier().identifier().getText())); - } - } - - return null; - } - - @Override - public void syntaxError( - Recognizer recognizer, - Object offendingSymbol, - int line, - int charPositionInLine, - String msg, - RecognitionException e) { - var libraryIdentifier = builder.getLibraryIdentifier(); - if (libraryIdentifier == null) { - // Attempt to extract a libraryIdentifier from the currently parsed content - if (recognizer instanceof cqlParser) { - libraryIdentifier = extractLibraryIdentifier((cqlParser) recognizer); - } - if (libraryIdentifier == null) { - libraryIdentifier = sourceInfo; - } - } - TrackBack trackback = new TrackBack(libraryIdentifier, line, charPositionInLine, line, charPositionInLine); - - if (detailedErrors) { - builder.recordParsingException(new CqlSyntaxException(msg, trackback, e)); - builder.recordParsingException(new CqlCompilerException(msg, trackback, e)); - } else { - if (offendingSymbol instanceof CommonToken) { - CommonToken token = (CommonToken) offendingSymbol; - builder.recordParsingException( - new CqlSyntaxException(String.format("Syntax error at %s", token.getText()), trackback, e)); - } else { - builder.recordParsingException(new CqlSyntaxException("Syntax error", trackback, e)); - } - } - } - } - - public Library run(File cqlFile) throws IOException { - return run(CharStreams.fromStream(new FileInputStream(cqlFile))); - } - - public Library run(String cqlText) { - return run(CharStreams.fromString(cqlText)); - } - - public Library run(InputStream is) throws IOException { - return run(CharStreams.fromStream(is)); - } - - public Library run(CharStream is) { - exceptions = new ArrayList<>(); - errors = new ArrayList<>(); - warnings = new ArrayList<>(); - messages = new ArrayList<>(); - - var options = libraryManager.getCqlCompilerOptions().getOptions(); - - LibraryBuilder builder = new LibraryBuilder(namespaceInfo, libraryManager, new IdObjectFactory()); - CqlCompiler.CqlErrorListener errorListener = new CqlCompiler.CqlErrorListener( - builder, options.contains(CqlCompilerOptions.Options.EnableDetailedErrors)); - - // Phase 1: Lexing - cqlLexer lexer = new cqlLexer(is); - lexer.removeErrorListeners(); - lexer.addErrorListener(errorListener); - CommonTokenStream tokens = new CommonTokenStream(lexer); - - // Phase 2: Parsing (the lexer is actually streaming, so Phase 1 and 2 happen together) - cqlParser parser = new cqlParser(tokens); - parser.setBuildParseTree(true); - parser.removeErrorListeners(); // Clear the default console listener - parser.addErrorListener(errorListener); - ParseTree tree = parser.library(); - - // Phase 3: preprocess the parse tree (generates the LibraryInfo with - // header information for definitions) - CqlPreprocessor preprocessor = new CqlPreprocessor(builder, tokens); - preprocessor.visit(tree); - - // Phase 4: generate the ELM (the ELM is generated with full type information that can be used - // for validation, optimization, rewriting, debugging, etc.) - Cql2ElmVisitor visitor = new Cql2ElmVisitor(builder, tokens, preprocessor.getLibraryInfo()); - visitResult = visitor.visit(tree); - library = builder.getLibrary(); - - // Phase 5: ELM optimization/reduction (this is where result types, annotations, etc. are removed - // and there will probably be a lot of other optimizations that happen here in the future) - var edits = allNonNull( - !options.contains(EnableAnnotations) ? ElmEdit.REMOVE_ANNOTATION : null, - !options.contains(EnableResultTypes) ? ElmEdit.REMOVE_RESULT_TYPE : null, - !options.contains(EnableLocators) ? ElmEdit.REMOVE_LOCATOR : null); - - new ElmEditor(edits).edit(library); - - compiledLibrary = builder.getCompiledLibrary(); - retrieves = visitor.getRetrieves(); - exceptions.addAll(builder.getExceptions()); - errors.addAll(builder.getErrors()); - warnings.addAll(builder.getWarnings()); - messages.addAll(builder.getMessages()); - - return library; - } - - private List allNonNull(IElmEdit... ts) { - return Arrays.stream(ts).filter(x -> x != null).collect(Collectors.toList()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompilerException.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompilerException.java deleted file mode 100644 index c8895b67f..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompilerException.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.List; -import org.cqframework.cql.elm.tracking.TrackBack; - -public class CqlCompilerException extends RuntimeException { - public enum ErrorSeverity { - Info, - Warning, - Error - } - - public static boolean hasErrors(List exceptions) { - for (CqlCompilerException exception : exceptions) { - if (exception.getSeverity() == ErrorSeverity.Error) { - return true; - } - } - - return false; - } - - public CqlCompilerException(String message) { - this(message, ErrorSeverity.Error, null, null); - } - - public CqlCompilerException(String message, ErrorSeverity severity) { - this(message, severity, null, null); - } - - public CqlCompilerException(String message, Throwable cause) { - this(message, ErrorSeverity.Error, null, cause); - } - - public CqlCompilerException(String message, ErrorSeverity severity, Throwable cause) { - this(message, severity, null, cause); - } - - public CqlCompilerException(String message, TrackBack locator) { - this(message, ErrorSeverity.Error, locator, null); - } - - public CqlCompilerException(String message, ErrorSeverity severity, TrackBack locator) { - this(message, severity, locator, null); - } - - public CqlCompilerException(String message, TrackBack locator, Throwable cause) { - this(message, ErrorSeverity.Error, locator, cause); - } - - public CqlCompilerException(String message, ErrorSeverity severity, TrackBack locator, Throwable cause) { - super(message, cause); - this.severity = severity; - this.locator = locator; - } - - private final ErrorSeverity severity; - - public ErrorSeverity getSeverity() { - return severity; - } - - private final transient TrackBack locator; - - public TrackBack getLocator() { - return locator; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompilerOptions.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompilerOptions.java deleted file mode 100644 index 27f179744..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlCompilerOptions.java +++ /dev/null @@ -1,416 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.EnumSet; -import java.util.Set; - -/** - * translation options for Cql source files - */ -public class CqlCompilerOptions { - public enum Options { - EnableDateRangeOptimization, - EnableAnnotations, - EnableLocators, - EnableResultTypes, - EnableDetailedErrors, - DisableListTraversal, - DisableListDemotion, - DisableListPromotion, - EnableIntervalDemotion, - EnableIntervalPromotion, - DisableMethodInvocation, - RequireFromKeyword, - DisableDefaultModelInfoLoad - } - - private EnumSet options = EnumSet.noneOf(Options.class); - private boolean validateUnits = true; - private boolean verifyOnly = false; - private boolean enableCqlOnly = false; - private String compatibilityLevel = "1.5"; - private CqlCompilerException.ErrorSeverity errorLevel = CqlCompilerException.ErrorSeverity.Info; - private LibraryBuilder.SignatureLevel signatureLevel = LibraryBuilder.SignatureLevel.Overloads; - private boolean analyzeDataRequirements = false; - private boolean collapseDataRequirements = false; - - /** - * Returns default translator options: - * EnableAnnotations - * EnableLocators - * DisableListDemotion - * DisableListPromotion - * ErrorSeverity.Info - * SignatureLevel.None - * @return - */ - public static CqlCompilerOptions defaultOptions() { - // Default options based on recommended settings: - // http://build.fhir.org/ig/HL7/cqf-measures/using-cql.html#translation-to-elm - CqlCompilerOptions result = new CqlCompilerOptions(); - result.options.add(Options.EnableAnnotations); - result.options.add(Options.EnableLocators); - result.options.add(Options.DisableListDemotion); - result.options.add(Options.DisableListPromotion); - return result; - } - - public CqlCompilerOptions() {} - - /** - * Constructor with arbitrary number of options utilizing default ErrorSeverity (Info) and SignatureLevel (None) - * @param options - */ - public CqlCompilerOptions(Options... options) { - this(CqlCompilerException.ErrorSeverity.Info, LibraryBuilder.SignatureLevel.None, options); - } - - public CqlCompilerOptions(CqlCompilerException.ErrorSeverity errorLevel, Options... options) { - this(errorLevel, LibraryBuilder.SignatureLevel.None, options); - } - - /** - * Constructor with defined ErrorSeverity, SignatureLevel, and arbitrary number of options. - * - * @param errorLevel - * @param signatureLevel - * @param options - */ - public CqlCompilerOptions( - CqlCompilerException.ErrorSeverity errorLevel, - LibraryBuilder.SignatureLevel signatureLevel, - Options... options) { - this.setOptions(options); - this.errorLevel = errorLevel; - this.signatureLevel = signatureLevel; - } - - /** - * Constructor using defined SignatureLevel, and Compatibility Level, boolean set to true denotes addition of predefined option - * - * @param dateRangeOptimizations boolean - * @param annotations boolean - * @param locators boolean - * @param resultTypes boolean - * @param verifyOnly boolean - * @param detailedErrors boolean - * @param errorLevel boolean - * @param disableListTraversal boolean - * @param disableListDemotion boolean - * @param disableListPromotion boolean - * @param enableIntervalDemotion boolean - * @param enableIntervalPromotion boolean - * @param disableMethodInvocation boolean - * @param requireFromKeyword boolean - * @param validateUnits boolean - * @param signatureLevel LibraryBuilder.SignatureLevel - * @param compatibilityLevel String - */ - public CqlCompilerOptions( - boolean dateRangeOptimizations, - boolean annotations, - boolean locators, - boolean resultTypes, - boolean verifyOnly, - boolean detailedErrors, - CqlCompilerException.ErrorSeverity errorLevel, - boolean disableListTraversal, - boolean disableListDemotion, - boolean disableListPromotion, - boolean enableIntervalDemotion, - boolean enableIntervalPromotion, - boolean disableMethodInvocation, - boolean requireFromKeyword, - boolean validateUnits, - boolean disableDefaultModelInfoLoad, - LibraryBuilder.SignatureLevel signatureLevel, - String compatibilityLevel) { - this.verifyOnly = verifyOnly; - this.errorLevel = errorLevel; - this.signatureLevel = signatureLevel; - this.validateUnits = validateUnits; - this.compatibilityLevel = compatibilityLevel; - - if (dateRangeOptimizations) { - options.add(Options.EnableDateRangeOptimization); - } - if (annotations) { - options.add(Options.EnableAnnotations); - } - if (locators) { - options.add(Options.EnableLocators); - } - if (resultTypes) { - options.add(Options.EnableResultTypes); - } - if (detailedErrors) { - options.add(Options.EnableDetailedErrors); - } - if (disableListTraversal) { - options.add(Options.DisableListTraversal); - } - if (disableListDemotion) { - options.add(Options.DisableListDemotion); - } - if (disableListPromotion) { - options.add(Options.DisableListPromotion); - } - if (enableIntervalDemotion) { - options.add(Options.EnableIntervalDemotion); - } - if (enableIntervalPromotion) { - options.add(Options.EnableIntervalPromotion); - } - if (disableMethodInvocation) { - options.add(Options.DisableMethodInvocation); - } - if (requireFromKeyword) { - options.add(Options.RequireFromKeyword); - } - if (disableDefaultModelInfoLoad) { - options.add(Options.DisableDefaultModelInfoLoad); - } - } - - /** - * Returns instance of CqlTranslatorOptions options - * @return - */ - public Set getOptions() { - return this.options; - } - - /** - * Set arbitrary number of options - * @param options - */ - public void setOptions(Options... options) { - if (options != null) { - for (Options option : options) { - this.options.add(option); - } - } - } - - /** - * Return this instance of CqlTranslatorOptions using new collection of arbitrary number of options - * @param options - * @return - */ - public CqlCompilerOptions withOptions(Options... options) { - setOptions(options); - return this; - } - - /** - * Return instance of CqlTranslatorOptions compatibilityLevel - * @return - */ - public String getCompatibilityLevel() { - return this.compatibilityLevel; - } - - /** - * Set new compatibilityLevel - * @param compatibilityLevel - */ - public void setCompatibilityLevel(String compatibilityLevel) { - this.compatibilityLevel = compatibilityLevel; - } - - /** - * Return this instance of CqlTranslatorOptions with addition of newly assigned compatibilityLevel - * @param compatibilityLevel - * @return - */ - public CqlCompilerOptions withCompatibilityLevel(String compatibilityLevel) { - setCompatibilityLevel(compatibilityLevel); - return this; - } - - /** - * Return instance of CqlTranslatorOptions verifyOnly boolean - * @return - */ - public boolean getVerifyOnly() { - return this.verifyOnly; - } - - /** - * Set new verifyOnly boolean - * @param verifyOnly - */ - public void setVerifyOnly(boolean verifyOnly) { - this.verifyOnly = verifyOnly; - } - - /** - * Return this instance of CqlTranslatorOptions with addition of newly assigned verifyOnly boolean - * @param verifyOnly - * @return - */ - public CqlCompilerOptions withVerifyOnly(boolean verifyOnly) { - setVerifyOnly(verifyOnly); - return this; - } - - /** - * Return instance of CqlTranslatorOptions enableCqlOnly boolean - * @return - */ - public boolean getEnableCqlOnly() { - return this.enableCqlOnly; - } - - /** - * Set new enableCqlOnly boolean - * @param enableCqlOnly - */ - public void setEnableCqlOnly(boolean enableCqlOnly) { - this.enableCqlOnly = enableCqlOnly; - } - - /** - * Return instance of CqlTranslatorOptions validateUnits boolean - * @return - */ - public boolean getValidateUnits() { - return this.validateUnits; - } - - /** - * Set new validateUnits boolean - * @param validateUnits - */ - public void setValidateUnits(boolean validateUnits) { - this.validateUnits = validateUnits; - } - - /** - * Return this instance of CqlTranslatorOptions with addition of newly assigned validateUnits boolean - * @param validateUnits - * @return - */ - public CqlCompilerOptions withValidateUnits(boolean validateUnits) { - setValidateUnits(validateUnits); - return this; - } - - /** - * Return instance of CqlTranslatorOptions errorLevel (CqlTranslatorException.ErrorSeverity) - * @return - */ - public CqlCompilerException.ErrorSeverity getErrorLevel() { - return this.errorLevel; - } - - /** - * Set new errorLevel (CqlTranslatorException.ErrorSeverity) - * @param errorLevel - */ - public void setErrorLevel(CqlCompilerException.ErrorSeverity errorLevel) { - this.errorLevel = errorLevel; - } - - /** - * Return this instance of CqlTranslatorOptions with addition of newly assigned errorLevel (CqlTranslatorException.ErrorSeverity) - * @param errorLevel - * @return - */ - public CqlCompilerOptions withErrorLevel(CqlCompilerException.ErrorSeverity errorLevel) { - setErrorLevel(errorLevel); - return this; - } - - /** - * Return instance of CqlTranslatorOptions signatureLevel (LibraryBuilder.SignatureLevel) - * @return - */ - public LibraryBuilder.SignatureLevel getSignatureLevel() { - return this.signatureLevel; - } - - /** - * Set new signatureLevel (LibraryBuilder.SignatureLevel) - * @param signatureLevel - */ - public void setSignatureLevel(LibraryBuilder.SignatureLevel signatureLevel) { - this.signatureLevel = signatureLevel; - } - - /** - * Return this instance of CqlTranslatorOptions with addition of newly assigned signatureLevel (LibraryBuilder.SignatureLevel) - * @param signatureLevel - * @return - */ - public CqlCompilerOptions withSignatureLevel(LibraryBuilder.SignatureLevel signatureLevel) { - setSignatureLevel(signatureLevel); - return this; - } - - /** - * Return instance of CqlTranslatorOptions collapseDataRequirements boolean - * @return - */ - public boolean getCollapseDataRequirements() { - return this.collapseDataRequirements; - } - - /** - * Set new collapseDataRequirements boolean - * @param collapseDataRequirements - */ - public void setCollapseDataRequirements(boolean collapseDataRequirements) { - this.collapseDataRequirements = collapseDataRequirements; - } - - /** - * Return this instance of CqlTranslatorOptions with addition of newly assigned collapseDataRequirements boolean - * @param collapseDataRequirements - * @return - */ - public CqlCompilerOptions withCollapseDataRequirements(boolean collapseDataRequirements) { - setCollapseDataRequirements(collapseDataRequirements); - return this; - } - - /** - * Return instance of CqlTranslatorOptions analyzeDataRequirements boolean - * @return - */ - public boolean getAnalyzeDataRequirements() { - return this.analyzeDataRequirements; - } - - /** - * Set new analyzeDataRequirements boolean - * @param analyzeDataRequirements - */ - public void setAnalyzeDataRequirements(boolean analyzeDataRequirements) { - this.analyzeDataRequirements = analyzeDataRequirements; - } - - /**git - * Return this instance of CqlTranslatorOptions with addition of newly assigned analyzedDataRequirements boolean - * @param analyzeDataRequirements - * @return - */ - public CqlCompilerOptions withAnalyzeDataRequirements(boolean analyzeDataRequirements) { - setAnalyzeDataRequirements(analyzeDataRequirements); - return this; - } - - @Override - public String toString() { - if (this.getOptions() != null) { - StringBuilder translatorOptions = new StringBuilder(); - for (Options option : this.getOptions()) { - if (translatorOptions.length() > 0) { - translatorOptions.append(","); - } - translatorOptions.append(option.name()); - } - return translatorOptions.toString(); - } - return null; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlIncludeException.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlIncludeException.java deleted file mode 100644 index acf276b0a..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlIncludeException.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cqframework.cql.cql2elm; - -public class CqlIncludeException extends RuntimeException { - private String librarySystem; - private String libraryId; - private String versionId; - - public CqlIncludeException(String message, String librarySystem, String libraryId, String versionId) { - super(message); - this.librarySystem = librarySystem; - this.libraryId = libraryId; - this.versionId = versionId; - } - - public CqlIncludeException( - String message, String librarySystem, String libraryId, String versionId, Throwable cause) { - super(message, cause); - this.librarySystem = librarySystem; - this.libraryId = libraryId; - this.versionId = versionId; - } - - public String getLibrarySystem() { - return librarySystem; - } - - public String getLibraryId() { - return libraryId; - } - - public String getVersionId() { - return versionId; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlInternalException.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlInternalException.java deleted file mode 100644 index 7439ae42c..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlInternalException.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import org.cqframework.cql.elm.tracking.TrackBack; - -/** - * Created by Bryn on 5/20/2017. - */ -public class CqlInternalException extends CqlCompilerException { - public CqlInternalException(String message) { - super(message, ErrorSeverity.Error); - } - - public CqlInternalException(String message, Throwable cause) { - super(message, ErrorSeverity.Error, cause); - } - - public CqlInternalException(String message, TrackBack locator) { - super(message, ErrorSeverity.Error, locator); - } - - public CqlInternalException(String message, TrackBack locator, Throwable cause) { - super(message, ErrorSeverity.Error, locator, cause); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlSemanticException.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlSemanticException.java deleted file mode 100644 index 800d7e324..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlSemanticException.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import org.cqframework.cql.elm.tracking.TrackBack; - -/** - * Created by Bryn on 3/27/2017. - */ -public class CqlSemanticException extends CqlCompilerException { - public CqlSemanticException(String message) { - super(message); - } - - public CqlSemanticException(String message, ErrorSeverity severity) { - super(message, severity); - } - - public CqlSemanticException(String message, Throwable cause) { - super(message, cause); - } - - public CqlSemanticException(String message, ErrorSeverity severity, Throwable cause) { - super(message, severity, cause); - } - - public CqlSemanticException(String message, TrackBack locator) { - super(message, locator); - } - - public CqlSemanticException(String message, ErrorSeverity severity, TrackBack locator) { - super(message, severity, locator); - } - - public CqlSemanticException(String message, TrackBack locator, Throwable cause) { - super(message, locator, cause); - } - - public CqlSemanticException(String message, ErrorSeverity severity, TrackBack locator, Throwable cause) { - super(message, severity, locator, cause); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlSyntaxException.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlSyntaxException.java deleted file mode 100644 index bffb9cdac..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlSyntaxException.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import org.cqframework.cql.elm.tracking.TrackBack; - -/** - * Created by Bryn on 3/27/2017. - */ -public class CqlSyntaxException extends CqlCompilerException { - public CqlSyntaxException(String message) { - super(message); - } - - public CqlSyntaxException(String message, ErrorSeverity severity) { - super(message, severity); - } - - public CqlSyntaxException(String message, Throwable cause) { - super(message, cause); - } - - public CqlSyntaxException(String message, ErrorSeverity severity, Throwable cause) { - super(message, severity, cause); - } - - public CqlSyntaxException(String message, TrackBack locator) { - super(message, locator); - } - - public CqlSyntaxException(String message, ErrorSeverity severity, TrackBack locator) { - super(message, severity, locator); - } - - public CqlSyntaxException(String message, TrackBack locator, Throwable cause) { - super(message, locator, cause); - } - - public CqlSyntaxException(String message, ErrorSeverity severity, TrackBack locator, Throwable cause) { - super(message, severity, locator, cause); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslator.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslator.java deleted file mode 100644 index 58ff24cbe..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslator.java +++ /dev/null @@ -1,211 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.*; -import java.util.*; -import org.antlr.v4.runtime.*; -import org.cqframework.cql.cql2elm.model.CompiledLibrary; -import org.cqframework.cql.elm.serializing.ElmLibraryWriterFactory; -import org.hl7.cql.model.NamespaceInfo; -import org.hl7.elm.r1.Library; -import org.hl7.elm.r1.Retrieve; -import org.hl7.elm.r1.VersionedIdentifier; - -public class CqlTranslator { - public enum Format { - XML, - JSON, - COFFEE - } - - private CqlCompiler compiler; - - public static CqlTranslator fromText(String cqlText, LibraryManager libraryManager) { - return new CqlTranslator(null, null, CharStreams.fromString(cqlText), libraryManager); - } - - public static CqlTranslator fromText(NamespaceInfo namespaceInfo, String cqlText, LibraryManager libraryManager) { - return new CqlTranslator(namespaceInfo, null, CharStreams.fromString(cqlText), libraryManager); - } - - public static CqlTranslator fromText( - NamespaceInfo namespaceInfo, - VersionedIdentifier sourceInfo, - String cqlText, - LibraryManager libraryManager) { - return new CqlTranslator(namespaceInfo, sourceInfo, CharStreams.fromString(cqlText), libraryManager); - } - - public static CqlTranslator fromStream( - NamespaceInfo namespaceInfo, InputStream cqlStream, LibraryManager libraryManager) throws IOException { - return new CqlTranslator(namespaceInfo, null, CharStreams.fromStream(cqlStream), libraryManager); - } - - public static CqlTranslator fromStream(InputStream cqlStream, LibraryManager libraryManager) throws IOException { - return new CqlTranslator(null, null, CharStreams.fromStream(cqlStream), libraryManager); - } - - public static CqlTranslator fromStream( - NamespaceInfo namespaceInfo, - VersionedIdentifier sourceInfo, - InputStream cqlStream, - LibraryManager libraryManager) - throws IOException { - return new CqlTranslator(namespaceInfo, sourceInfo, CharStreams.fromStream(cqlStream), libraryManager); - } - - public static CqlTranslator fromFile(String cqlFileName, LibraryManager libraryManager) throws IOException { - return new CqlTranslator( - null, - getSourceInfo(cqlFileName), - CharStreams.fromStream(new FileInputStream(cqlFileName)), - libraryManager); - } - - public static CqlTranslator fromFile(NamespaceInfo namespaceInfo, String cqlFileName, LibraryManager libraryManager) - throws IOException { - return new CqlTranslator( - namespaceInfo, - getSourceInfo(cqlFileName), - CharStreams.fromStream(new FileInputStream(cqlFileName)), - libraryManager); - } - - public static CqlTranslator fromFile(File cqlFile, LibraryManager libraryManager) throws IOException { - return new CqlTranslator( - null, getSourceInfo(cqlFile), CharStreams.fromStream(new FileInputStream(cqlFile)), libraryManager); - } - - public static CqlTranslator fromFile(NamespaceInfo namespaceInfo, File cqlFile, LibraryManager libraryManager) - throws IOException { - return new CqlTranslator( - namespaceInfo, - getSourceInfo(cqlFile), - CharStreams.fromStream(new FileInputStream(cqlFile)), - libraryManager); - } - - public static CqlTranslator fromFile( - NamespaceInfo namespaceInfo, VersionedIdentifier sourceInfo, File cqlFile, LibraryManager libraryManager) - throws IOException { - return new CqlTranslator( - namespaceInfo, sourceInfo, CharStreams.fromStream(new FileInputStream(cqlFile)), libraryManager); - } - - private CqlTranslator( - NamespaceInfo namespaceInfo, VersionedIdentifier sourceInfo, CharStream is, LibraryManager libraryManager) { - compiler = new CqlCompiler(namespaceInfo, sourceInfo, libraryManager); - compiler.run(is); - } - - private static VersionedIdentifier getSourceInfo(String cqlFileName) { - return getSourceInfo(new File(cqlFileName)); - } - - private static VersionedIdentifier getSourceInfo(File cqlFile) { - String name = cqlFile.getName(); - int extensionIndex = name.lastIndexOf('.'); - if (extensionIndex > 0) { - name = name.substring(0, extensionIndex); - } - String system = null; - try { - system = cqlFile.getCanonicalPath(); - } catch (IOException e) { - system = cqlFile.getAbsolutePath(); - } - - return new VersionedIdentifier().withId(name).withSystem(system); - } - - private String toXml(Library library) { - try { - return convertToXml(library); - } catch (IOException e) { - throw new IllegalArgumentException("Could not convert library to XML.", e); - } - } - - private String toJson(Library library) { - try { - return convertToJson(library); - } catch (IOException e) { - throw new IllegalArgumentException("Could not convert library to JSON using JAXB serializer.", e); - } - } - - public String toXml() { - return toXml(compiler.getLibrary()); - } - - public String toJson() { - return toJson(compiler.getLibrary()); - } - - public Library toELM() { - return compiler.getLibrary(); - } - - public CompiledLibrary getTranslatedLibrary() { - return compiler.getCompiledLibrary(); - } - - public Object toObject() { - return compiler.toObject(); - } - - public List toRetrieves() { - return compiler.toRetrieves(); - } - - public Map getLibraries() { - return compiler.getLibraries(); - } - - public Map getTranslatedLibraries() { - return compiler.getCompiledLibraries(); - } - - // public Map getLibrariesAsXML() { - // var result = new HashMap(); - // for (Map.Entry entry : getTranslatedLibraries().entrySet()) { - // result.put(entry.getKey(), toXml(entry.getValue().getLibrary())); - // } - // return result; - // } - - // public Map getLibrariesAsJSON() { - // var result = new HashMap(); - // for (Map.Entry entry : getTranslatedLibraries().entrySet()) { - // result.put(entry.getKey(), toJson(entry.getValue().getLibrary())); - // } - // return result; - // } - - public List getExceptions() { - return compiler.getExceptions(); - } - - public List getErrors() { - return compiler.getErrors(); - } - - public List getWarnings() { - return compiler.getWarnings(); - } - - public List getMessages() { - return compiler.getMessages(); - } - - public static String convertToXml(Library library) throws IOException { - StringWriter writer = new StringWriter(); - ElmLibraryWriterFactory.getWriter(LibraryContentType.XML.mimeType()).write(library, writer); - return writer.getBuffer().toString(); - } - - public static String convertToJson(Library library) throws IOException { - StringWriter writer = new StringWriter(); - ElmLibraryWriterFactory.getWriter(LibraryContentType.JSON.mimeType()).write(library, writer); - return writer.getBuffer().toString(); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslatorOptions.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslatorOptions.java deleted file mode 100644 index a11a7b1d5..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslatorOptions.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import java.util.EnumSet; -import java.util.Set; - -public class CqlTranslatorOptions { - - public enum Format { - XML, - JSON, - COFFEE - } - - @JsonUnwrapped - private CqlCompilerOptions cqlCompilerOptions; - - private Set formats; - - public static CqlTranslatorOptions defaultOptions() { - return new CqlTranslatorOptions() - .withCqlCompilerOptions(CqlCompilerOptions.defaultOptions()) - .withFormats(EnumSet.of(Format.XML)); - } - - public CqlCompilerOptions getCqlCompilerOptions() { - return this.cqlCompilerOptions; - } - - public void setCqlCompilerOptions(CqlCompilerOptions cqlCompilerOptions) { - this.cqlCompilerOptions = cqlCompilerOptions; - } - - public CqlTranslatorOptions withCqlCompilerOptions(CqlCompilerOptions cqlCompilerOptions) { - this.setCqlCompilerOptions(cqlCompilerOptions); - return this; - } - - public Set getFormats() { - return this.formats; - } - - public void setFormats(Set formats) { - this.formats = formats; - } - - public CqlTranslatorOptions withFormats(Set formats) { - this.setFormats(formats); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslatorOptionsMapper.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslatorOptionsMapper.java deleted file mode 100644 index 1d85b5510..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/CqlTranslatorOptionsMapper.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.*; - -public class CqlTranslatorOptionsMapper { - private static ObjectMapper om = new ObjectMapper(); - - public static CqlTranslatorOptions fromFile(String fileName) { - FileReader fr = null; - try { - fr = new FileReader(fileName); - return fromReader(fr); - } catch (IOException e) { - throw new RuntimeException(String.format("Errors occurred reading options: %s", e.getMessage())); - } - } - - public static CqlTranslatorOptions fromReader(Reader reader) { - try { - return om.readValue(reader, CqlTranslatorOptions.class); - } catch (IOException e) { - throw new RuntimeException(String.format("Errors occurred reading options: %s", e.getMessage())); - } - } - - public static void toFile(String fileName, CqlTranslatorOptions options) { - FileWriter fw = null; - try { - fw = new FileWriter(fileName); - toWriter(fw, options); - } catch (IOException e) { - throw new RuntimeException(String.format("Errors occurred writing options: %s", e.getMessage())); - } - } - - public static void toWriter(Writer writer, CqlTranslatorOptions options) { - ObjectMapper om = new ObjectMapper(); - try { - om.writeValue(writer, options); - } catch (IOException e) { - throw new RuntimeException(String.format("Errors occurred writing options: %s", e.getMessage())); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DataTypes.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DataTypes.java deleted file mode 100644 index e98748018..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DataTypes.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import org.hl7.cql.model.DataType; - -public class DataTypes { - public static void verifyType(DataType actualType, DataType expectedType) { - if (!subTypeOf(actualType, expectedType)) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Expected an expression of type '%s', but found an expression of type '%s'.", - expectedType != null ? expectedType.toLabel() : "", - actualType != null ? actualType.toLabel() : "")); - } - } - - public static void verifyCast(DataType targetType, DataType sourceType) { - // Casting can be used for compatible types as well as subtypes and supertypes - if (!(subTypeOf(targetType, sourceType) - || superTypeOf(targetType, sourceType) - || compatibleWith(sourceType, targetType))) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Expression of type '%s' cannot be cast as a value of type '%s'.", - sourceType != null ? sourceType.toLabel() : "", - targetType != null ? targetType.toLabel() : "")); - } - } - - public static boolean equal(DataType a, DataType b) { - return a != null && b != null && a.equals(b); - } - - public static boolean compatibleWith(DataType a, DataType b) { - return a != null && b != null && a.isCompatibleWith(b); - } - - public static boolean subTypeOf(DataType a, DataType b) { - return a != null && b != null && a.isSubTypeOf(b); - } - - public static boolean superTypeOf(DataType a, DataType b) { - return a != null && b != null && a.isSuperTypeOf(b); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultLibrarySourceLoader.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultLibrarySourceLoader.java deleted file mode 100644 index edfbb4f84..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultLibrarySourceLoader.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.InputStream; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import org.hl7.cql.model.NamespaceAware; -import org.hl7.cql.model.NamespaceManager; -import org.hl7.elm.r1.VersionedIdentifier; - -/** - * Used by LibraryManager to manage a set of library source providers that - * resolve library includes within CQL. Package private since its not intended - * to be used outside the context of the instantiating LibraryManager instance. - */ -class DefaultLibrarySourceLoader implements LibrarySourceLoader, NamespaceAware, PathAware { - private final List providers = new ArrayList<>(); - boolean initialized = false; - - @Override - public void registerProvider(LibrarySourceProvider provider) { - if (provider == null) { - throw new IllegalArgumentException("provider is null."); - } - - if (provider instanceof NamespaceAware) { - ((NamespaceAware) provider).setNamespaceManager(namespaceManager); - } - - if (provider instanceof PathAware) { - ((PathAware) provider).setPath(path); - } - - providers.add(provider); - } - - private Path path; - - public void setPath(Path path) { - if (path == null || !path.toFile().isDirectory()) { - throw new IllegalArgumentException(String.format("path '%s' is not a valid directory", path)); - } - - this.path = path; - - for (LibrarySourceProvider provider : getProviders()) { - if (provider instanceof PathAware) { - ((PathAware) provider).setPath(path); - } - } - } - - @Override - public void clearProviders() { - providers.clear(); - initialized = false; - } - - private List getProviders() { - if (!initialized) { - initialized = true; - for (Iterator it = LibrarySourceProviderFactory.providers(false); it.hasNext(); ) { - LibrarySourceProvider provider = it.next(); - registerProvider(provider); - } - } - - return providers; - } - - @Override - public InputStream getLibrarySource(VersionedIdentifier libraryIdentifier) { - if (libraryIdentifier == null) { - throw new IllegalArgumentException("libraryIdentifier is null."); - } - - if (libraryIdentifier.getId() == null || libraryIdentifier.getId().equals("")) { - throw new IllegalArgumentException("libraryIdentifier Id is null."); - } - - InputStream source = null; - for (LibrarySourceProvider provider : getProviders()) { - InputStream localSource = provider.getLibrarySource(libraryIdentifier); - if (localSource != null) { - if (source != null) { - throw new IllegalArgumentException(String.format( - "Multiple sources found for library %s, version %s.", - libraryIdentifier.getId(), libraryIdentifier.getVersion())); - } - - source = localSource; - } - } - - if (source == null) { - throw new IllegalArgumentException(String.format( - "Could not load source for library %s, version %s.", - libraryIdentifier.getId(), libraryIdentifier.getVersion())); - } - - return source; - } - - private NamespaceManager namespaceManager; - - @Override - public void setNamespaceManager(NamespaceManager namespaceManager) { - this.namespaceManager = namespaceManager; - - for (LibrarySourceProvider provider : getProviders()) { - if (provider instanceof NamespaceAware) { - ((NamespaceAware) provider).setNamespaceManager(namespaceManager); - } - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultLibrarySourceProvider.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultLibrarySourceProvider.java deleted file mode 100644 index 585408a80..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultLibrarySourceProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.*; -import java.nio.file.Path; -import org.cqframework.cql.cql2elm.model.Version; -import org.hl7.elm.r1.VersionedIdentifier; - -// NOTE: This implementation is naive and assumes library file names will always take the form: -// [-].cql -// And further that will never contain dashes, and that will always be of the form -// [.[.]] -// Usage outside these boundaries will result in errors or incorrect behavior. -public class DefaultLibrarySourceProvider implements LibrarySourceProvider, PathAware { - - public DefaultLibrarySourceProvider(Path path) { - setPath(path); - } - - private Path path; - - public void setPath(Path path) { - if (path == null || !path.toFile().isDirectory()) { - throw new IllegalArgumentException(String.format("path '%s' is not a valid directory", path)); - } - - this.path = path; - } - - @Override - public InputStream getLibrarySource(VersionedIdentifier libraryIdentifier) { - if (path != null) { - String libraryName = libraryIdentifier.getId(); - Path libraryPath = this.path.resolve(String.format( - "%s%s.cql", - libraryName, libraryIdentifier.getVersion() != null ? ("-" + libraryIdentifier.getVersion()) : "")); - File libraryFile = libraryPath.toFile(); - if (!libraryFile.exists()) { - FilenameFilter filter = new FilenameFilter() { - @Override - public boolean accept(File path, String name) { - return name.startsWith(libraryName) && name.endsWith(".cql"); - } - }; - - File mostRecentFile = null; - Version mostRecent = null; - Version requestedVersion = - libraryIdentifier.getVersion() == null ? null : new Version(libraryIdentifier.getVersion()); - for (File file : path.toFile().listFiles(filter)) { - String fileName = file.getName(); - int indexOfExtension = fileName.lastIndexOf("."); - if (indexOfExtension >= 0) { - fileName = fileName.substring(0, indexOfExtension); - } - - int indexOfVersionSeparator = fileName.indexOf("-"); - if (indexOfVersionSeparator >= 0) { - Version version = new Version(fileName.substring(indexOfVersionSeparator + 1)); - // If the file has a version, make sure it is compatible with the version we are looking for - if (indexOfVersionSeparator == libraryName.length() && requestedVersion == null - || version.compatibleWith(requestedVersion)) { - if (mostRecent == null - || ((version != null && version.isComparable()) - && (mostRecent != null && mostRecent.isComparable()) - && version.compareTo(mostRecent) > 0)) { - mostRecent = version; - mostRecentFile = file; - } else if (version != null && version.matchStrictly(mostRecent)) { - mostRecent = version; - mostRecentFile = file; - } - } - } else { - // If the file is named correctly, but has no version, consider it the most recent version - if (fileName.equals(libraryName) && mostRecent == null) { - mostRecentFile = file; - } - } - } - - // Do not throw, allow the loader to throw, just report null - // if (mostRecentFile == null) { - // throw new IllegalArgumentException(String.format("Could not resolve most recent source library for - // library %s.", libraryIdentifier.getId())); - // } - - libraryFile = mostRecentFile; - } - try { - if (libraryFile != null) { - return new FileInputStream(libraryFile); - } - } catch (FileNotFoundException e) { - throw new IllegalArgumentException( - String.format("Could not load source for library %s.", libraryIdentifier.getId()), e); - } - } - - return null; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultModelInfoProvider.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultModelInfoProvider.java deleted file mode 100644 index ad2272b91..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/DefaultModelInfoProvider.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.*; -import java.nio.file.Path; -import org.cqframework.cql.cql2elm.model.Version; -import org.hl7.cql.model.ModelIdentifier; -import org.hl7.cql.model.ModelInfoProvider; -import org.hl7.elm_modelinfo.r1.ModelInfo; -import org.hl7.elm_modelinfo.r1.serializing.ModelInfoReaderFactory; - -// NOTE: This implementation assumes modelinfo file names will always take the form: -// -modelinfo[-].cql -// And further that will never contain dashes, and that will always be of the form -// [.[.]] -// Usage outside these boundaries will result in errors or incorrect behavior. -public class DefaultModelInfoProvider implements ModelInfoProvider, PathAware { - - public DefaultModelInfoProvider() {} - - public DefaultModelInfoProvider(Path path) { - setPath(path); - } - - private Path path; - - public void setPath(Path path) { - if (path == null || !path.toFile().isDirectory()) { - throw new IllegalArgumentException(String.format("path '%s' is not a valid directory", path)); - } - - this.path = path; - } - - private void checkPath() { - if (path == null || path.equals("")) { - throw new IllegalArgumentException("Path is required for DefaultModelInfoProvider implementation"); - } - } - - public ModelInfo load(ModelIdentifier modelIdentifier) { - if (path != null) { - String modelName = modelIdentifier.getId(); - String modelVersion = modelIdentifier.getVersion(); - Path modelPath = this.path.resolve(String.format( - "%s-modelinfo%s.xml", modelName.toLowerCase(), modelVersion != null ? ("-" + modelVersion) : "")); - File modelFile = modelPath.toFile(); - if (!modelFile.exists()) { - FilenameFilter filter = new FilenameFilter() { - @Override - public boolean accept(File path, String name) { - return name.startsWith(modelName.toLowerCase() + "-modelinfo") && name.endsWith(".xml"); - } - }; - - File mostRecentFile = null; - Version mostRecent = null; - try { - Version requestedVersion = modelVersion == null ? null : new Version(modelVersion); - for (File file : path.toFile().listFiles(filter)) { - String fileName = file.getName(); - int indexOfExtension = fileName.lastIndexOf("."); - if (indexOfExtension >= 0) { - fileName = fileName.substring(0, indexOfExtension); - } - - String[] fileNameComponents = fileName.split("-"); - if (fileNameComponents.length == 3) { - Version version = new Version(fileNameComponents[2]); - if (requestedVersion == null || version.compatibleWith(requestedVersion)) { - if (mostRecent == null - || ((version != null && version.isComparable()) - && (mostRecent != null && mostRecent.isComparable()) - && version.compareTo(mostRecent) > 0)) { - mostRecent = version; - mostRecentFile = file; - } else if (version != null && version.matchStrictly(mostRecent)) { - mostRecent = version; - mostRecentFile = file; - } - } - } else { - if (mostRecent == null) { - mostRecentFile = file; - } - } - } - - modelFile = mostRecentFile; - } catch (IllegalArgumentException e) { - // do nothing, if the version can't be understood as a semantic version, don't allow unspecified - // version resolution - } - } - try { - if (modelFile != null) { - InputStream is = new FileInputStream(modelFile); - - return ModelInfoReaderFactory.getReader("application/xml").read(is); - } - } catch (IOException e) { - throw new IllegalArgumentException( - String.format("Could not load definition for model info %s.", modelIdentifier.getId()), e); - } - } - - return null; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/IdentifierContext.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/IdentifierContext.java deleted file mode 100644 index 351660a75..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/IdentifierContext.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.Objects; -import java.util.StringJoiner; -import org.cqframework.cql.elm.tracking.Trackable; - -/** - * Simple POJO using for identifier hider that maintains the identifier and Trackable type of the construct being evaluated. - */ -public class IdentifierContext { - private final String identifier; - private final Class elementSubclass; - - public IdentifierContext(String identifier, Class elementSubclass) { - this.identifier = identifier; - this.elementSubclass = elementSubclass; - } - - public String getIdentifier() { - return identifier; - } - - public Class getTrackableSubclass() { - return elementSubclass; - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; - } - IdentifierContext that = (IdentifierContext) other; - return Objects.equals(identifier, that.identifier) && Objects.equals(elementSubclass, that.elementSubclass); - } - - @Override - public int hashCode() { - return Objects.hash(identifier, elementSubclass); - } - - @Override - public String toString() { - return new StringJoiner(", ", IdentifierContext.class.getSimpleName() + "[", "]") - .add("identifier='" + identifier + "'") - .add("elementSubclass=" + elementSubclass) - .toString(); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java deleted file mode 100644 index 9f377a6a5..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java +++ /dev/null @@ -1,3555 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.math.BigDecimal; -import java.util.*; -import java.util.List; -import javax.xml.namespace.QName; -import org.apache.commons.lang3.StringUtils; -import org.cqframework.cql.cql2elm.model.*; -import org.cqframework.cql.cql2elm.model.invocation.*; -import org.cqframework.cql.elm.IdObjectFactory; -import org.cqframework.cql.elm.tracking.TrackBack; -import org.cqframework.cql.elm.tracking.Trackable; -import org.hl7.cql.model.*; -import org.hl7.cql_annotations.r1.CqlToElmError; -import org.hl7.cql_annotations.r1.CqlToElmInfo; -import org.hl7.cql_annotations.r1.ErrorSeverity; -import org.hl7.cql_annotations.r1.ErrorType; -import org.hl7.elm.r1.*; - -/** - * Created by Bryn on 12/29/2016. - */ -public class LibraryBuilder { - public enum SignatureLevel { - /* - Indicates signatures will never be included in operator invocations - */ - None, - - /* - Indicates signatures will only be included in invocations if the declared signature of the resolve operator is different than the invocation signature - */ - Differing, - - /* - Indicates signatures will only be included in invocations if the function has multiple overloads with the same number of arguments as the invocation - */ - Overloads, - - /* - Indicates signatures will always be included in invocations - */ - All - } - - public LibraryBuilder(LibraryManager libraryManager, IdObjectFactory objectFactory) { - this(null, libraryManager, objectFactory); - } - - public LibraryBuilder(NamespaceInfo namespaceInfo, LibraryManager libraryManager, IdObjectFactory objectFactory) { - this.libraryManager = Objects.requireNonNull(libraryManager); - this.of = Objects.requireNonNull(objectFactory); - - this.namespaceInfo = namespaceInfo; // Note: allowed to be null, implies global namespace - this.modelManager = libraryManager.getModelManager(); - this.typeBuilder = new TypeBuilder(of, this.modelManager); - this.systemFunctionResolver = new SystemFunctionResolver(this, this.of); - - this.library = of.createLibrary() - .withSchemaIdentifier(of.createVersionedIdentifier() - .withId("urn:hl7-org:elm") // TODO: Pull this from the ELM library namespace - .withVersion("r1")); - - this.cqlToElmInfo = af.createCqlToElmInfo(); - this.cqlToElmInfo.setTranslatorVersion(LibraryBuilder.class.getPackage().getImplementationVersion()); - - this.library.getAnnotation().add(this.cqlToElmInfo); - - this.options = Objects.requireNonNull( - libraryManager.getCqlCompilerOptions(), "libraryManager compilerOptions can not be null."); - - this.setCompilerOptions(this.options); - compiledLibrary = new CompiledLibrary(); - compiledLibrary.setLibrary(library); - } - - // Only exceptions of severity Error - private final java.util.List errors = new ArrayList<>(); - - public List getErrors() { - return errors; - } - - // Only exceptions of severity Warning - private final java.util.List warnings = new ArrayList<>(); - - public List getWarnings() { - return warnings; - } - - // Only exceptions of severity Info - private final java.util.List messages = new ArrayList<>(); - - public List getMessages() { - return messages; - } - - // All exceptions - private final java.util.List exceptions = new ArrayList<>(); - - public List getExceptions() { - return exceptions; - } - - public IdObjectFactory getObjectFactory() { - return of; - } - - public LibraryManager getLibraryManager() { - return libraryManager; - } - - private final Map models = new LinkedHashMap<>(); - - private final Map> nameTypeSpecifiers = new HashMap<>(); - private final Map libraries = new LinkedHashMap<>(); - private final SystemFunctionResolver systemFunctionResolver; - private final Stack expressionContext = new Stack<>(); - private final ExpressionDefinitionContextStack expressionDefinitions = new ExpressionDefinitionContextStack(); - private final Stack functionDefs = new Stack<>(); - private final Deque globalIdentifiers = new ArrayDeque<>(); - private final Stack> localIdentifierStack = new Stack<>(); - private int literalContext = 0; - private int typeSpecifierContext = 0; - private final NamespaceInfo namespaceInfo; - private final ModelManager modelManager; - private Model defaultModel = null; - private final LibraryManager libraryManager; - private Library library = null; - - public Library getLibrary() { - return library; - } - - private CompiledLibrary compiledLibrary = null; - - public CompiledLibrary getCompiledLibrary() { - return compiledLibrary; - } - - private final ConversionMap conversionMap = new ConversionMap(); - - public ConversionMap getConversionMap() { - return conversionMap; - } - - private final IdObjectFactory of; - private final org.hl7.cql_annotations.r1.ObjectFactory af = new org.hl7.cql_annotations.r1.ObjectFactory(); - private boolean listTraversal = true; - private final CqlCompilerOptions options; - private final CqlToElmInfo cqlToElmInfo; - private final TypeBuilder typeBuilder; - - public void enableListTraversal() { - listTraversal = true; - } - - private void setCompilerOptions(CqlCompilerOptions options) { - if (options.getOptions().contains(CqlCompilerOptions.Options.DisableListTraversal)) { - this.listTraversal = false; - } - if (options.getOptions().contains(CqlCompilerOptions.Options.DisableListDemotion)) { - this.getConversionMap().disableListDemotion(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.DisableListPromotion)) { - this.getConversionMap().disableListPromotion(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableIntervalDemotion)) { - this.getConversionMap().enableIntervalDemotion(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableIntervalPromotion)) { - this.getConversionMap().enableIntervalPromotion(); - } - setCompatibilityLevel(options.getCompatibilityLevel()); - this.cqlToElmInfo.setTranslatorOptions(options.toString()); - this.cqlToElmInfo.setSignatureLevel(options.getSignatureLevel().name()); - } - - private String compatibilityLevel = null; - - public boolean isCompatibilityLevel3() { - return "1.3".equals(compatibilityLevel); - } - - public boolean isCompatibilityLevel4() { - return "1.4".equals(compatibilityLevel); - } - - private Version compatibilityVersion; - - public String getCompatibilityLevel() { - return this.compatibilityLevel; - } - - public void setCompatibilityLevel(String compatibilityLevel) { - this.compatibilityLevel = compatibilityLevel; - if (compatibilityLevel != null) { - this.compatibilityVersion = new Version(compatibilityLevel); - } - } - - public boolean isCompatibleWith(String sinceCompatibilityLevel) { - if (compatibilityVersion == null) { - // No compatibility version is specified, assume latest functionality - return true; - } - - if (sinceCompatibilityLevel == null || sinceCompatibilityLevel.isEmpty()) { - throw new IllegalArgumentException( - "Internal Translator Error: compatibility level is required to perform a compatibility check"); - } - - Version sinceVersion = new Version(sinceCompatibilityLevel); - return compatibilityVersion.compatibleWith(sinceVersion); - } - - public void checkCompatibilityLevel(String featureName, String sinceCompatibilityLevel) { - if (featureName == null || featureName.isEmpty()) { - throw new IllegalArgumentException( - "Internal Translator Error: feature name is required to perform a compatibility check"); - } - - if (!isCompatibleWith(sinceCompatibilityLevel)) { - throw new IllegalArgumentException(String.format( - "Feature %s was introduced in version %s and so cannot be used at compatibility level %s", - featureName, sinceCompatibilityLevel, compatibilityLevel)); - } - } - - /* - A "well-known" model name is one that is allowed to resolve without a namespace in a namespace-aware context - */ - public boolean isWellKnownModelName(String unqualifiedIdentifier) { - if (namespaceInfo == null) { - return false; - } - - return modelManager.isWellKnownModelName(unqualifiedIdentifier); - } - - /* - A "well-known" library name is a library name that is allowed to resolve without a namespace in a namespace-aware context - */ - public boolean isWellKnownLibraryName(String unqualifiedIdentifier) { - if (namespaceInfo == null) { - return false; - } - - return libraryManager.isWellKnownLibraryName(unqualifiedIdentifier); - } - - public NamespaceInfo getNamespaceInfo() { - return this.namespaceInfo; - } - - private Model loadModel(ModelIdentifier modelIdentifier) { - Model model = modelManager.resolveModel(modelIdentifier); - loadConversionMap(model); - return model; - } - - public Model getDefaultModel() { - return defaultModel; - } - - private void setDefaultModel(Model model) { - // The default model is the first model that is not System - if (defaultModel == null && !model.getModelInfo().getName().equals("System")) { - defaultModel = model; - } - } - - public Model getModel(ModelIdentifier modelIdentifier, String localIdentifier) { - Model model = models.get(localIdentifier); - if (model == null) { - model = loadModel(modelIdentifier); - setDefaultModel(model); - models.put(localIdentifier, model); - // Add the model using def to the output - buildUsingDef(modelIdentifier, model, localIdentifier); - } - - if (modelIdentifier.getVersion() != null - && !modelIdentifier.getVersion().equals(model.getModelInfo().getVersion())) { - throw new IllegalArgumentException(String.format( - "Could not load model information for model %s, version %s because version %s is already loaded.", - modelIdentifier.getId(), - modelIdentifier.getVersion(), - model.getModelInfo().getVersion())); - } - - return model; - } - - public ResultWithPossibleError getNamedTypeSpecifierResult( - String namedTypeSpecifierIdentifier) { - return nameTypeSpecifiers.get(namedTypeSpecifierIdentifier); - } - - public void addNamedTypeSpecifierResult( - String namedTypeSpecifierIdentifier, ResultWithPossibleError namedTypeSpecifierResult) { - if (!nameTypeSpecifiers.containsKey(namedTypeSpecifierIdentifier)) { - nameTypeSpecifiers.put(namedTypeSpecifierIdentifier, namedTypeSpecifierResult); - } - } - - private void loadConversionMap(Model model) { - for (Conversion conversion : model.getConversions()) { - conversionMap.add(conversion); - } - } - - private UsingDef buildUsingDef(ModelIdentifier modelIdentifier, Model model, String localIdentifier) { - UsingDef usingDef = of.createUsingDef() - .withLocalIdentifier(localIdentifier) - .withVersion(modelIdentifier.getVersion()) - .withUri(model.getModelInfo().getUrl()); - // TODO: Needs to write xmlns and schemalocation to the resulting ELM XML document... - - addUsing(usingDef); - return usingDef; - } - - public boolean hasUsings() { - for (Model model : models.values()) { - if (!model.getModelInfo().getName().equals("System")) { - return true; - } - } - - return false; - } - - private void addUsing(UsingDef usingDef) { - if (library.getUsings() == null) { - library.setUsings(of.createLibraryUsings()); - } - library.getUsings().getDef().add(usingDef); - - compiledLibrary.add(usingDef); - } - - public ClassType resolveLabel(String modelName, String label) { - ClassType result = null; - if (modelName == null || modelName.equals("")) { - for (Model model : models.values()) { - ClassType modelResult = model.resolveLabel(label); - if (modelResult != null) { - if (result != null) { - throw new IllegalArgumentException(String.format( - "Label %s is ambiguous between %s and %s.", - label, result.getLabel(), modelResult.getLabel())); - } - - result = modelResult; - } - } - } else { - result = getModel(modelName).resolveLabel(label); - } - - return result; - } - - public ModelContext resolveContextName(String modelName, String contextName) { - // Attempt to resolve as a label first - ModelContext result = null; - - if (modelName == null || modelName.equals("")) { - // Attempt to resolve in the default model if one is available - if (defaultModel != null) { - ModelContext modelResult = defaultModel.resolveContextName(contextName); - if (modelResult != null) { - return modelResult; - } - } - - // Otherwise, resolve across all models and throw for ambiguous resolution - for (Model model : models.values()) { - ModelContext modelResult = model.resolveContextName(contextName); - if (modelResult != null) { - if (result != null) { - throw new IllegalArgumentException(String.format( - "Context name %s is ambiguous between %s and %s.", - contextName, result.getName(), modelResult.getName())); - } - - result = modelResult; - } - } - } else { - result = getModel(modelName).resolveContextName(contextName); - } - - return result; - } - - public DataType resolveTypeName(String typeName) { - return resolveTypeName(null, typeName); - } - - public DataType resolveTypeName(String modelName, String typeName) { - // Attempt to resolve as a label first - DataType result = resolveLabel(modelName, typeName); - - if (result == null) { - if (modelName == null || modelName.equals("")) { - // Attempt to resolve in the default model if one is available - if (defaultModel != null) { - DataType modelResult = defaultModel.resolveTypeName(typeName); - if (modelResult != null) { - return modelResult; - } - } - - // Otherwise, resolve across all models and throw for ambiguous resolution - for (Model model : models.values()) { - DataType modelResult = model.resolveTypeName(typeName); - if (modelResult != null) { - if (result != null) { - throw new IllegalArgumentException(String.format( - "Type name %s is ambiguous between %s and %s.", - typeName, ((NamedType) result).getName(), ((NamedType) modelResult).getName())); - } - - result = modelResult; - } - } - } else { - result = getModel(modelName).resolveTypeName(typeName); - } - } - - // Types introduced in 1.5: Long, Vocabulary, ValueSet, CodeSystem - if (result != null && result instanceof NamedType) { - switch (((NamedType) result).getName()) { - case "System.Long": - case "System.Vocabulary": - case "System.CodeSystem": - case "System.ValueSet": - // NOTE: This is a hack to allow the new ToValueSet operator in FHIRHelpers for - // backwards-compatibility - // The operator still cannot be used in 1.4, but the definition will compile. This really should be - // being done with preprocessor directives, - // but that's a whole other project in and of itself. - if (!isCompatibleWith("1.5") && !isFHIRHelpers(compiledLibrary)) { - throw new IllegalArgumentException(String.format( - "The type %s was introduced in CQL 1.5 and cannot be referenced at compatibility level %s", - ((NamedType) result).getName(), getCompatibilityLevel())); - } - } - } - - return result; - } - - private boolean isFHIRHelpers(CompiledLibrary library) { - if (library != null - && library.getIdentifier() != null - && library.getIdentifier().getId() != null - && library.getIdentifier().getId().equals("FHIRHelpers")) { - return true; - } - - return false; - } - - public DataType resolveTypeSpecifier(String typeSpecifier) { - if (typeSpecifier == null) { - throw new IllegalArgumentException("typeSpecifier is null"); - } - - // typeSpecifier: simpleTypeSpecifier | intervalTypeSpecifier | listTypeSpecifier - // simpleTypeSpecifier: (identifier '.')? identifier - // intervalTypeSpecifier: 'interval' '<' typeSpecifier '>' - // listTypeSpecifier: 'list' '<' typeSpecifier '>' - if (typeSpecifier.toLowerCase().startsWith("interval<")) { - DataType pointType = resolveTypeSpecifier( - typeSpecifier.substring(typeSpecifier.indexOf('<') + 1, typeSpecifier.lastIndexOf('>'))); - return new IntervalType(pointType); - } else if (typeSpecifier.toLowerCase().startsWith("list<")) { - DataType elementType = resolveTypeName( - typeSpecifier.substring(typeSpecifier.indexOf('<') + 1, typeSpecifier.lastIndexOf('>'))); - return new ListType(elementType); - } else if (typeSpecifier.indexOf(".") >= 0) { - String modelName = typeSpecifier.substring(0, typeSpecifier.indexOf(".")); - String typeName = typeSpecifier.substring(typeSpecifier.indexOf(".") + 1); - return resolveTypeName(modelName, typeName); - } else { - return resolveTypeName(typeSpecifier); - } - } - - public UsingDef resolveUsingRef(String modelName) { - return compiledLibrary.resolveUsingRef(modelName); - } - - public SystemModel getSystemModel() { - // TODO: Support loading different versions of the system library - return (SystemModel) getModel(new ModelIdentifier().withId("System"), "System"); - } - - public Model getModel(String modelName) { - UsingDef usingDef = resolveUsingRef(modelName); - if (usingDef == null && modelName.equals("FHIR")) { - // Special case for FHIR-derived models that include FHIR Helpers - var model = this.modelManager.resolveModelByUri("http://hl7.org/fhir"); - if (model != null) { - return model; - } - } - - if (usingDef == null) { - throw new IllegalArgumentException(String.format("Could not resolve model name %s", modelName)); - } - - return getModel(usingDef); - } - - public Model getModel(UsingDef usingDef) { - if (usingDef == null) { - throw new IllegalArgumentException("usingDef required"); - } - - return getModel( - new ModelIdentifier() - .withSystem(NamespaceManager.getUriPart(usingDef.getUri())) - .withId(NamespaceManager.getNamePart(usingDef.getUri())) - .withVersion(usingDef.getVersion()), - usingDef.getLocalIdentifier()); - } - - private void loadSystemLibrary() { - CompiledLibrary systemLibrary = SystemLibraryHelper.load(getSystemModel(), typeBuilder); - libraries.put(systemLibrary.getIdentifier().getId(), systemLibrary); - loadConversionMap(systemLibrary); - } - - private void loadConversionMap(CompiledLibrary library) { - for (Conversion conversion : library.getConversions()) { - conversionMap.add(conversion); - } - } - - public CompiledLibrary getSystemLibrary() { - return resolveLibrary("System"); - } - - public CompiledLibrary resolveLibrary(String identifier) { - - if (!identifier.equals("System")) { - checkLiteralContext(); - } - - CompiledLibrary result = libraries.get(identifier); - if (result == null) { - throw new IllegalArgumentException(String.format("Could not resolve library name %s.", identifier)); - } - return result; - } - - public String resolveNamespaceUri(String namespaceName, boolean mustResolve) { - String namespaceUri = libraryManager.getNamespaceManager().resolveNamespaceUri(namespaceName); - - if (namespaceUri == null && mustResolve) { - throw new IllegalArgumentException(String.format("Could not resolve namespace name %s", namespaceName)); - } - - return namespaceUri; - } - - private ErrorSeverity toErrorSeverity(CqlCompilerException.ErrorSeverity severity) { - if (severity == CqlCompilerException.ErrorSeverity.Info) { - return ErrorSeverity.INFO; - } else if (severity == CqlCompilerException.ErrorSeverity.Warning) { - return ErrorSeverity.WARNING; - } else if (severity == CqlCompilerException.ErrorSeverity.Error) { - return ErrorSeverity.ERROR; - } else { - throw new IllegalArgumentException(String.format("Unknown error severity %s", severity.toString())); - } - } - - private void addException(CqlCompilerException e) { - // Always add to the list of all exceptions - exceptions.add(e); - - if (e.getSeverity() == CqlCompilerException.ErrorSeverity.Error) { - errors.add(e); - } else if (e.getSeverity() == CqlCompilerException.ErrorSeverity.Warning) { - warnings.add(e); - } else if (e.getSeverity() == CqlCompilerException.ErrorSeverity.Info) { - messages.add(e); - } - } - - private boolean shouldReport(CqlCompilerException.ErrorSeverity errorSeverity) { - switch (options.getErrorLevel()) { - case Info: - return errorSeverity == CqlCompilerException.ErrorSeverity.Info - || errorSeverity == CqlCompilerException.ErrorSeverity.Warning - || errorSeverity == CqlCompilerException.ErrorSeverity.Error; - case Warning: - return errorSeverity == CqlCompilerException.ErrorSeverity.Warning - || errorSeverity == CqlCompilerException.ErrorSeverity.Error; - case Error: - return errorSeverity == CqlCompilerException.ErrorSeverity.Error; - default: - throw new IllegalArgumentException( - String.format("Unknown error severity %s", errorSeverity.toString())); - } - } - - /** - * Record any errors while parsing in both the list of errors but also in the library - * itself so they can be processed easily by a remote client - * @param e the exception to record - */ - public void recordParsingException(CqlCompilerException e) { - addException(e); - if (shouldReport(e.getSeverity())) { - CqlToElmError err = af.createCqlToElmError(); - err.setMessage(e.getMessage()); - err.setErrorType( - e instanceof CqlSyntaxException - ? ErrorType.SYNTAX - : (e instanceof CqlSemanticException ? ErrorType.SEMANTIC : ErrorType.INTERNAL)); - err.setErrorSeverity(toErrorSeverity(e.getSeverity())); - if (e.getLocator() != null) { - if (e.getLocator().getLibrary() != null) { - err.setLibrarySystem(e.getLocator().getLibrary().getSystem()); - err.setLibraryId(e.getLocator().getLibrary().getId()); - err.setLibraryVersion(e.getLocator().getLibrary().getVersion()); - } - err.setStartLine(e.getLocator().getStartLine()); - err.setEndLine(e.getLocator().getEndLine()); - err.setStartChar(e.getLocator().getStartChar()); - err.setEndChar(e.getLocator().getEndChar()); - } - - if (e.getCause() != null && e.getCause() instanceof CqlIncludeException) { - CqlIncludeException incEx = (CqlIncludeException) e.getCause(); - err.setTargetIncludeLibrarySystem(incEx.getLibrarySystem()); - err.setTargetIncludeLibraryId(incEx.getLibraryId()); - err.setTargetIncludeLibraryVersionId(incEx.getVersionId()); - err.setErrorType(ErrorType.INCLUDE); - } - library.getAnnotation().add(err); - } - } - - private String getLibraryName() { - String libraryName = library.getIdentifier().getId(); - if (libraryName == null) { - libraryName = "Anonymous"; - } - - if (library.getIdentifier().getSystem() != null) { - libraryName = library.getIdentifier().getSystem() + "/" + libraryName; - } - - return libraryName; - } - - public void beginTranslation() { - loadSystemLibrary(); - - // libraryManager.beginCompilation(getLibraryName()); - } - - public VersionedIdentifier getLibraryIdentifier() { - return library.getIdentifier(); - } - - public void setLibraryIdentifier(VersionedIdentifier vid) { - library.setIdentifier(vid); - compiledLibrary.setIdentifier(vid); - } - - public void endTranslation() { - applyTargetModelMaps(); - // libraryManager.endCompilation(getLibraryName()); - } - - public boolean canResolveLibrary(IncludeDef includeDef) { - VersionedIdentifier libraryIdentifier = new VersionedIdentifier() - .withSystem(NamespaceManager.getUriPart(includeDef.getPath())) - .withId(NamespaceManager.getNamePart(includeDef.getPath())) - .withVersion(includeDef.getVersion()); - return libraryManager.canResolveLibrary(libraryIdentifier); - } - - public void addInclude(IncludeDef includeDef) { - if (library.getIdentifier() == null || library.getIdentifier().getId() == null) { - throw new IllegalArgumentException("Unnamed libraries cannot reference other libraries."); - } - - if (library.getIncludes() == null) { - library.setIncludes(of.createLibraryIncludes()); - } - library.getIncludes().getDef().add(includeDef); - - compiledLibrary.add(includeDef); - - VersionedIdentifier libraryIdentifier = new VersionedIdentifier() - .withSystem(NamespaceManager.getUriPart(includeDef.getPath())) - .withId(NamespaceManager.getNamePart(includeDef.getPath())) - .withVersion(includeDef.getVersion()); - - var errors = new ArrayList(); - CompiledLibrary referencedLibrary = libraryManager.resolveLibrary(libraryIdentifier, errors); - for (CqlCompilerException error : errors) { - this.recordParsingException(error); - } - - // Note that translation of a referenced library may result in implicit specification of the namespace - // In this case, the referencedLibrary will have a namespaceUri different than the currently resolved - // namespaceUri - // of the IncludeDef. - String currentNamespaceUri = NamespaceManager.getUriPart(includeDef.getPath()); - if ((currentNamespaceUri == null && libraryIdentifier.getSystem() != null) - || (currentNamespaceUri != null && !currentNamespaceUri.equals(libraryIdentifier.getSystem()))) { - includeDef.setPath(NamespaceManager.getPath(libraryIdentifier.getSystem(), libraryIdentifier.getId())); - } - - libraries.put(includeDef.getLocalIdentifier(), referencedLibrary); - loadConversionMap(referencedLibrary); - } - - public void addParameter(ParameterDef paramDef) { - if (library.getParameters() == null) { - library.setParameters(of.createLibraryParameters()); - } - library.getParameters().getDef().add(paramDef); - - compiledLibrary.add(paramDef); - } - - public void addCodeSystem(CodeSystemDef cs) { - if (library.getCodeSystems() == null) { - library.setCodeSystems(of.createLibraryCodeSystems()); - } - library.getCodeSystems().getDef().add(cs); - - compiledLibrary.add(cs); - } - - public void addValueSet(ValueSetDef vs) { - if (library.getValueSets() == null) { - library.setValueSets(of.createLibraryValueSets()); - } - library.getValueSets().getDef().add(vs); - - compiledLibrary.add(vs); - } - - public void addCode(CodeDef cd) { - if (library.getCodes() == null) { - library.setCodes(of.createLibraryCodes()); - } - library.getCodes().getDef().add(cd); - - compiledLibrary.add(cd); - } - - public void addConcept(ConceptDef cd) { - if (library.getConcepts() == null) { - library.setConcepts(of.createLibraryConcepts()); - } - library.getConcepts().getDef().add(cd); - - compiledLibrary.add(cd); - } - - public void addContext(ContextDef cd) { - if (library.getContexts() == null) { - library.setContexts(of.createLibraryContexts()); - } - library.getContexts().getDef().add(cd); - } - - public void addExpression(ExpressionDef expDef) { - if (library.getStatements() == null) { - library.setStatements(of.createLibraryStatements()); - } - library.getStatements().getDef().add(expDef); - - compiledLibrary.add(expDef); - } - - public void removeExpression(ExpressionDef expDef) { - if (library.getStatements() != null) { - library.getStatements().getDef().remove(expDef); - compiledLibrary.remove(expDef); - } - } - - public ResolvedIdentifierContext resolve(String identifier) { - return compiledLibrary.resolve(identifier); - } - - public IncludeDef resolveIncludeRef(String identifier) { - return compiledLibrary.resolveIncludeRef(identifier); - } - - public String resolveIncludeAlias(VersionedIdentifier libraryIdentifier) { - return compiledLibrary.resolveIncludeAlias(libraryIdentifier); - } - - public CodeSystemDef resolveCodeSystemRef(String identifier) { - return compiledLibrary.resolveCodeSystemRef(identifier); - } - - public ValueSetDef resolveValueSetRef(String identifier) { - return compiledLibrary.resolveValueSetRef(identifier); - } - - public CodeDef resolveCodeRef(String identifier) { - return compiledLibrary.resolveCodeRef(identifier); - } - - public ConceptDef resolveConceptRef(String identifier) { - return compiledLibrary.resolveConceptRef(identifier); - } - - public ParameterDef resolveParameterRef(String identifier) { - checkLiteralContext(); - return compiledLibrary.resolveParameterRef(identifier); - } - - public ExpressionDef resolveExpressionRef(String identifier) { - checkLiteralContext(); - return compiledLibrary.resolveExpressionRef(identifier); - } - - public Conversion findConversion( - DataType fromType, DataType toType, boolean implicit, boolean allowPromotionAndDemotion) { - return conversionMap.findConversion( - fromType, toType, implicit, allowPromotionAndDemotion, compiledLibrary.getOperatorMap()); - } - - public Expression resolveUnaryCall(String libraryName, String operatorName, UnaryExpression expression) { - return resolveCall(libraryName, operatorName, new UnaryExpressionInvocation(expression), false, false); - } - - public Invocation resolveBinaryInvocation(String libraryName, String operatorName, BinaryExpression expression) { - return resolveBinaryInvocation(libraryName, operatorName, expression, true, false); - } - - public Expression resolveBinaryCall(String libraryName, String operatorName, BinaryExpression expression) { - Invocation invocation = resolveBinaryInvocation(libraryName, operatorName, expression); - return invocation != null ? invocation.getExpression() : null; - } - - public Invocation resolveBinaryInvocation( - String libraryName, - String operatorName, - BinaryExpression expression, - boolean mustResolve, - boolean allowPromotionAndDemotion) { - return resolveInvocation( - libraryName, - operatorName, - new BinaryExpressionInvocation(expression), - mustResolve, - allowPromotionAndDemotion, - false); - } - - public Expression resolveBinaryCall( - String libraryName, - String operatorName, - BinaryExpression expression, - boolean mustResolve, - boolean allowPromotionAndDemotion) { - Invocation invocation = - resolveBinaryInvocation(libraryName, operatorName, expression, mustResolve, allowPromotionAndDemotion); - return invocation != null ? invocation.getExpression() : null; - } - - public Expression resolveTernaryCall(String libraryName, String operatorName, TernaryExpression expression) { - return resolveCall(libraryName, operatorName, new TernaryExpressionInvocation(expression), false, false); - } - - public Expression resolveNaryCall(String libraryName, String operatorName, NaryExpression expression) { - return resolveCall(libraryName, operatorName, new NaryExpressionInvocation(expression), false, false); - } - - public Expression resolveAggregateCall(String libraryName, String operatorName, AggregateExpression expression) { - return resolveCall(libraryName, operatorName, new AggregateExpressionInvocation(expression), false, false); - } - - private class BinaryWrapper { - public Expression left; - public Expression right; - - public BinaryWrapper(Expression left, Expression right) { - this.left = left; - this.right = right; - } - } - - private BinaryWrapper normalizeListTypes(Expression left, Expression right) { - // for union of lists - // collect list of types in either side - // cast both operands to a choice type with all types - - // for intersect of lists - // collect list of types in both sides - // cast both operands to a choice type with all types - // TODO: cast the result to a choice type with only types in both sides - - // for difference of lists - // collect list of types in both sides - // cast both operands to a choice type with all types - // TODO: cast the result to the initial type of the left - - if (left.getResultType() instanceof ListType && right.getResultType() instanceof ListType) { - ListType leftListType = (ListType) left.getResultType(); - ListType rightListType = (ListType) right.getResultType(); - - if (!(leftListType.isSuperTypeOf(rightListType) || rightListType.isSuperTypeOf(leftListType)) - && !(leftListType.isCompatibleWith(rightListType) - || rightListType.isCompatibleWith(leftListType))) { - Set elementTypes = new HashSet(); - if (leftListType.getElementType() instanceof ChoiceType) { - for (DataType choice : ((ChoiceType) leftListType.getElementType()).getTypes()) { - elementTypes.add(choice); - } - } else { - elementTypes.add(leftListType.getElementType()); - } - - if (rightListType.getElementType() instanceof ChoiceType) { - for (DataType choice : ((ChoiceType) rightListType.getElementType()).getTypes()) { - elementTypes.add(choice); - } - } else { - elementTypes.add(rightListType.getElementType()); - } - - if (elementTypes.size() > 1) { - ListType targetType = new ListType(new ChoiceType(elementTypes)); - left = of.createAs().withOperand(left).withAsTypeSpecifier(dataTypeToTypeSpecifier(targetType)); - left.setResultType(targetType); - - right = of.createAs().withOperand(right).withAsTypeSpecifier(dataTypeToTypeSpecifier(targetType)); - right.setResultType(targetType); - } - } - } - - return new BinaryWrapper(left, right); - } - - public Expression resolveUnion(Expression left, Expression right) { - // Create right-leaning bushy instead of left-deep - if (left instanceof Union) { - Union leftUnion = (Union) left; - Expression leftUnionLeft = leftUnion.getOperand().get(0); - Expression leftUnionRight = leftUnion.getOperand().get(1); - if (leftUnionLeft instanceof Union && !(leftUnionRight instanceof Union)) { - left = leftUnionLeft; - right = resolveUnion(leftUnionRight, right); - } - } - - // TODO: Take advantage of nary unions - BinaryWrapper wrapper = normalizeListTypes(left, right); - Union union = of.createUnion().withOperand(wrapper.left, wrapper.right); - resolveNaryCall("System", "Union", union); - return union; - } - - public Expression resolveIntersect(Expression left, Expression right) { - // Create right-leaning bushy instead of left-deep - if (left instanceof Intersect) { - Intersect leftIntersect = (Intersect) left; - Expression leftIntersectLeft = leftIntersect.getOperand().get(0); - Expression leftIntersectRight = leftIntersect.getOperand().get(1); - if (leftIntersectLeft instanceof Intersect && !(leftIntersectRight instanceof Intersect)) { - left = leftIntersectLeft; - right = resolveIntersect(leftIntersectRight, right); - } - } - - // TODO: Take advantage of nary intersect - BinaryWrapper wrapper = normalizeListTypes(left, right); - Intersect intersect = of.createIntersect().withOperand(wrapper.left, wrapper.right); - resolveNaryCall("System", "Intersect", intersect); - return intersect; - } - - public Expression resolveExcept(Expression left, Expression right) { - BinaryWrapper wrapper = normalizeListTypes(left, right); - Except except = of.createExcept().withOperand(wrapper.left, wrapper.right); - resolveNaryCall("System", "Except", except); - return except; - } - - public Expression resolveIn(Expression left, Expression right) { - if (right instanceof ValueSetRef - || (isCompatibleWith("1.5") - && right.getResultType().isCompatibleWith(resolveTypeName("System", "ValueSet")) - && !right.getResultType().equals(resolveTypeName("System", "Any")))) { - if (left.getResultType() instanceof ListType) { - AnyInValueSet anyIn = of.createAnyInValueSet() - .withCodes(left) - .withValueset(right instanceof ValueSetRef ? (ValueSetRef) right : null) - .withValuesetExpression(right instanceof ValueSetRef ? null : right); - - resolveCall("System", "AnyInValueSet", new AnyInValueSetInvocation(anyIn)); - return anyIn; - } - - InValueSet in = of.createInValueSet() - .withCode(left) - .withValueset(right instanceof ValueSetRef ? (ValueSetRef) right : null) - .withValuesetExpression(right instanceof ValueSetRef ? null : right); - resolveCall("System", "InValueSet", new InValueSetInvocation(in)); - return in; - } - - if (right instanceof CodeSystemRef - || (isCompatibleWith("1.5") - && right.getResultType().isCompatibleWith(resolveTypeName("System", "CodeSystem")) - && !right.getResultType().equals(resolveTypeName("System", "Any")))) { - if (left.getResultType() instanceof ListType) { - AnyInCodeSystem anyIn = of.createAnyInCodeSystem() - .withCodes(left) - .withCodesystem(right instanceof CodeSystemRef ? (CodeSystemRef) right : null) - .withCodesystemExpression(right instanceof CodeSystemRef ? null : right); - resolveCall("System", "AnyInCodeSystem", new AnyInCodeSystemInvocation(anyIn)); - return anyIn; - } - - InCodeSystem in = of.createInCodeSystem() - .withCode(left) - .withCodesystem(right instanceof CodeSystemRef ? (CodeSystemRef) right : null) - .withCodesystemExpression(right instanceof CodeSystemRef ? null : right); - resolveCall("System", "InCodeSystem", new InCodeSystemInvocation(in)); - return in; - } - - In in = of.createIn().withOperand(left, right); - resolveBinaryCall("System", "In", in); - return in; - } - - public Expression resolveContains(Expression left, Expression right) { - // TODO: Add terminology overloads - Contains contains = of.createContains().withOperand(left, right); - resolveBinaryCall("System", "Contains", contains); - return contains; - } - - public Expression resolveIn(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - Invocation result = resolveInInvocation(left, right, dateTimePrecision); - return result != null ? result.getExpression() : null; - } - - public Invocation resolveInInvocation(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - In in = of.createIn().withOperand(left, right).withPrecision(dateTimePrecision); - return resolveBinaryInvocation("System", "In", in); - } - - public Expression resolveProperIn(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - Invocation result = resolveProperInInvocation(left, right, dateTimePrecision); - return result != null ? result.getExpression() : null; - } - - public Invocation resolveProperInInvocation( - Expression left, Expression right, DateTimePrecision dateTimePrecision) { - ProperIn properIn = of.createProperIn().withOperand(left, right).withPrecision(dateTimePrecision); - return resolveBinaryInvocation("System", "ProperIn", properIn); - } - - public Expression resolveContains(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - Invocation result = resolveContainsInvocation(left, right, dateTimePrecision); - return result != null ? result.getExpression() : null; - } - - public Invocation resolveContainsInvocation( - Expression left, Expression right, DateTimePrecision dateTimePrecision) { - Contains contains = of.createContains().withOperand(left, right).withPrecision(dateTimePrecision); - return resolveBinaryInvocation("System", "Contains", contains); - } - - public Expression resolveProperContains(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - Invocation result = resolveProperContainsInvocation(left, right, dateTimePrecision); - return result != null ? result.getExpression() : null; - } - - public Invocation resolveProperContainsInvocation( - Expression left, Expression right, DateTimePrecision dateTimePrecision) { - ProperContains properContains = - of.createProperContains().withOperand(left, right).withPrecision(dateTimePrecision); - return resolveBinaryInvocation("System", "ProperContains", properContains); - } - - private int getTypeScore(OperatorResolution resolution) { - int typeScore = ConversionMap.ConversionScore.ExactMatch.score(); - for (DataType operand : resolution.getOperator().getSignature().getOperandTypes()) { - typeScore += ConversionMap.getTypePrecedenceScore(operand); - } - - return typeScore; - } - - private Expression lowestScoringInvocation(Invocation primary, Invocation secondary) { - if (primary != null) { - if (secondary != null) { - if (secondary.getResolution().getScore() - < primary.getResolution().getScore()) { - return secondary.getExpression(); - } else if (primary.getResolution().getScore() - < secondary.getResolution().getScore()) { - return primary.getExpression(); - } - if (primary.getResolution().getScore() - == secondary.getResolution().getScore()) { - int primaryTypeScore = getTypeScore(primary.getResolution()); - int secondaryTypeScore = getTypeScore(secondary.getResolution()); - - if (secondaryTypeScore < primaryTypeScore) { - return secondary.getExpression(); - } else if (primaryTypeScore < secondaryTypeScore) { - return primary.getExpression(); - } else { - // ERROR: - StringBuilder message = new StringBuilder("Call to operator ") - .append(primary.getResolution().getOperator().getName()) - .append("/") - .append(secondary.getResolution().getOperator().getName()) - .append(" is ambiguous with: ") - .append("\n - ") - .append(primary.getResolution().getOperator().getName()) - .append(primary.getResolution().getOperator().getSignature()) - .append("\n - ") - .append(secondary.getResolution().getOperator().getName()) - .append(secondary.getResolution().getOperator().getSignature()); - throw new IllegalArgumentException(message.toString()); - } - } - } - - return primary.getExpression(); - } - - if (secondary != null) { - return secondary.getExpression(); - } - - return null; - } - - public Expression resolveIncludes(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - Includes includes = of.createIncludes().withOperand(left, right).withPrecision(dateTimePrecision); - Invocation includesInvocation = resolveBinaryInvocation("System", "Includes", includes, false, false); - - Contains contains = of.createContains().withOperand(left, right).withPrecision(dateTimePrecision); - Invocation containsInvocation = resolveBinaryInvocation("System", "Contains", contains, false, false); - - Expression result = lowestScoringInvocation(includesInvocation, containsInvocation); - if (result != null) { - return result; - } - - // Neither operator resolved, so force a resolve to throw - return resolveBinaryCall("System", "Includes", includes); - } - - public Expression resolveProperIncludes(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - ProperIncludes properIncludes = - of.createProperIncludes().withOperand(left, right).withPrecision(dateTimePrecision); - Invocation properIncludesInvocation = - resolveBinaryInvocation("System", "ProperIncludes", properIncludes, false, false); - - ProperContains properContains = - of.createProperContains().withOperand(left, right).withPrecision(dateTimePrecision); - Invocation properContainsInvocation = - resolveBinaryInvocation("System", "ProperContains", properContains, false, false); - - Expression result = lowestScoringInvocation(properIncludesInvocation, properContainsInvocation); - if (result != null) { - return result; - } - - // Neither operator resolved, so force a resolve to throw - return resolveBinaryCall("System", "ProperIncludes", properIncludes); - } - - public Expression resolveIncludedIn(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - IncludedIn includedIn = of.createIncludedIn().withOperand(left, right).withPrecision(dateTimePrecision); - Invocation includedInInvocation = resolveBinaryInvocation("System", "IncludedIn", includedIn, false, false); - - In in = of.createIn().withOperand(left, right).withPrecision(dateTimePrecision); - Invocation inInvocation = resolveBinaryInvocation("System", "In", in, false, false); - - Expression result = lowestScoringInvocation(includedInInvocation, inInvocation); - if (result != null) { - return result; - } - - // Neither operator resolved, so force a resolve to throw - return resolveBinaryCall("System", "IncludedIn", includedIn); - } - - public Expression resolveProperIncludedIn(Expression left, Expression right, DateTimePrecision dateTimePrecision) { - ProperIncludedIn properIncludedIn = - of.createProperIncludedIn().withOperand(left, right).withPrecision(dateTimePrecision); - Invocation properIncludedInInvocation = - resolveBinaryInvocation("System", "ProperIncludedIn", properIncludedIn, false, false); - - ProperIn properIn = of.createProperIn().withOperand(left, right).withPrecision(dateTimePrecision); - Invocation properInInvocation = resolveBinaryInvocation("System", "ProperIn", properIn, false, false); - - Expression result = lowestScoringInvocation(properIncludedInInvocation, properInInvocation); - if (result != null) { - return result; - } - - // Neither operator resolved, so force a resolve to throw - return resolveBinaryCall("System", "ProperIncludedIn", properIncludedIn); - } - - public Expression resolveCall(String libraryName, String operatorName, Invocation invocation) { - return resolveCall(libraryName, operatorName, invocation, true, false, false); - } - - public Expression resolveCall( - String libraryName, - String operatorName, - Invocation invocation, - boolean allowPromotionAndDemotion, - boolean allowFluent) { - return resolveCall(libraryName, operatorName, invocation, true, allowPromotionAndDemotion, allowFluent); - } - - public Expression resolveCall( - String libraryName, - String operatorName, - Invocation invocation, - boolean mustResolve, - boolean allowPromotionAndDemotion, - boolean allowFluent) { - Invocation result = resolveInvocation( - libraryName, operatorName, invocation, mustResolve, allowPromotionAndDemotion, allowFluent); - return result != null ? result.getExpression() : null; - } - - public Invocation resolveInvocation(String libraryName, String operatorName, Invocation invocation) { - return resolveInvocation(libraryName, operatorName, invocation, true, false, false); - } - - public Invocation resolveInvocation( - String libraryName, - String operatorName, - Invocation invocation, - boolean allowPromotionAndDemotion, - boolean allowFluent) { - return resolveInvocation(libraryName, operatorName, invocation, true, allowPromotionAndDemotion, allowFluent); - } - - public CallContext buildCallContext( - String libraryName, - String operatorName, - Iterable operands, - boolean mustResolve, - boolean allowPromotionAndDemotion, - boolean allowFluent) { - List dataTypes = new ArrayList<>(); - for (Expression operand : operands) { - if (operand == null || operand.getResultType() == null) { - throw new IllegalArgumentException(String.format( - "Could not determine signature for invocation of operator %s%s.", - libraryName == null ? "" : libraryName + ".", operatorName)); - } - dataTypes.add(operand.getResultType()); - } - - return new CallContext( - libraryName, - operatorName, - allowPromotionAndDemotion, - allowFluent, - mustResolve, - dataTypes.toArray(new DataType[dataTypes.size()])); - } - - public Invocation resolveInvocation( - String libraryName, - String operatorName, - Invocation invocation, - boolean mustResolve, - boolean allowPromotionAndDemotion, - boolean allowFluent) { - Iterable operands = invocation.getOperands(); - CallContext callContext = buildCallContext( - libraryName, operatorName, operands, mustResolve, allowPromotionAndDemotion, allowFluent); - OperatorResolution resolution = resolveCall(callContext); - if (resolution == null && !mustResolve) { - return null; - } - - checkOperator(callContext, resolution); - List convertedOperands = new ArrayList<>(); - Iterator operandIterator = operands.iterator(); - Iterator signatureTypes = - resolution.getOperator().getSignature().getOperandTypes().iterator(); - Iterator conversionIterator = - resolution.hasConversions() ? resolution.getConversions().iterator() : null; - while (operandIterator.hasNext()) { - Expression operand = operandIterator.next(); - Conversion conversion = conversionIterator != null ? conversionIterator.next() : null; - if (conversion != null) { - operand = convertExpression(operand, conversion); - } - - DataType signatureType = signatureTypes.next(); - operand = pruneChoices(operand, signatureType); - - convertedOperands.add(operand); - } - - invocation.setOperands(convertedOperands); - - if (options.getSignatureLevel() == SignatureLevel.All - || (options.getSignatureLevel() == SignatureLevel.Differing - && !resolution.getOperator().getSignature().equals(callContext.getSignature())) - || (options.getSignatureLevel() == SignatureLevel.Overloads && resolution.getOperatorHasOverloads())) { - invocation.setSignature(dataTypesToTypeSpecifiers( - resolution.getOperator().getSignature().getOperandTypes())); - } else if (resolution.getOperatorHasOverloads() - && !resolution.getOperator().getLibraryName().equals("System")) { - // NOTE: Because system functions only deal with CQL system-defined types, and there is one and only one - // runtime representation of each system-defined type, there is no possibility of ambiguous overload - // resolution with system functions - // WARNING: - reportWarning( - String.format( - "The function %s.%s has multiple overloads and due to the SignatureLevel setting (%s), " - + "the overload signature is not being included in the output. This may result in ambiguous function resolution " - + "at runtime, consider setting the SignatureLevel to Overloads or All to ensure that the output includes sufficient " - + "information to support correct overload selection at runtime.", - resolution.getOperator().getLibraryName(), - resolution.getOperator().getName(), - options.getSignatureLevel().name()), - invocation.getExpression()); - } - - invocation.setResultType(resolution.getOperator().getResultType()); - if (resolution.getLibraryIdentifier() != null) { - resolution.setLibraryName(resolveIncludeAlias(resolution.getLibraryIdentifier())); - } - invocation.setResolution(resolution); - return invocation; - } - - private Expression pruneChoices(Expression expression, DataType targetType) { - // TODO: In theory, we could collapse expressions that are unnecessarily broad, given the targetType (type - // leading) - // This is a placeholder for where this functionality would be added in the future. - return expression; - } - - public Operator resolveFunctionDefinition(FunctionDef fd) { - - String libraryName = compiledLibrary.getIdentifier().getId(); - String operatorName = fd.getName(); - List dataTypes = new ArrayList<>(); - for (OperandDef operand : fd.getOperand()) { - if (operand == null || operand.getResultType() == null) { - throw new IllegalArgumentException(String.format( - "Could not determine signature for invocation of operator %s%s.", - libraryName == null ? "" : libraryName + ".", operatorName)); - } - dataTypes.add(operand.getResultType()); - } - - CallContext callContext = new CallContext( - compiledLibrary.getIdentifier().getId(), - fd.getName(), - false, - fd.isFluent() != null && fd.isFluent(), - false, - dataTypes.toArray(new DataType[dataTypes.size()])); - // Resolve exact, no conversion map - OperatorResolution resolution = compiledLibrary.resolveCall(callContext, null); - if (resolution != null) { - return resolution.getOperator(); - } - return null; - } - - public OperatorResolution resolveCall(CallContext callContext) { - OperatorResolution result = null; - if (callContext.getLibraryName() == null || callContext.getLibraryName().equals("")) { - result = compiledLibrary.resolveCall(callContext, conversionMap); - if (result == null) { - result = getSystemLibrary().resolveCall(callContext, conversionMap); - if (result == null && callContext.getAllowFluent()) { - // attempt to resolve in each non-system included library, in order of inclusion, first resolution - // wins - for (var lib : libraries.values()) { - if (!lib.equals(getSystemLibrary())) { - result = lib.resolveCall(callContext, conversionMap); - if (result != null) { - break; - } - } - } - } - /* - // Implicit resolution is only allowed for the system library functions. - // Except for fluent functions, so consider whether we should have an ambiguous warnings for fluent function resolution? - for (CompiledLibrary library : libraries.values()) { - OperatorResolution libraryResult = library.resolveCall(callContext, libraryBuilder.getConversionMap()); - if (libraryResult != null) { - if (result != null) { - throw new IllegalArgumentException(String.format("Operator name %s is ambiguous between %s and %s.", - callContext.getOperatorName(), result.getOperator().getName(), libraryResult.getOperator().getName())); - } - - result = libraryResult; - } - } - */ - } - } else { - result = resolveLibrary(callContext.getLibraryName()).resolveCall(callContext, conversionMap); - } - - if (result != null) { - checkAccessLevel( - result.getOperator().getLibraryName(), - result.getOperator().getName(), - result.getOperator().getAccessLevel()); - } - - return result; - } - - private boolean isInterFunctionAccess(String f1, String f2) { - if (StringUtils.isNoneBlank(f1) && StringUtils.isNoneBlank(f2)) { - return !f1.equalsIgnoreCase(f2); - } - return false; - } - - public void checkOperator(CallContext callContext, OperatorResolution resolution) { - if (resolution == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not resolve call to operator %s with signature %s.", - callContext.getOperatorName(), callContext.getSignature())); - } - - if (resolution.getOperator().getFluent() && !callContext.getAllowFluent()) { - throw new IllegalArgumentException(String.format( - "Operator %s with signature %s is a fluent function and can only be invoked with fluent syntax.", - callContext.getOperatorName(), callContext.getSignature())); - } - - if (callContext.getAllowFluent() && !resolution.getOperator().getFluent() && !resolution.getAllowFluent()) { - throw new IllegalArgumentException(String.format( - "Invocation of operator %s with signature %s uses fluent syntax, but the operator is not defined as a fluent function.", - callContext.getOperatorName(), callContext.getSignature())); - } - } - - public void checkAccessLevel(String libraryName, String objectName, AccessModifier accessModifier) { - if (accessModifier == AccessModifier.PRIVATE - && isInterFunctionAccess(this.library.getIdentifier().getId(), libraryName)) { - // ERROR: - throw new CqlSemanticException(String.format( - "Identifier %s in library %s is marked private and cannot be referenced from another library.", - objectName, libraryName)); - } - } - - public Expression resolveFunction(String libraryName, String functionName, Iterable paramList) { - return resolveFunction(libraryName, functionName, paramList, true, false, false) - .getExpression(); - } - - private FunctionRef buildFunctionRef(String libraryName, String functionName, Iterable paramList) { - FunctionRef fun = of.createFunctionRef().withLibraryName(libraryName).withName(functionName); - - for (Expression param : paramList) { - fun.getOperand().add(param); - } - - return fun; - } - - public Invocation resolveFunction( - String libraryName, - String functionName, - Iterable paramList, - boolean mustResolve, - boolean allowPromotionAndDemotion, - boolean allowFluent) { - FunctionRef fun = buildFunctionRef(libraryName, functionName, paramList); - - // Attempt normal resolution, but don't require one - Invocation invocation = new FunctionRefInvocation(fun); - fun = (FunctionRef) resolveCall( - fun.getLibraryName(), fun.getName(), invocation, false, allowPromotionAndDemotion, allowFluent); - if (fun != null) { - if ("System".equals(invocation.getResolution().getOperator().getLibraryName())) { - FunctionRef systemFun = buildFunctionRef( - libraryName, - functionName, - paramList); // Rebuild the fun from the original arguments, otherwise it will resolve with - // conversions in place - Invocation systemFunctionInvocation = systemFunctionResolver.resolveSystemFunction(systemFun); - if (systemFunctionInvocation != null) { - return systemFunctionInvocation; - } - } else { - // If the invocation is to a local function or a function in a non-system library, check literal context - if (mustResolve) { - checkLiteralContext(); - } - } - } - - // If it didn't resolve, there are two possibilities - // 1. It is a special system function resolution that only resolves with the systemFunctionResolver - // 2. It is an error condition that needs to be reported - if (fun == null) { - fun = buildFunctionRef(libraryName, functionName, paramList); - invocation = new FunctionRefInvocation(fun); - - if (!allowFluent) { - // Only attempt to resolve as a system function if this is not a fluent call or it is a required - // resolution - Invocation systemFunction = systemFunctionResolver.resolveSystemFunction(fun); - if (systemFunction != null) { - return systemFunction; - } - - checkLiteralContext(); - } - - fun = (FunctionRef) resolveCall( - fun.getLibraryName(), - fun.getName(), - invocation, - mustResolve, - allowPromotionAndDemotion, - allowFluent); - if (fun == null) { - return null; - } - } - - return invocation; - } - - public void verifyComparable(DataType dataType) { - Expression left = (Expression) of.createLiteral().withResultType(dataType); - Expression right = (Expression) of.createLiteral().withResultType(dataType); - BinaryExpression comparison = of.createLess().withOperand(left, right); - resolveBinaryCall("System", "Less", comparison); - } - - public Expression convertExpression(Expression expression, DataType targetType) { - return convertExpression(expression, targetType, true); - } - - public Expression convertExpression(Expression expression, DataType targetType, boolean implicit) { - Conversion conversion = findConversion(expression.getResultType(), targetType, implicit, false); - if (conversion != null) { - return convertExpression(expression, conversion); - } - - DataTypes.verifyType(expression.getResultType(), targetType); - return expression; - } - - private Expression convertListExpression(Expression expression, Conversion conversion) { - ListType fromType = (ListType) conversion.getFromType(); - ListType toType = (ListType) conversion.getToType(); - - Query query = (Query) of.createQuery() - .withSource((AliasedQuerySource) of.createAliasedQuerySource() - .withAlias("X") - .withExpression(expression) - .withResultType(fromType)) - .withReturn((ReturnClause) of.createReturnClause() - .withDistinct(false) - .withExpression(convertExpression( - (AliasRef) of.createAliasRef().withName("X").withResultType(fromType.getElementType()), - conversion.getConversion())) - .withResultType(toType)) - .withResultType(toType); - return query; - } - - private void reportWarning(String message, Trackable expression) { - TrackBack trackback = expression != null - && expression.getTrackbacks() != null - && !expression.getTrackbacks().isEmpty() - ? expression.getTrackbacks().get(0) - : null; - CqlSemanticException warning = - new CqlSemanticException(message, CqlCompilerException.ErrorSeverity.Warning, trackback); - recordParsingException(warning); - } - - private Expression demoteListExpression(Expression expression, Conversion conversion) { - ListType fromType = (ListType) conversion.getFromType(); - DataType toType = conversion.getToType(); - - SingletonFrom singletonFrom = of.createSingletonFrom().withOperand(expression); - singletonFrom.setResultType(fromType.getElementType()); - resolveUnaryCall("System", "SingletonFrom", singletonFrom); - // WARNING: - reportWarning("List-valued expression was demoted to a singleton.", expression); - - if (conversion.getConversion() != null) { - return convertExpression(singletonFrom, conversion.getConversion()); - } else { - return singletonFrom; - } - } - - private Expression promoteListExpression(Expression expression, Conversion conversion) { - if (conversion.getConversion() != null) { - expression = convertExpression(expression, conversion.getConversion()); - } - - if (expression.getResultType().equals(resolveTypeName("System", "Boolean"))) { - // WARNING: - reportWarning("Boolean-valued expression was promoted to a list.", expression); - } - - return resolveToList(expression); - } - - public Expression resolveToList(Expression expression) { - // Use a ToList operator here to avoid duplicate evaluation of the operand. - ToList toList = of.createToList().withOperand(expression); - toList.setResultType(new ListType(expression.getResultType())); - return toList; - } - - private Expression demoteIntervalExpression(Expression expression, Conversion conversion) { - IntervalType fromType = (IntervalType) conversion.getFromType(); - DataType toType = conversion.getToType(); - - PointFrom pointFrom = of.createPointFrom().withOperand(expression); - pointFrom.setResultType(fromType.getPointType()); - resolveUnaryCall("System", "PointFrom", pointFrom); - // WARNING: - reportWarning("Interval-valued expression was demoted to a point.", expression); - - if (conversion.getConversion() != null) { - return convertExpression(pointFrom, conversion.getConversion()); - } else { - return pointFrom; - } - } - - private Expression promoteIntervalExpression(Expression expression, Conversion conversion) { - if (conversion.getConversion() != null) { - expression = convertExpression(expression, conversion.getConversion()); - } - - return resolveToInterval(expression); - } - - // When promoting a point to an interval, if the point is null, the result is null, rather than constructing an - // interval - // with null boundaries - public Expression resolveToInterval(Expression expression) { - If condition = of.createIf(); - condition.setCondition(buildIsNull(expression)); - condition.setThen(buildNull(new IntervalType(expression.getResultType()))); - Interval toInterval = of.createInterval() - .withLow(expression) - .withHigh(expression) - .withLowClosed(true) - .withHighClosed(true); - toInterval.setResultType(new IntervalType(expression.getResultType())); - condition.setElse(toInterval); - condition.setResultType(resolveTypeName("System", "Boolean")); - return condition; - } - - private Expression convertIntervalExpression(Expression expression, Conversion conversion) { - IntervalType fromType = (IntervalType) conversion.getFromType(); - IntervalType toType = (IntervalType) conversion.getToType(); - return (Interval) of.createInterval() - .withLow(convertExpression( - (Property) of.createProperty() - .withSource(expression) - .withPath("low") - .withResultType(fromType.getPointType()), - conversion.getConversion())) - .withLowClosedExpression((Property) of.createProperty() - .withSource(expression) - .withPath("lowClosed") - .withResultType(resolveTypeName("System", "Boolean"))) - .withHigh(convertExpression( - (Property) of.createProperty() - .withSource(expression) - .withPath("high") - .withResultType(fromType.getPointType()), - conversion.getConversion())) - .withHighClosedExpression((Property) of.createProperty() - .withSource(expression) - .withPath("highClosed") - .withResultType(resolveTypeName("System", "Boolean"))) - .withResultType(toType); - } - - public As buildAs(Expression expression, DataType asType) { - As result = (As) of.createAs().withOperand(expression).withResultType(asType); - if (result.getResultType() instanceof NamedType) { - result.setAsType(dataTypeToQName(result.getResultType())); - } else { - result.setAsTypeSpecifier(dataTypeToTypeSpecifier(result.getResultType())); - } - - return result; - } - - public Is buildIs(Expression expression, DataType isType) { - Is result = (Is) of.createIs().withOperand(expression).withResultType(resolveTypeName("System", "Boolean")); - if (isType instanceof NamedType) { - result.setIsType(dataTypeToQName(isType)); - } else { - result.setIsTypeSpecifier(dataTypeToTypeSpecifier(isType)); - } - - return result; - } - - public Null buildNull(DataType nullType) { - Null result = (Null) of.createNull().withResultType(nullType); - if (nullType instanceof NamedType) { - result.setResultTypeName(dataTypeToQName(nullType)); - } else { - result.setResultTypeSpecifier(dataTypeToTypeSpecifier(nullType)); - } - return result; - } - - public IsNull buildIsNull(Expression expression) { - IsNull isNull = of.createIsNull().withOperand(expression); - isNull.setResultType(resolveTypeName("System", "Boolean")); - return isNull; - } - - public Not buildIsNotNull(Expression expression) { - IsNull isNull = buildIsNull(expression); - Not not = of.createNot().withOperand(isNull); - not.setResultType(resolveTypeName("System", "Boolean")); - return not; - } - - public MinValue buildMinimum(DataType dataType) { - MinValue minimum = of.createMinValue(); - minimum.setValueType(dataTypeToQName(dataType)); - minimum.setResultType(dataType); - return minimum; - } - - public MaxValue buildMaximum(DataType dataType) { - MaxValue maximum = of.createMaxValue(); - maximum.setValueType(dataTypeToQName(dataType)); - maximum.setResultType(dataType); - return maximum; - } - - public Expression buildPredecessor(Expression source) { - Predecessor result = of.createPredecessor().withOperand(source); - resolveUnaryCall("System", "Predecessor", result); - return result; - } - - public Expression buildSuccessor(Expression source) { - Successor result = of.createSuccessor().withOperand(source); - resolveUnaryCall("System", "Successor", result); - return result; - } - - public Expression convertExpression(Expression expression, Conversion conversion) { - if (conversion.isCast() - && (conversion.getFromType().isSuperTypeOf(conversion.getToType()) - || conversion.getFromType().isCompatibleWith(conversion.getToType()))) { - if (conversion.getFromType() instanceof ChoiceType && conversion.getToType() instanceof ChoiceType) { - if (((ChoiceType) conversion.getFromType()).isSubSetOf((ChoiceType) conversion.getToType())) { - // conversion between compatible choice types requires no cast (i.e. choice can be - // safely passed to choice - return expression; - } - // Otherwise, the choice is narrowing and a run-time As is required (to use only the expected target - // types) - } - As castedOperand = buildAs(expression, conversion.getToType()); - return collapseTypeCase(castedOperand); - } else if (conversion.isCast() - && conversion.getConversion() != null - && (conversion - .getFromType() - .isSuperTypeOf(conversion.getConversion().getFromType()) - || conversion - .getFromType() - .isCompatibleWith(conversion.getConversion().getFromType()))) { - As castedOperand = buildAs(expression, conversion.getConversion().getFromType()); - - Expression result = convertExpression(castedOperand, conversion.getConversion()); - - if (conversion.hasAlternativeConversions()) { - Case caseResult = of.createCase(); - caseResult.setResultType(result.getResultType()); - caseResult.withCaseItem(of.createCaseItem() - .withWhen(buildIs(expression, conversion.getConversion().getFromType())) - .withThen(result)); - - for (Conversion alternative : conversion.getAlternativeConversions()) { - caseResult.withCaseItem(of.createCaseItem() - .withWhen(buildIs(expression, alternative.getFromType())) - .withThen(convertExpression(buildAs(expression, alternative.getFromType()), alternative))); - } - - caseResult.withElse(buildNull(result.getResultType())); - result = caseResult; - } - - return result; - } else if (conversion.isListConversion()) { - return convertListExpression(expression, conversion); - } else if (conversion.isListDemotion()) { - return demoteListExpression(expression, conversion); - } else if (conversion.isListPromotion()) { - return promoteListExpression(expression, conversion); - } else if (conversion.isIntervalConversion()) { - return convertIntervalExpression(expression, conversion); - } else if (conversion.isIntervalDemotion()) { - return demoteIntervalExpression(expression, conversion); - } else if (conversion.isIntervalPromotion()) { - return promoteIntervalExpression(expression, conversion); - } else if (conversion.getOperator() != null) { - FunctionRef functionRef = (FunctionRef) of.createFunctionRef() - .withLibraryName(conversion.getOperator().getLibraryName()) - .withName(conversion.getOperator().getName()) - .withOperand(expression); - - Invocation systemFunctionInvocation = systemFunctionResolver.resolveSystemFunction(functionRef); - if (systemFunctionInvocation != null) { - return systemFunctionInvocation.getExpression(); - } - - resolveCall( - functionRef.getLibraryName(), - functionRef.getName(), - new FunctionRefInvocation(functionRef), - false, - false); - - return functionRef; - } else { - if (conversion.getToType().equals(resolveTypeName("System", "Boolean"))) { - return (Expression) of.createToBoolean().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "Integer"))) { - return (Expression) of.createToInteger().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "Long"))) { - return (Expression) of.createToLong().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "Decimal"))) { - return (Expression) of.createToDecimal().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "String"))) { - return (Expression) of.createToString().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "Date"))) { - return (Expression) of.createToDate().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "DateTime"))) { - return (Expression) - of.createToDateTime().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "Time"))) { - return (Expression) of.createToTime().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "Quantity"))) { - return (Expression) - of.createToQuantity().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "Ratio"))) { - return (Expression) of.createToRatio().withOperand(expression).withResultType(conversion.getToType()); - } else if (conversion.getToType().equals(resolveTypeName("System", "Concept"))) { - return (Expression) of.createToConcept().withOperand(expression).withResultType(conversion.getToType()); - } else { - Convert convertedOperand = - (Convert) of.createConvert().withOperand(expression).withResultType(conversion.getToType()); - - if (convertedOperand.getResultType() instanceof NamedType) { - convertedOperand.setToType(dataTypeToQName(convertedOperand.getResultType())); - } else { - convertedOperand.setToTypeSpecifier(dataTypeToTypeSpecifier(convertedOperand.getResultType())); - } - - return convertedOperand; - } - } - } - - /** - * If the operand to an As is a "type case", meaning a case expression whose only cases have the form: - * when X is T then X as T - * If one of the type cases is the same type as the As, the operand of the As can be set to the operand - * of the type case with the same type, optimizing the case as effectively a no-op - * @param as - * @return - */ - private Expression collapseTypeCase(As as) { - if (as.getOperand() instanceof Case) { - Case c = (Case) as.getOperand(); - if (isTypeCase(c)) { - for (CaseItem ci : c.getCaseItem()) { - if (DataTypes.equal(as.getResultType(), ci.getThen().getResultType())) { - return ci.getThen(); - } - } - } - } - - return as; - } - - private boolean isTypeCase(Case c) { - if (c.getComparand() != null) { - return false; - } - for (CaseItem ci : c.getCaseItem()) { - if (!(ci.getWhen() instanceof Is)) { - return false; - } - if (ci.getThen().getResultType() == null) { - return false; - } - } - if (!(c.getElse() instanceof Null)) { - return false; - } - if (!(c.getResultType() instanceof ChoiceType)) { - return false; - } - return true; - } - - public void verifyType(DataType actualType, DataType expectedType) { - if (expectedType.isSuperTypeOf(actualType) || actualType.isCompatibleWith(expectedType)) { - return; - } - - Conversion conversion = findConversion(actualType, expectedType, true, false); - if (conversion != null) { - return; - } - - DataTypes.verifyType(actualType, expectedType); - } - - public DataType findCompatibleType(DataType first, DataType second) { - if (first == null || second == null) { - return null; - } - - if (first.equals(DataType.ANY)) { - return second; - } - - if (second.equals(DataType.ANY)) { - return first; - } - - if (first.isSuperTypeOf(second) || second.isCompatibleWith(first)) { - return first; - } - - if (second.isSuperTypeOf(first) || first.isCompatibleWith(second)) { - return second; - } - - // If either side is a choice type, don't allow conversions because they will incorrectly eliminate choices - // based on convertibility - if (!(first instanceof ChoiceType || second instanceof ChoiceType)) { - Conversion conversion = findConversion(second, first, true, false); - if (conversion != null) { - return first; - } - - conversion = findConversion(first, second, true, false); - if (conversion != null) { - return second; - } - } - - return null; - } - - public DataType ensureCompatibleTypes(DataType first, DataType second) { - DataType compatibleType = findCompatibleType(first, second); - if (compatibleType != null) { - return compatibleType; - } - - if (!second.isSubTypeOf(first)) { - return new ChoiceType(Arrays.asList(first, second)); - } - - // The above construction of a choice type guarantees this will never be hit - DataTypes.verifyType(second, first); - return first; - } - - public Expression ensureCompatible(Expression expression, DataType targetType) { - if (targetType == null) { - return of.createNull(); - } - - if (!targetType.isSuperTypeOf(expression.getResultType())) { - return convertExpression(expression, targetType, true); - } - - return expression; - } - - public Expression enforceCompatible(Expression expression, DataType targetType) { - if (targetType == null) { - return of.createNull(); - } - - if (!targetType.isSuperTypeOf(expression.getResultType())) { - return convertExpression(expression, targetType, false); - } - - return expression; - } - - public Literal createLiteral(String val, String type) { - DataType resultType = resolveTypeName("System", type); - Literal result = of.createLiteral().withValue(val).withValueType(dataTypeToQName(resultType)); - result.setResultType(resultType); - return result; - } - - public Literal createLiteral(String string) { - return createLiteral(String.valueOf(string), "String"); - } - - public Literal createLiteral(Boolean bool) { - return createLiteral(String.valueOf(bool), "Boolean"); - } - - public Literal createLiteral(Integer integer) { - return createLiteral(String.valueOf(integer), "Integer"); - } - - public Literal createLiteral(Double value) { - return createLiteral(String.valueOf(value), "Decimal"); - } - - public Literal createNumberLiteral(String value) { - DataType resultType = resolveTypeName("System", value.contains(".") ? "Decimal" : "Integer"); - Literal result = of.createLiteral().withValue(value).withValueType(dataTypeToQName(resultType)); - result.setResultType(resultType); - return result; - } - - public Literal createLongNumberLiteral(String value) { - DataType resultType = resolveTypeName("System", "Long"); - Literal result = of.createLiteral().withValue(value).withValueType(dataTypeToQName(resultType)); - result.setResultType(resultType); - return result; - } - - public void validateUnit(String unit) { - switch (unit) { - case "year": - case "years": - case "month": - case "months": - case "week": - case "weeks": - case "day": - case "days": - case "hour": - case "hours": - case "minute": - case "minutes": - case "second": - case "seconds": - case "millisecond": - case "milliseconds": - // CQL-defined temporal precisions are valid units - break; - - default: - validateUcumUnit(unit); - break; - } - } - - public String ensureUcumUnit(String unit) { - switch (unit) { - case "year": - case "years": - return "a"; - case "month": - case "months": - return "mo"; - case "week": - case "weeks": - return "wk"; - case "day": - case "days": - return "d"; - case "hour": - case "hours": - return "h"; - case "minute": - case "minutes": - return "min"; - case "second": - case "seconds": - return "s"; - case "millisecond": - case "milliseconds": - return "ms"; - - default: - validateUcumUnit(unit); - break; - } - - return unit; - } - - private void validateUcumUnit(String unit) { - if (libraryManager.getUcumService() != null) { - var ucumService = libraryManager.getUcumService(); - String message = ucumService.validate(unit); - if (message != null) { - // ERROR: - throw new IllegalArgumentException(message); - } - } - } - - public Quantity createQuantity(BigDecimal value, String unit) { - validateUnit(unit); - Quantity result = of.createQuantity().withValue(value).withUnit(unit); - DataType resultType = resolveTypeName("System", "Quantity"); - result.setResultType(resultType); - return result; - } - - public Ratio createRatio(Quantity numerator, Quantity denominator) { - Ratio result = of.createRatio().withNumerator(numerator).withDenominator(denominator); - DataType resultType = resolveTypeName("System", "Ratio"); - result.setResultType(resultType); - return result; - } - - public Interval createInterval(Expression low, boolean lowClosed, Expression high, boolean highClosed) { - Interval result = of.createInterval() - .withLow(low) - .withLowClosed(lowClosed) - .withHigh(high) - .withHighClosed(highClosed); - - DataType pointType = ensureCompatibleTypes( - result.getLow().getResultType(), result.getHigh().getResultType()); - result.setResultType(new IntervalType(pointType)); - - result.setLow(ensureCompatible(result.getLow(), pointType)); - result.setHigh(ensureCompatible(result.getHigh(), pointType)); - - return result; - } - - public QName dataTypeToQName(DataType type) { - return typeBuilder.dataTypeToQName(type); - } - - public Iterable dataTypesToTypeSpecifiers(Iterable types) { - return typeBuilder.dataTypesToTypeSpecifiers(types); - } - - public TypeSpecifier dataTypeToTypeSpecifier(DataType type) { - return typeBuilder.dataTypeToTypeSpecifier(type); - } - - public DataType resolvePath(DataType sourceType, String path) { - // TODO: This is using a naive implementation for now... needs full path support (but not full FluentPath - // support...) - String[] identifiers = path.split("\\."); - for (int i = 0; i < identifiers.length; i++) { - PropertyResolution resolution = resolveProperty(sourceType, identifiers[i]); - sourceType = resolution.getType(); - // Actually, this doesn't matter for this call, we're just resolving the type... - // if (!resolution.getTargetMap().equals(identifiers[i])) { - // throw new IllegalArgumentException(String.format("Identifier %s references an element with a target - // mapping defined and cannot be resolved as part of a path", identifiers[i])); - // } - } - - return sourceType; - } - - public PropertyResolution resolveProperty(DataType sourceType, String identifier) { - return resolveProperty(sourceType, identifier, true); - } - - // TODO: Support case-insensitive models - public PropertyResolution resolveProperty(DataType sourceType, String identifier, boolean mustResolve) { - DataType currentType = sourceType; - while (currentType != null) { - if (currentType instanceof ClassType) { - ClassType classType = (ClassType) currentType; - if (identifier.startsWith("?") && isCompatibleWith("1.5")) { - String searchPath = identifier.substring(1); - for (SearchType s : classType.getSearches()) { - if (s.getName().equals(searchPath)) { - return new PropertyResolution(s); - } - } - } else { - for (ClassTypeElement e : classType.getElements()) { - if (e.getName().equals(identifier)) { - if (e.isProhibited()) { - throw new IllegalArgumentException(String.format( - "Element %s cannot be referenced because it is marked prohibited in type %s.", - e.getName(), ((ClassType) currentType).getName())); - } - - return new PropertyResolution(e); - } - } - } - } else if (currentType instanceof TupleType) { - TupleType tupleType = (TupleType) currentType; - for (TupleTypeElement e : tupleType.getElements()) { - if (e.getName().equals(identifier)) { - return new PropertyResolution(e); - } - } - } else if (currentType instanceof IntervalType) { - IntervalType intervalType = (IntervalType) currentType; - switch (identifier) { - case "low": - case "high": - return new PropertyResolution(intervalType.getPointType(), identifier); - case "lowClosed": - case "highClosed": - return new PropertyResolution(resolveTypeName("System", "Boolean"), identifier); - default: - // ERROR: - throw new IllegalArgumentException( - String.format("Invalid interval property name %s.", identifier)); - } - } else if (currentType instanceof ChoiceType) { - ChoiceType choiceType = (ChoiceType) currentType; - // TODO: Issue a warning if the property does not resolve against every type in the choice - - // Resolve the property against each type in the choice - Set resultTypes = new HashSet<>(); - Map resultTargetMaps = new HashMap(); - String name = null; - for (DataType choice : choiceType.getTypes()) { - PropertyResolution resolution = resolveProperty(choice, identifier, false); - if (resolution != null) { - resultTypes.add(resolution.getType()); - if (resolution.getTargetMap() != null) { - if (resultTargetMaps.containsKey(resolution.getType())) { - if (!resultTargetMaps.get(resolution.getType()).equals(resolution.getTargetMap())) { - throw new IllegalArgumentException(String.format( - "Inconsistent target maps %s and %s for choice type %s", - resultTargetMaps.get(resolution.getType()), - resolution.getTargetMap(), - resolution.getType().toString())); - } - } else { - resultTargetMaps.put(resolution.getType(), resolution.getTargetMap()); - } - } - - if (name == null) { - name = resolution.getName(); - } else if (!name.equals(resolution.getName())) { - throw new IllegalArgumentException(String.format( - "Inconsistent property resolution for choice type %s (was %s, is %s)", - choice.toString(), name, resolution.getName())); - } - - if (name == null) { - name = resolution.getName(); - } else if (!name.equals(resolution.getName())) { - throw new IllegalArgumentException(String.format( - "Inconsistent property resolution for choice type %s (was %s, is %s)", - choice.toString(), name, resolution.getName())); - } - } - } - - // The result type is a choice of all the resolved types - if (resultTypes.size() > 1) { - return new PropertyResolution(new ChoiceType(resultTypes), name, resultTargetMaps); - } - - if (resultTypes.size() == 1) { - for (DataType resultType : resultTypes) { - return new PropertyResolution(resultType, name, resultTargetMaps); - } - } - } else if (currentType instanceof ListType && listTraversal) { - // NOTE: FHIRPath path traversal support - // Resolve property as a list of items of property of the element type - ListType listType = (ListType) currentType; - PropertyResolution resolution = resolveProperty(listType.getElementType(), identifier); - return new PropertyResolution(new ListType(resolution.getType()), resolution.getTargetMap()); - } - - if (currentType.getBaseType() != null) { - currentType = currentType.getBaseType(); - } else { - break; - } - } - - if (mustResolve) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Member %s not found for type %s.", identifier, sourceType != null ? sourceType.toLabel() : null)); - } - - return null; - } - - public Expression resolveIdentifier(String identifier, boolean mustResolve) { - // An Identifier will always be: - // 1: The name of an alias - // 2: The name of a query define clause - // 3: The name of an expression - // 4: The name of a parameter - // 5: The name of a valueset - // 6: The name of a codesystem - // 7: The name of a code - // 8: The name of a concept - // 9: The name of a library - // 10: The name of a property on a specific context - // 11: An unresolved identifier error is thrown - - // In a type specifier context, return the identifier as a Literal for resolution as a type by the caller - if (inTypeSpecifierContext()) { - return this.createLiteral(identifier); - } - - // In the sort clause of a plural query, names may be resolved based on the result type of the query - Expression resultElement = resolveQueryResultElement(identifier); - if (resultElement != null) { - return resultElement; - } - - // In the case of a $this alias, names may be resolved as implicit property references - Expression thisElement = resolveQueryThisElement(identifier); - if (thisElement != null) { - return thisElement; - } - - if (identifier.equals("$index")) { - Iteration result = of.createIteration(); - result.setResultType(resolveTypeName("System", "Integer")); - return result; - } - - if (identifier.equals("$total")) { - Total result = of.createTotal(); - result.setResultType(resolveTypeName( - "System", - "Decimal")); // TODO: This isn't right, but we don't set up a query for the Aggregate operator right - // now... - return result; - } - - AliasedQuerySource alias = resolveAlias(identifier); - if (alias != null) { - AliasRef result = of.createAliasRef().withName(identifier); - if (alias.getResultType() instanceof ListType) { - result.setResultType(((ListType) alias.getResultType()).getElementType()); - } else { - result.setResultType(alias.getResultType()); - } - return result; - } - - LetClause let = resolveQueryLet(identifier); - if (let != null) { - QueryLetRef result = of.createQueryLetRef().withName(identifier); - result.setResultType(let.getResultType()); - return result; - } - - OperandRef operandRef = resolveOperandRef(identifier); - if (operandRef != null) { - return operandRef; - } - - final ResolvedIdentifierContext resolvedIdentifierContext = resolve(identifier); - final Optional optElement = resolvedIdentifierContext.getExactMatchElement(); - - if (optElement.isPresent()) { - final Element element = optElement.get(); - if (element instanceof ExpressionDef) { - checkLiteralContext(); - ExpressionRef expressionRef = of.createExpressionRef().withName(((ExpressionDef) element).getName()); - expressionRef.setResultType(getExpressionDefResultType((ExpressionDef) element)); - if (expressionRef.getResultType() == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not validate reference to expression %s because its definition contains errors.", - expressionRef.getName())); - } - return expressionRef; - } - - if (element instanceof ParameterDef) { - checkLiteralContext(); - ParameterRef parameterRef = of.createParameterRef().withName(((ParameterDef) element).getName()); - parameterRef.setResultType(element.getResultType()); - if (parameterRef.getResultType() == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not validate reference to parameter %s because its definition contains errors.", - parameterRef.getName())); - } - return parameterRef; - } - - if (element instanceof ValueSetDef) { - checkLiteralContext(); - ValueSetRef valuesetRef = of.createValueSetRef().withName(((ValueSetDef) element).getName()); - valuesetRef.setResultType(element.getResultType()); - if (valuesetRef.getResultType() == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not validate reference to valueset %s because its definition contains errors.", - valuesetRef.getName())); - } - if (isCompatibleWith("1.5")) { - valuesetRef.setPreserve(true); - } - return valuesetRef; - } - - if (element instanceof CodeSystemDef) { - checkLiteralContext(); - CodeSystemRef codesystemRef = of.createCodeSystemRef().withName(((CodeSystemDef) element).getName()); - codesystemRef.setResultType(element.getResultType()); - if (codesystemRef.getResultType() == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not validate reference to codesystem %s because its definition contains errors.", - codesystemRef.getName())); - } - return codesystemRef; - } - - if (element instanceof CodeDef) { - checkLiteralContext(); - CodeRef codeRef = of.createCodeRef().withName(((CodeDef) element).getName()); - codeRef.setResultType(element.getResultType()); - if (codeRef.getResultType() == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not validate reference to code %s because its definition contains errors.", - codeRef.getName())); - } - return codeRef; - } - - if (element instanceof ConceptDef) { - checkLiteralContext(); - ConceptRef conceptRef = of.createConceptRef().withName(((ConceptDef) element).getName()); - conceptRef.setResultType(element.getResultType()); - if (conceptRef.getResultType() == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not validate reference to concept %s because its definition contains error.", - conceptRef.getName())); - } - return conceptRef; - } - - if (element instanceof IncludeDef) { - checkLiteralContext(); - LibraryRef libraryRef = new LibraryRef(); - libraryRef.setLocalId(of.nextId()); - libraryRef.setLibraryName(((IncludeDef) element).getLocalIdentifier()); - return libraryRef; - } - } - - // If no other resolution occurs, and we are in a specific context, and there is a parameter with the same name - // as the context, - // the identifier may be resolved as an implicit property reference on that context. - ParameterRef parameterRef = resolveImplicitContext(); - if (parameterRef != null) { - PropertyResolution resolution = resolveProperty(parameterRef.getResultType(), identifier, false); - if (resolution != null) { - Expression contextAccessor = - buildProperty(parameterRef, resolution.getName(), resolution.isSearch(), resolution.getType()); - contextAccessor = applyTargetMap(contextAccessor, resolution.getTargetMap()); - return contextAccessor; - } - } - - if (mustResolve) { - // ERROR: - final String exceptionMessage = resolvedIdentifierContext - .warnCaseInsensitiveIfApplicable() - .orElse(String.format("Could not resolve identifier %s in the current library.", identifier)); - - throw new IllegalArgumentException(exceptionMessage); - } - - return null; - } - - private static String lookupElementWarning(Object element) { - // TODO: this list is not exhaustive and may need to be updated - if (element instanceof ExpressionDef) { - return "An expression"; - } else if (element instanceof ParameterDef) { - return "A parameter"; - } else if (element instanceof ValueSetDef) { - return "A valueset"; - } else if (element instanceof CodeSystemDef) { - return "A codesystem"; - } else if (element instanceof CodeDef) { - return "A code"; - } else if (element instanceof ConceptDef) { - return "A concept"; - } else if (element instanceof IncludeDef) { - return "An include"; - } else if (element instanceof AliasedQuerySource) { - return "An alias"; - } else if (element instanceof LetClause) { - return "A let"; - } else if (element instanceof OperandDef) { - return "An operand"; - } else if (element instanceof UsingDef) { - return "A using"; - } else if (element instanceof Literal) { - return "A literal"; - } - // default message if no match is made: - return "An [unknown structure]"; - } - - /** - * An implicit context is one where the context has the same name as a parameter. Implicit contexts are used to - * allow FHIRPath expressions to resolve on the implicit context of the expression - * - * For example, in a Patient context, with a parameter of type Patient, the expression `birthDate` resolves to a property reference. - * @return A reference to the parameter providing the implicit context value - */ - public ParameterRef resolveImplicitContext() { - if (!inLiteralContext() && inSpecificContext()) { - ResolvedIdentifierContext resolvedIdentifierContext = resolve(currentExpressionContext()); - final Optional optParameterDef = - resolvedIdentifierContext.getElementOfType(ParameterDef.class); - if (optParameterDef.isPresent()) { - final ParameterDef contextParameter = optParameterDef.get(); - - checkLiteralContext(); - ParameterRef parameterRef = of.createParameterRef().withName(contextParameter.getName()); - parameterRef.setResultType(contextParameter.getResultType()); - if (parameterRef.getResultType() == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not validate reference to parameter %s because its definition contains errors.", - parameterRef.getName())); - } - return parameterRef; - } - } - - return null; - } - - public Property buildProperty(String scope, String path, boolean isSearch, DataType resultType) { - if (isSearch) { - Search result = of.createSearch().withScope(scope).withPath(path); - result.setResultType(resultType); - return result; - } else { - Property result = of.createProperty().withScope(scope).withPath(path); - result.setResultType(resultType); - return result; - } - } - - public Property buildProperty(Expression source, String path, boolean isSearch, DataType resultType) { - if (isSearch) { - Search result = of.createSearch().withSource(source).withPath(path); - result.setResultType(resultType); - return result; - } else { - Property result = of.createProperty().withSource(source).withPath(path); - result.setResultType(resultType); - return result; - } - } - - private VersionedIdentifier getModelMapping(Expression sourceContext) { - VersionedIdentifier result = null; - if (library.getUsings() != null && library.getUsings().getDef() != null) { - for (UsingDef usingDef : library.getUsings().getDef()) { - Model model = getModel(usingDef); - if (model.getModelInfo().getTargetUrl() != null) { - if (result != null) { - this.reportWarning( - String.format( - "Duplicate mapped model %s:%s%s", - model.getModelInfo().getName(), - model.getModelInfo().getTargetUrl(), - model.getModelInfo().getTargetVersion() != null - ? ("|" + model.getModelInfo().getTargetVersion()) - : ""), - sourceContext); - } - result = of.createVersionedIdentifier() - .withId(model.getModelInfo().getName()) - .withSystem(model.getModelInfo().getTargetUrl()) - .withVersion(model.getModelInfo().getTargetVersion()); - } - } - } - - return result; - } - - private void ensureLibraryIncluded(String libraryName, Expression sourceContext) { - IncludeDef includeDef = compiledLibrary.resolveIncludeRef(libraryName); - if (includeDef == null) { - VersionedIdentifier modelMapping = getModelMapping(sourceContext); - String path = libraryName; - if (this.getNamespaceInfo() != null && modelMapping != null && modelMapping.getSystem() != null) { - path = NamespaceManager.getPath(modelMapping.getSystem(), path); - } - includeDef = of.createIncludeDef().withLocalIdentifier(libraryName).withPath(path); - if (modelMapping != null) { - includeDef.setVersion(modelMapping.getVersion()); - } - compiledLibrary.add(includeDef); - } - } - - private void applyTargetModelMaps() { - if (library.getUsings() != null && library.getUsings().getDef() != null) { - for (UsingDef usingDef : library.getUsings().getDef()) { - Model model = getModel(usingDef); - if (model.getModelInfo().getTargetUrl() != null) { - usingDef.setUri(model.getModelInfo().getTargetUrl()); - usingDef.setVersion(model.getModelInfo().getTargetVersion()); - } - } - } - } - - public Expression applyTargetMap(Expression source, String targetMap) { - if (targetMap == null || targetMap.equals("null")) { - return source; - } - - // TODO: Consider whether the mapping should remove this in the ModelInfo...this is really a FHIR-specific - // hack... - // Remove any "choice" paths... - targetMap = targetMap.replace("[x]", ""); - - // TODO: This only works for simple mappings, nested mappings will require the targetMap.g4 parser - // Supported target mapping syntax: - // %value. - // Resolves as a property accessor with the given source and as the path - // (%value) - // Resolves as a function ref with the given function name and the source as an operand - // :;:... - // Semi-colon delimited list of type names and associated maps - // Resolves as a case with whens for each type, with target mapping applied per the target map for that type - // %parent.[=,=,...]. - // Resolves as a replacement of the property on which it appears - // Replaces the path of the property on which it appears with the given qualified path, which then becomes the - // source of a query with a where clause with criteria built for each comparison in the indexer - // If there is a trailing qualified path, the query is wrapped in a singletonFrom and a property access - // Any other target map results in an exception - - if (targetMap.contains(";")) { - String[] typeCases = targetMap.split(";"); - Case c = of.createCase(); - for (String typeCase : typeCases) { - if (!typeCase.isEmpty()) { - int splitIndex = typeCase.indexOf(':'); - if (splitIndex <= 0) { - throw new IllegalArgumentException( - String.format("Malformed type case in targetMap %s", targetMap)); - } - String typeCaseElement = typeCase.substring(0, splitIndex); - DataType typeCaseType = resolveTypeName(typeCaseElement); - String typeCaseMap = typeCase.substring(splitIndex + 1); - CaseItem ci = of.createCaseItem() - .withWhen(of.createIs() - .withOperand(applyTargetMap(source, typeCaseMap)) - .withIsType(dataTypeToQName(typeCaseType))) - .withThen(applyTargetMap(source, typeCaseMap)); - ci.getThen().setResultType(typeCaseType); - c.getCaseItem().add(ci); - } - } - if (c.getCaseItem().size() == 0) { - return this.buildNull(source.getResultType()); - } else if (c.getCaseItem().size() == 1) { - return c.getCaseItem().get(0).getThen(); - } else { - c.setElse(this.buildNull(source.getResultType())); - c.setResultType(source.getResultType()); - return c; - } - } else if (targetMap.contains("(")) { - int invocationStart = targetMap.indexOf("("); - String qualifiedFunctionName = targetMap.substring(0, invocationStart); - String[] nameParts = qualifiedFunctionName.split("\\."); - String libraryName = null; - String functionName = qualifiedFunctionName; - if (nameParts.length == 2) { - libraryName = nameParts[0]; - functionName = nameParts[1]; - - ensureLibraryIncluded(libraryName, source); - } - - String functionArgument = targetMap.substring(invocationStart + 1, targetMap.lastIndexOf(')')); - Expression argumentSource = - functionArgument.equals("%value") ? source : applyTargetMap(source, functionArgument); - - // NOTE: This is needed to work around the mapping for ToInterval - // FHIRHelpers defines multiple overloads of ToInterval, but the type mapping - // does not have the type of the source data type. - // All the mappings for ToInterval use FHIR.Period, so this is safe to assume - // In addition, no other FHIRHelpers functions use overloads (except ToString and ToDateTime, - // but those mappings expand the value element directly, rather than invoking the FHIRHelpers function) - TypeSpecifier argumentSignature = null; - if (this.options.getSignatureLevel() != SignatureLevel.None) { - if (qualifiedFunctionName.equals("FHIRHelpers.ToInterval")) { - // Force loading of the FHIR model, as it's an implicit - // dependency of the the target mapping here. - var fhirVersion = "4.0.1"; - var qiCoreModel = this.getModel("QICore"); - if (qiCoreModel != null) { - var version = qiCoreModel.getModelInfo().getVersion(); - if (version.equals("3.3.0")) { - fhirVersion = "4.0.0"; - } else if (version.startsWith("3")) { - fhirVersion = "3.0.1"; - } - } - - // Force the FHIR model to be loaded. - this.modelManager.resolveModel("FHIR", fhirVersion); - - NamedTypeSpecifier namedTypeSpecifier = - new NamedTypeSpecifier().withName(dataTypeToQName(resolveTypeName("FHIR", "Period"))); - argumentSignature = namedTypeSpecifier; - } - } - - if (argumentSource.getResultType() instanceof ListType) { - Query query = of.createQuery() - .withSource(of.createAliasedQuerySource() - .withExpression(argumentSource) - .withAlias("$this")); - FunctionRef fr = of.createFunctionRef() - .withLibraryName(libraryName) - .withName(functionName) - .withOperand(of.createAliasRef().withName("$this")); - - if (argumentSignature != null) { - fr.getSignature().add(argumentSignature); - } - - // This doesn't quite work because the US.Core types aren't subtypes of FHIR types. - // resolveCall(libraryName, functionName, new FunctionRefInvocation(fr), false, false); - query.setReturn(of.createReturnClause().withDistinct(false).withExpression(fr)); - query.setResultType(source.getResultType()); - return query; - } else { - FunctionRef fr = of.createFunctionRef() - .withLibraryName(libraryName) - .withName(functionName) - .withOperand(argumentSource); - fr.setResultType(source.getResultType()); - - if (argumentSignature != null) { - fr.getSignature().add(argumentSignature); - } - - return fr; - // This doesn't quite work because the US.Core types aren't subtypes of FHIR types, - // or they are defined as System types and not FHIR types - // return resolveCall(libraryName, functionName, new FunctionRefInvocation(fr), false, false); - } - } else if (targetMap.contains("[")) { - int indexerStart = targetMap.indexOf("["); - int indexerEnd = targetMap.indexOf("]"); - String indexer = targetMap.substring(indexerStart + 1, indexerEnd); - String indexerPath = targetMap.substring(0, indexerStart); - - Expression result = null; - - // Apply sourcePaths to get to the indexer - String[] indexerPaths = indexerPath.split("\\."); - for (String path : indexerPaths) { - if (path.equals("%parent")) { - if (!(source instanceof Property)) { - throw new IllegalArgumentException(String.format( - "Cannot expand target map %s for non-property-accessor type %s", - targetMap, source.getClass().getSimpleName())); - } - Property sourceProperty = (Property) source; - if (sourceProperty.getSource() != null) { - result = sourceProperty.getSource(); - } else if (sourceProperty.getScope() != null) { - result = resolveIdentifier(sourceProperty.getScope(), true); - } else { - throw new IllegalArgumentException( - String.format("Cannot resolve %%parent reference in targetMap %s", targetMap)); - } - } else { - Property p = of.createProperty().withSource(result).withPath(path); - result = p; - } - } - - // Build a query with the current result as source and the indexer content as criteria in the where clause - AliasedQuerySource querySource = - of.createAliasedQuerySource().withExpression(result).withAlias("$this"); - - Expression criteria = null; - for (String indexerItem : indexer.split(",")) { - String[] indexerItems = indexerItem.split("="); - if (indexerItems.length != 2) { - throw new IllegalArgumentException( - String.format("Invalid indexer item %s in targetMap %s", indexerItem, targetMap)); - } - - Expression left = null; - for (String path : indexerItems[0].split("\\.")) { - if (left == null) { - left = of.createProperty().withScope("$this").withPath(path); - } else { - left = of.createProperty().withSource(left).withPath(path); - } - - // HACK: Workaround the fact that we don't have type information for the mapping expansions... - if (path.equals("coding")) { - left = (Expression) of.createFirst() - .withSource(left) - .withResultType(this.getModel("FHIR").resolveTypeName("FHIR.coding")); - } - if (path.equals("url")) { - // HACK: This special cases FHIR model resolution - left.setResultType(this.getModel("FHIR").resolveTypeName("FHIR.uri")); - var ref = of.createFunctionRef() - .withLibraryName("FHIRHelpers") - .withName("ToString") - .withOperand(left); - left = resolveCall( - ref.getLibraryName(), ref.getName(), new FunctionRefInvocation(ref), false, false); - } - } - - // HACK: Workaround the fact that we don't have type information for the mapping expansions... - // These hacks will be removed when addressed by the model info - if (indexerItems[0].equals("code.coding.system")) { - // HACK: This special cases FHIR model resolution - left.setResultType(this.getModel("FHIR").resolveTypeName("FHIR.uri")); - var ref = (FunctionRef) of.createFunctionRef() - .withLibraryName("FHIRHelpers") - .withName("ToString") - .withOperand(left); - - left = resolveCall( - ref.getLibraryName(), ref.getName(), new FunctionRefInvocation(ref), false, false); - } - if (indexerItems[0].equals("code.coding.code")) { - // HACK: This special cases FHIR model resolution - left.setResultType(this.getModel("FHIR").resolveTypeName("FHIR.code")); - var ref = of.createFunctionRef() - .withLibraryName("FHIRHelpers") - .withName("ToString") - .withOperand(left); - left = resolveCall( - ref.getLibraryName(), ref.getName(), new FunctionRefInvocation(ref), false, false); - } - - String rightValue = indexerItems[1].substring(1, indexerItems[1].length() - 1); - Expression right = this.createLiteral(StringEscapeUtils.unescapeCql(rightValue)); - - Expression criteriaItem = of.createEqual().withOperand(left, right); - if (criteria == null) { - criteria = criteriaItem; - } else { - criteria = of.createAnd().withOperand(criteria, criteriaItem); - } - } - - Query query = of.createQuery().withSource(querySource).withWhere(criteria); - result = query; - - if (indexerEnd + 1 < targetMap.length()) { - // There are additional paths following the indexer, apply them - String targetPath = targetMap.substring(indexerEnd + 1); - if (targetPath.startsWith(".")) { - targetPath = targetPath.substring(1); - } - - if (!targetPath.isEmpty()) { - query.setReturn(of.createReturnClause() - .withDistinct(false) - .withExpression(of.createProperty() - .withSource(of.createAliasRef().withName("$this")) - .withPath(targetPath))); - } - - // The value reference should go inside the query, rather than being applied as a property outside of it - // for (String path : targetPath.split("\\.")) { - // result = of.createProperty().withSource(result).withPath(path); - // } - } - - if (!(source.getResultType() instanceof ListType)) { - // Use a singleton from since the source of the query is a list - result = of.createSingletonFrom().withOperand(result); - } - - result.setResultType(source.getResultType()); - return result; - } else if (targetMap.startsWith("%value.")) { - String propertyName = targetMap.substring(7); - // If the source is a list, the mapping is expected to apply to every element in the list - // ((source $this return all $this.value) - if (source.getResultType() instanceof ListType) { - AliasedQuerySource s = - of.createAliasedQuerySource().withExpression(source).withAlias("$this"); - Property p = of.createProperty().withScope("$this").withPath(propertyName); - p.setResultType(((ListType) source.getResultType()).getElementType()); - ReturnClause r = of.createReturnClause().withDistinct(false).withExpression(p); - Query q = of.createQuery().withSource(s).withReturn(r); - q.setResultType(source.getResultType()); - return q; - } else { - Property p = of.createProperty().withSource(source).withPath(propertyName); - p.setResultType(source.getResultType()); - return p; - } - } - - throw new IllegalArgumentException(String.format("TargetMapping not implemented: %s", targetMap)); - } - - public Expression resolveAccessor(Expression left, String memberIdentifier) { - // if left is a LibraryRef - // if right is an identifier - // right may be an ExpressionRef, a CodeSystemRef, a ValueSetRef, a CodeRef, a ConceptRef, or a ParameterRef -- - // need to resolve on the referenced library - // if left is an ExpressionRef - // if right is an identifier - // return a Property with the ExpressionRef as source and identifier as Path - // if left is a Property - // if right is an identifier - // modify the Property to append the identifier to the path - // if left is an AliasRef - // return a Property with a Path and no source, and Scope set to the Alias - // if left is an Identifier - // return a new Identifier with left as a qualifier - // else - // throws an error as an unresolved identifier - - if (left instanceof LibraryRef) { - String libraryName = ((LibraryRef) left).getLibraryName(); - CompiledLibrary referencedLibrary = resolveLibrary(libraryName); - - ResolvedIdentifierContext resolvedIdentifierContext = referencedLibrary.resolve(memberIdentifier); - - final Optional optElement = resolvedIdentifierContext.getExactMatchElement(); - - if (optElement.isPresent()) { - final Element element = optElement.get(); - - if (element instanceof ExpressionDef) { - checkAccessLevel(libraryName, memberIdentifier, ((ExpressionDef) element).getAccessLevel()); - Expression result = of.createExpressionRef() - .withLibraryName(libraryName) - .withName(memberIdentifier); - result.setResultType(getExpressionDefResultType((ExpressionDef) element)); - return result; - } - - if (element instanceof ParameterDef) { - checkAccessLevel(libraryName, memberIdentifier, ((ParameterDef) element).getAccessLevel()); - Expression result = - of.createParameterRef().withLibraryName(libraryName).withName(memberIdentifier); - result.setResultType(element.getResultType()); - return result; - } - - if (element instanceof ValueSetDef) { - checkAccessLevel(libraryName, memberIdentifier, ((ValueSetDef) element).getAccessLevel()); - ValueSetRef result = - of.createValueSetRef().withLibraryName(libraryName).withName(memberIdentifier); - result.setResultType(element.getResultType()); - if (isCompatibleWith("1.5")) { - result.setPreserve(true); - } - return result; - } - - if (element instanceof CodeSystemDef) { - checkAccessLevel(libraryName, memberIdentifier, ((CodeSystemDef) element).getAccessLevel()); - CodeSystemRef result = of.createCodeSystemRef() - .withLibraryName(libraryName) - .withName(memberIdentifier); - result.setResultType(element.getResultType()); - return result; - } - - if (element instanceof CodeDef) { - checkAccessLevel(libraryName, memberIdentifier, ((CodeDef) element).getAccessLevel()); - CodeRef result = - of.createCodeRef().withLibraryName(libraryName).withName(memberIdentifier); - result.setResultType(element.getResultType()); - return result; - } - - if (element instanceof ConceptDef) { - checkAccessLevel(libraryName, memberIdentifier, ((ConceptDef) element).getAccessLevel()); - ConceptRef result = - of.createConceptRef().withLibraryName(libraryName).withName(memberIdentifier); - result.setResultType(element.getResultType()); - return result; - } - } - - // ERROR: - throw new IllegalArgumentException(String.format( - "Could not resolve identifier %s in library %s.", - memberIdentifier, referencedLibrary.getIdentifier().getId())); - } else if (left instanceof AliasRef) { - PropertyResolution resolution = resolveProperty(left.getResultType(), memberIdentifier); - Expression result = buildProperty( - ((AliasRef) left).getName(), resolution.getName(), resolution.isSearch(), resolution.getType()); - return applyTargetMap(result, resolution.getTargetMap()); - } else if (left.getResultType() instanceof ListType && listTraversal) { - // NOTE: FHIRPath path traversal support - // Resolve property access of a list of items as a query: - // listValue.property ::= listValue X where X.property is not null return all X.property - ListType listType = (ListType) left.getResultType(); - PropertyResolution resolution = resolveProperty(listType.getElementType(), memberIdentifier); - Expression accessor = buildProperty( - of.createAliasRef().withName("$this"), - resolution.getName(), - resolution.isSearch(), - resolution.getType()); - accessor = applyTargetMap(accessor, resolution.getTargetMap()); - Expression not = buildIsNotNull(accessor); - - // Recreate property, it needs to be accessed twice - accessor = buildProperty( - of.createAliasRef().withName("$this"), - resolution.getName(), - resolution.isSearch(), - resolution.getType()); - accessor = applyTargetMap(accessor, resolution.getTargetMap()); - - AliasedQuerySource source = - of.createAliasedQuerySource().withExpression(left).withAlias("$this"); - source.setResultType(left.getResultType()); - Query query = of.createQuery() - .withSource(source) - .withWhere(not) - .withReturn(of.createReturnClause().withDistinct(false).withExpression(accessor)); - query.setResultType(new ListType(accessor.getResultType())); - - if (accessor.getResultType() instanceof ListType) { - Flatten result = of.createFlatten().withOperand(query); - result.setResultType(accessor.getResultType()); - return result; - } - - return query; - } else { - PropertyResolution resolution = resolveProperty(left.getResultType(), memberIdentifier); - Expression result = buildProperty(left, resolution.getName(), resolution.isSearch(), resolution.getType()); - result = applyTargetMap(result, resolution.getTargetMap()); - return result; - } - } - - private Expression resolveQueryResultElement(String identifier) { - if (inQueryContext()) { - QueryContext query = peekQueryContext(); - if (query.inSortClause() && !query.isSingular()) { - if (identifier.equals("$this")) { - IdentifierRef result = of.createIdentifierRef().withName(identifier); - result.setResultType(query.getResultElementType()); - return result; - } - - PropertyResolution resolution = resolveProperty(query.getResultElementType(), identifier, false); - if (resolution != null) { - IdentifierRef result = of.createIdentifierRef().withName(resolution.getName()); - result.setResultType(resolution.getType()); - return applyTargetMap(result, resolution.getTargetMap()); - } - } - } - - return null; - } - - private AliasedQuerySource resolveAlias(String identifier) { - // Need to use a for loop to go through backwards, iteration on a Stack is bottom up - if (inQueryContext()) { - for (int i = getScope().getQueries().size() - 1; i >= 0; i--) { - AliasedQuerySource source = getScope().getQueries().get(i).resolveAlias(identifier); - if (source != null) { - return source; - } - } - } - - return null; - } - - private Expression resolveQueryThisElement(String identifier) { - if (inQueryContext()) { - QueryContext query = peekQueryContext(); - if (query.isImplicit()) { - AliasedQuerySource source = resolveAlias("$this"); - if (source != null) { - AliasRef aliasRef = of.createAliasRef().withName("$this"); - if (source.getResultType() instanceof ListType) { - aliasRef.setResultType(((ListType) source.getResultType()).getElementType()); - } else { - aliasRef.setResultType(source.getResultType()); - } - - PropertyResolution result = resolveProperty(aliasRef.getResultType(), identifier, false); - if (result != null) { - return resolveAccessor(aliasRef, identifier); - } - } - } - } - - return null; - } - - private LetClause resolveQueryLet(String identifier) { - // Need to use a for loop to go through backwards, iteration on a Stack is bottom up - if (inQueryContext()) { - for (int i = getScope().getQueries().size() - 1; i >= 0; i--) { - LetClause let = getScope().getQueries().get(i).resolveLet(identifier); - if (let != null) { - return let; - } - } - } - - return null; - } - - private OperandRef resolveOperandRef(String identifier) { - if (!functionDefs.empty()) { - for (OperandDef operand : functionDefs.peek().getOperand()) { - if (operand.getName().equals(identifier)) { - return (OperandRef) - of.createOperandRef().withName(identifier).withResultType(operand.getResultType()); - } - } - } - - return null; - } - - private DataType getExpressionDefResultType(ExpressionDef expressionDef) { - // If the current expression context is the same as the expression def context, return the expression def result - // type. - if (currentExpressionContext().equals(expressionDef.getContext())) { - return expressionDef.getResultType(); - } - - // If the current expression context is specific, a reference to an unfiltered context expression will indicate - // a full - // evaluation of the population context expression, and the result type is the same. - if (inSpecificContext()) { - return expressionDef.getResultType(); - } - - // If the current expression context is unfiltered, a reference to a specific context expression will need to be - // performed for every context in the system, so the result type is promoted to a list (if it is not already). - if (inUnfilteredContext()) { - // If we are in the source clause of a query, indicate that the source references patient context - if (inQueryContext() && getScope().getQueries().peek().inSourceClause()) { - getScope().getQueries().peek().referenceSpecificContext(); - } - - DataType resultType = expressionDef.getResultType(); - if (!(resultType instanceof ListType)) { - return new ListType(resultType); - } else { - return resultType; - } - } - - throw new IllegalArgumentException(String.format( - "Invalid context reference from %s context to %s context.", - currentExpressionContext(), expressionDef.getContext())); - } - - public enum IdentifierScope { - GLOBAL, - LOCAL - } - - /** - * Add an identifier to the deque to indicate that we are considering it for consideration for identifier hiding and - * adding a compiler warning if this is the case. - *

- * For example, if an alias within an expression body has the same name as a parameter, execution would have - * added the parameter identifier and the next execution would consider an alias with the same name, thus resulting - * in a warning. - *

- * Exact case matching as well as case-insensitive matching are considered. If known, the type of the structure - * in question will be considered in crafting the warning message, as per the {@link Element} parameter. - *

- * Also, special case function overloads so that only a single overloaded function name is taken into account. - * - * Default scope is {@link IdentifierScope#LOCAL} - * - * @param identifier The identifier belonging to the parameter, expression, function, alias, etc, to be evaluated. - * @param trackable The construct trackable, for example {@link ExpressionRef}. - */ - void pushIdentifier(String identifier, Trackable trackable) { - pushIdentifier(identifier, trackable, IdentifierScope.LOCAL); - } - - /** - * Add an identifier to the deque to indicate that we are considering it for consideration for identifier hiding and - * adding a compiler warning if this is the case. - *

- * For example, if an alias within an expression body has the same name as a parameter, execution would have - * added the parameter identifier and the next execution would consider an alias with the same name, thus resulting - * in a warning. - *

- * Exact case matching as well as case-insensitive matching are considered. If known, the type of the structure - * in question will be considered in crafting the warning message, as per the {@link Element} parameter. - *

- * Also, special case function overloads so that only a single overloaded function name is taken into account. - * - * @param identifier The identifier belonging to the parameter, expression, function, alias, etc, to be evaluated. - * @param trackable The construct trackable, for example {@link ExpressionRef}. - * @param scope the scope of the current identifier - */ - void pushIdentifier(String identifier, Trackable trackable, IdentifierScope scope) { - var localMatch = !localIdentifierStack.isEmpty() - ? findMatchingIdentifierContext(localIdentifierStack.peek(), identifier) - : Optional.empty(); - var globalMatch = findMatchingIdentifierContext(globalIdentifiers, identifier); - - if (globalMatch.isPresent() || localMatch.isPresent()) { - var matchedContext = globalMatch.isPresent() ? globalMatch.get() : localMatch.get(); - - boolean matchedOnFunctionOverloads = - matchedContext.getTrackableSubclass().equals(FunctionDef.class) && trackable instanceof FunctionDef; - - if (!matchedOnFunctionOverloads) { - reportWarning(resolveWarningMessage(matchedContext.getIdentifier(), identifier, trackable), trackable); - } - } - - if (shouldAddIdentifierContext(trackable)) { - final Class trackableOrNull = trackable != null ? trackable.getClass() : null; - // Sometimes the underlying Trackable doesn't resolve in the calling code - if (scope == IdentifierScope.GLOBAL) { - globalIdentifiers.push(new IdentifierContext(identifier, trackableOrNull)); - } else { - localIdentifierStack.peek().push(new IdentifierContext(identifier, trackableOrNull)); - } - } - } - - private Optional findMatchingIdentifierContext( - Collection identifierContext, String identifier) { - return identifierContext.stream() - .filter(innerContext -> innerContext.getIdentifier().equals(identifier)) - .findFirst(); - } - - /** - * Pop the last resolved identifier off the deque. This is needed in case of a context in which an identifier - * falls out of scope, for an example, an alias within an expression or function body. - */ - void popIdentifier() { - popIdentifier(IdentifierScope.LOCAL); - } - - void popIdentifier(IdentifierScope scope) { - if (scope == IdentifierScope.GLOBAL) { - globalIdentifiers.pop(); - } else { - localIdentifierStack.peek().pop(); - } - } - - void pushIdentifierScope() { - localIdentifierStack.push(new ArrayDeque<>()); - } - - void popIdentifierScope() { - localIdentifierStack.pop(); - } - - // TODO: consider other structures that should only trigger a readonly check of identifier hiding - private boolean shouldAddIdentifierContext(Trackable trackable) { - return !(trackable instanceof Literal); - } - - private String resolveWarningMessage(String matchedIdentifier, String identifierParam, Trackable trackable) { - final String elementString = lookupElementWarning(trackable); - - if (trackable instanceof Literal) { - return String.format( - "You used a string literal: [%s] here that matches an identifier in scope: [%s]. Did you mean to use the identifier instead?", - identifierParam, matchedIdentifier); - } - - return String.format( - "%s identifier [%s] is hiding another identifier of the same name.", elementString, identifierParam); - } - - private class Scope { - private final Stack targets = new Stack<>(); - private final Stack queries = new Stack<>(); - - public Stack getTargets() { - return targets; - } - - public Stack getQueries() { - return queries; - } - } - - private class ExpressionDefinitionContext { - public ExpressionDefinitionContext(String identifier) { - this.identifier = identifier; - } - - private String identifier; - - public String getIdentifier() { - return identifier; - } - - private Scope scope = new Scope(); - - public Scope getScope() { - return scope; - } - - private Exception rootCause; - - public Exception getRootCause() { - return rootCause; - } - - public void setRootCause(Exception rootCause) { - this.rootCause = rootCause; - } - } - - private class ExpressionDefinitionContextStack extends Stack { - public boolean contains(String identifier) { - for (int i = 0; i < this.elementCount; i++) { - if (this.elementAt(i).getIdentifier().equals(identifier)) { - return true; - } - } - - return false; - } - } - - public Exception determineRootCause() { - if (!expressionDefinitions.isEmpty()) { - ExpressionDefinitionContext currentContext = expressionDefinitions.peek(); - if (currentContext != null) { - return currentContext.getRootCause(); - } - } - - return null; - } - - public void setRootCause(Exception rootCause) { - if (!expressionDefinitions.isEmpty()) { - ExpressionDefinitionContext currentContext = expressionDefinitions.peek(); - if (currentContext != null) { - currentContext.setRootCause(rootCause); - } - } - } - - public void pushExpressionDefinition(String identifier) { - if (expressionDefinitions.contains(identifier)) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Cannot resolve reference to expression or function %s because it results in a circular reference.", - identifier)); - } - expressionDefinitions.push(new ExpressionDefinitionContext(identifier)); - } - - public void popExpressionDefinition() { - expressionDefinitions.pop(); - } - - private boolean hasScope() { - return !expressionDefinitions.empty(); - } - - private Scope getScope() { - return expressionDefinitions.peek().getScope(); - } - - public void pushExpressionContext(String context) { - if (context == null) { - throw new IllegalArgumentException("Expression context cannot be null"); - } - expressionContext.push(context); - } - - public void popExpressionContext() { - if (expressionContext.empty()) { - throw new IllegalStateException("Expression context stack is empty."); - } - - expressionContext.pop(); - } - - public String currentExpressionContext() { - if (expressionContext.empty()) { - throw new IllegalStateException("Expression context stack is empty."); - } - - return expressionContext.peek(); - } - - public boolean inSpecificContext() { - return !inUnfilteredContext(); - } - - public boolean inUnfilteredContext() { - return currentExpressionContext().equals("Unfiltered") - || (isCompatibilityLevel3() && currentExpressionContext().equals("Population")); - } - - public boolean inQueryContext() { - return hasScope() && getScope().getQueries().size() > 0; - } - - public void pushQueryContext(QueryContext context) { - getScope().getQueries().push(context); - } - - public QueryContext popQueryContext() { - return getScope().getQueries().pop(); - } - - public QueryContext peekQueryContext() { - return getScope().getQueries().peek(); - } - - public void pushExpressionTarget(Expression target) { - getScope().getTargets().push(target); - } - - public Expression popExpressionTarget() { - return getScope().getTargets().pop(); - } - - public boolean hasExpressionTarget() { - return hasScope() && !getScope().getTargets().isEmpty(); - } - - public void beginFunctionDef(FunctionDef functionDef) { - functionDefs.push(functionDef); - } - - public void endFunctionDef() { - functionDefs.pop(); - } - - public void pushLiteralContext() { - literalContext++; - } - - public void popLiteralContext() { - if (!inLiteralContext()) { - throw new IllegalStateException("Not in literal context"); - } - - literalContext--; - } - - public boolean inLiteralContext() { - return literalContext > 0; - } - - public void checkLiteralContext() { - if (inLiteralContext()) { - // ERROR: - throw new IllegalStateException( - "Expressions in this context must be able to be evaluated at compile-time."); - } - } - - public void pushTypeSpecifierContext() { - typeSpecifierContext++; - } - - public void popTypeSpecifierContext() { - if (!inTypeSpecifierContext()) { - throw new IllegalStateException("Not in type specifier context"); - } - - typeSpecifierContext--; - } - - public boolean inTypeSpecifierContext() { - return typeSpecifierContext > 0; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryContentType.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryContentType.java deleted file mode 100644 index 116112ec2..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryContentType.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.cqframework.cql.cql2elm; - -/** - * This enum lists all the encodings for CQL libraries - */ -public enum LibraryContentType implements MimeType { - CQL("text/cql"), - XML("application/elm+xml"), - JSON("application/elm+json"), - COFFEE("application/elm+coffee"); - - private LibraryContentType(String mimeType) { - this.mimeType = mimeType; - } - - private final String mimeType; - - @Override - public String mimeType() { - return mimeType; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryManager.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryManager.java deleted file mode 100644 index 518d33e46..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryManager.java +++ /dev/null @@ -1,492 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import static org.cqframework.cql.cql2elm.CqlCompilerException.hasErrors; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.apache.commons.lang3.StringUtils; -import org.cqframework.cql.cql2elm.model.CompiledLibrary; -import org.cqframework.cql.elm.serializing.ElmLibraryReaderFactory; -import org.fhir.ucum.UcumEssenceService; -import org.fhir.ucum.UcumException; -import org.fhir.ucum.UcumService; -import org.hl7.cql.model.NamespaceManager; -import org.hl7.elm.r1.CodeDef; -import org.hl7.elm.r1.CodeSystemDef; -import org.hl7.elm.r1.ConceptDef; -import org.hl7.elm.r1.ExpressionDef; -import org.hl7.elm.r1.FunctionDef; -import org.hl7.elm.r1.FunctionRef; -import org.hl7.elm.r1.IncludeDef; -import org.hl7.elm.r1.Library; -import org.hl7.elm.r1.ParameterDef; -import org.hl7.elm.r1.UsingDef; -import org.hl7.elm.r1.ValueSetDef; -import org.hl7.elm.r1.VersionedIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Manages a set of CQL libraries. As new library references are encountered - * during compilation, the corresponding source is obtained via - * librarySourceLoader, compiled and cached for later use. - */ -public class LibraryManager { - public enum CacheMode { - NONE, - READ_ONLY, - READ_WRITE - } - - private static final Logger logger = LoggerFactory.getLogger(LibraryManager.class); - - private final ModelManager modelManager; - private final NamespaceManager namespaceManager; - private final CqlCompilerOptions cqlCompilerOptions; - private final Map compiledLibraries; - private final LibrarySourceLoader librarySourceLoader; - - private UcumService ucumService; - - private static final LibraryContentType[] supportedContentTypes = { - LibraryContentType.JSON, LibraryContentType.XML, LibraryContentType.CQL - }; - - public LibraryManager(ModelManager modelManager) { - this(modelManager, CqlCompilerOptions.defaultOptions(), null); - } - - public LibraryManager(ModelManager modelManager, CqlCompilerOptions cqlCompilerOptions) { - this(modelManager, cqlCompilerOptions, null); - } - - public LibraryManager( - ModelManager modelManager, - CqlCompilerOptions cqlCompilerOptions, - Map libraryCache) { - if (modelManager == null) { - throw new IllegalArgumentException("modelManager is null"); - } - this.modelManager = modelManager; - this.cqlCompilerOptions = cqlCompilerOptions; - if (this.modelManager.getNamespaceManager() != null) { - this.namespaceManager = modelManager.getNamespaceManager(); - } else { - this.namespaceManager = new NamespaceManager(); - } - - if (libraryCache != null) { - this.compiledLibraries = libraryCache; - } else { - this.compiledLibraries = new HashMap<>(); - } - - this.librarySourceLoader = new PriorityLibrarySourceLoader(); - } - - public CqlCompilerOptions getCqlCompilerOptions() { - return this.cqlCompilerOptions; - } - - public ModelManager getModelManager() { - return this.modelManager; - } - - public NamespaceManager getNamespaceManager() { - return this.namespaceManager; - } - - public Map getCompiledLibraries() { - return this.compiledLibraries; - } - - public UcumService getUcumService() { - if (this.ucumService == null) { - this.ucumService = getDefaultUcumService(); - } - - return ucumService; - } - - protected synchronized UcumService getDefaultUcumService() { - try { - return new UcumEssenceService(UcumEssenceService.class.getResourceAsStream("/ucum-essence.xml")); - } catch (UcumException e) { - logger.warn("Error creating shared UcumService", e); - } - - return null; - } - - public void setUcumService(UcumService ucumService) { - this.ucumService = ucumService; - } - - public LibrarySourceLoader getLibrarySourceLoader() { - return librarySourceLoader; - } - - /* - * A "well-known" library name is one that is allowed to resolve without a - * namespace in a namespace-aware context - */ - public boolean isWellKnownLibraryName(String unqualifiedIdentifier) { - if (unqualifiedIdentifier == null) { - return false; - } - - switch (unqualifiedIdentifier) { - case "FHIRHelpers": - return true; - default: - return false; - } - } - - public CompiledLibrary resolveLibrary(VersionedIdentifier libraryIdentifier, CacheMode cacheMode) { - return this.resolveLibrary(libraryIdentifier, new ArrayList<>(), cacheMode); - } - - public CompiledLibrary resolveLibrary(VersionedIdentifier libraryIdentifier) { - return this.resolveLibrary(libraryIdentifier, new ArrayList<>(), CacheMode.READ_WRITE); - } - - public boolean canResolveLibrary(VersionedIdentifier libraryIdentifier) { - var lib = this.resolveLibrary(libraryIdentifier); - return lib != null; - } - - public CompiledLibrary resolveLibrary(VersionedIdentifier libraryIdentifier, List errors) { - return this.resolveLibrary(libraryIdentifier, errors, CacheMode.READ_WRITE); - } - - public CompiledLibrary resolveLibrary( - VersionedIdentifier libraryIdentifier, List errors, CacheMode cacheMode) { - if (libraryIdentifier == null) { - throw new IllegalArgumentException("libraryIdentifier is null."); - } - - if (libraryIdentifier.getId() == null || libraryIdentifier.getId().equals("")) { - throw new IllegalArgumentException("libraryIdentifier Id is null"); - } - - CompiledLibrary library = null; - if (cacheMode != CacheMode.NONE) { - library = compiledLibraries.get(libraryIdentifier); - if (library != null) { - return library; - } - } - - library = compileLibrary(libraryIdentifier, errors); - if (!hasErrors(errors) && cacheMode == CacheMode.READ_WRITE) { - compiledLibraries.put(libraryIdentifier, library); - } - - return library; - } - - private CompiledLibrary compileLibrary(VersionedIdentifier libraryIdentifier, List errors) { - - CompiledLibrary result = null; - if (!this.cqlCompilerOptions.getEnableCqlOnly()) { - result = tryCompiledLibraryElm(libraryIdentifier, this.cqlCompilerOptions); - if (result != null) { - sortStatements(result); - return result; - } - } - - String libraryPath = NamespaceManager.getPath(libraryIdentifier.getSystem(), libraryIdentifier.getId()); - - try { - InputStream cqlSource = librarySourceLoader.getLibrarySource(libraryIdentifier); - if (cqlSource == null) { - throw new CqlIncludeException( - String.format( - "Could not load source for library %s, version %s.", - libraryIdentifier.getId(), libraryIdentifier.getVersion()), - libraryIdentifier.getSystem(), - libraryIdentifier.getId(), - libraryIdentifier.getVersion()); - } - - CqlCompiler compiler = new CqlCompiler( - namespaceManager.getNamespaceInfoFromUri(libraryIdentifier.getSystem()), libraryIdentifier, this); - compiler.run(cqlSource); - if (errors != null) { - errors.addAll(compiler.getExceptions()); - } - - result = compiler.getCompiledLibrary(); - if (libraryIdentifier.getVersion() != null - && !libraryIdentifier - .getVersion() - .equals(result.getIdentifier().getVersion())) { - throw new CqlIncludeException( - String.format( - "Library %s was included as version %s, but version %s of the library was found.", - libraryPath, - libraryIdentifier.getVersion(), - result.getIdentifier().getVersion()), - libraryIdentifier.getSystem(), - libraryIdentifier.getId(), - libraryIdentifier.getVersion()); - } - - } catch (IOException e) { - throw new CqlIncludeException( - String.format( - "Errors occurred translating library %s, version %s.", - libraryPath, libraryIdentifier.getVersion()), - libraryIdentifier.getSystem(), - libraryIdentifier.getId(), - libraryIdentifier.getVersion(), - e); - } - - if (result == null) { - throw new CqlIncludeException( - String.format( - "Could not load source for library %s, version %s.", - libraryPath, libraryIdentifier.getVersion()), - libraryIdentifier.getSystem(), - libraryIdentifier.getId(), - libraryIdentifier.getVersion()); - } else { - sortStatements(result); - return result; - } - } - - private void sortStatements(CompiledLibrary compiledLibrary) { - if (compiledLibrary == null || compiledLibrary.getLibrary().getStatements() == null) { - return; - } - - compiledLibrary.getLibrary().getStatements().getDef().sort((a, b) -> a.getName() - .compareTo(b.getName())); - } - - private CompiledLibrary tryCompiledLibraryElm(VersionedIdentifier libraryIdentifier, CqlCompilerOptions options) { - InputStream elm = null; - for (LibraryContentType type : supportedContentTypes) { - if (LibraryContentType.CQL == type) { - continue; - } - - elm = librarySourceLoader.getLibraryContent(libraryIdentifier, type); - if (elm == null) { - continue; - } - - return generateCompiledLibraryFromElm(libraryIdentifier, elm, type, options); - } - - return null; - } - - private CompiledLibrary generateCompiledLibraryFromElm( - VersionedIdentifier libraryIdentifier, - InputStream librarySource, - LibraryContentType type, - CqlCompilerOptions options) { - - Library library = null; - CompiledLibrary compiledLibrary = null; - try { - library = ElmLibraryReaderFactory.getReader(type.mimeType()).read(new InputStreamReader(librarySource)); - } catch (IOException e) { - // intentionally ignored - } - - if (library != null && checkBinaryCompatibility(library)) { - compiledLibrary = generateCompiledLibrary(library); - } - - return compiledLibrary; - } - - private CompiledLibrary generateCompiledLibrary(Library library) { - - if (library == null) { - return null; - } - boolean compilationSuccess = true; - CompiledLibrary compiledLibrary = new CompiledLibrary(); - try { - if (library != null) { - compiledLibrary.setLibrary(library); - } - if (library.getIdentifier() != null) { - compiledLibrary.setIdentifier(library.getIdentifier()); - } - - if (library.getUsings() != null && library.getUsings().getDef() != null) { - for (UsingDef usingDef : library.getUsings().getDef()) { - compiledLibrary.add(usingDef); - } - } - if (library.getIncludes() != null && library.getIncludes().getDef() != null) { - for (IncludeDef includeDef : library.getIncludes().getDef()) { - compiledLibrary.add(includeDef); - } - } - if (library.getCodeSystems() != null && library.getCodeSystems().getDef() != null) { - for (CodeSystemDef codeSystemDef : library.getCodeSystems().getDef()) { - compiledLibrary.add(codeSystemDef); - } - } - for (ValueSetDef valueSetDef : library.getValueSets().getDef()) { - compiledLibrary.add(valueSetDef); - } - - if (library.getCodes() != null && library.getCodes().getDef() != null) { - for (CodeDef codeDef : library.getCodes().getDef()) { - compiledLibrary.add(codeDef); - } - } - if (library.getConcepts() != null && library.getConcepts().getDef() != null) { - for (ConceptDef conceptDef : library.getConcepts().getDef()) { - compiledLibrary.add(conceptDef); - } - } - if (library.getParameters() != null && library.getParameters().getDef() != null) { - for (ParameterDef parameterDef : library.getParameters().getDef()) { - compiledLibrary.add(parameterDef); - } - } - if (library.getStatements() != null && library.getStatements().getDef() != null) { - for (ExpressionDef expressionDef : library.getStatements().getDef()) { - - // to do implement an ElmTypeInferencingVisitor; make sure that the resultType - // is set for each node - if (expressionDef.getResultType() != null) { - compiledLibrary.add(expressionDef); - } else { - compilationSuccess = false; - break; - } - } - } - } catch (Exception e) { - compilationSuccess = false; - } - - if (compilationSuccess) { - return compiledLibrary; - } - - return null; - } - - protected Boolean compilerOptionsMatch(Library library) { - Set compilerOptions = CompilerOptions.getCompilerOptions(library); - if (compilerOptions == null) { - return false; - } - return compilerOptions.equals(this.cqlCompilerOptions.getOptions()); - } - - private boolean checkBinaryCompatibility(Library library) { - if (library == null) { - return false; - } - - return this.isSignatureCompatible(library) - && this.isVersionCompatible(library) - && this.compilerOptionsMatch(library); - } - - private boolean isSignatureCompatible(Library library) { - return !hasOverloadedFunctions(library) || hasSignature(library); - } - - private boolean hasOverloadedFunctions(Library library) { - if (library == null || library.getStatements() == null) { - return false; - } - - Set functionNames = new HashSet<>(); - for (ExpressionDef ed : library.getStatements().getDef()) { - if (ed instanceof FunctionDef) { - FunctionDef fd = (FunctionDef) ed; - var sig = new FunctionSig( - fd.getName(), - fd.getOperand() == null ? 0 : fd.getOperand().size()); - if (functionNames.contains(sig)) { - return true; - } else { - functionNames.add(sig); - } - } - } - return false; - } - - boolean hasSignature(Library library) { - if (library != null && library.getStatements() != null) { - // Just a quick top-level scan for signatures. To fully verify we'd have to - // recurse all - // the way down. At that point, let's just translate. - for (ExpressionDef ed : library.getStatements().getDef()) { - if (ed.getExpression() instanceof FunctionRef) { - FunctionRef fr = (FunctionRef) ed.getExpression(); - if (fr.getSignature() != null && !fr.getSignature().isEmpty()) { - return true; - } - } - } - } - return false; - } - - private boolean isVersionCompatible(Library library) { - if (!StringUtils.isEmpty(this.cqlCompilerOptions.getCompatibilityLevel())) { - if (library.getAnnotation() != null) { - String version = CompilerOptions.getCompilerVersion(library); - if (version != null) { - return version.equals(this.cqlCompilerOptions.getCompatibilityLevel()); - } - } - } - - return false; - } - - static class FunctionSig { - - private final String name; - private final int numArguments; - - public FunctionSig(String name, int numArguments) { - this.name = name; - this.numArguments = numArguments; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + name.hashCode(); - result = prime * result + numArguments; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - FunctionSig other = (FunctionSig) obj; - return other.name.equals(this.name) && other.numArguments == this.numArguments; - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryReaderUtil.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryReaderUtil.java deleted file mode 100644 index a15a772d1..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryReaderUtil.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; - -public class LibraryReaderUtil { - - /** - * Creates {@link Source} from various JSON representation. - */ - public static Source toSource(Object json) throws IOException { - if (json == null) throw new CqlCompilerException("no JSON is given"); - - if (json instanceof String) { - try { - json = new URI((String) json); - } catch (URISyntaxException e) { - json = new File((String) json); - } - } - - if (json instanceof File) { - return new StreamSource((File) json); - } - - if (json instanceof URI) { - json = ((URI) json).toURL(); - } - - if (json instanceof URL) { - return new StreamSource(((URL) json).toExternalForm()); - } - - if (json instanceof InputStream) { - return new StreamSource((InputStream) json); - } - - if (json instanceof Reader) { - return new StreamSource((Reader) json); - } - - if (json instanceof Source) { - return (Source) json; - } - - throw new CqlCompilerException( - String.format("Could not determine access path for input of type %s.", json.getClass())); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceLoader.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceLoader.java deleted file mode 100644 index 6ed411410..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceLoader.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.InputStream; -import org.hl7.elm.r1.VersionedIdentifier; - -/** - * - * @author mhadley - */ -public interface LibrarySourceLoader { - - void clearProviders(); - - InputStream getLibrarySource(VersionedIdentifier libraryIdentifier); - - void registerProvider(LibrarySourceProvider provider); - - default InputStream getLibraryContent(VersionedIdentifier libraryIdentifier, LibraryContentType type) { - if (LibraryContentType.CQL == type) { - return getLibrarySource(libraryIdentifier); - } - - return null; - } - - default boolean isLibraryContentAvailable(VersionedIdentifier libraryIdentifier, LibraryContentType type) { - if (LibraryContentType.CQL == type) { - return getLibrarySource(libraryIdentifier) != null; - } - - return false; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceProvider.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceProvider.java deleted file mode 100644 index 038c0b307..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.InputStream; -import org.hl7.elm.r1.VersionedIdentifier; - -public interface LibrarySourceProvider { - InputStream getLibrarySource(VersionedIdentifier libraryIdentifier); - - default InputStream getLibraryContent(VersionedIdentifier libraryIdentifier, LibraryContentType type) { - if (LibraryContentType.CQL == type) { - return getLibrarySource(libraryIdentifier); - } - - return null; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceProviderFactory.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceProviderFactory.java deleted file mode 100644 index 96d2d7d52..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibrarySourceProviderFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.Iterator; -import java.util.ServiceLoader; - -public class LibrarySourceProviderFactory { - private LibrarySourceProviderFactory() {} - - public static Iterator providers(boolean refresh) { - var loader = ServiceLoader.load(LibrarySourceProvider.class); - if (refresh) { - loader.reload(); - } - - return loader.iterator(); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/MimeType.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/MimeType.java deleted file mode 100644 index 01c67afa7..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/MimeType.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.cqframework.cql.cql2elm; - -public interface MimeType { - String mimeType(); -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelInfoLoader.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelInfoLoader.java deleted file mode 100644 index 892742b5d..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelInfoLoader.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import org.hl7.cql.model.ModelIdentifier; -import org.hl7.cql.model.ModelInfoProvider; -import org.hl7.cql.model.NamespaceAware; -import org.hl7.cql.model.NamespaceManager; -import org.hl7.elm_modelinfo.r1.ModelInfo; - -public class ModelInfoLoader implements NamespaceAware, PathAware { - - private Path path; - - private NamespaceManager namespaceManager; - - private final List providers = new ArrayList<>(); - - private boolean initialized = false; - - public ModelInfoLoader() {} - - private List getProviders() { - if (!initialized) { - initialized = true; - for (Iterator it = ModelInfoProviderFactory.providers(false); it.hasNext(); ) { - ModelInfoProvider provider = it.next(); - registerModelInfoProvider(provider); - } - } - return providers; - } - - public ModelInfo getModelInfo(ModelIdentifier modelIdentifier) { - checkModelIdentifier(modelIdentifier); - - ModelInfo modelInfo = null; - - for (ModelInfoProvider provider : getProviders()) { - modelInfo = provider.load(modelIdentifier); - if (modelInfo != null) { - break; - } - } - - if (modelInfo == null) { - throw new IllegalArgumentException(String.format( - "Could not resolve model info provider for model %s, version %s.", - modelIdentifier.getSystem() == null - ? modelIdentifier.getId() - : NamespaceManager.getPath(modelIdentifier.getSystem(), modelIdentifier.getId()), - modelIdentifier.getVersion())); - } - - return modelInfo; - } - - public void registerModelInfoProvider(ModelInfoProvider provider) { - registerModelInfoProvider(provider, false); - } - - public void registerModelInfoProvider(ModelInfoProvider provider, boolean priority) { - if (provider == null) { - throw new IllegalArgumentException("Provider is null"); - } - - if (namespaceManager != null) { - if (provider instanceof NamespaceAware) { - ((NamespaceAware) provider).setNamespaceManager(namespaceManager); - } - } - - if (path != null) { - if (provider instanceof PathAware) { - ((PathAware) provider).setPath(path); - } - } - - if (priority) { - providers.add(0, provider); - } else { - providers.add(provider); - } - } - - public void unregisterModelInfoProvider(ModelInfoProvider provider) { - providers.remove(provider); - } - - public void clearModelInfoProviders() { - providers.clear(); - initialized = false; - } - - private void checkModelIdentifier(ModelIdentifier modelIdentifier) { - if (modelIdentifier == null) { - throw new IllegalArgumentException("modelIdentifier is null."); - } - - if (modelIdentifier.getId() == null || modelIdentifier.getId().equals("")) { - throw new IllegalArgumentException("modelIdentifier Id is null or empty."); - } - } - - @Override - public void setNamespaceManager(NamespaceManager namespaceManager) { - this.namespaceManager = namespaceManager; - - for (ModelInfoProvider provider : getProviders()) { - if (provider instanceof NamespaceAware) { - ((NamespaceAware) provider).setNamespaceManager(namespaceManager); - } - } - } - - public void setPath(Path path) { - if (path == null || !path.toFile().isDirectory()) { - throw new IllegalArgumentException(String.format("path '%s' is not a valid directory", path)); - } - - this.path = path; - - for (ModelInfoProvider provider : getProviders()) { - if (provider instanceof PathAware) { - ((PathAware) provider).setPath(path); - } - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelInfoProviderFactory.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelInfoProviderFactory.java deleted file mode 100644 index 68f82bec4..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelInfoProviderFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.Iterator; -import java.util.ServiceLoader; -import org.hl7.cql.model.ModelInfoProvider; - -public class ModelInfoProviderFactory { - private ModelInfoProviderFactory() {} - - public static Iterator providers(boolean refresh) { - var loader = ServiceLoader.load(ModelInfoProvider.class); - if (refresh) { - loader.reload(); - } - - return loader.iterator(); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelManager.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelManager.java deleted file mode 100644 index c7df5b276..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelManager.java +++ /dev/null @@ -1,306 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import static java.util.Objects.requireNonNull; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import org.cqframework.cql.cql2elm.model.Model; -import org.cqframework.cql.cql2elm.model.SystemModel; -import org.hl7.cql.model.ModelIdentifier; -import org.hl7.cql.model.NamespaceManager; -import org.hl7.elm_modelinfo.r1.ModelInfo; - -/** - * Created by Bryn on 12/29/2016. - */ -public class ModelManager { - private final NamespaceManager namespaceManager; - private Path path; - private ModelInfoLoader modelInfoLoader; - private final Map models = new HashMap<>(); - private final Set loadingModels = new HashSet<>(); - private final Map modelsByUri = new HashMap<>(); - private final Map globalCache; - private boolean enableDefaultModelInfoLoading = true; - - public ModelManager() { - this.namespaceManager = new NamespaceManager(); - this.globalCache = new ConcurrentHashMap<>(); - initialize(); - } - - /** - * @param globalCache cache for Models by ModelIdentifier. Expected to be thread-safe. - */ - public ModelManager(Map globalCache) { - requireNonNull(globalCache, "globalCache can not be null."); - - this.namespaceManager = new NamespaceManager(); - this.globalCache = globalCache; - initialize(); - } - - public ModelManager(Path path) { - this.namespaceManager = new NamespaceManager(); - this.globalCache = new ConcurrentHashMap<>(); - this.path = path; - initialize(); - } - - public ModelManager(Path path, Map globalCache) { - requireNonNull(globalCache, "globalCache can not be null."); - - this.namespaceManager = new NamespaceManager(); - this.globalCache = globalCache; - this.path = path; - initialize(); - } - - public ModelManager(boolean enableDefaultModelInfoLoading) { - this.namespaceManager = new NamespaceManager(); - this.globalCache = new ConcurrentHashMap<>(); - this.enableDefaultModelInfoLoading = enableDefaultModelInfoLoading; - initialize(); - } - - public ModelManager(boolean enableDefaultModelInfoLoading, Map globalCache) { - requireNonNull(globalCache, "globalCache can not be null."); - this.namespaceManager = new NamespaceManager(); - this.globalCache = globalCache; - this.enableDefaultModelInfoLoading = enableDefaultModelInfoLoading; - initialize(); - } - - public ModelManager(boolean enableDefaultModelInfoLoading, Path path) { - this.namespaceManager = new NamespaceManager(); - this.globalCache = new ConcurrentHashMap<>(); - this.path = path; - this.enableDefaultModelInfoLoading = enableDefaultModelInfoLoading; - initialize(); - } - - public ModelManager(boolean enableDefaultModelInfoLoading, Path path, Map globalCache) { - requireNonNull(globalCache, "globalCache can not be null."); - this.namespaceManager = new NamespaceManager(); - this.globalCache = globalCache; - this.path = path; - this.enableDefaultModelInfoLoading = enableDefaultModelInfoLoading; - initialize(); - } - - public ModelManager(NamespaceManager namespaceManager) { - this.namespaceManager = namespaceManager; - this.globalCache = new ConcurrentHashMap<>(); - initialize(); - } - - public ModelManager(NamespaceManager namespaceManager, Map globalCache) { - requireNonNull(globalCache, "globalCache can not be null."); - this.namespaceManager = namespaceManager; - this.globalCache = globalCache; - initialize(); - } - - public ModelManager(NamespaceManager namespaceManager, Path path) { - this.namespaceManager = namespaceManager; - this.globalCache = new ConcurrentHashMap<>(); - this.path = path; - initialize(); - } - - public ModelManager(NamespaceManager namespaceManager, Path path, Map globalCache) { - requireNonNull(globalCache, "globalCache can not be null."); - this.namespaceManager = namespaceManager; - this.globalCache = globalCache; - this.path = path; - initialize(); - } - - public ModelManager(NamespaceManager namespaceManager, boolean enableDefaultModelInfoLoading) { - this.namespaceManager = namespaceManager; - this.globalCache = new ConcurrentHashMap<>(); - this.enableDefaultModelInfoLoading = enableDefaultModelInfoLoading; - initialize(); - } - - public ModelManager( - NamespaceManager namespaceManager, - boolean enableDefaultModelInfoLoading, - Map globalCache) { - requireNonNull(globalCache, "globalCache can not be null."); - this.namespaceManager = namespaceManager; - this.globalCache = globalCache; - this.enableDefaultModelInfoLoading = enableDefaultModelInfoLoading; - initialize(); - } - - public ModelManager(NamespaceManager namespaceManager, boolean enableDefaultModelInfoLoading, Path path) { - this.namespaceManager = namespaceManager; - this.globalCache = new ConcurrentHashMap<>(); - this.path = path; - this.enableDefaultModelInfoLoading = enableDefaultModelInfoLoading; - initialize(); - } - - public ModelManager( - NamespaceManager namespaceManager, - boolean enableDefaultModelInfoLoading, - Path path, - Map globalCache) { - requireNonNull(globalCache, "globalCache can not be null."); - this.namespaceManager = namespaceManager; - this.globalCache = globalCache; - this.path = path; - this.enableDefaultModelInfoLoading = enableDefaultModelInfoLoading; - initialize(); - } - - private void initialize() { - modelInfoLoader = new ModelInfoLoader(); - modelInfoLoader.setNamespaceManager(namespaceManager); - if (path != null) { - modelInfoLoader.setPath(path); - } - } - - public NamespaceManager getNamespaceManager() { - return this.namespaceManager; - } - - public ModelInfoLoader getModelInfoLoader() { - return this.modelInfoLoader; - } - - public boolean isDefaultModelInfoLoadingEnabled() { - return enableDefaultModelInfoLoading; - } - - /** - * The global cache is by @{org.hl7.cql.model.ModelIdentifier}, while the local cache is by name. This is because the translator expects the ModelManager to only permit loading - * of a single version of a given Model in a single translation context, while the global cache is for all versions of Models - */ - public Map getGlobalCache() { - return this.globalCache; - } - - /* - A "well-known" model name is one that is allowed to resolve without a namespace in a namespace-aware context - */ - public boolean isWellKnownModelName(String unqualifiedIdentifier) { - if (unqualifiedIdentifier == null) { - return false; - } - - switch (unqualifiedIdentifier) { - case "FHIR": - case "QDM": - case "USCore": - case "QICore": - case "QUICK": - return true; - default: - return false; - } - } - - private Model buildModel(ModelIdentifier identifier) { - Model model = null; - if (identifier == null) { - throw new IllegalArgumentException("Model identifier is required"); - } - if (identifier.getId() == null || identifier.getId().equals("")) { - throw new IllegalArgumentException("Model identifier Id is required"); - } - String modelPath = NamespaceManager.getPath(identifier.getSystem(), identifier.getId()); - pushLoading(modelPath); - try { - ModelInfo modelInfo = modelInfoLoader.getModelInfo(identifier); - if (identifier.getId().equals("System")) { - model = new SystemModel(modelInfo); - } else { - model = new Model(modelInfo, this); - } - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException(String.format( - "Could not load model information for model %s, version %s.", - identifier.getId(), identifier.getVersion())); - } finally { - popLoading(modelPath); - } - - return model; - } - - private void pushLoading(String modelId) { - if (loadingModels.contains(modelId)) { - throw new IllegalArgumentException(String.format("Circular model reference %s", modelId)); - } - loadingModels.add(modelId); - } - - private void popLoading(String modelId) { - loadingModels.remove(modelId); - } - - public Model resolveModel(String modelName) { - return resolveModel(modelName, null); - } - - public Model resolveModel(String modelName, String version) { - return resolveModel(new ModelIdentifier().withId(modelName).withVersion(version)); - } - - /** - * @param modelIdentifier the identifier of the model to resolve - * @return the model - * @throws IllegalArgumentException if an attempt to resolve multiple versions of the same model is made or if the model that resolved is not compatible with the requested version - */ - public Model resolveModel(ModelIdentifier modelIdentifier) { - String modelPath = NamespaceManager.getPath(modelIdentifier.getSystem(), modelIdentifier.getId()); - Model model = models.get(modelPath); - if (model != null) { - checkModelVersion(modelIdentifier, model); - } - - if (model == null && this.globalCache.containsKey(modelIdentifier)) { - model = this.globalCache.get(modelIdentifier); - models.put(modelPath, model); - modelsByUri.put(model.getModelInfo().getUrl(), model); - } - - if (model == null) { - model = buildModel(modelIdentifier); - this.globalCache.put(modelIdentifier, model); - checkModelVersion(modelIdentifier, model); - models.put(modelPath, model); - modelsByUri.put(model.getModelInfo().getUrl(), model); - } - - return model; - } - - private void checkModelVersion(ModelIdentifier modelIdentifier, Model model) { - if (modelIdentifier.getVersion() != null - && !modelIdentifier.getVersion().equals(model.getModelInfo().getVersion())) { - throw new IllegalArgumentException(String.format( - "Could not load model information for model %s, version %s because version %s is already loaded.", - modelIdentifier.getId(), - modelIdentifier.getVersion(), - model.getModelInfo().getVersion())); - } - } - - public Model resolveModelByUri(String namespaceUri) { - Model model = modelsByUri.get(namespaceUri); - if (model == null) { - throw new IllegalArgumentException( - String.format("Could not resolve model with namespace %s", namespaceUri)); - } - - return model; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelResolver.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelResolver.java deleted file mode 100644 index 48863412d..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ModelResolver.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import org.cqframework.cql.cql2elm.model.Model; - -public interface ModelResolver { - Model getModel(String modelName); -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/PathAware.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/PathAware.java deleted file mode 100644 index b89f2aaed..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/PathAware.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.nio.file.Path; - -public interface PathAware { - void setPath(Path path); -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/PriorityLibrarySourceLoader.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/PriorityLibrarySourceLoader.java deleted file mode 100644 index 6eb900831..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/PriorityLibrarySourceLoader.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.InputStream; -import java.nio.file.Path; -import java.util.*; -import org.hl7.cql.model.NamespaceAware; -import org.hl7.cql.model.NamespaceManager; -import org.hl7.elm.r1.VersionedIdentifier; - -/** - * Used by LibraryManager to manage a set of library source providers that - * resolve library includes within CQL. Package private since its not intended - * to be used outside the context of the instantiating LibraryManager instance. - */ -public class PriorityLibrarySourceLoader implements LibrarySourceLoader, NamespaceAware, PathAware { - private final List providers = new ArrayList<>(); - private boolean initialized = false; - - @Override - public void registerProvider(LibrarySourceProvider provider) { - if (provider == null) { - throw new IllegalArgumentException("provider is null."); - } - - if (provider instanceof NamespaceAware) { - ((NamespaceAware) provider).setNamespaceManager(namespaceManager); - } - - if (path != null && provider instanceof PathAware) { - ((PathAware) provider).setPath(path); - } - - providers.add(provider); - } - - private Path path; - - public void setPath(Path path) { - if (path == null || !path.toFile().isDirectory()) { - throw new IllegalArgumentException(String.format("path '%s' is not a valid directory", path)); - } - - this.path = path; - - for (LibrarySourceProvider provider : getProviders()) { - if (provider instanceof PathAware) { - ((PathAware) provider).setPath(path); - } - } - } - - @Override - public void clearProviders() { - providers.clear(); - initialized = false; - } - - private List getProviders() { - if (!initialized) { - initialized = true; - for (Iterator it = LibrarySourceProviderFactory.providers(false); it.hasNext(); ) { - LibrarySourceProvider provider = it.next(); - registerProvider(provider); - } - } - - return providers; - } - - @Override - public InputStream getLibrarySource(VersionedIdentifier libraryIdentifier) { - return getLibraryContent(libraryIdentifier, LibraryContentType.CQL); - } - - @Override - public InputStream getLibraryContent(VersionedIdentifier libraryIdentifier, LibraryContentType type) { - - validateInput(libraryIdentifier, type); - InputStream content = null; - for (LibrarySourceProvider provider : getProviders()) { - content = provider.getLibraryContent(libraryIdentifier, type); - - if (content != null) { - return content; - } - } - - return null; - } - - private NamespaceManager namespaceManager; - - @Override - public void setNamespaceManager(NamespaceManager namespaceManager) { - this.namespaceManager = namespaceManager; - - for (LibrarySourceProvider provider : getProviders()) { - if (provider instanceof NamespaceAware) { - ((NamespaceAware) provider).setNamespaceManager(namespaceManager); - } - } - } - - private void validateInput(VersionedIdentifier libraryIdentifier, LibraryContentType type) { - if (type == null) { - throw new IllegalArgumentException("libraryContentType is null."); - } - - if (libraryIdentifier == null) { - throw new IllegalArgumentException("libraryIdentifier is null."); - } - - if (libraryIdentifier.getId() == null || libraryIdentifier.getId().equals("")) { - throw new IllegalArgumentException("libraryIdentifier Id is null."); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ResultWithPossibleError.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ResultWithPossibleError.java deleted file mode 100644 index e7dc56e7b..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/ResultWithPossibleError.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.cqframework.cql.cql2elm; - -/** - * Indicate either a populated result or the presence of an error that prevented the result from being created. - */ -public class ResultWithPossibleError { - private final T underlyingThingOrNull; - - public static ResultWithPossibleError withError() { - return new ResultWithPossibleError<>(null); - } - - public static ResultWithPossibleError withTypeSpecifier(T underlyingThingOrNull) { - return new ResultWithPossibleError<>(underlyingThingOrNull); - } - - private ResultWithPossibleError(T namedTypeSpecifierOrNull) { - this.underlyingThingOrNull = namedTypeSpecifierOrNull; - } - - public boolean hasError() { - return (underlyingThingOrNull == null); - } - - public T getUnderlyingResultIfExists() { - if (hasError()) { - throw new IllegalArgumentException("Should have called hasError() first"); - } - - return underlyingThingOrNull; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/StringEscapeUtils.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/StringEscapeUtils.java deleted file mode 100644 index d9529d83c..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/StringEscapeUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.HashMap; -import java.util.Map; -import org.apache.commons.text.translate.*; - -/** - * Created by Bryn on 3/22/2017. - */ -@SuppressWarnings("checkstyle:methodname") -public class StringEscapeUtils { - - /** - * Mapping to escape the CQL control characters. - * - * Namely: {@code \n \t \f \r} - * @return the mapping table - */ - public static Map CQL_CTRL_CHARS_ESCAPE() { - return new HashMap(CQL_CTRL_CHARS_ESCAPE); - } - - private static final Map CQL_CTRL_CHARS_ESCAPE = - new HashMap() { - { - put("\n", "\\n"); - put("\t", "\\t"); - put("\f", "\\f"); - put("\r", "\\r"); - } - }; - - /** - * Reverse of {@link #CQL_CTRL_CHARS_ESCAPE()} for unescaping purposes. - * @return the mapping table - */ - public static Map CQL_CTRL_CHARS_UNESCAPE() { - return new HashMap(CQL_CTRL_CHARS_UNESCAPE); - } - - private static final Map CQL_CTRL_CHARS_UNESCAPE = - new HashMap() { - { - put("\\n", "\n"); - put("\\t", "\t"); - put("\\f", "\f"); - put("\\r", "\r"); - } - }; - - public static final CharSequenceTranslator ESCAPE_CQL = new LookupTranslator( - new HashMap() { - { - put("\"", "\\\""); - put("\\", "\\\\"); - put("'", "\\'"); - } - }) - .with(new LookupTranslator(CQL_CTRL_CHARS_ESCAPE())) - .with(JavaUnicodeEscaper.outsideOf(32, 0x7f)); - - public static final CharSequenceTranslator UNESCAPE_CQL = new AggregateTranslator( - new UnicodeUnescaper(), - new LookupTranslator(CQL_CTRL_CHARS_UNESCAPE()), - new LookupTranslator(new HashMap() { - { - put("\\\\", "\\"); - put("\\\"", "\""); - put("\\'", "\'"); - put("\\`", "`"); - put("\\/", "/"); - put("\\", ""); - } - })); - - public static final String escapeCql(final String input) { - return ESCAPE_CQL.translate(input); - } - - public static final String unescapeCql(final String input) { - // CQL supports the following escape characters in both strings and identifiers: - // \" - double-quote - // \' - single-quote - // \` - backtick - // \\ - backslash - // \/ - slash - // \f - form feed - // \n - newline - // \r - carriage return - // \t - tab - // \\u - unicode hex representation (e.g. \u0020) - return UNESCAPE_CQL.translate(input); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/StringLibrarySourceProvider.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/StringLibrarySourceProvider.java deleted file mode 100644 index 46b68abce..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/StringLibrarySourceProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * This class implements the LibrarySourceProvider API, using a set of strings representing CQL - * library content as a source. - */ -public class StringLibrarySourceProvider implements LibrarySourceProvider { - - private List libraries; - - public StringLibrarySourceProvider(List libraries) { - this.libraries = libraries; - } - - @Override - public InputStream getLibrarySource(org.hl7.elm.r1.VersionedIdentifier libraryIdentifier) { - String id = libraryIdentifier.getId(); - String version = libraryIdentifier.getVersion(); - - var maybeQuotedIdPattern = "(\"" + id + "\"|" + id + ")"; - - String matchText = "(?s).*library\\s+\"?" + maybeQuotedIdPattern; - if (version != null) { - matchText += ("\\s+version\\s+'" + version + "'\\s+(?s).*"); - } else { - matchText += "\\s+(?s).*"; - } - - var matches = new ArrayList(); - - for (String library : this.libraries) { - - if (library.matches(matchText)) { - matches.add(library); - } - } - - if (matches.size() > 1) { - throw new IllegalArgumentException(String.format( - "Multiple libraries for id : %s resolved.%nEnsure that there are no duplicates in the input set.", - libraryIdentifier.toString())); - } - - return matches.size() == 1 ? new ByteArrayInputStream(matches.get(0).getBytes()) : null; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemFunctionResolver.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemFunctionResolver.java deleted file mode 100644 index 39376caf5..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemFunctionResolver.java +++ /dev/null @@ -1,767 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.ArrayList; -import java.util.List; -import org.cqframework.cql.cql2elm.model.Conversion; -import org.cqframework.cql.cql2elm.model.Invocation; -import org.cqframework.cql.cql2elm.model.PropertyResolution; -import org.cqframework.cql.cql2elm.model.SystemModel; -import org.cqframework.cql.cql2elm.model.invocation.*; -import org.cqframework.cql.elm.IdObjectFactory; -import org.hl7.elm.r1.*; - -public class SystemFunctionResolver { - private final IdObjectFactory of; - private final LibraryBuilder builder; - - public SystemFunctionResolver(LibraryBuilder builder, IdObjectFactory of) { - this.builder = builder; - this.of = builder.getObjectFactory(); - } - - public Invocation resolveSystemFunction(FunctionRef fun) { - if (fun.getLibraryName() == null || "System".equals(fun.getLibraryName())) { - switch (fun.getName()) { - // Aggregate Functions - case "AllTrue": - case "AnyTrue": - case "Avg": - case "Count": - case "GeometricMean": - case "Max": - case "Median": - case "Min": - case "Mode": - case "PopulationStdDev": - case "PopulationVariance": - case "Product": - case "StdDev": - case "Sum": - case "Variance": { - return resolveAggregate(fun); - } - - // Arithmetic Functions - case "Abs": - case "Ceiling": - case "Exp": - case "Floor": - case "Ln": - case "Negate": - case "Precision": - case "Predecessor": - case "Successor": - case "Truncate": { - return resolveUnary(fun); - } - - case "HighBoundary": - case "Log": - case "LowBoundary": - case "Modulo": - case "Power": - case "TruncatedDivide": { - return resolveBinary(fun); - } - - case "Round": { - return resolveRound(fun); - } - - // Clinical Functions - case "AgeInYears": - case "AgeInMonths": { - checkNumberOfOperands(fun, 0); - return resolveCalculateAge( - builder.enforceCompatible( - getPatientBirthDateProperty(), builder.resolveTypeName("System", "Date")), - resolveAgeRelatedFunctionPrecision(fun)); - } - - case "AgeInWeeks": - case "AgeInDays": - case "AgeInHours": - case "AgeInMinutes": - case "AgeInSeconds": - case "AgeInMilliseconds": { - checkNumberOfOperands(fun, 0); - return resolveCalculateAge( - builder.ensureCompatible( - getPatientBirthDateProperty(), builder.resolveTypeName("System", "DateTime")), - resolveAgeRelatedFunctionPrecision(fun)); - } - - case "AgeInYearsAt": - case "AgeInMonthsAt": - case "AgeInWeeksAt": - case "AgeInDaysAt": { - checkNumberOfOperands(fun, 1); - List ops = new ArrayList<>(); - Expression op = fun.getOperand().get(0); - // If the op is not a Date or DateTime, attempt to get it to convert it to a Date or DateTime - // If the op can be converted to both a Date and a DateTime, throw an ambiguous error - if (!(op.getResultType().isSubTypeOf(builder.resolveTypeName("System", "Date")) - || op.getResultType().isSubTypeOf(builder.resolveTypeName("System", "DateTime")))) { - Conversion dateConversion = builder.findConversion( - op.getResultType(), builder.resolveTypeName("System", "Date"), true, false); - Conversion dateTimeConversion = builder.findConversion( - op.getResultType(), builder.resolveTypeName("System", "DateTime"), true, false); - if (dateConversion != null && dateTimeConversion != null) { - if (dateConversion.getScore() == dateTimeConversion.getScore()) { - // ERROR - throw new IllegalArgumentException(String.format( - "Ambiguous implicit conversion from %s to %s or %s.", - op.getResultType().toString(), - dateConversion.getToType().toString(), - dateTimeConversion.getToType().toString())); - } else if (dateConversion.getScore() < dateTimeConversion.getScore()) { - op = builder.convertExpression(op, dateConversion); - } else { - op = builder.convertExpression(op, dateTimeConversion); - } - } else if (dateConversion != null) { - op = builder.convertExpression(op, dateConversion); - } else if (dateTimeConversion != null) { - op = builder.convertExpression(op, dateTimeConversion); - } else { - // ERROR - throw new IllegalArgumentException(String.format( - "Could not resolve call to operator %s with argument of type %s.", - fun.getName(), op.getResultType().toString())); - } - } - ops.add(builder.enforceCompatible(getPatientBirthDateProperty(), op.getResultType())); - ops.add(op); - return resolveCalculateAgeAt(ops, resolveAgeRelatedFunctionPrecision(fun)); - } - - case "AgeInHoursAt": - case "AgeInMinutesAt": - case "AgeInSecondsAt": - case "AgeInMillisecondsAt": { - List ops = new ArrayList<>(); - ops.add(getPatientBirthDateProperty()); - ops.addAll(fun.getOperand()); - return resolveCalculateAgeAt(ops, resolveAgeRelatedFunctionPrecision(fun)); - } - - case "CalculateAgeInYears": - case "CalculateAgeInMonths": - case "CalculateAgeInWeeks": - case "CalculateAgeInDays": - case "CalculateAgeInHours": - case "CalculateAgeInMinutes": - case "CalculateAgeInSeconds": - case "CalculateAgeInMilliseconds": { - checkNumberOfOperands(fun, 1); - return resolveCalculateAge(fun.getOperand().get(0), resolveAgeRelatedFunctionPrecision(fun)); - } - - case "CalculateAgeInYearsAt": - case "CalculateAgeInMonthsAt": - case "CalculateAgeInWeeksAt": - case "CalculateAgeInDaysAt": - case "CalculateAgeInHoursAt": - case "CalculateAgeInMinutesAt": - case "CalculateAgeInSecondsAt": - case "CalculateAgeInMillisecondsAt": { - return resolveCalculateAgeAt(fun.getOperand(), resolveAgeRelatedFunctionPrecision(fun)); - } - - // DateTime Functions - case "DateTime": { - return resolveDateTime(fun); - } - - case "Date": { - return resolveDate(fun); - } - - case "Time": { - return resolveTime(fun); - } - - case "Now": - case "now": { - return resolveNow(fun); - } - - case "Today": - case "today": { - return resolveToday(fun); - } - - case "TimeOfDay": - case "timeOfDay": { - return resolveTimeOfDay(fun); - } - - // List Functions - case "IndexOf": { - return resolveIndexOf(fun); - } - - case "First": { - return resolveFirst(fun); - } - - case "Last": { - return resolveLast(fun); - } - - case "Skip": { - return resolveSkip(fun); - } - - case "Take": { - return resolveTake(fun); - } - - case "Tail": { - return resolveTail(fun); - } - - case "Contains": - case "Expand": - case "In": - case "Includes": - case "IncludedIn": - case "ProperIncludes": - case "ProperIncludedIn": { - return resolveBinary(fun); - } - - case "Distinct": - case "Exists": - case "Flatten": - case "Collapse": - case "SingletonFrom": - case "ExpandValueSet": { - return resolveUnary(fun); - } - - case "Coalesce": - case "Intersect": - case "Union": - case "Except": { - return resolveNary(fun); - } - - case "IsNull": - case "IsTrue": - case "IsFalse": { - return resolveUnary(fun); - } - - // Overloaded Functions - case "Length": - case "Width": - case "Size": { - return resolveUnary(fun); - } - - // String Functions - case "Indexer": - case "StartsWith": - case "EndsWith": - case "Matches": { - return resolveBinary(fun); - } - - case "ReplaceMatches": { - return resolveTernary(fun); - } - - case "Concatenate": { - return resolveNary(fun); - } - - case "Combine": { - return resolveCombine(fun); - } - - case "Split": { - return resolveSplit(fun); - } - - case "SplitOnMatches": { - return resolveSplitOnMatches(fun); - } - - case "Upper": - case "Lower": { - return resolveUnary(fun); - } - - case "PositionOf": { - return resolvePositionOf(fun); - } - - case "LastPositionOf": { - return resolveLastPositionOf(fun); - } - - case "Substring": { - return resolveSubstring(fun); - } - - // Logical Functions - case "Not": { - return resolveUnary(fun); - } - - case "And": - case "Or": - case "Xor": - case "Implies": { - return resolveBinary(fun); - } - - // Type Functions - case "ConvertsToString": - case "ConvertsToBoolean": - case "ConvertsToInteger": - case "ConvertsToLong": - case "ConvertsToDecimal": - case "ConvertsToDateTime": - case "ConvertsToDate": - case "ConvertsToTime": - case "ConvertsToQuantity": - case "ConvertsToRatio": - case "ToString": - case "ToBoolean": - case "ToInteger": - case "ToLong": - case "ToDecimal": - case "ToDateTime": - case "ToDate": - case "ToTime": - case "ToQuantity": - case "ToRatio": - case "ToConcept": - case "ToChars": { - return resolveUnary(fun); - } - - // Quantity Conversion - case "CanConvertQuantity": - case "ConvertQuantity": { - return resolveBinary(fun); - } - - // Comparison Functions - case "Equal": - case "NotEqual": - case "Greater": - case "GreaterOrEqual": - case "Less": - case "LessOrEqual": - case "Equivalent": { - return resolveBinary(fun); - } - - // Error Functions - case "Message": - return resolveMessage(fun); - } - } - - return null; - } - - // Age-Related Function Support - - private UnaryExpressionInvocation resolveCalculateAge(Expression e, DateTimePrecision p) { - CalculateAge operator = of.createCalculateAge().withPrecision(p).withOperand(e); - - UnaryExpressionInvocation invocation = new UnaryExpressionInvocation(operator); - builder.resolveInvocation("System", "CalculateAge", invocation); - return invocation; - } - - private BinaryExpressionInvocation resolveCalculateAgeAt(List e, DateTimePrecision p) { - CalculateAgeAt operator = of.createCalculateAgeAt().withPrecision(p).withOperand(e); - - BinaryExpressionInvocation invocation = new BinaryExpressionInvocation(operator); - builder.resolveInvocation("System", "CalculateAgeAt", invocation); - return invocation; - } - - private static DateTimePrecision resolveAgeRelatedFunctionPrecision(FunctionRef fun) { - String name = fun.getName(); - if (name.contains("Years")) { - return DateTimePrecision.YEAR; - } else if (name.contains("Months")) { - return DateTimePrecision.MONTH; - } else if (name.contains("Weeks")) { - return DateTimePrecision.WEEK; - } else if (name.contains("Days")) { - return DateTimePrecision.DAY; - } else if (name.contains("Hours")) { - return DateTimePrecision.HOUR; - } else if (name.contains("Minutes")) { - return DateTimePrecision.MINUTE; - } else if (name.contains("Second")) { - return DateTimePrecision.SECOND; - } else if (name.contains("Milliseconds")) { - return DateTimePrecision.MILLISECOND; - } - - throw new IllegalArgumentException(String.format("Unknown precision '%s'.", name)); - } - - private Expression getPatientBirthDateProperty() { - Expression source = builder.resolveIdentifier("Patient", true); - String birthDateProperty = builder.getDefaultModel().getModelInfo().getPatientBirthDatePropertyName(); - // If the property has a qualifier, resolve it as a path (without model mapping) - if (birthDateProperty.indexOf('.') >= 1) { - Property property = of.createProperty().withSource(source).withPath(birthDateProperty); - property.setResultType(builder.resolvePath(source.getResultType(), property.getPath())); - return property; - } else { - PropertyResolution resolution = builder.resolveProperty(source.getResultType(), birthDateProperty); - Expression result = - builder.buildProperty(source, resolution.getName(), resolution.isSearch(), resolution.getType()); - result = builder.applyTargetMap(result, resolution.getTargetMap()); - return result; - } - } - - // Arithmetic Function Support - - private RoundInvocation resolveRound(FunctionRef fun) { - if (fun.getOperand().isEmpty() || fun.getOperand().size() > 2) { - throw new IllegalArgumentException( - "Could not resolve call to system operator Round. Expected 1 or 2 arguments."); - } - final Round round = of.createRound().withOperand(fun.getOperand().get(0)); - if (fun.getOperand().size() == 2) { - round.setPrecision(fun.getOperand().get(1)); - } - RoundInvocation invocation = new RoundInvocation(round); - builder.resolveInvocation("System", "Round", new RoundInvocation(round)); - return invocation; - } - - // DateTime Function Support - - private DateTimeInvocation resolveDateTime(FunctionRef fun) { - final DateTime dt = of.createDateTime(); - DateTimeInvocation.setDateTimeFieldsFromOperands(dt, fun.getOperand()); - DateTimeInvocation invocation = new DateTimeInvocation(dt); - builder.resolveInvocation("System", "DateTime", invocation); - return invocation; - } - - private DateInvocation resolveDate(FunctionRef fun) { - final Date d = of.createDate(); - DateInvocation.setDateFieldsFromOperands(d, fun.getOperand()); - DateInvocation invocation = new DateInvocation(d); - builder.resolveInvocation("System", "Date", invocation); - return invocation; - } - - private TimeInvocation resolveTime(FunctionRef fun) { - final Time t = of.createTime(); - TimeInvocation.setTimeFieldsFromOperands(t, fun.getOperand()); - TimeInvocation invocation = new TimeInvocation(t); - builder.resolveInvocation("System", "Time", invocation); - return invocation; - } - - private ZeroOperandExpressionInvocation resolveNow(FunctionRef fun) { - checkNumberOfOperands(fun, 0); - final Now now = of.createNow(); - ZeroOperandExpressionInvocation invocation = new ZeroOperandExpressionInvocation(now); - builder.resolveInvocation("System", "Now", invocation); - return invocation; - } - - private ZeroOperandExpressionInvocation resolveToday(FunctionRef fun) { - checkNumberOfOperands(fun, 0); - final Today today = of.createToday(); - ZeroOperandExpressionInvocation invocation = new ZeroOperandExpressionInvocation(today); - builder.resolveInvocation("System", "Today", invocation); - return invocation; - } - - private ZeroOperandExpressionInvocation resolveTimeOfDay(FunctionRef fun) { - checkNumberOfOperands(fun, 0); - final TimeOfDay timeOfDay = of.createTimeOfDay(); - ZeroOperandExpressionInvocation invocation = new ZeroOperandExpressionInvocation(timeOfDay); - builder.resolveInvocation("System", "TimeOfDay", invocation); - return invocation; - } - - // List Function Support - - private IndexOfInvocation resolveIndexOf(FunctionRef fun) { - checkNumberOfOperands(fun, 2); - final IndexOf indexOf = of.createIndexOf(); - indexOf.setSource(fun.getOperand().get(0)); - indexOf.setElement(fun.getOperand().get(1)); - IndexOfInvocation invocation = new IndexOfInvocation(indexOf); - builder.resolveInvocation("System", "IndexOf", invocation); - return invocation; - } - - private FirstInvocation resolveFirst(FunctionRef fun) { - checkNumberOfOperands(fun, 1); - final First first = of.createFirst(); - first.setSource(fun.getOperand().get(0)); - FirstInvocation invocation = new FirstInvocation(first); - builder.resolveInvocation("System", "First", invocation); - return invocation; - } - - private LastInvocation resolveLast(FunctionRef fun) { - checkNumberOfOperands(fun, 1); - final Last last = of.createLast(); - last.setSource(fun.getOperand().get(0)); - LastInvocation invocation = new LastInvocation(last); - builder.resolveInvocation("System", "Last", invocation); - return invocation; - } - - private SkipInvocation resolveSkip(FunctionRef fun) { - checkNumberOfOperands(fun, 2); - Slice slice = of.createSlice(); - slice.setSource(fun.getOperand().get(0)); - slice.setStartIndex(fun.getOperand().get(1)); - slice.setEndIndex(builder.buildNull(fun.getOperand().get(1).getResultType())); - SkipInvocation invocation = new SkipInvocation(slice); - builder.resolveInvocation("System", "Skip", invocation); - return invocation; - } - - private TakeInvocation resolveTake(FunctionRef fun) { - checkNumberOfOperands(fun, 2); - Slice slice = of.createSlice(); - slice.setSource(fun.getOperand().get(0)); - slice.setStartIndex(builder.createLiteral(0)); - Coalesce coalesce = of.createCoalesce().withOperand(fun.getOperand().get(1), builder.createLiteral(0)); - NaryExpressionInvocation naryInvocation = new NaryExpressionInvocation(coalesce); - builder.resolveInvocation("System", "Coalesce", naryInvocation); - slice.setEndIndex(coalesce); - TakeInvocation invocation = new TakeInvocation(slice); - builder.resolveInvocation("System", "Take", invocation); - return invocation; - } - - private TailInvocation resolveTail(FunctionRef fun) { - checkNumberOfOperands(fun, 1); - Slice slice = of.createSlice(); - slice.setSource(fun.getOperand().get(0)); - slice.setStartIndex(builder.createLiteral(1)); - slice.setEndIndex(builder.buildNull(builder.resolveTypeName("System", "Integer"))); - TailInvocation invocation = new TailInvocation(slice); - builder.resolveInvocation("System", "Tail", invocation); - return invocation; - } - - // String Function Support - - private CombineInvocation resolveCombine(FunctionRef fun) { - if (fun.getOperand().isEmpty() || fun.getOperand().size() > 2) { - throw new IllegalArgumentException( - "Could not resolve call to system operator Combine. Expected 1 or 2 arguments."); - } - final Combine combine = of.createCombine().withSource(fun.getOperand().get(0)); - if (fun.getOperand().size() == 2) { - combine.setSeparator(fun.getOperand().get(1)); - } - CombineInvocation invocation = new CombineInvocation(combine); - builder.resolveInvocation("System", "Combine", invocation); - return invocation; - } - - private SplitInvocation resolveSplit(FunctionRef fun) { - checkNumberOfOperands(fun, 2); - final Split split = of.createSplit() - .withStringToSplit(fun.getOperand().get(0)) - .withSeparator(fun.getOperand().get(1)); - SplitInvocation invocation = new SplitInvocation(split); - builder.resolveInvocation("System", "Split", invocation); - return invocation; - } - - private SplitOnMatchesInvocation resolveSplitOnMatches(FunctionRef fun) { - checkNumberOfOperands(fun, 2); - final SplitOnMatches splitOnMatches = of.createSplitOnMatches() - .withStringToSplit(fun.getOperand().get(0)) - .withSeparatorPattern(fun.getOperand().get(1)); - SplitOnMatchesInvocation invocation = new SplitOnMatchesInvocation(splitOnMatches); - builder.resolveInvocation("System", "SplitOnMatches", invocation); - return invocation; - } - - private PositionOfInvocation resolvePositionOf(FunctionRef fun) { - checkNumberOfOperands(fun, 2); - final PositionOf pos = of.createPositionOf() - .withPattern(fun.getOperand().get(0)) - .withString(fun.getOperand().get(1)); - PositionOfInvocation invocation = new PositionOfInvocation(pos); - builder.resolveInvocation("System", "PositionOf", invocation); - return invocation; - } - - private LastPositionOfInvocation resolveLastPositionOf(FunctionRef fun) { - checkNumberOfOperands(fun, 2); - final LastPositionOf pos = of.createLastPositionOf() - .withPattern(fun.getOperand().get(0)) - .withString(fun.getOperand().get(1)); - LastPositionOfInvocation invocation = new LastPositionOfInvocation(pos); - builder.resolveInvocation("System", "LastPositionOf", invocation); - return invocation; - } - - private SubstringInvocation resolveSubstring(FunctionRef fun) { - if (fun.getOperand().size() < 2 || fun.getOperand().size() > 3) { - throw new IllegalArgumentException( - "Could not resolve call to system operator Substring. Expected 2 or 3 arguments."); - } - final Substring substring = of.createSubstring() - .withStringToSub(fun.getOperand().get(0)) - .withStartIndex(fun.getOperand().get(1)); - if (fun.getOperand().size() == 3) { - substring.setLength(fun.getOperand().get(2)); - } - SubstringInvocation invocation = new SubstringInvocation(substring); - builder.resolveInvocation("System", "Substring", invocation); - return invocation; - } - - // Error Functions - private MessageInvocation resolveMessage(FunctionRef fun) { - if (fun.getOperand().size() != 5) { - throw new IllegalArgumentException( - "Could not resolve call to system operator Message. Expected 5 arguments."); - } - - Message message = of.createMessage() - .withSource(fun.getOperand().get(0)) - .withCondition(fun.getOperand().get(1)) - .withCode(fun.getOperand().get(2)) - .withSeverity(fun.getOperand().get(3)) - .withMessage(fun.getOperand().get(4)); - - MessageInvocation invocation = new MessageInvocation(message); - builder.resolveInvocation("System", "Message", invocation); - return invocation; - } - - // Type Functions - - private ConvertInvocation resolveConvert(FunctionRef fun) { - checkNumberOfOperands(fun, 1); - final Convert convert = of.createConvert().withOperand(fun.getOperand().get(0)); - final SystemModel sm = builder.getSystemModel(); - switch (fun.getName()) { - case "ToString": - convert.setToType(builder.dataTypeToQName(sm.getString())); - break; - case "ToBoolean": - convert.setToType(builder.dataTypeToQName(sm.getBoolean())); - break; - case "ToInteger": - convert.setToType(builder.dataTypeToQName(sm.getInteger())); - break; - case "ToLong": - convert.setToType(builder.dataTypeToQName(sm.getLong())); - break; - case "ToDecimal": - convert.setToType(builder.dataTypeToQName(sm.getDecimal())); - break; - case "ToQuantity": - convert.setToType(builder.dataTypeToQName(sm.getQuantity())); - break; - case "ToRatio": - convert.setToType(builder.dataTypeToQName(sm.getRatio())); - break; - case "ToDate": - convert.setToType(builder.dataTypeToQName(sm.getDate())); - break; - case "ToDateTime": - convert.setToType(builder.dataTypeToQName(sm.getDateTime())); - break; - case "ToTime": - convert.setToType(builder.dataTypeToQName(sm.getTime())); - break; - case "ToConcept": - convert.setToType(builder.dataTypeToQName(sm.getConcept())); - break; - default: - throw new IllegalArgumentException(String.format( - "Could not resolve call to system operator %s. Unknown conversion type.", fun.getName())); - } - ConvertInvocation invocation = new ConvertInvocation(convert); - builder.resolveInvocation("System", fun.getName(), invocation); - return invocation; - } - - // General Function Support - - @SuppressWarnings("unchecked") - private T createExpression(FunctionRef fun) { - try { - return (T) of.getClass().getMethod("create" + fun.getName()).invoke(of); - } catch (Exception e) { - throw new CqlInternalException( - String.format("Could not create instance of Element \"%s\"", fun.getName()), - !fun.getTrackbacks().isEmpty() ? fun.getTrackbacks().get(0) : null, - e); - } - } - - private UnaryExpressionInvocation resolveUnary(FunctionRef fun) { - UnaryExpression operator = createExpression(fun); - checkNumberOfOperands(fun, 1); - operator.setOperand(fun.getOperand().get(0)); - UnaryExpressionInvocation invocation = new UnaryExpressionInvocation(operator); - builder.resolveInvocation("System", fun.getName(), invocation); - return invocation; - } - - private BinaryExpressionInvocation resolveBinary(FunctionRef fun) { - BinaryExpression operator = createExpression(fun); - checkNumberOfOperands(fun, 2); - operator.getOperand().addAll(fun.getOperand()); - BinaryExpressionInvocation invocation = new BinaryExpressionInvocation(operator); - builder.resolveInvocation("System", fun.getName(), invocation); - return invocation; - } - - private TernaryExpressionInvocation resolveTernary(FunctionRef fun) { - TernaryExpression operator = createExpression(fun); - checkNumberOfOperands(fun, 3); - operator.getOperand().addAll(fun.getOperand()); - TernaryExpressionInvocation invocation = new TernaryExpressionInvocation(operator); - builder.resolveInvocation("System", fun.getName(), invocation); - return invocation; - } - - private NaryExpressionInvocation resolveNary(FunctionRef fun) { - NaryExpression operator = createExpression(fun); - operator.getOperand().addAll(fun.getOperand()); - NaryExpressionInvocation invocation = new NaryExpressionInvocation(operator); - builder.resolveInvocation("System", fun.getName(), invocation); - return invocation; - } - - private AggregateExpressionInvocation resolveAggregate(FunctionRef fun) { - AggregateExpression operator = createExpression(fun); - checkNumberOfOperands(fun, 1); - operator.setSource(fun.getOperand().get(0)); - AggregateExpressionInvocation invocation = new AggregateExpressionInvocation(operator); - builder.resolveInvocation("System", fun.getName(), invocation); - return invocation; - } - - private void checkNumberOfOperands(FunctionRef fun, int expectedOperands) { - if (fun.getOperand().size() != expectedOperands) { - throw new IllegalArgumentException(String.format( - "Could not resolve call to system operator %s. Expected %d arguments.", - fun.getName(), expectedOperands)); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemMethodResolver.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemMethodResolver.java deleted file mode 100644 index b8a2c31f2..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/SystemMethodResolver.java +++ /dev/null @@ -1,566 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import org.cqframework.cql.cql2elm.model.QueryContext; -import org.cqframework.cql.elm.IdObjectFactory; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.cql.model.*; -import org.hl7.elm.r1.*; - -/** - * Created by Bryn on 12/27/2016. - */ -public class SystemMethodResolver { - private final IdObjectFactory of; - private final Cql2ElmVisitor visitor; - private final LibraryBuilder builder; - - public SystemMethodResolver(Cql2ElmVisitor visitor, LibraryBuilder builder) { - this.visitor = Objects.requireNonNull(visitor, "visitor required"); - this.builder = Objects.requireNonNull(builder, "builder required"); - this.of = Objects.requireNonNull(builder.getObjectFactory(), "builder must have an object factory"); - } - - private List getParams(Expression target, cqlParser.ParamListContext ctx) { - List params = new ArrayList(); - params.add(target); - if (ctx != null && ctx.expression() != null) { - for (cqlParser.ExpressionContext param : ctx.expression()) { - params.add((Expression) visitor.visit(param)); - } - } - - return params; - } - - private void checkArgumentCount(cqlParser.ParamListContext ctx, String functionName, int expectedCount) { - int actualCount = 0; - if (ctx != null && ctx.expression() != null) { - actualCount = ctx.expression().size(); - } - if (actualCount != expectedCount) { - throw new IllegalArgumentException(String.format( - "Expected %s argument for method %s.", - Integer.valueOf(expectedCount).toString(), functionName)); - } - } - - private AliasedQuerySource enterQueryContext(Expression target) { - QueryContext queryContext = new QueryContext(); - queryContext.setIsImplicit( - true); // Set to an implicit context to allow for implicit resolution of property names - List sources = new ArrayList<>(); - AliasedQuerySource source = - of.createAliasedQuerySource().withExpression(target).withAlias("$this"); - source.setResultType(target.getResultType()); - sources.add(source); - queryContext.addPrimaryQuerySources(sources); - builder.pushQueryContext(queryContext); - return source; - } - - private Query createQuery(AliasedQuerySource source, LetClause let, Expression where, ReturnClause ret) { - QueryContext queryContext = builder.peekQueryContext(); - Collection lets = null; - if (let != null) { - lets = new ArrayList<>(); - lets.add(let); - } - - Query query = of.createQuery() - .withSource(queryContext.getQuerySources()) - .withLet(lets) - .withWhere(where) - .withReturn(ret); - - if (ret != null) { - query.setResultType(ret.getResultType()); - } else { - query.setResultType(source.getResultType()); - } - - return query; - } - - private void exitQueryContext() { - builder.popQueryContext(); - } - - private Query createWhere(Expression target, String functionName, cqlParser.ParamListContext ctx) { - AliasedQuerySource source = enterQueryContext(target); - try { - - checkArgumentCount(ctx, functionName, 1); - Expression where = (Expression) visitor.visit(ctx.expression(0)); - if (visitor.getDateRangeOptimization()) { - where = visitor.optimizeDateRangeInQuery(where, source); - } - - return createQuery(source, null, where, null); - } finally { - exitQueryContext(); - } - } - - // X.ofType(T) === X $this where $this is T - private Expression createOfType(Expression target, String functionName, cqlParser.ParamListContext ctx) { - AliasedQuerySource source = enterQueryContext(target); - try { - checkArgumentCount(ctx, functionName, 1); - Expression typeArgument = null; - builder.pushTypeSpecifierContext(); - try { - typeArgument = (Expression) visitor.visit(ctx.expression(0)); - } finally { - builder.popTypeSpecifierContext(); - } - - if (!(typeArgument instanceof Literal)) { - throw new IllegalArgumentException("Expected literal argument"); - } - - Literal typeLiteral = (Literal) typeArgument; - if (!(DataTypes.equal(typeLiteral.getResultType(), builder.resolveTypeName("System", "String")))) { - throw new IllegalArgumentException("Expected string literal argument"); - } - - String typeSpecifier = ((Literal) typeArgument).getValue(); - DataType isType = builder.resolveTypeSpecifier(typeSpecifier); - - AliasRef thisRef = of.createAliasRef().withName(source.getAlias()); - boolean isSingular = !(source.getResultType() instanceof ListType); - DataType elementType = - isSingular ? source.getResultType() : ((ListType) source.getResultType()).getElementType(); - thisRef.setResultType(elementType); - - Is is = of.createIs().withOperand(thisRef); - if (isType instanceof NamedType) { - is.setIsType(builder.dataTypeToQName(isType)); - } else { - is.setIsTypeSpecifier(builder.dataTypeToTypeSpecifier(isType)); - } - is.setResultType(builder.resolveTypeName("System", "Boolean")); - - return createQuery(source, null, is, null); - } finally { - exitQueryContext(); - } - } - - private Expression createRepeat(Expression target, String functionName, cqlParser.ParamListContext ctx) { - AliasedQuerySource source = enterQueryContext(target); - try { - boolean isSingular = !(source.getResultType() instanceof ListType); - checkArgumentCount(ctx, functionName, 1); - Expression select = (Expression) visitor.visit(ctx.expression(0)); - Repeat repeat = of.createRepeat(); - repeat.setSource(target); - repeat.setElement(select); - repeat.setScope("$this"); - // TODO: This isn't quite right, it glosses over the fact that the type of the result may include the result - // of invoking the element expression on intermediate results - if (isSingular) { - repeat.setResultType(new ListType(select.getResultType())); - } else { - repeat.setResultType(select.getResultType()); - } - - return repeat; - } finally { - exitQueryContext(); - } - } - - private Expression createSelect(Expression target, String functionName, cqlParser.ParamListContext ctx) { - boolean isListResult = false; - boolean isSingular = false; - AliasedQuerySource source = enterQueryContext(target); - try { - isSingular = !(source.getResultType() instanceof ListType); - checkArgumentCount(ctx, functionName, 1); - Expression select = (Expression) visitor.visit(ctx.expression(0)); - QueryContext queryContext = builder.peekQueryContext(); - LetClause let = of.createLetClause().withExpression(select).withIdentifier("$a"); - let.setResultType(select.getResultType()); - queryContext.addLetClause(let); - - isListResult = select.getResultType() instanceof ListType; - QueryLetRef letRef = of.createQueryLetRef().withName("$a"); - letRef.setResultType(select.getResultType()); - List params = new ArrayList<>(); - params.add(letRef); - Expression where = builder.resolveFunction(null, "IsNull", params); - params = new ArrayList<>(); - params.add(where); - where = builder.resolveFunction(null, "Not", params); - - ReturnClause returnClause = of.createReturnClause(); - letRef = of.createQueryLetRef().withName("$a"); - letRef.setResultType(select.getResultType()); - returnClause.setExpression(letRef); - returnClause.setResultType(isSingular ? letRef.getResultType() : new ListType(letRef.getResultType())); - - Query query = createQuery(source, let, where, returnClause); - - if (!isSingular && isListResult) { - params = new ArrayList<>(); - params.add(query); - return builder.resolveFunction(null, "Flatten", params); - } else { - return query; - } - } finally { - exitQueryContext(); - } - } - - private void gatherChildTypes(DataType dataType, boolean recurse, Set dataTypes) { - if (dataType instanceof ClassType) { - for (ClassTypeElement element : ((ClassType) dataType).getElements()) { - DataType elementType = element.getType() instanceof ListType - ? ((ListType) element.getType()).getElementType() - : element.getType(); - dataTypes.add(elementType); - if (recurse) { - gatherChildTypes(elementType, recurse, dataTypes); - } - } - } else if (dataType instanceof TupleType) { - for (TupleTypeElement element : ((TupleType) dataType).getElements()) { - DataType elementType = element.getType() instanceof ListType - ? ((ListType) element.getType()).getElementType() - : element.getType(); - dataTypes.add(elementType); - if (recurse) { - gatherChildTypes(elementType, recurse, dataTypes); - } - } - } else if (dataType instanceof ListType) { - DataType elementType = ((ListType) dataType).getElementType(); - dataTypes.add(elementType); - if (recurse) { - gatherChildTypes(elementType, recurse, dataTypes); - } - } else { - dataTypes.add(builder.resolveTypeName("System.Any")); - } - } - - public Expression resolveMethod( - Expression target, String functionName, cqlParser.ParamListContext ctx, boolean mustResolve) { - switch (functionName) { - case "aggregate": - return builder.resolveFunction(null, "Aggregate", getParams(target, ctx)); - case "abs": - return builder.resolveFunction(null, "Abs", getParams(target, ctx)); - case "all": { - // .all(criteria) resolves as .where(criteria).select(true).allTrue() - Query query = createWhere(target, functionName, ctx); - ReturnClause returnClause = of.createReturnClause(); - returnClause.setExpression(builder.createLiteral(Boolean.valueOf(true))); - if (query.getResultType() instanceof ListType) { - returnClause.setResultType( - new ListType(returnClause.getExpression().getResultType())); - } else { - returnClause.setResultType(returnClause.getExpression().getResultType()); - } - query.setReturn(returnClause); - query.setResultType(returnClause.getResultType()); - - List params = new ArrayList<>(); - params.add(query); - return builder.resolveFunction(null, "AllTrue", params); - } - case "allTrue": - return builder.resolveFunction(null, "AllTrue", getParams(target, ctx)); - case "anyTrue": - return builder.resolveFunction(null, "AnyTrue", getParams(target, ctx)); - case "allFalse": - return builder.resolveFunction(null, "AllFalse", getParams(target, ctx)); - case "anyFalse": - return builder.resolveFunction(null, "AnyFalse", getParams(target, ctx)); - case "ceiling": - return builder.resolveFunction(null, "Ceiling", getParams(target, ctx)); - case "children": { - checkArgumentCount(ctx, functionName, 0); - Children children = of.createChildren(); - children.setSource(target); - Set dataTypes = new java.util.HashSet(); - gatherChildTypes(target.getResultType(), false, dataTypes); - if (dataTypes.size() == 1) { - children.setResultType(new ListType((DataType) dataTypes.toArray()[0])); - } else { - children.setResultType(new ListType(new ChoiceType(dataTypes))); - } - return children; - } - case "combine": { - checkArgumentCount(ctx, functionName, 1); - List elements = new ArrayList<>(); - Expression argument = (Expression) visitor.visit(ctx.expression(0)); - elements.add(target); - elements.add(argument); - DataType elementType = builder.ensureCompatibleTypes(target.getResultType(), argument.getResultType()); - org.hl7.elm.r1.List list = of.createList(); - list.setResultType(new ListType(elementType)); - list.getElement().add(builder.ensureCompatible(target, elementType)); - list.getElement().add(builder.ensureCompatible(argument, elementType)); - ArrayList params = new ArrayList(); - params.add(list); - return builder.resolveFunction(null, "Flatten", params); - } - case "contains": { - checkArgumentCount(ctx, functionName, 1); - List params = new ArrayList(); - Expression argument = (Expression) visitor.visit(ctx.expression(0)); - params.add(argument); - params.add(target); - Expression result = builder.resolveFunction(null, "PositionOf", params); - params = new ArrayList(); - params.add(result); - params.add(builder.createLiteral(0)); - return builder.resolveFunction(null, "GreaterOrEqual", params); - } - case "convertsToBoolean": - return builder.resolveFunction(null, "ConvertsToBoolean", getParams(target, ctx)); - case "convertsToDate": - return builder.resolveFunction(null, "ConvertsToDate", getParams(target, ctx)); - case "convertsToDateTime": - return builder.resolveFunction(null, "ConvertsToDateTime", getParams(target, ctx)); - case "convertsToDecimal": - return builder.resolveFunction(null, "ConvertsToDecimal", getParams(target, ctx)); - case "convertsToInteger": - return builder.resolveFunction(null, "ConvertsToInteger", getParams(target, ctx)); - case "convertsToQuantity": - return builder.resolveFunction(null, "ConvertsToQuantity", getParams(target, ctx)); - case "convertsToString": - return builder.resolveFunction(null, "ConvertsToString", getParams(target, ctx)); - case "convertsToTime": - return builder.resolveFunction(null, "ConvertsToTime", getParams(target, ctx)); - case "count": - return builder.resolveFunction(null, "Count", getParams(target, ctx)); - case "descendents": { - checkArgumentCount(ctx, functionName, 0); - Descendents descendents = of.createDescendents(); - descendents.setSource(target); - Set dataTypes = new java.util.HashSet(); - gatherChildTypes(target.getResultType(), true, dataTypes); - if (dataTypes.size() == 1) { - descendents.setResultType(new ListType((DataType) dataTypes.toArray()[0])); - } else { - descendents.setResultType(new ListType(new ChoiceType(dataTypes))); - } - return descendents; - } - case "distinct": - return builder.resolveFunction(null, "Distinct", getParams(target, ctx)); - case "empty": { - List params = getParams(target, ctx); - Expression exists = builder.resolveFunction(null, "Exists", params); - params = new ArrayList<>(); - params.add(exists); - return builder.resolveFunction(null, "Not", params); - } - case "endsWith": - return builder.resolveFunction(null, "EndsWith", getParams(target, ctx)); - case "exclude": - return builder.resolveFunction(null, "Except", getParams(target, ctx)); - case "exists": { - if (ctx == null || ctx.expression() == null || ctx.expression().isEmpty()) { - List params = getParams(target, ctx); - return builder.resolveFunction(null, "Exists", params); - } else { - // .exists(criteria) resolves as a .where(criteria).exists() - Query query = createWhere(target, functionName, ctx); - List params = new ArrayList<>(); - params.add(query); - return builder.resolveFunction(null, "Exists", params); - } - } - case "exp": - return builder.resolveFunction(null, "Exp", getParams(target, ctx)); - case "first": - return builder.resolveFunction(null, "First", getParams(target, ctx)); - case "floor": - return builder.resolveFunction(null, "Floor", getParams(target, ctx)); - case "hasValue": { - List params = getParams(target, ctx); - Expression isNull = builder.resolveFunction(null, "IsNull", params); - params = new ArrayList<>(); - params.add(isNull); - return builder.resolveFunction(null, "Not", params); - } - case "iif": { - Expression result = target; - List params = null; - if (result.getResultType() instanceof ListType) { - params = new ArrayList<>(); - params.add(result); - result = builder.resolveFunction(null, "SingletonFrom", params); - } - Expression thenExpression = (Expression) visitor.visit(ctx.expression(0)); - Expression elseExpression = - ctx.expression().size() == 2 ? (Expression) visitor.visit(ctx.expression(1)) : of.createNull(); - result = of.createIf() - .withCondition(result) - .withThen(thenExpression) - .withElse(elseExpression); - return visitor.resolveIfThenElse((If) result); - } - case "indexOf": { - checkArgumentCount(ctx, functionName, 1); - List params = new ArrayList(); - Expression argument = (Expression) visitor.visit(ctx.expression(0)); - params.add(argument); - params.add(target); - return builder.resolveFunction(null, "PositionOf", params); - } - case "intersect": - return builder.resolveFunction(null, "Intersect", getParams(target, ctx)); - case "is": - case "as": { - checkArgumentCount(ctx, functionName, 1); - Expression typeArgument = null; - builder.pushTypeSpecifierContext(); - try { - typeArgument = (Expression) visitor.visit(ctx.expression(0)); - } finally { - builder.popTypeSpecifierContext(); - } - - if (!(typeArgument instanceof Literal)) { - throw new IllegalArgumentException("Expected literal argument"); - } - - Literal typeLiteral = (Literal) typeArgument; - if (!(DataTypes.equal(typeLiteral.getResultType(), builder.resolveTypeName("System", "String")))) { - throw new IllegalArgumentException("Expected string literal argument"); - } - - String typeSpecifier = ((Literal) typeArgument).getValue(); - DataType isType = builder.resolveTypeSpecifier(typeSpecifier); - - return functionName.equals("is") ? builder.buildIs(target, isType) : builder.buildAs(target, isType); - } - // TODO: isDistinct // resolves as .count() = .distinct().count() // somewhat tricky in that it needs to - // duplicate the target expression... - case "last": - return builder.resolveFunction(null, "Last", getParams(target, ctx)); - case "lastIndexOf": { - checkArgumentCount(ctx, functionName, 1); - List params = new ArrayList(); - Expression argument = (Expression) visitor.visit(ctx.expression(0)); - params.add(argument); - params.add(target); - return builder.resolveFunction(null, "LastPositionOf", params); - } - case "length": - return builder.resolveFunction(null, "Length", getParams(target, ctx)); - case "ln": - return builder.resolveFunction(null, "Ln", getParams(target, ctx)); - case "log": - return builder.resolveFunction(null, "Log", getParams(target, ctx)); - case "lower": - return builder.resolveFunction(null, "Lower", getParams(target, ctx)); - case "matches": - return builder.resolveFunction(null, "Matches", getParams(target, ctx)); - case "memberOf": - return builder.resolveFunction(null, "InValueSet", getParams(target, ctx)); - case "not": - return builder.resolveFunction(null, "Not", getParams(target, ctx)); - // now could never resolve as a method because it has no arguments - // case "now": return builder.resolveFunction(null, "Now", getParams(target, ctx)); - case "ofType": - return createOfType(target, functionName, ctx); - case "power": - return builder.resolveFunction(null, "Power", getParams(target, ctx)); - case "repeat": - return createRepeat(target, functionName, ctx); - case "replace": - return builder.resolveFunction(null, "Replace", getParams(target, ctx)); - case "replaceMatches": - return builder.resolveFunction(null, "ReplaceMatches", getParams(target, ctx)); - case "round": - return builder.resolveFunction(null, "Round", getParams(target, ctx)); - case "select": { - return createSelect(target, functionName, ctx); - } - case "single": - return builder.resolveFunction(null, "SingletonFrom", getParams(target, ctx)); - case "skip": - return builder.resolveFunction(null, "Skip", getParams(target, ctx)); - case "sqrt": { - checkArgumentCount(ctx, functionName, 0); - List params = new ArrayList(); - params.add(target); - params.add(builder.createLiteral(0.5)); - return builder.resolveFunction(null, "Power", params); - } - case "startsWith": - return builder.resolveFunction(null, "StartsWith", getParams(target, ctx)); - case "subsetOf": - return builder.resolveFunction(null, "IncludedIn", getParams(target, ctx)); - case "substring": - return builder.resolveFunction(null, "Substring", getParams(target, ctx)); - case "subsumes": - return builder.resolveFunction(null, "Subsumes", getParams(target, ctx)); - case "subsumedBy": - return builder.resolveFunction(null, "SubsumedBy", getParams(target, ctx)); - case "supersetOf": - return builder.resolveFunction(null, "Includes", getParams(target, ctx)); - case "tail": - return builder.resolveFunction(null, "Tail", getParams(target, ctx)); - case "take": - return builder.resolveFunction(null, "Take", getParams(target, ctx)); - // timeOfDay could never resolve as a method because it has no arguments - // case "timeOfDay": return builder.resolveFunction(null, "TimeOfDay", getParams(target, ctx)); - case "toBoolean": - return builder.resolveFunction(null, "ToBoolean", getParams(target, ctx)); - case "toChars": - return builder.resolveFunction(null, "ToChars", getParams(target, ctx)); - case "toDate": - return builder.resolveFunction(null, "ToDate", getParams(target, ctx)); - case "toDateTime": - return builder.resolveFunction(null, "ToDateTime", getParams(target, ctx)); - // today could never resolve as a method because it has no arguments - // case "today": return builder.resolveFunction(null, "Today", getParams(target, ctx)); - case "toDecimal": - return builder.resolveFunction(null, "ToDecimal", getParams(target, ctx)); - case "toInteger": - return builder.resolveFunction(null, "ToInteger", getParams(target, ctx)); - case "toQuantity": - return builder.resolveFunction(null, "ToQuantity", getParams(target, ctx)); - case "toString": - return builder.resolveFunction(null, "ToString", getParams(target, ctx)); - case "toTime": - return builder.resolveFunction(null, "ToTime", getParams(target, ctx)); - case "trace": { - checkArgumentCount(ctx, functionName, 1); - List params = new ArrayList(); - params.add(target); - params.add(builder.createLiteral(true)); - params.add(builder.createLiteral("TRACE")); - params.add(builder.createLiteral("Trace")); - params.add((Expression) visitor.visit(ctx.expression(0))); - return builder.resolveFunction(null, "Message", params); - } - case "truncate": - return builder.resolveFunction(null, "Truncate", getParams(target, ctx)); - case "union": - return builder.resolveFunction(null, "Union", getParams(target, ctx)); - case "upper": - return builder.resolveFunction(null, "Upper", getParams(target, ctx)); - case "where": { - return createWhere(target, functionName, ctx); - } - - default: { - return visitor.resolveFunction(null, functionName, getParams(target, ctx), mustResolve, false, true); - } - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/TypeBuilder.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/TypeBuilder.java deleted file mode 100644 index 7063136a2..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/TypeBuilder.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.cqframework.cql.cql2elm; - -import java.util.ArrayList; -import java.util.List; -import javax.xml.namespace.QName; -import org.cqframework.cql.cql2elm.model.Model; -import org.cqframework.cql.elm.IdObjectFactory; -import org.hl7.cql.model.*; -import org.hl7.elm.r1.ParameterTypeSpecifier; -import org.hl7.elm.r1.TupleElementDefinition; -import org.hl7.elm.r1.TypeSpecifier; -import org.hl7.elm_modelinfo.r1.ModelInfo; - -public class TypeBuilder { - - private IdObjectFactory of; - private ModelResolver mr; - - public static class InternalModelResolver implements ModelResolver { - private ModelManager modelManager; - - public InternalModelResolver(ModelManager modelManager) { - this.modelManager = modelManager; - } - - public Model getModel(String modelName) { - return this.modelManager.resolveModel(modelName); - } - } - - public TypeBuilder(IdObjectFactory of, ModelResolver mr) { - this.of = of; - this.mr = mr; - } - - public TypeBuilder(IdObjectFactory of, ModelManager modelManager) { - this(of, new InternalModelResolver(modelManager)); - } - - public QName dataTypeToQName(DataType type) { - if (type instanceof NamedType) { - NamedType namedType = (NamedType) type; - ModelInfo modelInfo = mr.getModel(namedType.getNamespace()).getModelInfo(); - return new QName( - modelInfo.getTargetUrl() != null ? modelInfo.getTargetUrl() : modelInfo.getUrl(), - namedType.getTarget() != null ? namedType.getTarget() : namedType.getSimpleName()); - } - - // ERROR: - throw new IllegalArgumentException("A named type is required in this context."); - } - - public Iterable dataTypesToTypeSpecifiers(Iterable types) { - var result = new ArrayList(); - for (DataType type : types) { - result.add(dataTypeToTypeSpecifier(type)); - } - return result; - } - - public TypeSpecifier dataTypeToTypeSpecifier(DataType type) { - // Convert the given type into an ELM TypeSpecifier representation. - if (type instanceof NamedType) { - return (TypeSpecifier) of.createNamedTypeSpecifier() - .withName(dataTypeToQName(type)) - .withResultType(type); - } else if (type instanceof ListType) { - return listTypeToTypeSpecifier((ListType) type); - } else if (type instanceof IntervalType) { - return intervalTypeToTypeSpecifier((IntervalType) type); - } else if (type instanceof TupleType) { - return tupleTypeToTypeSpecifier((TupleType) type); - } else if (type instanceof ChoiceType) { - return choiceTypeToTypeSpecifier((ChoiceType) type); - } else if (type instanceof TypeParameter) { - return typeParameterToTypeSpecifier((TypeParameter) type); - } else { - throw new IllegalArgumentException(String.format("Could not convert type %s to a type specifier.", type)); - } - } - - private TypeSpecifier listTypeToTypeSpecifier(ListType type) { - return (TypeSpecifier) of.createListTypeSpecifier() - .withElementType(dataTypeToTypeSpecifier(type.getElementType())) - .withResultType(type); - } - - private TypeSpecifier intervalTypeToTypeSpecifier(IntervalType type) { - return (TypeSpecifier) of.createIntervalTypeSpecifier() - .withPointType(dataTypeToTypeSpecifier(type.getPointType())) - .withResultType(type); - } - - private TypeSpecifier tupleTypeToTypeSpecifier(TupleType type) { - return (TypeSpecifier) of.createTupleTypeSpecifier() - .withElement(tupleTypeElementsToTupleElementDefinitions(type.getElements())) - .withResultType(type); - } - - private TupleElementDefinition[] tupleTypeElementsToTupleElementDefinitions(Iterable elements) { - List definitions = new ArrayList<>(); - - for (TupleTypeElement element : elements) { - definitions.add(of.createTupleElementDefinition() - .withName(element.getName()) - .withElementType(dataTypeToTypeSpecifier(element.getType()))); - } - - return definitions.toArray(new TupleElementDefinition[definitions.size()]); - } - - private TypeSpecifier choiceTypeToTypeSpecifier(ChoiceType type) { - return (TypeSpecifier) of.createChoiceTypeSpecifier() - .withChoice(choiceTypeTypesToTypeSpecifiers(type)) - .withResultType(type); - } - - private TypeSpecifier[] choiceTypeTypesToTypeSpecifiers(ChoiceType choiceType) { - List specifiers = new ArrayList<>(); - - for (DataType type : choiceType.getTypes()) { - specifiers.add(dataTypeToTypeSpecifier(type)); - } - - return specifiers.toArray(new TypeSpecifier[specifiers.size()]); - } - - private TypeSpecifier typeParameterToTypeSpecifier(TypeParameter type) { - return new ParameterTypeSpecifier().withParameterName(type.getIdentifier()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/ElmEdit.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/ElmEdit.java deleted file mode 100644 index 34ce11dd0..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/ElmEdit.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.cqframework.cql.cql2elm.elm; - -import org.hl7.cql_annotations.r1.Annotation; -import org.hl7.elm.r1.Element; - -public enum ElmEdit implements IElmEdit { - REMOVE_LOCATOR { - @Override - public void edit(Element element) { - element.setLocator(null); - } - }, - REMOVE_ANNOTATION { - @Override - public void edit(Element element) { - element.setLocalId(null); - if (element.getAnnotation() != null) { - for (int i = 0; i < element.getAnnotation().size(); i++) { - var x = element.getAnnotation().get(i); - if (x instanceof Annotation) { - var a = (Annotation) x; - // TODO: Remove narrative but _not_ tags - // Tags are necessary for `allowFluent` compiler resolution - // to work correctly - a.setS(null); - if (a.getT().isEmpty()) { - element.getAnnotation().remove(i); - i--; - } - } - } - } - } - }, - REMOVE_RESULT_TYPE { - @Override - public void edit(Element element) { - element.setResultTypeName(null); - element.setResultTypeSpecifier(null); - } - }; -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/ElmEditor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/ElmEditor.java deleted file mode 100644 index 4ecd00113..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/ElmEditor.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cqframework.cql.cql2elm.elm; - -import java.util.List; -import java.util.Objects; -import org.cqframework.cql.elm.tracking.Trackable; -import org.cqframework.cql.elm.utility.Visitors; -import org.cqframework.cql.elm.visiting.FunctionalElmVisitor; -import org.hl7.elm.r1.Element; -import org.hl7.elm.r1.Library; - -public class ElmEditor { - - private final List edits; - private final FunctionalElmVisitor visitor; - - public ElmEditor(List edits) { - this.edits = Objects.requireNonNull(edits); - this.visitor = Visitors.from((elm, context) -> elm, this::aggregateResults); - } - - public void edit(Library library) { - this.visitor.visitLibrary(library, null); - - // This is needed because aggregateResults is not called on the library itself. - this.applyEdits(library); - } - - protected Trackable aggregateResults(Trackable aggregate, Trackable nextResult) { - applyEdits(nextResult); - return aggregate; - } - - protected void applyEdits(Trackable trackable) { - if (trackable instanceof Element) { - for (IElmEdit edit : edits) { - edit.edit((Element) trackable); - } - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/IElmEdit.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/IElmEdit.java deleted file mode 100644 index ee6ba3d5d..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/elm/IElmEdit.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.cqframework.cql.cql2elm.elm; - -import org.hl7.elm.r1.Element; - -public interface IElmEdit { - void edit(Element element); -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/CallContext.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/CallContext.java deleted file mode 100644 index 41f3380e8..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/CallContext.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.cql.model.DataType; - -public class CallContext { - public CallContext( - String libraryName, - String operatorName, - boolean allowPromotionAndDemotion, - boolean allowFluent, - boolean mustResolve, - DataType... signature) { - this.libraryName = libraryName; // allowed to be null - - if (operatorName == null || operatorName.equals("")) { - throw new IllegalArgumentException("operatorName is null"); - } - - this.operatorName = operatorName; - this.signature = new Signature(signature); - this.allowPromotionAndDemotion = allowPromotionAndDemotion; - this.allowFluent = allowFluent; - this.mustResolve = mustResolve; - } - - private String libraryName; - - public String getLibraryName() { - return libraryName; - } - - private String operatorName; - - public String getOperatorName() { - return operatorName; - } - - private Signature signature; - - public Signature getSignature() { - return signature; - } - - private boolean allowPromotionAndDemotion; - - public boolean getAllowPromotionAndDemotion() { - return allowPromotionAndDemotion; - } - - private boolean allowFluent; - - public boolean getAllowFluent() { - return allowFluent; - } - - private boolean mustResolve; - - public boolean getMustResolve() { - return this.mustResolve; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Chunk.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Chunk.java deleted file mode 100644 index 28b7977b0..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Chunk.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.ArrayList; -import java.util.List; -import org.antlr.v4.runtime.misc.Interval; -import org.hl7.elm.r1.Element; - -/** - * Created by Bryn on 6/14/2017. - */ -public class Chunk { - private Interval interval; - - public Interval getInterval() { - return interval; - } - - public void setInterval(Interval interval) { - this.interval = interval; - } - - public Chunk withInterval(Interval interval) { - setInterval(interval); - return this; - } - - /* - If a chunk is a header chunk then the narrative construction can choose to "trim" the content - to avoid the inclusion of whitespace between definitions in the narrative output. - This does have the side-affect of needing to reconstitute whitespace when reconstructing the - entire narrative from the source annotations, but that isn't a use case we're optimizing for, - we're focusing on providing minimal required narrative per definition. - */ - private boolean headerChunk = false; - - public boolean isHeaderChunk() { - return this.headerChunk; - } - - public void setHeaderChunk(boolean isHeaderChunk) { - this.headerChunk = isHeaderChunk; - } - - public Chunk withIsHeaderChunk(boolean isHeaderChunk) { - setHeaderChunk(isHeaderChunk); - return this; - } - - private Element element; - - public Element getElement() { - return element; - } - - public void setElement(Element element) { - this.element = element; - } - - public Chunk withElement(Element element) { - setElement(element); - return this; - } - - private void ensureChunks() { - if (chunks == null) { - chunks = new ArrayList(); - chunks.add(new Chunk().withInterval(interval)); - } - } - - private List chunks; - - public Iterable getChunks() { - ensureChunks(); - return chunks; - } - - public boolean hasChunks() { - return chunks != null; - } - - public void addChunk(Chunk chunk) { - if (chunk.getInterval().a < interval.a || chunk.getInterval().b > interval.b) { - throw new IllegalArgumentException( - "Child chunk cannot be added because it is not contained within the parent chunk."); - } - - ensureChunks(); - int chunkIndex = -1; - Chunk targetChunk = null; - for (int i = 0; i < chunks.size(); i++) { - if (chunk.getInterval().a >= chunks.get(i).getInterval().a - && chunk.getInterval().a <= chunks.get(i).getInterval().b) { - chunkIndex = i; - targetChunk = chunks.get(chunkIndex); - break; - } - } - - if (chunk.getInterval().a == targetChunk.getInterval().a) { - // the chunk being added starts the targetChunk - // insert the chunk at the targetChunk's index - // update the targetChunk's interval start to be the chunk's interval end + 1 - chunks.add(chunkIndex, chunk); - chunkIndex++; - int newA = chunk.getInterval().b + 1; - while (newA > chunks.get(chunkIndex).getInterval().b) { - chunks.remove(chunkIndex); - if (chunkIndex >= chunks.size()) { - break; - } - } - if (chunkIndex < chunks.size()) { - chunks.get(chunkIndex) - .setInterval(new Interval(newA, chunks.get(chunkIndex).getInterval().b)); - } - } else { - int newB = chunk.getInterval().a - 1; - int newA = chunk.getInterval().b + 1; - int oldA = chunks.get(chunkIndex).getInterval().a; - int oldB = chunks.get(chunkIndex).getInterval().b; - chunks.get(chunkIndex).setInterval(new Interval(oldA, newB)); - chunkIndex++; - chunks.add(chunkIndex, chunk); - chunkIndex++; - if (newA <= oldB) { - chunks.add(chunkIndex, new Chunk().withInterval(new Interval(newA, oldB))); - } - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ClassDetail.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ClassDetail.java deleted file mode 100644 index f7a6d4cf4..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ClassDetail.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.cql.model.TupleType; -import org.hl7.elm_modelinfo.r1.ClassInfo; - -public class ClassDetail { - public ClassInfo getClassInfo() { - return classInfo; - } - - public void setClassInfo(ClassInfo classInfo) { - this.classInfo = classInfo; - } - - public TupleType getModelClass() { - return modelClass; - } - - public void setModelClass(TupleType modelClass) { - this.modelClass = modelClass; - } - - private ClassInfo classInfo; - private TupleType modelClass; -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/CompiledLibrary.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/CompiledLibrary.java deleted file mode 100644 index 1fa44d700..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/CompiledLibrary.java +++ /dev/null @@ -1,304 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.*; -import java.util.List; -import org.hl7.cql.model.DataType; -import org.hl7.cql.model.NamespaceManager; -import org.hl7.cql_annotations.r1.Annotation; -import org.hl7.cql_annotations.r1.Tag; -import org.hl7.elm.r1.*; - -public class CompiledLibrary { - private VersionedIdentifier identifier; - private Library library; - private final Map namespace = new HashMap<>(); - private final OperatorMap operators = new OperatorMap(); - private final Map functionDefs = new HashMap<>(); - private final java.util.List conversions = new ArrayList<>(); - - public VersionedIdentifier getIdentifier() { - return identifier; - } - - public void setIdentifier(VersionedIdentifier identifier) { - this.identifier = identifier; - } - - public Library getLibrary() { - return library; - } - - public void setLibrary(Library library) { - this.library = library; - } - - private void checkNamespace(String identifier) { - final ResolvedIdentifierContext existingResolvedIdentifierContext = resolve(identifier); - existingResolvedIdentifierContext.getExactMatchElement().ifPresent(element -> { - throw new IllegalArgumentException( - String.format("Identifier %s is already in use in this library.", identifier)); - }); - } - - public void add(UsingDef using) { - checkNamespace(using.getLocalIdentifier()); - namespace.put(using.getLocalIdentifier(), using); - } - - public void add(IncludeDef include) { - checkNamespace(include.getLocalIdentifier()); - namespace.put(include.getLocalIdentifier(), include); - } - - public void add(CodeSystemDef codesystem) { - checkNamespace(codesystem.getName()); - namespace.put(codesystem.getName(), codesystem); - } - - public void add(ValueSetDef valueset) { - checkNamespace(valueset.getName()); - namespace.put(valueset.getName(), valueset); - } - - public void add(CodeDef code) { - checkNamespace(code.getName()); - namespace.put(code.getName(), code); - } - - public void add(ConceptDef concept) { - checkNamespace(concept.getName()); - namespace.put(concept.getName(), concept); - } - - public void add(ParameterDef parameter) { - checkNamespace(parameter.getName()); - namespace.put(parameter.getName(), parameter); - } - - public void add(ExpressionDef expression) { - if (expression instanceof FunctionDef) { - // Register the operator signature - add((FunctionDef) expression, Operator.fromFunctionDef((FunctionDef) expression)); - } else { - checkNamespace(expression.getName()); - namespace.put(expression.getName(), expression); - } - } - - public void remove(ExpressionDef expression) { - if (expression instanceof FunctionDef) { - throw new IllegalArgumentException("FunctionDef cannot be removed."); - } - namespace.remove(expression.getName()); - } - - private void ensureLibrary(Operator operator) { - // Functions can be defined in an anonymous library - if (this.identifier != null && this.identifier.getId() != null) { - if (operator.getLibraryName() == null) { - operator.setLibraryName(this.identifier.getId()); - } else { - if (!operator.getLibraryName().equals(this.identifier.getId())) { - throw new IllegalArgumentException(String.format( - "Operator %s cannot be registered in library %s because it is defined in library %s.", - operator.getName(), this.identifier.getId(), operator.getLibraryName())); - } - } - } - } - - private void ensureResultType(Operator operator) { - if (operator.getResultType() == null) { - throw new IllegalArgumentException(String.format( - "Operator %s cannot be registered in library %s because it does not have a result type defined.", - operator.getName(), this.identifier.getId())); - } - } - - public void add(FunctionDef functionDef, Operator operator) { - ensureLibrary(operator); - // ensureResultType(operator); - operators.addOperator(operator); - functionDefs.put(operator, functionDef); - } - - public boolean contains(FunctionDef functionDef) { - return contains(Operator.fromFunctionDef(functionDef)); - } - - public boolean contains(Operator operator) { - return operators.containsOperator(operator); - } - - public void add(Conversion conversion) { - if (conversion.isCast()) { - throw new IllegalArgumentException("Casting conversions cannot be registered as part of a library."); - } - - conversions.add(conversion); - } - - public ResolvedIdentifierContext resolve(String identifier) { - if (namespace.containsKey(identifier)) { - return ResolvedIdentifierContext.exactMatch(identifier, namespace.get(identifier)); - } - - return namespace.entrySet().stream() - .filter(entry -> entry.getKey().equalsIgnoreCase(identifier)) - .map(Map.Entry::getValue) - .map(element -> ResolvedIdentifierContext.caseInsensitiveMatch(identifier, element)) - .findFirst() - .orElse(ResolvedIdentifierContext.caseInsensitiveMatch(identifier, null)); - } - - public UsingDef resolveUsingRef(String identifier) { - return resolveIdentifier(identifier, UsingDef.class); - } - - public IncludeDef resolveIncludeRef(String identifier) { - return resolveIdentifier(identifier, IncludeDef.class); - } - - public String resolveIncludeAlias(VersionedIdentifier identifier) { - if (identifier != null - && library != null - && library.getIncludes() != null - && library.getIncludes().getDef() != null) { - String libraryPath = NamespaceManager.getPath(identifier.getSystem(), identifier.getId()); - for (IncludeDef id : library.getIncludes().getDef()) { - if (id.getPath().equals(libraryPath)) { - return id.getLocalIdentifier(); - } - } - } - - return null; - } - - public CodeSystemDef resolveCodeSystemRef(String identifier) { - return resolveIdentifier(identifier, CodeSystemDef.class); - } - - public ValueSetDef resolveValueSetRef(String identifier) { - return resolveIdentifier(identifier, ValueSetDef.class); - } - - public CodeDef resolveCodeRef(String identifier) { - return resolveIdentifier(identifier, CodeDef.class); - } - - public ConceptDef resolveConceptRef(String identifier) { - return resolveIdentifier(identifier, ConceptDef.class); - } - - public ParameterDef resolveParameterRef(String identifier) { - return resolveIdentifier(identifier, ParameterDef.class); - } - - public ExpressionDef resolveExpressionRef(String identifier) { - return resolveIdentifier(identifier, ExpressionDef.class); - } - - private T resolveIdentifier(String identifier, Class clazz) { - return resolve(identifier).resolveIdentifier(clazz); - } - - public Iterable resolveFunctionRef(String identifier) { - List results = new ArrayList(); - for (ExpressionDef ed : getLibrary().getStatements().getDef()) { - if (ed instanceof FunctionDef) { - if (ed.getName().equals(identifier)) { - results.add((FunctionDef) ed); - } - } - } - - return results; - } - - public Iterable resolveFunctionRef(String identifier, List signature) { - if (signature == null) { - return resolveFunctionRef(identifier); - } else { - CallContext cc = new CallContext( - this.getIdentifier().getId(), - identifier, - false, - false, - false, - signature.toArray(new DataType[signature.size()])); - OperatorResolution resolution = resolveCall(cc, null); - ArrayList results = new ArrayList(); - if (resolution != null) { - results.add(resolution.getOperator().getFunctionDef()); - } - return results; - } - } - - public OperatorResolution resolveCall(CallContext callContext, ConversionMap conversionMap) { - OperatorResolution resolution = operators.resolveOperator(callContext, conversionMap); - - if (resolution != null && resolution.getOperator() != null) { - // For backwards compatibility, a library can indicate that functions it exports are allowed to be invoked - // with fluent syntax. This is used in FHIRHelpers to allow fluent resolution, which is implicit in 1.4. - if (callContext.getAllowFluent() && !resolution.getOperator().getFluent()) { - resolution.setAllowFluent(getBooleanTag("allowFluent")); - } - - // The resolution needs to carry with it the full versioned identifier of the library so that it can be - // correctly - // reflected via the alias for the library in the calling context. - resolution.setLibraryIdentifier(this.getIdentifier()); - } - - return resolution; - } - - public OperatorMap getOperatorMap() { - return operators; - } - - public Iterable getConversions() { - return conversions; - } - - private Annotation getAnnotation() { - if (library != null && library.getAnnotation() != null) { - for (Object o : library.getAnnotation()) { - if (o instanceof Annotation) { - return (Annotation) o; - } - } - } - - return null; - } - - public String getTag(String tagName) { - Annotation a = getAnnotation(); - if (a != null && a.getT() != null) { - for (Tag t : a.getT()) { - if (t.getName().equals(tagName)) { - return t.getValue(); - } - } - } - - return null; - } - - public boolean getBooleanTag(String tagName) { - String tagValue = getTag(tagName); - if (tagValue != null) { - try { - return Boolean.valueOf(tagValue); - } catch (Exception e) { - // Do not throw - return false; - } - } - - return false; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Conversion.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Conversion.java deleted file mode 100644 index e65551ebc..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Conversion.java +++ /dev/null @@ -1,323 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.ArrayList; -import java.util.List; -import org.hl7.cql.model.*; - -public class Conversion { - public Conversion(Operator operator, boolean isImplicit) { - setIsImplicit(isImplicit); - setOperator(operator); - } - - public Conversion(DataType fromType, DataType toType) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - if (toType == null) { - throw new IllegalArgumentException("toType is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.isCastFlag = true; - } - - public Conversion(ChoiceType fromType, DataType toType, Conversion choiceConversion) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - if (toType == null) { - throw new IllegalArgumentException("toType is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.conversionField = choiceConversion; - this.isCastFlag = true; - } - - public Conversion(DataType fromType, ChoiceType toType, Conversion choiceConversion) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - if (toType == null) { - throw new IllegalArgumentException("toType is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.conversionField = choiceConversion; - this.isCastFlag = true; - } - - public Conversion(ListType fromType, ListType toType, Conversion elementConversion) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - if (toType == null) { - throw new IllegalArgumentException("toType is null"); - } - - if (elementConversion == null) { - throw new IllegalArgumentException("elementConversion is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.conversionField = elementConversion; - this.isListConversionFlag = true; - } - - public Conversion(ListType fromType, DataType toType, Conversion elementConversion) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - if (toType == null) { - throw new IllegalArgumentException("toType is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.conversionField = elementConversion; - this.isListDemotionFlag = true; - } - - public Conversion(DataType fromType, ListType toType, Conversion elementConversion) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - if (toType == null) { - throw new IllegalArgumentException("toType is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.conversionField = elementConversion; - this.isListPromotionFlag = true; - } - - public Conversion(IntervalType fromType, DataType toType, Conversion elementConversion) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.conversionField = elementConversion; - this.isIntervalDemotionFlag = true; - } - - public Conversion(DataType fromType, IntervalType toType, Conversion elementConversion) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - if (toType == null) { - throw new IllegalArgumentException("toType is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.conversionField = elementConversion; - this.isIntervalPromotionFlag = true; - } - - public Conversion(IntervalType fromType, IntervalType toType, Conversion pointConversion) { - if (fromType == null) { - throw new IllegalArgumentException("fromType is null"); - } - - if (toType == null) { - throw new IllegalArgumentException("toType is null"); - } - - if (pointConversion == null) { - throw new IllegalArgumentException("pointConversion is null"); - } - - setIsImplicit(true); - this.fromType = fromType; - this.toType = toType; - this.conversionField = pointConversion; - this.isIntervalConversionFlag = true; - } - - private boolean implicit; - - public boolean isImplicit() { - return implicit; - } - - public void setIsImplicit(boolean implicit) { - this.implicit = implicit; - } - - private Operator operator; - - public Operator getOperator() { - return operator; - } - - public void setOperator(Operator operator) { - if (operator == null) { - throw new IllegalArgumentException("operator is null"); - } - - // NOTE: FHIRPath Support, need to allow generic conversion operators - // if (operator instanceof GenericOperator) { - // throw new IllegalArgumentException("Generic conversion operators are not supported."); - // } - - fromType = null; - for (DataType dataType : operator.getSignature().getOperandTypes()) { - if (fromType != null) { - throw new IllegalArgumentException("Conversion operator must be unary."); - } - - fromType = dataType; - } - - if (fromType == null) { - throw new IllegalArgumentException("Conversion operator must be unary."); - } - - toType = operator.getResultType(); - - this.operator = operator; - } - - private Conversion conversionField; - - public Conversion getConversion() { - return conversionField; - } - - private List alternativeConversions; - - public List getAlternativeConversions() { - if (alternativeConversions == null) { - alternativeConversions = new ArrayList(); - } - - return alternativeConversions; - } - - public boolean hasAlternativeConversions() { - return alternativeConversions != null; - } - - public void addAlternativeConversion(Conversion alternativeConversion) { - if (!(fromType instanceof ChoiceType)) { - throw new IllegalArgumentException("Alternative conversions can only be used with choice types"); - } - - // TODO: Should also guard against adding an alternative that is not one of the component types of the fromType - // This should never happen though with current usage - - getAlternativeConversions().add(alternativeConversion); - } - - public int getScore() { - int nestedScore = conversionField != null ? conversionField.getScore() : 0; - if (isCast()) { - return ConversionMap.ConversionScore.Cast.score() + nestedScore; - } else if (isIntervalDemotion()) { - return ConversionMap.ConversionScore.IntervalDemotion.score() + nestedScore; - } else if (isListDemotion()) { - return ConversionMap.ConversionScore.ListDemotion.score() + nestedScore; - } else if (isIntervalPromotion()) { - return ConversionMap.ConversionScore.IntervalPromotion.score() + nestedScore; - } else if (isListPromotion()) { - return ConversionMap.ConversionScore.ListPromotion.score() + nestedScore; - } else if (isListConversion()) { - if (((ListType) getToType()).getElementType() instanceof SimpleType) { - return ConversionMap.ConversionScore.SimpleConversion.score() + nestedScore; - } else { - return ConversionMap.ConversionScore.ComplexConversion.score() + nestedScore; - } - } else if (isIntervalConversion()) { - if (((IntervalType) getToType()).getPointType() instanceof SimpleType) { - return ConversionMap.ConversionScore.SimpleConversion.score() + nestedScore; - } else { - return ConversionMap.ConversionScore.ComplexConversion.score() + nestedScore; - } - } else if (getToType() instanceof ClassType) { - return ConversionMap.ConversionScore.ComplexConversion.score() + nestedScore; - } else { - return ConversionMap.ConversionScore.SimpleConversion.score() + nestedScore; - } - } - - public boolean isGeneric() { - return this.operator instanceof GenericOperator; - } - - private boolean isCastFlag; - - public boolean isCast() { - return isCastFlag; - } - - private boolean isListConversionFlag; - - public boolean isListConversion() { - return isListConversionFlag; - } - - private boolean isListPromotionFlag; - - public boolean isListPromotion() { - return isListPromotionFlag; - } - - private boolean isListDemotionFlag; - - public boolean isListDemotion() { - return isListDemotionFlag; - } - - private boolean isIntervalConversionFlag; - - public boolean isIntervalConversion() { - return isIntervalConversionFlag; - } - - private boolean isIntervalPromotionFlag; - - public boolean isIntervalPromotion() { - return isIntervalPromotionFlag; - } - - private boolean isIntervalDemotionFlag; - - public boolean isIntervalDemotion() { - return isIntervalDemotionFlag; - } - - private DataType fromType; - - public DataType getFromType() { - return fromType; - } - - private DataType toType; - - public DataType getToType() { - return toType; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionContext.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionContext.java deleted file mode 100644 index e4f8219da..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionContext.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.cql.model.DataType; - -/** - * Created by Bryn on 6/25/2018. - */ -public class ConversionContext { - - public ConversionContext(DataType fromType, DataType toType, boolean isImplicit, OperatorMap operatorMap) { - this.fromType = fromType; - this.toType = toType; - this.isImplicit = isImplicit; - this.operatorMap = operatorMap; - } - - private DataType fromType; - - public DataType getFromType() { - return fromType; - } - - private DataType toType; - - public DataType getToType() { - return toType; - } - - private boolean isImplicit; - - public boolean getIsImplicit() { - return isImplicit; - } - - private OperatorMap operatorMap; - - public OperatorMap getOperatorMap() { - return operatorMap; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionMap.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionMap.java deleted file mode 100644 index 8ffbdaa79..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ConversionMap.java +++ /dev/null @@ -1,463 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.hl7.cql.model.*; - -public class ConversionMap { - public enum TypePrecedenceScore { - Simple(1), - Tuple(2), - Class(3), - Interval(4), - List(5), - Choice(6), - Other(7); - - private final int score; - - public int score() { - return score; - } - - TypePrecedenceScore(int score) { - this.score = score; - } - } - - public enum ConversionScore { - ExactMatch(0), - SubType(1), - Compatible(2), - Cast(3), - SimpleConversion(4), - ComplexConversion(5), - IntervalPromotion(6), - ListDemotion(7), - IntervalDemotion(8), - ListPromotion(9); - - private final int score; - - public int score() { - return score; - } - - ConversionScore(int score) { - this.score = score; - } - } - - public static int getTypePrecedenceScore(DataType operand) { - switch (operand.getClass().getSimpleName()) { - case "SimpleType": - return ConversionMap.TypePrecedenceScore.Simple.score(); - case "TupleType": - return ConversionMap.TypePrecedenceScore.Tuple.score(); - case "ClassType": - return ConversionMap.TypePrecedenceScore.Class.score(); - case "IntervalType": - return ConversionMap.TypePrecedenceScore.Interval.score(); - case "ListType": - return ConversionMap.TypePrecedenceScore.List.score(); - case "ChoiceType": - return ConversionMap.TypePrecedenceScore.Choice.score(); - default: - return ConversionMap.TypePrecedenceScore.Other.score(); - } - } - - public static int getConversionScore(DataType callOperand, DataType operand, Conversion conversion) { - if (operand.equals(callOperand)) { - return ConversionMap.ConversionScore.ExactMatch.score(); - } else if (operand.isSuperTypeOf(callOperand)) { - return ConversionMap.ConversionScore.SubType.score(); - } else if (callOperand.isCompatibleWith(operand)) { - return ConversionMap.ConversionScore.Compatible.score(); - } else if (conversion != null) { - return conversion.getScore(); - } - - throw new IllegalArgumentException("Could not determine conversion score for conversion"); - } - - private Map> map = new HashMap<>(); - private List genericConversions = new ArrayList<>(); - private boolean listDemotion = true; - private boolean listPromotion = true; - private boolean intervalDemotion = false; - private boolean intervalPromotion = false; - - public void enableListDemotion() { - listDemotion = true; - } - - public void disableListDemotion() { - listDemotion = false; - } - - public boolean isListDemotionEnabled() { - return listDemotion; - } - - public void enableListPromotion() { - listPromotion = true; - } - - public void disableListPromotion() { - listPromotion = false; - } - - public boolean isListPromotionEnabled() { - return listPromotion; - } - - public void enableIntervalDemotion() { - intervalDemotion = true; - } - - public void disableIntervalDemotion() { - intervalDemotion = false; - } - - public boolean isIntervalDemotionEnabled() { - return intervalDemotion; - } - - public void enableIntervalPromotion() { - intervalPromotion = true; - } - - public void disableIntervalPromotion() { - intervalPromotion = false; - } - - public boolean isIntervalPromotionEnabled() { - return intervalPromotion; - } - - private boolean hasConversion(Conversion conversion, List conversions) { - for (Conversion c : conversions) { - if (conversion.getToType().equals(c.getToType())) { - return true; - } - } - - return false; - } - - public Operator getConversionOperator(DataType fromType, DataType toType) { - for (Conversion c : this.getConversions(fromType)) { - if (c.getToType().equals(toType)) { - return c.getOperator(); - } - } - - return null; - } - - public void add(Conversion conversion) { - if (conversion == null) { - throw new IllegalArgumentException("conversion is null."); - } - - // NOTE: The conversion map supports generic conversions, however, they turned out to be quite expensive - // computationally - // so we introduced list promotion and demotion instead (we should add interval promotion and demotion too, - // would be quite useful) - // Generic conversions could still be potentially useful, so I left the code, but it's never used because the - // generic conversions - // are not added in the SystemLibraryHelper. - if (conversion.isGeneric()) { - List conversions = getGenericConversions(); - if (hasConversion(conversion, conversions)) { - throw new IllegalArgumentException(String.format( - "Conversion from %s to %s is already defined.", - conversion.getFromType().toString(), - conversion.getToType().toString())); - } - - conversions.add(conversion); - } else { - List conversions = getConversions(conversion.getFromType()); - if (hasConversion(conversion, conversions)) { - throw new IllegalArgumentException(String.format( - "Conversion from %s to %s is already defined.", - conversion.getFromType().toString(), - conversion.getToType().toString())); - } - - conversions.add(conversion); - } - } - - public List getGenericConversions() { - return genericConversions; - } - - public List getConversions(DataType fromType) { - List conversions = map.get(fromType); - if (conversions == null) { - conversions = new ArrayList(); - map.put(fromType, conversions); - } - - return conversions; - } - - /* - Returns conversions for the given type, or any supertype, recursively - */ - public List getAllConversions(DataType fromType) { - List conversions = new ArrayList(); - DataType currentType = fromType; - while (currentType != null) { - conversions.addAll(getConversions(currentType)); - currentType = currentType.getBaseType(); - } - return conversions; - } - - public Conversion findCompatibleConversion(DataType fromType, DataType toType) { - if (fromType.isCompatibleWith(toType)) { - return new Conversion(fromType, toType); - } - - return null; - } - - public Conversion findChoiceConversion( - ChoiceType fromType, DataType toType, boolean allowPromotionAndDemotion, OperatorMap operatorMap) { - Conversion result = null; - for (DataType choice : fromType.getTypes()) { - Conversion choiceConversion = findConversion(choice, toType, true, allowPromotionAndDemotion, operatorMap); - if (choiceConversion != null) { - if (result == null) { - result = new Conversion(fromType, toType, choiceConversion); - } else { - result.addAlternativeConversion(choiceConversion); - } - } - } - - return result; - } - - public Conversion findTargetChoiceConversion( - DataType fromType, ChoiceType toType, boolean allowPromotionAndDemotion, OperatorMap operatorMap) { - for (DataType choice : toType.getTypes()) { - Conversion choiceConversion = - findConversion(fromType, choice, true, allowPromotionAndDemotion, operatorMap); - if (choiceConversion != null) { - return new Conversion(fromType, toType, choiceConversion); - } - } - - return null; - } - - public Conversion findListConversion(ListType fromType, ListType toType, OperatorMap operatorMap) { - Conversion elementConversion = - findConversion(fromType.getElementType(), toType.getElementType(), true, false, operatorMap); - - if (elementConversion != null) { - return new Conversion(fromType, toType, elementConversion); - } - - return null; - } - - public Conversion findIntervalConversion(IntervalType fromType, IntervalType toType, OperatorMap operatorMap) { - Conversion pointConversion = - findConversion(fromType.getPointType(), toType.getPointType(), true, false, operatorMap); - - if (pointConversion != null) { - return new Conversion(fromType, toType, pointConversion); - } - - return null; - } - - public Conversion findListDemotion(ListType fromType, DataType toType, OperatorMap operatorMap) { - DataType elementType = fromType.getElementType(); - if (elementType.isSubTypeOf(toType)) { - return new Conversion(fromType, toType, null); - } else { - Conversion elementConversion = findConversion(elementType, toType, true, false, operatorMap); - if (elementConversion != null) { - return new Conversion(fromType, toType, elementConversion); - } - } - - return null; - } - - public Conversion findListPromotion(DataType fromType, ListType toType, OperatorMap operatorMap) { - if (fromType.isSubTypeOf(toType.getElementType())) { - return new Conversion(fromType, toType, null); - } else { - Conversion elementConversion = findConversion(fromType, toType.getElementType(), true, false, operatorMap); - if (elementConversion != null) { - return new Conversion(fromType, toType, elementConversion); - } - } - - return null; - } - - public Conversion findIntervalDemotion(IntervalType fromType, DataType toType, OperatorMap operatorMap) { - DataType pointType = fromType.getPointType(); - if (pointType.isSubTypeOf(toType)) { - return new Conversion(fromType, toType, null); - } else { - Conversion pointConversion = findConversion(pointType, toType, true, false, operatorMap); - if (pointConversion != null) { - return new Conversion(fromType, toType, pointConversion); - } - } - - return null; - } - - public Conversion findIntervalPromotion(DataType fromType, IntervalType toType, OperatorMap operatorMap) { - if (fromType.isSubTypeOf(toType.getPointType())) { - return new Conversion(fromType, toType, null); - } else { - Conversion pointConversion = findConversion(fromType, toType.getPointType(), true, false, operatorMap); - if (pointConversion != null) { - return new Conversion(fromType, toType, pointConversion); - } - } - - return null; - } - - public boolean ensureGenericConversionInstantiated( - DataType fromType, DataType toType, boolean isImplicit, OperatorMap operatorMap) { - boolean operatorsInstantiated = false; - for (Conversion c : getGenericConversions()) { - if (c.getOperator() != null) { - // instantiate the generic... - InstantiationResult instantiationResult = ((GenericOperator) c.getOperator()) - .instantiate(new Signature(fromType), operatorMap, this, false); - Operator operator = instantiationResult.getOperator(); - if (operator != null && !operatorMap.containsOperator(operator)) { - operatorMap.addOperator(operator); - Conversion conversion = new Conversion(operator, true); - this.add(conversion); - operatorsInstantiated = true; - } - } - } - - return operatorsInstantiated; - } - - private Conversion internalFindConversion(DataType fromType, DataType toType, boolean isImplicit) { - Conversion result = null; - int score = Integer.MAX_VALUE; - for (Conversion conversion : getAllConversions(fromType)) { - if ((!isImplicit || conversion.isImplicit())) { - if (conversion.getToType().isSuperTypeOf(toType) - || conversion.getToType().isGeneric()) { - // Lower score is better. If the conversion matches the target type exactly, the score is 0. - // If the conversion is generic, the score is 1 (because that will be instantiated to an exact - // match) - // If the conversion is a super type, it should only be used if an exact match cannot be found. - // If the score is equal to an existing, it indicates a duplicate conversion - int newScore = (conversion.getFromType().equals(fromType) - ? 0 - : (conversion.getFromType().isGeneric() ? 1 : 2)) - + (conversion.getToType().equals(toType) - ? 0 - : (conversion.getToType().isGeneric() ? 1 : 2)); - if (newScore < score) { - result = conversion; - score = newScore; - } else if (newScore == score) { - // ERROR - throw new IllegalArgumentException(String.format( - "Ambiguous implicit conversion from %s to %s or %s.", - fromType.toString(), - result.getToType().toString(), - conversion.getToType().toString())); - } - } - } - } - - return result; - } - - public Conversion findConversion( - DataType fromType, - DataType toType, - boolean isImplicit, - boolean allowPromotionAndDemotion, - OperatorMap operatorMap) { - Conversion result = findCompatibleConversion(fromType, toType); - if (result == null) { - result = internalFindConversion(fromType, toType, isImplicit); - } - - if (result == null) { - if (ensureGenericConversionInstantiated(fromType, toType, isImplicit, operatorMap)) { - result = internalFindConversion(fromType, toType, isImplicit); - } - } - - if (result == null) { - // NOTE: FHIRPath Implicit conversion from list to singleton - // If the from type is a list and the target type is a singleton (potentially with a compatible conversion), - // Convert by invoking a singleton - if (fromType instanceof ListType - && !(toType instanceof ListType) - && (allowPromotionAndDemotion || listDemotion)) { - result = findListDemotion((ListType) fromType, toType, operatorMap); - } - - if (!(fromType instanceof ListType) - && toType instanceof ListType - && (allowPromotionAndDemotion || listPromotion)) { - result = findListPromotion(fromType, (ListType) toType, operatorMap); - } - - if (fromType instanceof IntervalType - && !(toType instanceof IntervalType) - && (allowPromotionAndDemotion || intervalDemotion)) { - result = findIntervalDemotion((IntervalType) fromType, toType, operatorMap); - } - - if (!(fromType instanceof IntervalType) - && toType instanceof IntervalType - && (allowPromotionAndDemotion || intervalPromotion)) { - result = findIntervalPromotion(fromType, (IntervalType) toType, operatorMap); - } - - // If the from type is a choice, attempt to find a conversion from one of the choice types - if (fromType instanceof ChoiceType) { - result = findChoiceConversion((ChoiceType) fromType, toType, allowPromotionAndDemotion, operatorMap); - } - - // If the target type is a choice, attempt to find a conversion to one of the choice types - if (!(fromType instanceof ChoiceType) && toType instanceof ChoiceType) { - result = findTargetChoiceConversion( - fromType, (ChoiceType) toType, allowPromotionAndDemotion, operatorMap); - } - - // If both types are lists, attempt to find a conversion between the element types - if (fromType instanceof ListType && toType instanceof ListType) { - result = findListConversion((ListType) fromType, (ListType) toType, operatorMap); - } - - // If both types are intervals, attempt to find a conversion between the point types - if (fromType instanceof IntervalType && toType instanceof IntervalType) { - result = findIntervalConversion((IntervalType) fromType, (IntervalType) toType, operatorMap); - } - } - - return result; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/FunctionHeader.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/FunctionHeader.java deleted file mode 100644 index ac2ff72e7..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/FunctionHeader.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.Objects; -import java.util.StringJoiner; -import org.hl7.elm.r1.FunctionDef; -import org.hl7.elm.r1.OperandDef; -import org.hl7.elm.r1.TypeSpecifier; - -/** - * POJO for the result of a pre compile operation (AKA: partial compile of function headers) - */ -public class FunctionHeader { - private final FunctionDef functionDef; - private final TypeSpecifier resultType; - - private boolean isCompiled = false; - - public FunctionDef getFunctionDef() { - return functionDef; - } - - public TypeSpecifier getResultType() { - return resultType; - } - - public static FunctionHeader noReturnType(FunctionDef functionDef) { - return new FunctionHeader(functionDef, null); - } - - public static FunctionHeader withReturnType(FunctionDef functionDef, TypeSpecifier resultType) { - return new FunctionHeader(functionDef, resultType); - } - - private FunctionHeader(FunctionDef functionDef, TypeSpecifier resultType) { - this.functionDef = functionDef; - this.resultType = resultType; - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; - } - FunctionHeader that = (FunctionHeader) other; - return Objects.equals(functionDef, that.functionDef) && Objects.equals(resultType, that.resultType); - } - - @Override - public int hashCode() { - return Objects.hash(functionDef, resultType); - } - - @Override - public String toString() { - return new StringJoiner(", ", FunctionHeader.class.getSimpleName() + "[", "]") - .add("functionDef=" + functionDef) - .add("resultType=" + resultType) - .toString(); - } - - public String getMangledName() { - StringBuilder sb = new StringBuilder(); - sb.append(functionDef.getName()); - sb.append("_"); - for (OperandDef od : functionDef.getOperand()) { - sb.append( - od.getOperandTypeSpecifier() != null - ? od.getOperandTypeSpecifier().toString() - : "void"); - } - sb.append("_"); - return sb.toString(); - } - - public boolean getIsCompiled() { - return isCompiled; - } - - public void setIsCompiled() { - isCompiled = true; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/GenericOperator.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/GenericOperator.java deleted file mode 100644 index 0b0588226..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/GenericOperator.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.hl7.cql.model.DataType; -import org.hl7.cql.model.TypeParameter; - -public class GenericOperator extends Operator { - public GenericOperator(String name, Signature signature, DataType resultType, TypeParameter... typeParameters) { - super(name, signature, resultType); - - // TODO: This constructor really ought to be replacing the TypeParameter references in its signature with copies - // of the referenced type parameter given here, - // but the constructor order and signature hiding of the base make that quite difficult here... - for (TypeParameter typeParameter : typeParameters) { - this.typeParameters.add(typeParameter); - } - } - - private List typeParameters = new ArrayList<>(); - - public Iterable getTypeParameters() { - return this.typeParameters; - } - - public InstantiationResult instantiate( - Signature callSignature, - OperatorMap operatorMap, - ConversionMap conversionMap, - boolean allowPromotionAndDemotion) { - return instantiate(callSignature, null, operatorMap, conversionMap, allowPromotionAndDemotion); - } - - public InstantiationResult instantiate( - Signature callSignature, - Map parameters, - OperatorMap operatorMap, - ConversionMap conversionMap, - boolean allowPromotionAndDemotion) { - Map typeMap = new HashMap<>(); - - for (TypeParameter p : typeParameters) { - typeMap.put(p, null); - } - - if (parameters != null) { - for (Map.Entry entry : parameters.entrySet()) { - typeMap.put(entry.getKey(), entry.getValue()); - } - } - - InstantiationContextImpl context = - new InstantiationContextImpl(typeMap, operatorMap, conversionMap, allowPromotionAndDemotion); - - Boolean instantiable = getSignature().isInstantiable(callSignature, context); - if (instantiable) { - Operator result = new Operator( - getName(), - getSignature().instantiate(context), - getResultType().instantiate(context)); - result.setAccessLevel(getAccessLevel()); - result.setLibraryName(getLibraryName()); - return new InstantiationResult(this, result, context.getConversionScore()); - } - - return new InstantiationResult(this, null, context.getConversionScore()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/InstantiationContextImpl.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/InstantiationContextImpl.java deleted file mode 100644 index 2012675b1..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/InstantiationContextImpl.java +++ /dev/null @@ -1,294 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.ArrayList; -import java.util.Map; -import org.hl7.cql.model.*; - -public class InstantiationContextImpl implements InstantiationContext { - public InstantiationContextImpl( - Map typeMap, - OperatorMap operatorMap, - ConversionMap conversionMap, - boolean allowPromotionAndDemotion) { - if (typeMap == null) { - throw new IllegalArgumentException("typeMap is null"); - } - - if (operatorMap == null) { - throw new IllegalArgumentException("operatorMap is null"); - } - - if (conversionMap == null) { - throw new IllegalArgumentException("conversionMap is null"); - } - - this.typeMap = typeMap; - this.operatorMap = operatorMap; - this.conversionMap = conversionMap; - this.allowPromotionAndDemotion = allowPromotionAndDemotion; - } - - private Map typeMap; - private OperatorMap operatorMap; - private ConversionMap conversionMap; - private boolean allowPromotionAndDemotion; - private int conversionScore; - - public int getConversionScore() { - return conversionScore; - } - - @Override - public boolean isInstantiable(TypeParameter parameter, DataType callType) { - // If the type is not yet bound, bind it to the call type. - DataType boundType = typeMap.get(parameter); - if (boundType == null) { - if (parameter.canBind(callType)) { - typeMap.put(parameter, callType); - return true; - } else { - return false; - } - } else { - // If the type is bound, and is a super type of the call type, return true; - if (boundType.isSuperTypeOf(callType) || callType.isCompatibleWith(boundType)) { - return true; - } else if (callType.isSuperTypeOf(boundType) || boundType.isCompatibleWith(callType)) { - // If the call type is a super type of the bound type, switch the bound type for this parameter to the - // call type - if (parameter.canBind(callType)) { - typeMap.put(parameter, callType); - return true; - } else { - return false; - } - } else { - // If there is an implicit conversion path from the call type to the bound type, return true - Conversion conversion = - conversionMap.findConversion(callType, boundType, true, allowPromotionAndDemotion, operatorMap); - if (conversion != null) { - // if the conversion is a list promotion, switch the bound type to the call type - if (boundType instanceof ListType) { - ListType boundListType = (ListType) boundType; - if (boundListType.getElementType().isSuperTypeOf(callType) - || callType.isCompatibleWith(boundListType.getElementType())) { - if (parameter.canBind(callType)) { - typeMap.put(parameter, callType); - conversionScore -= - ConversionMap.ConversionScore.ListPromotion - .score(); // This removes the list promotion - return true; - } else { - return false; - } - } - } - return true; - } - - // If there is an implicit conversion path from the bound type to the call type - conversion = - conversionMap.findConversion(boundType, callType, true, allowPromotionAndDemotion, operatorMap); - if (conversion != null) { - // switch the bound type to the call type and return true - if (parameter.canBind(callType)) { - typeMap.put(parameter, callType); - conversionScore -= ((conversion.getToType() instanceof SimpleType) - ? ConversionMap.ConversionScore.SimpleConversion.score() - : ConversionMap.ConversionScore.ComplexConversion - .score()); // This removes the conversion from the instantiation - return true; - } else { - return false; - } - } - - // Find the first supertype that is a supertype of both types - /* - // This code doesn't play well with generic signatures... it ends up treating everything like an Any, resulting in all sorts of surprising resolutions - DataType boundCommonSuperType = boundType.getCommonSuperTypeOf(callType); - DataType callCommonSuperType = callType.getCommonSuperTypeOf(boundType); - if (boundCommonSuperType != null && callCommonSuperType != null) { - if (boundCommonSuperType.isSuperTypeOf(callCommonSuperType)) { - if (parameter.canBind(boundCommonSuperType)) { - typeMap.put(parameter, boundCommonSuperType); - return true; - } - else { - return false; - } - } - else { - if (parameter.canBind(callCommonSuperType)) { - typeMap.put(parameter, callCommonSuperType); - return true; - } - else { - return false; - } - } - } - */ - } - } - - return false; - } - - @Override - public DataType instantiate(TypeParameter parameter) { - DataType result = typeMap.get(parameter); - if (result == null) { - throw new IllegalArgumentException( - String.format("Could not resolve type parameter %s.", parameter.getIdentifier())); - } - - return result; - } - - @Override - public Iterable getIntervalConversionTargets(DataType callType) { - ArrayList results = new ArrayList(); - for (Conversion c : conversionMap.getConversions(callType)) { - if (c.getToType() instanceof IntervalType) { - results.add((IntervalType) c.getToType()); - conversionScore += ConversionMap.ConversionScore.ComplexConversion.score(); - } - } - - if (results.isEmpty()) { - for (Conversion c : conversionMap.getGenericConversions()) { - if (c.getOperator() != null) { - if (c.getToType() instanceof IntervalType) { - // instantiate the generic... - InstantiationResult instantiationResult = ((GenericOperator) c.getOperator()) - .instantiate(new Signature(callType), operatorMap, conversionMap, false); - Operator operator = instantiationResult.getOperator(); - // TODO: Consider impact of conversion score of the generic instantiation on this conversion - // score - if (operator != null) { - operatorMap.addOperator(operator); - Conversion conversion = new Conversion(operator, true); - conversionMap.add(conversion); - results.add((IntervalType) conversion.getToType()); - } - } - } - } - } - - // Add interval promotion if no other conversion is found - if (results.isEmpty()) { - if (!(callType instanceof IntervalType) - && operatorMap.isPointType(callType) - && (allowPromotionAndDemotion || conversionMap.isIntervalPromotionEnabled())) { - results.add(new IntervalType(callType)); - conversionScore += ConversionMap.ConversionScore.IntervalPromotion.score(); - } - } - - return results; - } - - @Override - public Iterable getListConversionTargets(DataType callType) { - ArrayList results = new ArrayList(); - for (Conversion c : conversionMap.getConversions(callType)) { - if (c.getToType() instanceof ListType) { - results.add((ListType) c.getToType()); - conversionScore += ConversionMap.ConversionScore.ComplexConversion.score(); - } - } - - if (results.isEmpty()) { - for (Conversion c : conversionMap.getGenericConversions()) { - if (c.getOperator() != null) { - if (c.getToType() instanceof ListType) { - // instantiate the generic... - InstantiationResult instantiationResult = ((GenericOperator) c.getOperator()) - .instantiate(new Signature(callType), operatorMap, conversionMap, false); - Operator operator = instantiationResult.getOperator(); - // TODO: Consider impact of conversion score of the generic instantiation on this conversion - // score - if (operator != null) { - operatorMap.addOperator(operator); - Conversion conversion = new Conversion(operator, true); - conversionMap.add(conversion); - results.add((ListType) conversion.getToType()); - } - } - } - } - } - - // NOTE: FHIRPath support - // Add list promotion if no other conversion is found - if (results.isEmpty()) { - if (!(callType instanceof ListType) - && (allowPromotionAndDemotion || conversionMap.isListPromotionEnabled())) { - results.add(new ListType(callType)); - conversionScore += ConversionMap.ConversionScore.ListPromotion.score(); - } - } - - return results; - } - - @Override - public Iterable getSimpleConversionTargets(DataType callType) { - ArrayList results = new ArrayList(); - for (Conversion c : conversionMap.getConversions(callType)) { - if (c.getToType() instanceof SimpleType) { - results.add((SimpleType) c.getToType()); - conversionScore += ConversionMap.ConversionScore.SimpleConversion.score(); - } - } - - if (results.isEmpty()) { - for (Conversion c : conversionMap.getGenericConversions()) { - if (c.getOperator() != null) { - if (c.getToType() instanceof SimpleType) { - InstantiationResult instantiationResult = ((GenericOperator) c.getOperator()) - .instantiate(new Signature(callType), operatorMap, conversionMap, false); - Operator operator = instantiationResult.getOperator(); - // TODO: Consider impact of conversion score of the generic instantiation on this conversion - // score - if (operator != null) { - operatorMap.addOperator(operator); - Conversion conversion = new Conversion(operator, true); - conversionMap.add(conversion); - results.add((SimpleType) conversion.getToType()); - } - } - } - } - } - - // Add interval demotion if no other conversion is found - if (results.isEmpty()) { - if (callType instanceof IntervalType) { - IntervalType callIntervalType = (IntervalType) callType; - if (callIntervalType.getPointType() instanceof SimpleType - && (allowPromotionAndDemotion || conversionMap.isIntervalDemotionEnabled())) { - results.add((SimpleType) callIntervalType.getPointType()); - conversionScore += ConversionMap.ConversionScore.IntervalDemotion.score(); - } - } - } - - // NOTE: FHIRPath Support - // Add list demotion if no other conversion is found - if (results.isEmpty()) { - if (callType instanceof ListType) { - ListType callListType = (ListType) callType; - if (callListType.getElementType() instanceof SimpleType - && (allowPromotionAndDemotion || conversionMap.isListDemotionEnabled())) { - results.add((SimpleType) callListType.getElementType()); - conversionScore += ConversionMap.ConversionScore.ListDemotion.score(); - } - } - } - - return results; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/InstantiationResult.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/InstantiationResult.java deleted file mode 100644 index d44787a0e..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/InstantiationResult.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -/** - * Created by Bryn on 12/22/2016. - */ -public class InstantiationResult { - public InstantiationResult(GenericOperator genericOperator, Operator operator, int conversionScore) { - if (genericOperator == null) { - throw new IllegalArgumentException("genericOperator is required"); - } - - this.genericOperator = genericOperator; - this.operator = operator; - this.conversionScore = conversionScore; - } - - private GenericOperator genericOperator; - - public GenericOperator getGenericOperator() { - return genericOperator; - } - - private Operator operator; - - public Operator getOperator() { - return operator; - } - - private int conversionScore; - - public int getConversionScore() { - return conversionScore; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Invocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Invocation.java deleted file mode 100644 index 1ff3c347f..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Invocation.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.cql.model.DataType; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.TypeSpecifier; - -public interface Invocation { - Iterable getSignature(); - - void setSignature(Iterable signature); - - Iterable getOperands(); - - void setOperands(Iterable operands); - - void setResultType(DataType resultType); - - Expression getExpression(); - - void setResolution(OperatorResolution resolution); - - OperatorResolution getResolution(); -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/LibraryRef.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/LibraryRef.java deleted file mode 100644 index 89dc0c017..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/LibraryRef.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.elm.r1.Expression; - -// Note: This class is only used as a place-holder during resolution in a translator (or compiler...) -public class LibraryRef extends Expression { - - private String libraryName; - - public String getLibraryName() { - return libraryName; - } - - public void setLibraryName(String value) { - libraryName = value; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Model.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Model.java deleted file mode 100644 index 3f24f4e18..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Model.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.*; -import org.cqframework.cql.cql2elm.ModelManager; -import org.hl7.cql.model.*; -import org.hl7.elm_modelinfo.r1.ModelInfo; - -public class Model { - public Model(ModelInfo modelInfo, ModelManager modelManager) throws ClassNotFoundException { - info = modelInfo; - index = new HashMap<>(); - nameIndex = new HashMap<>(); - classIndex = new HashMap<>(); - conversions = new ArrayList<>(); - contexts = new ArrayList<>(); - - ModelImporter importer = new ModelImporter(info, modelManager); - index = importer.getTypes(); - for (Conversion c : importer.getConversions()) { - conversions.add(c); - } - - for (ModelContext c : importer.getContexts()) { - contexts.add(c); - } - - defaultContext = importer.getDefaultContextName(); - - for (DataType t : index.values()) { - if (t instanceof ClassType && ((ClassType) t).getLabel() != null) { - classIndex.put(casify(((ClassType) t).getLabel()), (ClassType) t); - } - - if (t instanceof NamedType) { - nameIndex.put(casify(((NamedType) t).getSimpleName()), t); - } - } - } - - private ModelInfo info; - - public ModelInfo getModelInfo() { - return info; - } - - private Map index; - private Map classIndex; - private Map nameIndex; - - protected Map getNameIndex() { - return nameIndex; - } - - private List conversions; - private List contexts; - private String defaultContext; - - public String getDefaultContext() { - return defaultContext; - } - - public Iterable getConversions() { - return conversions; - } - - public DataType resolveTypeName(String typeName) { - typeName = casify(typeName); - DataType result = index.get(typeName); - if (result == null) { - result = nameIndex.get(typeName); - } - - return result; - } - - public ModelContext resolveContextName(String contextName) { - return resolveContextName(contextName, true); - } - - public ModelContext resolveContextName(String contextName, boolean mustResolve) { - for (ModelContext context : contexts) { - if (context.getName().equals(contextName)) { - return context; - } - } - - // Resolve to a "default" context definition if the context name matches a type name exactly - DataType contextType = resolveTypeName(contextName); - if (contextType instanceof ClassType) { - ClassType contextClassType = (ClassType) contextType; - String keyName = null; - for (ClassTypeElement cte : contextClassType.getElements()) { - if (cte.getName().equals("id")) { - keyName = cte.getName(); - break; - } - } - - return new ModelContext( - contextName, (ClassType) contextType, keyName != null ? Arrays.asList(keyName) : null, null); - } - - if (mustResolve) { - // ERROR: - throw new IllegalArgumentException( - String.format("Could not resolve context name %s in model %s.", contextName, this.info.getName())); - } - - return null; - } - - public ClassType resolveLabel(String label) { - return classIndex.get(casify(label)); - } - - private String casify(String typeName) { - return (this.info.isCaseSensitive() != null ? this.info.isCaseSensitive() : false) - ? typeName.toLowerCase() - : typeName; - } - - private DataType internalResolveTypeName(String typeName, Model systemModel) { - DataType result = resolveTypeName(typeName); - if (result == null) { - result = systemModel.resolveTypeName(typeName); - if (result == null) { - // ERROR: - throw new IllegalArgumentException( - String.format("Could not resolve type name %s in model %s.", typeName, info.getName())); - } - } - - return result; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ModelImporter.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ModelImporter.java deleted file mode 100644 index 72f479aaa..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ModelImporter.java +++ /dev/null @@ -1,757 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.*; -import org.cqframework.cql.cql2elm.ModelManager; -import org.hl7.cql.model.*; -import org.hl7.cql.model.NamespaceManager; -import org.hl7.elm_modelinfo.r1.*; - -public class ModelImporter { - - private ModelInfo modelInfo; - private ModelManager modelManager; - private Map modelIndex; - private Map typeInfoIndex; - private Map resolvedTypes; - private List dataTypes; - private List conversions; - private List contexts; - private ModelContext defaultContext; - - public ModelImporter(ModelInfo modelInfo, ModelManager modelManager) { - if (modelInfo == null) { - throw new IllegalArgumentException("modelInfo is null"); - } - - this.modelInfo = modelInfo; - this.modelManager = modelManager; - this.modelIndex = new HashMap<>(); - this.typeInfoIndex = new HashMap<>(); - this.resolvedTypes = new HashMap<>(); - this.dataTypes = new ArrayList<>(); - this.conversions = new ArrayList<>(); - this.contexts = new ArrayList<>(); - - if (modelManager != null) { - // Import required models - for (ModelSpecifier requiredModel : modelInfo.getRequiredModelInfo()) { - Model model = modelManager.resolveModel(new ModelIdentifier() - .withSystem(NamespaceManager.getUriPart(requiredModel.getUrl())) - .withId(requiredModel.getName()) - .withVersion(requiredModel.getVersion())); - if (model != null) { - modelIndex.put(requiredModel.getName(), model); - } - } - - // Ensure System model is registered - if (!modelIndex.containsKey("System")) { - Model systemModel = modelManager.resolveModel(new ModelIdentifier().withId("System")); - if (systemModel != null) { - modelIndex.put("System", systemModel); - } - } - } - - // Import model types - for (TypeInfo t : this.modelInfo.getTypeInfo()) { - if (t instanceof SimpleTypeInfo) { - typeInfoIndex.put(ensureUnqualified(((SimpleTypeInfo) t).getName()), t); - } else if (t instanceof ClassInfo) { - ClassInfo classInfo = (ClassInfo) t; - if (classInfo.getName() != null) { - typeInfoIndex.put(ensureUnqualified(classInfo.getName()), classInfo); - } - } - } - - // Import model conversions - for (ConversionInfo c : this.modelInfo.getConversionInfo()) { - DataType fromType = resolveTypeNameOrSpecifier(c.getFromType(), c.getFromTypeSpecifier()); - DataType toType = resolveTypeNameOrSpecifier(c.getToType(), c.getToTypeSpecifier()); - int qualifierIndex = c.getFunctionName().indexOf('.'); - String libraryName = qualifierIndex >= 0 ? c.getFunctionName().substring(0, qualifierIndex) : null; - String functionName = qualifierIndex >= 0 ? c.getFunctionName().substring(qualifierIndex + 1) : null; - Operator operator = new Operator(functionName, new Signature(fromType), toType); - if (libraryName != null) { - operator.setLibraryName(libraryName); - } - - // All conversions loaded as part of a model are implicit - Conversion conversion = new Conversion(operator, true); - conversions.add(conversion); - } - - // Import model contexts - for (ContextInfo c : this.modelInfo.getContextInfo()) { - DataType contextType = resolveTypeSpecifier(c.getContextType()); - if (!(contextType instanceof ClassType)) { - // ERROR: - throw new IllegalArgumentException( - String.format("Model context %s must be a class type.", c.getName())); - } - ModelContext modelContext = new ModelContext( - c.getName(), - (ClassType) contextType, - Arrays.asList(c.getKeyElement().split(";")), - c.getBirthDateElement()); - // TODO: Validate key elements correspond to attributes of the class type - contexts.add(modelContext); - } - - // For backwards compatibility with model info files that don't specify contexts, create a default context based - // on the patient class information if it's present - if (contexts.size() == 0 && this.modelInfo.getPatientClassName() != null) { - DataType contextType = resolveTypeName(this.modelInfo.getPatientClassName()); - if (contextType instanceof ClassType) { - ModelContext modelContext = new ModelContext( - ((ClassType) contextType).getSimpleName(), - (ClassType) contextType, - Arrays.asList("id"), - this.modelInfo.getPatientBirthDatePropertyName()); - contexts.add(modelContext); - defaultContext = modelContext; - } - } - - for (TypeInfo t : this.modelInfo.getTypeInfo()) { - DataType type = resolveTypeInfo(t); - dataTypes.add(type); - - if (t instanceof ClassInfo) { - importRelationships((ClassInfo) t, (ClassType) type); - } - } - } - - public Map getTypes() { - return resolvedTypes; - } - - public Iterable getConversions() { - return conversions; - } - - public Iterable getContexts() { - return contexts; - } - - public String getDefaultContextName() { - if (this.modelInfo.getDefaultContext() != null) { - return this.modelInfo.getDefaultContext(); - } - - if (this.defaultContext != null) { - return this.defaultContext.getName(); - } - - return null; - } - - private String casify(String typeName) { - return casify(typeName, this.modelInfo.isCaseSensitive() != null ? this.modelInfo.isCaseSensitive() : false); - } - - private String casify(String typeName, boolean caseSensitive) { - return caseSensitive ? typeName.toLowerCase() : typeName; - } - - private DataType resolveTypeInfo(TypeInfo t) { - if (t instanceof SimpleTypeInfo) { - return resolveSimpleType((SimpleTypeInfo) t); - } else if (t instanceof ClassInfo) { - return resolveClassType((ClassInfo) t); - } else if (t instanceof TupleTypeInfo) { - return resolveTupleType((TupleTypeInfo) t); - } else if (t instanceof IntervalTypeInfo) { - return resolveIntervalType((IntervalTypeInfo) t); - } else if (t instanceof ListTypeInfo) { - return resolveListType((ListTypeInfo) t); - } else if (t instanceof ChoiceTypeInfo) { - return resolveChoiceType((ChoiceTypeInfo) t); - } - - return null; - } - - private DataType resolveTypeSpecifier(TypeSpecifier typeSpecifier) { - if (typeSpecifier == null) { - return null; - } - - if (typeSpecifier instanceof NamedTypeSpecifier) { - NamedTypeSpecifier namedTypeSpecifier = (NamedTypeSpecifier) typeSpecifier; - String qualifier = namedTypeSpecifier.getNamespace(); - if (qualifier == null || qualifier.isEmpty()) { - qualifier = - namedTypeSpecifier - .getModelName(); // For backwards compatibility, modelName is deprecated in favor of - // namespace - } - if (qualifier == null || qualifier.isEmpty()) { - qualifier = this.modelInfo.getName(); - } - - String qualifiedTypeName = String.format("%s.%s", qualifier, namedTypeSpecifier.getName()); - return resolveTypeName(qualifiedTypeName); - } - - if (typeSpecifier instanceof IntervalTypeSpecifier) { - IntervalTypeSpecifier intervalTypeSpecifier = (IntervalTypeSpecifier) typeSpecifier; - DataType pointType = resolveTypeNameOrSpecifier( - intervalTypeSpecifier.getPointType(), intervalTypeSpecifier.getPointTypeSpecifier()); - return new IntervalType(pointType); - } - - if (typeSpecifier instanceof ListTypeSpecifier) { - ListTypeSpecifier listTypeSpecifier = (ListTypeSpecifier) typeSpecifier; - DataType elementType = resolveTypeNameOrSpecifier( - listTypeSpecifier.getElementType(), listTypeSpecifier.getElementTypeSpecifier()); - if (elementType != null) { - return new ListType(elementType); - } - } - - if (typeSpecifier instanceof TupleTypeSpecifier) { - TupleTypeSpecifier tupleTypeSpecifier = (TupleTypeSpecifier) typeSpecifier; - TupleType tupleType = new TupleType(); - for (TupleTypeSpecifierElement specifierElement : tupleTypeSpecifier.getElement()) { - TupleTypeElement element = new TupleTypeElement( - specifierElement.getName(), resolveTypeSpecifier(specifierElement.getElementType())); - tupleType.addElement(element); - } - } - - if (typeSpecifier instanceof ChoiceTypeSpecifier) { - ChoiceTypeSpecifier choiceTypeSpecifier = (ChoiceTypeSpecifier) typeSpecifier; - List choices = new ArrayList<>(); - for (TypeSpecifier choice : choiceTypeSpecifier.getChoice()) { - DataType choiceType = resolveTypeSpecifier(choice); - choices.add(choiceType); - } - return new ChoiceType(choices); - } - - return null; - } - - private DataType resolveTypeName(String typeName) { - if (typeName == null) { - throw new IllegalArgumentException("typeName is null"); - } - - // NOTE: Preserving the ability to parse string type specifiers for backwards loading compatibility - // typeSpecifier: simpleTypeSpecifier | intervalTypeSpecifier | listTypeSpecifier - // simpleTypeSpecifier: (identifier '.')? identifier - // intervalTypeSpecifier: 'interval' '<' typeSpecifier '>' - // listTypeSpecifier: 'list' '<' typeSpecifier '>' - if (typeName.toLowerCase().startsWith("interval<")) { - DataType pointType = - resolveTypeName(typeName.substring(typeName.indexOf('<') + 1, typeName.lastIndexOf('>'))); - return new IntervalType(pointType); - } else if (typeName.toLowerCase().startsWith("list<")) { - DataType elementType = - resolveTypeName(typeName.substring(typeName.indexOf('<') + 1, typeName.lastIndexOf('>'))); - return new ListType(elementType); - } - - DataType result = lookupType(typeName); - if (result == null) { - TypeInfo typeInfo = lookupTypeInfo(ensureUnqualified(typeName)); - if (typeInfo == null) { - throw new IllegalArgumentException( - String.format("Could not resolve type info for type name %s.", typeName)); - } - - result = resolveTypeInfo(typeInfo); - } - - return result; - } - - private DataType resolveTypeNameOrSpecifier(String typeName, TypeSpecifier typeSpecifier) { - if ((typeName == null || typeName.isEmpty()) && typeSpecifier == null) { - return null; - } - - if (typeSpecifier != null) { - return resolveTypeSpecifier(typeSpecifier); - } - - return resolveTypeName(typeName); - } - - private DataType lookupType(String typeName) { - if (typeName == null) { - throw new IllegalArgumentException("typeName is null"); - } - - DataType resolvedType = resolvedTypes.get(casify(typeName)); - if (resolvedType != null) { - return resolvedType; - } - - int qualifierIndex = typeName.indexOf("."); - if (qualifierIndex < 0) { - return null; - } - - String qualifier = typeName.substring(0, qualifierIndex); - if (qualifier.equals("")) { - return null; - } - - if (!qualifier.equals(this.modelInfo.getName())) { - Model model = resolveModel(qualifier); - if (model == null) { - return null; - } - - return model.resolveTypeName(typeName); - } - - return null; - } - - private Model resolveModel(String localIdentifier) { - return modelIndex.get(localIdentifier); - } - - private TypeInfo lookupTypeInfo(String typeName) { - if (typeName == null) { - throw new IllegalArgumentException("typeName is null"); - } - - return typeInfoIndex.get(typeName); - } - - // This method is used to ensure backwards compatible loading, type names in model info may be qualified with the - // model name - private String ensureQualified(String name) { - String qualifier = String.format("%s.", this.modelInfo.getName()); - if (!name.startsWith(qualifier)) { - return String.format("%s%s", qualifier, name); - } - - return name; - } - - // This method is used to ensure backwards compatible loading, type names in model info may be qualified with the - // model name - private String ensureUnqualified(String name) { - if (name.startsWith(String.format("%s.", this.modelInfo.getName()))) { - return name.substring(name.indexOf('.') + 1); - } - - return name; - } - - private SimpleType resolveSimpleType(SimpleTypeInfo t) { - String qualifiedTypeName = ensureQualified(t.getName()); - DataType lookupType = lookupType(qualifiedTypeName); - if (lookupType instanceof ClassType) { - throw new IllegalArgumentException( - "Expected instance of SimpleType but found instance of ClassType instead."); - } - SimpleType result = (SimpleType) lookupType(qualifiedTypeName); - if (result == null) { - if (qualifiedTypeName.equals(DataType.ANY.getName())) { - result = DataType.ANY; - } else { - result = new SimpleType( - qualifiedTypeName, resolveTypeNameOrSpecifier(t.getBaseType(), t.getBaseTypeSpecifier())); - result.setTarget(t.getTarget()); - } - resolvedTypes.put(casify(result.getName()), result); - } - - return result; - } - - private DataType resolveTypeNameOrSpecifier(TupleTypeInfoElement element) { - DataType result = resolveTypeNameOrSpecifier(element.getElementType(), element.getElementTypeSpecifier()); - if (result == null) { - result = resolveTypeNameOrSpecifier(element.getType(), element.getTypeSpecifier()); - } - - return result; - } - - private Collection resolveTupleTypeElements(Collection infoElements) { - List elements = new ArrayList<>(); - for (TupleTypeInfoElement e : infoElements) { - elements.add(new TupleTypeElement(e.getName(), resolveTypeNameOrSpecifier(e))); - } - return elements; - } - - private TupleType resolveTupleType(TupleTypeInfo t) { - TupleType result = new TupleType(resolveTupleTypeElements(t.getElement())); - return result; - } - - private DataType resolveTypeNameOrSpecifier(ClassInfoElement element) { - DataType result = resolveTypeNameOrSpecifier(element.getElementType(), element.getElementTypeSpecifier()); - if (result == null) { - result = resolveTypeNameOrSpecifier(element.getType(), element.getTypeSpecifier()); - } - - return result; - } - - /** - * Converts a list of GenericParameterInfo definitions into their corresponding TypeParameter representations. - * - * @param parameterInfoList - * @return - */ - private List resolveGenericParameterDeclarations(List parameterInfoList) { - List genericParameters = new ArrayList<>(); - for (TypeParameterInfo parameterInfo : parameterInfoList) { - String constraint = parameterInfo.getConstraint(); - TypeParameter.TypeParameterConstraint typeConstraint = null; - if (constraint.equalsIgnoreCase(TypeParameter.TypeParameterConstraint.NONE.name())) { - typeConstraint = TypeParameter.TypeParameterConstraint.NONE; - } else if (constraint.equalsIgnoreCase(TypeParameter.TypeParameterConstraint.CLASS.name())) { - typeConstraint = TypeParameter.TypeParameterConstraint.CLASS; - } else if (constraint.equalsIgnoreCase(TypeParameter.TypeParameterConstraint.TUPLE.name())) { - typeConstraint = TypeParameter.TypeParameterConstraint.TUPLE; - } else if (constraint.equalsIgnoreCase(TypeParameter.TypeParameterConstraint.VALUE.name())) { - typeConstraint = TypeParameter.TypeParameterConstraint.VALUE; - } else if (constraint.equalsIgnoreCase(TypeParameter.TypeParameterConstraint.CHOICE.name())) { - typeConstraint = TypeParameter.TypeParameterConstraint.CHOICE; - } else if (constraint.equalsIgnoreCase(TypeParameter.TypeParameterConstraint.INTERVAL.name())) { - typeConstraint = TypeParameter.TypeParameterConstraint.INTERVAL; - } else if (constraint.equalsIgnoreCase(TypeParameter.TypeParameterConstraint.TYPE.name())) { - typeConstraint = TypeParameter.TypeParameterConstraint.TYPE; - } - genericParameters.add(new TypeParameter( - parameterInfo.getName(), typeConstraint, resolveTypeName(parameterInfo.getConstraintType()))); - } - return genericParameters; - } - - /** - * Method resolves the types associated with class elements (i.e., class fields). - * If the type is not resolved, the type System.Any is assigned to this element. - * - * @param classType - * @param infoElements - * @return - */ - private Collection resolveClassTypeElements( - ClassType classType, Collection infoElements) { - List elements = new ArrayList<>(); - for (ClassInfoElement e : infoElements) { - DataType elementType = null; - if (isOpenType(e)) { - elementType = resolveOpenType(classType, e); - } else if (isBoundParameterType(e)) { - elementType = resolveBoundType(classType, e); - } else { - elementType = resolveTypeNameOrSpecifier(e); - } - if (elementType == null) { - elementType = resolveTypeName("System.Any"); - } - elements.add( - new ClassTypeElement(e.getName(), elementType, e.isProhibited(), e.isOneBased(), e.getTarget())); - } - return elements; - } - - /** - * Method returns true if class element is an open element bound to a specific type. - * For instance, if the generic class defines a field: - *

T field1;
- * A subclass my bind T to a specific type such as System.Quantity such that the definition above - * becomes: - *
System.Quantity field1;
- * - * @param element - * @return - */ - private boolean isBoundParameterType(ClassInfoElement element) { - return element.getElementTypeSpecifier() instanceof BoundParameterTypeSpecifier; - } - - /** - * Method resolves the bound type declaration and returns the type if valid. Method throws an exception - * if the type cannot be resolved (does not exist) or if the parameter that this type is bound to is not defined - * in the generic class. Types must be bound to existing generic parameters. - * - * @param classType - * @param e - * @return - */ - private DataType resolveBoundType(ClassType classType, ClassInfoElement e) { - DataType boundType = null; - - BoundParameterTypeSpecifier boundParameterTypeSpecifier = - (BoundParameterTypeSpecifier) e.getElementTypeSpecifier(); - String parameterName = boundParameterTypeSpecifier.getParameterName(); - TypeParameter genericParameter = classType.getGenericParameterByIdentifier(parameterName); - - if (genericParameter == null) { - throw new RuntimeException("Unknown symbol " + parameterName); - } else { - boundType = resolveTypeName(boundParameterTypeSpecifier.getBoundType()); - } - - return boundType; - } - - /** - * Returns true if the element's type is a parameterized (non-bound, non-concrete) type - * such as - * - *
T myField;
- * - * @param element - * @return - */ - private boolean isOpenType(ClassInfoElement element) { - return element.getElementTypeSpecifier() instanceof ParameterTypeSpecifier; - } - - /** - * Method to validate open types. An open type must reference a parameter defined in the generic class by name - * and the generic parameter must exist. - * - *

- * Open types are class attribute types that reference one of the generic parameter of the class - * and that have not been bound to a concrete type. - *

- * - * @param classType - * @param e - * @return - */ - private DataType resolveOpenType(ClassType classType, ClassInfoElement e) { - DataType elementType; - ParameterTypeSpecifier parameterTypeSpecifier = (ParameterTypeSpecifier) e.getElementTypeSpecifier(); - String parameterName = parameterTypeSpecifier.getParameterName(); - if (parameterName == null - || parameterName.trim().length() == 0 - || classType.getGenericParameterByIdentifier(parameterName) == null) { - throw new RuntimeException( - "Open types must reference a valid generic parameter and cannot be null or blank"); - } - elementType = new TypeParameter( - parameterTypeSpecifier.getParameterName(), TypeParameter.TypeParameterConstraint.TYPE, null); - return elementType; - } - - private SearchType resolveClassTypeSearch(ClassType t, SearchInfo s) { - return new SearchType(s.getName(), s.getPath(), resolveTypeNameOrSpecifier(s.getType(), s.getTypeSpecifier())); - } - - private ClassType resolveClassType(ClassInfo t) { - if (t.getName() == null) { - throw new IllegalArgumentException("Class definition must have a name."); - } - - String qualifiedName = ensureQualified(t.getName()); - ClassType result = (ClassType) lookupType(qualifiedName); - - if (result == null) { - if (t instanceof ProfileInfo) { - result = new ProfileType( - qualifiedName, resolveTypeNameOrSpecifier(t.getBaseType(), t.getBaseTypeSpecifier())); - } else { - // Added to support generic notation in ModelInfo file for class type names (e.g., MyGeneric) and - // base classes (e.g., Map). - if (t.getName().contains("<")) { - result = handleGenericType(t.getName(), t.getBaseType()); - } else { - if (t.getBaseType() != null && t.getBaseType().contains("<")) { - result = handleGenericType(t.getName(), t.getBaseType()); - } else { - result = new ClassType( - qualifiedName, resolveTypeNameOrSpecifier(t.getBaseType(), t.getBaseTypeSpecifier())); - } - } - } - - resolvedTypes.put(casify(result.getName()), result); - - if (t.getParameter() != null) { - result.addGenericParameter(resolveGenericParameterDeclarations(t.getParameter())); - } - - if (t.getElement() != null) { - result.addElements(resolveClassTypeElements(result, t.getElement())); - } - - if (t.getSearch() != null) { - for (SearchInfo si : t.getSearch()) { - result.addSearch(resolveClassTypeSearch(result, si)); - } - } - - // Here we handle the case when a type is not a generic but its base type is a generic type whose parameters - // have all been bound to concrete types (no remaining degrees of freedom) and is not expressed in generic - // notation in the model-info file. - if (isParentGeneric(result) && !t.getBaseType().contains("<")) { - validateFreeAndBoundParameters(result, t); - } - - result.setIdentifier(t.getIdentifier()); - result.setLabel(t.getLabel()); - result.setTarget(t.getTarget()); - result.setRetrievable(t.isRetrievable()); - result.setPrimaryCodePath(t.getPrimaryCodePath()); - } - - return result; - } - - private ModelContext resolveContext(String contextName) { - for (ModelContext context : this.contexts) { - if (context.getName().equals(contextName)) { - return context; - } - } - - throw new IllegalArgumentException(String.format("Could not resolve context name %s.", contextName)); - } - - private Relationship resolveRelationship(RelationshipInfo relationshipInfo) { - ModelContext modelContext = resolveContext(relationshipInfo.getContext()); - Relationship relationship = new Relationship( - modelContext, - Arrays.asList(relationshipInfo.getRelatedKeyElement().split(";"))); - // TODO: Validate relatedKeyElements match keyElements of the referenced context - return relationship; - } - - private void importRelationships(ClassInfo c, ClassType t) { - for (RelationshipInfo r : c.getContextRelationship()) { - t.addRelationship(resolveRelationship(r)); - } - - for (RelationshipInfo r : c.getTargetContextRelationship()) { - t.addTargetRelationship(resolveRelationship(r)); - } - } - - private IntervalType resolveIntervalType(IntervalTypeInfo t) { - IntervalType result = new IntervalType(resolveTypeNameOrSpecifier(t.getPointType(), t.getPointTypeSpecifier())); - return result; - } - - private ListType resolveListType(ListTypeInfo t) { - ListType result = new ListType(resolveTypeNameOrSpecifier(t.getElementType(), t.getElementTypeSpecifier())); - return result; - } - - private ChoiceType resolveChoiceType(ChoiceTypeInfo t) { - ArrayList types = new ArrayList(); - if (t.getChoice() != null && t.getChoice().size() > 0) { - for (TypeSpecifier typeSpecifier : t.getChoice()) { - types.add(resolveTypeSpecifier(typeSpecifier)); - } - } else { - for (TypeSpecifier typeSpecifier : t.getType()) { - types.add(resolveTypeSpecifier(typeSpecifier)); - } - } - return new ChoiceType(types); - } - - /** - * Method checks to see if a class' parameters covers its parent parameters. These represent - * remaining degrees of freedom in the child class. For instance, - *
MyGeneric<T> extends SomeOtherGeneric<String,T>
- * All parameters in the parent class, not covered by the child class must be bound - * to a concrete type. In the above example, the parameter S is bound to the type String. - *

If a parameter in the parent type is not covered by a child parameter nor bound to - * a concrete type, an exception is thrown indicating that the symbol is not known. In the example - * below, T is neither covered nor bound and thus is an unknown symbol.

- *
MyGeneric extends SomeOtherGeneric<String,T>
- * - * @param type - * @param definition - */ - public void validateFreeAndBoundParameters(ClassType type, ClassInfo definition) { - List coveredParameters = new ArrayList<>(); - List boundParameters = new ArrayList<>(); - - ((ClassType) type.getBaseType()).getGenericParameters().forEach(typeParameter -> { - String parameterName = typeParameter.getIdentifier(); - if (type.getGenericParameterByIdentifier(parameterName, true) != null) { - coveredParameters.add(parameterName); - } else { - boundParameters.add(parameterName); - } - }); - - if (boundParameters.size() > 0) { - if (definition.getElement() != null) { - definition.getElement().forEach(classInfoElement -> { - if (classInfoElement.getElementTypeSpecifier() instanceof BoundParameterTypeSpecifier) { - String name = ((BoundParameterTypeSpecifier) classInfoElement.getElementTypeSpecifier()) - .getParameterName(); - int paramIndex = boundParameters.indexOf(name); - if (paramIndex >= 0) { - boundParameters.remove(paramIndex); - } - } - }); - if (boundParameters.size() > 0) { - throw new RuntimeException("Unknown symbols " + boundParameters); - } - } else { - throw new RuntimeException("Unknown symbols " + boundParameters); - } - } - } - - /** - * Method returns true if the class' base type is a generic type. - * - * @param type - * @return True if the parent of class 'type' is a generic class. - */ - public boolean isParentGeneric(ClassType type) { - DataType baseType = type.getBaseType(); - return baseType != null && baseType instanceof ClassType && ((ClassType) baseType).isGeneric(); - } - - /** - * Converts a generic type declaration represented as a string into the corresponding - * generic ClassType (i.e., a class type that specifies generic parameters). - * - * @param baseType The base type for the generic class type. - * @param genericSignature The signature of the generic type such as Map<K,V>. - * @return - */ - private ClassType handleGenericType(String genericSignature, String baseType) { - if (genericSignature == null) { - throw new IllegalArgumentException("genericSignature is null"); - } - - GenericClassSignatureParser parser = - new GenericClassSignatureParser(genericSignature, baseType, null, resolvedTypes); - ClassType genericClassType = parser.parseGenericSignature(); - - return genericClassType; - } - - /** - * Checks whether descendant is a valid subtype of ancestor. - * - * @param descendant - * @param ancestor - * @return - */ - private boolean conformsTo(DataType descendant, DataType ancestor) { - boolean conforms = false; - if (descendant != null && ancestor != null && descendant.equals(ancestor)) { - conforms = true; - } else { - conforms = conformsTo(descendant.getBaseType(), ancestor); - } - return conforms; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Operator.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Operator.java deleted file mode 100644 index cc800bc59..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Operator.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.ArrayList; -import org.hl7.cql.model.DataType; -import org.hl7.elm.r1.AccessModifier; -import org.hl7.elm.r1.FunctionDef; -import org.hl7.elm.r1.OperandDef; - -public class Operator { - - public static Operator fromFunctionDef(FunctionDef functionDef) { - java.util.List operandTypes = new ArrayList<>(); - for (OperandDef operand : functionDef.getOperand()) { - operandTypes.add(operand.getResultType()); - } - return new Operator( - functionDef, - functionDef.getName(), - new Signature(operandTypes.toArray(new DataType[operandTypes.size()])), - functionDef.getResultType()) - .withAccessLevel(functionDef.getAccessLevel()) - .withFluent(functionDef.isFluent() != null ? functionDef.isFluent() : false) - .withExternal(functionDef.isExternal() != null ? functionDef.isExternal() : false); - } - - public Operator(String name, Signature signature, DataType resultType) { - this(null, name, signature, resultType); - } - - public Operator(FunctionDef functionDef, String name, Signature signature, DataType resultType) { - if (name == null || name.equals("")) { - throw new IllegalArgumentException("name is null or empty"); - } - - if (signature == null) { - throw new IllegalArgumentException("signature is null"); - } - - this.functionDef = functionDef; - this.name = name; - this.signature = signature; - this.resultType = resultType; - } - - private String libraryName; - - public String getLibraryName() { - return this.libraryName; - } - - public void setLibraryName(String libraryName) { - if (libraryName == null || libraryName.equals("")) { - throw new IllegalArgumentException("libraryName is null."); - } - - this.libraryName = libraryName; - } - - private AccessModifier accessLevel = AccessModifier.PUBLIC; - - public AccessModifier getAccessLevel() { - return accessLevel; - } - - public void setAccessLevel(AccessModifier accessLevel) { - this.accessLevel = accessLevel; - } - - public Operator withAccessLevel(AccessModifier accessLevel) { - setAccessLevel(accessLevel); - return this; - } - - private boolean isFluent = false; - - public boolean getFluent() { - return isFluent; - } - - public void setFluent(boolean isFluent) { - this.isFluent = isFluent; - } - - public Operator withFluent(boolean isFluent) { - setFluent(isFluent); - return this; - } - - private boolean isExternal = false; - - public boolean getExternal() { - return isExternal; - } - - public void setExternal(boolean isExternal) { - this.isExternal = isExternal; - } - - public Operator withExternal(boolean isExternal) { - setExternal(isExternal); - return this; - } - - private FunctionDef functionDef; - - public FunctionDef getFunctionDef() { - return this.functionDef; - } - - public void setFunctionDef(FunctionDef functionDef) { - this.functionDef = functionDef; - } - - public Operator withFunctionDef(FunctionDef functionDef) { - setFunctionDef(functionDef); - return this; - } - - private String name; - - public String getName() { - return this.name; - } - - private Signature signature; - - public Signature getSignature() { - return this.signature; - } - - private DataType resultType; - - public DataType getResultType() { - return this.resultType; - } - - public void setResultType(DataType resultType) { - this.resultType = resultType; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorEntry.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorEntry.java deleted file mode 100644 index ed1a17a34..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorEntry.java +++ /dev/null @@ -1,410 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.*; -import org.hl7.cql.model.ChoiceType; -import org.hl7.cql.model.DataType; - -public class OperatorEntry { - public OperatorEntry(String name) { - if (name == null || name.equals("")) { - throw new IllegalArgumentException("name is null or empty"); - } - - this.name = name; - } - - private String name; - - public String getName() { - return this.name; - } - - private SignatureNodes signatures = new SignatureNodes(); - private Map genericOperators = new HashMap<>(); - - private static class SignatureNode { - public SignatureNode(Operator operator) { - if (operator == null) { - throw new IllegalArgumentException("operator is null."); - } - - this.operator = operator; - } - - private Operator operator; - - public Operator getOperator() { - return operator; - } - - public Signature getSignature() { - return operator.getSignature(); - } - - /* - The invocation signature is the call signature with arguments of type Any set to the operand types - */ - private Signature getInvocationSignature(Signature callSignature, Signature operatorSignature) { - if (callSignature.getSize() == operatorSignature.getSize()) { - DataType[] invocationTypes = new DataType[callSignature.getSize()]; - Iterator callTypes = callSignature.getOperandTypes().iterator(); - Iterator operatorTypes = - operatorSignature.getOperandTypes().iterator(); - boolean isResolved = false; - for (int i = 0; i < invocationTypes.length; i++) { - DataType callType = callTypes.next(); - DataType operatorType = operatorTypes.next(); - if (callType.equals(DataType.ANY) && !operatorType.equals(DataType.ANY)) { - isResolved = true; - invocationTypes[i] = operatorType; - } else { - invocationTypes[i] = callType; - } - } - if (isResolved) { - return new Signature(invocationTypes); - } - } - return callSignature; - } - - private OperatorResolution getOperatorResolution( - Operator operator, - Signature callSignature, - Signature invocationSignature, - ConversionMap conversionMap, - OperatorMap operatorMap, - boolean allowPromotionAndDemotion, - boolean requireConversions) { - Conversion[] conversions = getConversions( - callSignature, operator.getSignature(), conversionMap, operatorMap, allowPromotionAndDemotion); - OperatorResolution result = new OperatorResolution(operator, conversions); - if (requireConversions && conversions == null) { - return null; - } - return result; - } - - public List resolve( - CallContext callContext, ConversionMap conversionMap, OperatorMap operatorMap) { - List results = null; - Signature invocationSignature = getInvocationSignature(callContext.getSignature(), operator.getSignature()); - - // Attempt exact match against this signature - if (operator.getSignature().equals(invocationSignature)) { - OperatorResolution result = getOperatorResolution( - operator, - callContext.getSignature(), - invocationSignature, - conversionMap, - operatorMap, - callContext.getAllowPromotionAndDemotion(), - false); - if (result != null) { - results = new ArrayList<>(); - results.add(result); - return results; - } - } - - // Attempt to resolve against sub signatures - results = subSignatures.resolve(callContext, conversionMap, operatorMap); - - // If no subsignatures match, attempt subType match against this signature - if (results == null && operator.getSignature().isSuperTypeOf(invocationSignature)) { - OperatorResolution result = getOperatorResolution( - operator, - callContext.getSignature(), - invocationSignature, - conversionMap, - operatorMap, - callContext.getAllowPromotionAndDemotion(), - false); - if (result != null) { - results = new ArrayList<>(); - results.add(result); - return results; - } - } - - if (results == null && conversionMap != null) { - // Attempt to find a conversion path from the call signature to the target signature - OperatorResolution result = getOperatorResolution( - operator, - callContext.getSignature(), - invocationSignature, - conversionMap, - operatorMap, - callContext.getAllowPromotionAndDemotion(), - true); - if (result != null) { - if (results == null) { - results = new ArrayList<>(); - } - results.add(result); - } - } - - return results; - } - - private Conversion[] getConversions( - Signature callSignature, - Signature operatorSignature, - ConversionMap conversionMap, - OperatorMap operatorMap, - boolean allowPromotionAndDemotion) { - if (callSignature == null - || operatorSignature == null - || callSignature.getSize() != operatorSignature.getSize()) { - return null; - } - - Conversion[] conversions = new Conversion[callSignature.getSize()]; - boolean isConvertible = callSignature.isConvertibleTo( - operatorSignature, conversionMap, operatorMap, allowPromotionAndDemotion, conversions); - - if (isConvertible) { - return conversions; - } - - return null; - } - - private SignatureNodes subSignatures = new SignatureNodes(); - - public boolean hasSubSignatures() { - return subSignatures.hasSignatures(); - } - - @Override - public int hashCode() { - return operator.getSignature().hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof SignatureNode) { - SignatureNode that = (SignatureNode) o; - return this.operator.getName().equals(that.operator.getName()) - && this.getSignature().equals(that.getSignature()); - } - - return false; - } - - public String toString() { - return operator.toString(); - } - } - - private static class SignatureNodes { - private Map signatures = new HashMap<>(); - - public boolean hasSignatures() { - return signatures.size() > 0; - } - - public boolean contains(Operator operator) { - boolean result = signatures.containsKey(operator.getSignature()); - if (!result) { - for (SignatureNode n : signatures.values()) { - result = n.subSignatures.contains(operator); - if (result) { - break; - } - } - } - - return result; - } - - public void add(SignatureNode node) { - if (node == null) { - throw new IllegalArgumentException("node is null."); - } - - if (signatures.containsKey(node.getSignature())) { - throw new IllegalArgumentException(String.format( - "Operator %s already has a registration for signature: %s.", - node.operator.getName(), node.getSignature().toString())); - } - - boolean added = false; - for (SignatureNode n : signatures.values()) { - if (n.getSignature().isSuperTypeOf(node.getSignature())) { - n.subSignatures.add(node); - added = true; - break; - } - } - - if (!added) { - for (SignatureNode n : signatures.values().toArray(new SignatureNode[signatures.size()])) { - if (node.getSignature().isSuperTypeOf(n.getSignature())) { - signatures.remove(n.getSignature()); - node.subSignatures.add(n); - } - } - - signatures.put(node.getSignature(), node); - } - } - - public List resolve( - CallContext callContext, ConversionMap conversionMap, OperatorMap operatorMap) { - ArrayList results = null; - - int signatureCount = 0; - for (SignatureNode n : signatures.values()) { - - if (n.getSignature().getSize() == callContext.getSignature().getSize()) { - signatureCount++; - - // Any subSignature will count as an overload - if (n.hasSubSignatures()) { - signatureCount++; - } - } - - List nodeResults = n.resolve(callContext, conversionMap, operatorMap); - if (nodeResults != null) { - if (results == null) { - results = new ArrayList<>(); - } - results.addAll(nodeResults); - } - } - - if (results != null && signatureCount > 1) { - for (OperatorResolution result : results) { - result.setOperatorHasOverloads(); - } - } - - return results; - } - } - - public boolean containsOperator(Operator operator) { - if (operator instanceof GenericOperator) { - return containsGenericOperator((GenericOperator) operator); - } else { - return signatures.contains(operator); - } - } - - public void addOperator(Operator operator) { - if (operator instanceof GenericOperator) { - addGenericOperator((GenericOperator) operator); - } else { - signatures.add(new SignatureNode(operator)); - } - } - - private boolean containsGenericOperator(GenericOperator operator) { - return genericOperators.containsKey(operator.getSignature()); - } - - private void addGenericOperator(GenericOperator operator) { - if (genericOperators.containsKey(operator.getSignature())) { - throw new IllegalArgumentException(String.format( - "Operator %s already has a generic registration for signature: %s.", - name, operator.getSignature().toString())); - } - - genericOperators.put(operator.getSignature(), operator); - } - - private boolean allResultsUseConversion(List results) { - for (OperatorResolution resolution : results) { - if (!resolution.hasConversions()) { - return false; - } - } - - return true; - } - - public List expandChoices(Signature callSignature) { - ArrayList signatures = new ArrayList(); - if (callSignature.containsChoices()) { - - ArrayList> operandList = new ArrayList>(); - for (DataType operand : callSignature.getOperandTypes()) { - ArrayList list = new ArrayList(); - if (operand instanceof ChoiceType) { - for (DataType type : ((ChoiceType) operand).getTypes()) { - list.add(type); - } - } else { - list.add(operand); - } - operandList.add(list); - } - - DataType[] result = new DataType[callSignature.getSize()]; - collectSignatures(operandList, result, 0, signatures); - } else { - signatures.add(callSignature); - } - return signatures; - } - - private void collectSignatures( - ArrayList> operandList, DataType[] result, int k, List signatures) { - if (k == operandList.size()) { - signatures.add(new Signature(result)); - } else { - for (int j = 0; j < operandList.get(k).size(); j++) { - result[k] = operandList.get(k).get(j); - collectSignatures(operandList, result, k + 1, signatures); - } - } - } - - public List resolve( - CallContext callContext, OperatorMap operatorMap, ConversionMap conversionMap) { - if (callContext == null) { - throw new IllegalArgumentException("callContext is null"); - } - - // Attempt to instantiate any generic signatures - // If the callContext signature contains choices, attempt instantiation with all possible combinations of - // the call signature (ouch, this could really hurt...) - boolean signaturesInstantiated = false; - List callSignatures = expandChoices(callContext.getSignature()); - for (Signature callSignature : callSignatures) { - List instantiations = - instantiate(callSignature, operatorMap, conversionMap, callContext.getAllowPromotionAndDemotion()); - for (Operator instantiation : instantiations) { - // If the generic signature was instantiated, store it as an actual signature. - if (!signatures.contains(instantiation)) { - signatures.add(new SignatureNode(instantiation)); - } - } - } - - List results = signatures.resolve(callContext, conversionMap, operatorMap); - - return results; - } - - private List instantiate( - Signature signature, - OperatorMap operatorMap, - ConversionMap conversionMap, - boolean allowPromotionAndDemotion) { - List instantiations = new ArrayList(); - - for (GenericOperator genericOperator : genericOperators.values()) { - InstantiationResult instantiationResult = - genericOperator.instantiate(signature, operatorMap, conversionMap, allowPromotionAndDemotion); - if (instantiationResult.getOperator() != null) { - instantiations.add(instantiationResult.getOperator()); - } - } - - return instantiations; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorMap.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorMap.java deleted file mode 100644 index 246c250be..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorMap.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.*; -import org.hl7.cql.model.*; - -public class OperatorMap { - private Map operators = new HashMap<>(); - - public boolean containsOperator(Operator operator) { - OperatorEntry entry = getEntry(operator.getName()); - return entry.containsOperator(operator); - } - - public void addOperator(Operator operator) { - OperatorEntry entry = getEntry(operator.getName()); - entry.addOperator(operator); - } - - private OperatorEntry getEntry(String operatorName) { - if (operatorName == null || operatorName.equals("")) { - throw new IllegalArgumentException("operatorName is null or empty."); - } - - OperatorEntry entry = operators.get(operatorName); - if (entry == null) { - entry = new OperatorEntry(operatorName); - operators.put(operatorName, entry); - } - - return entry; - } - - public boolean supportsOperator(String libraryName, String operatorName, DataType... signature) { - CallContext call = new CallContext(libraryName, operatorName, false, false, false, signature); - try { - OperatorResolution resolution = resolveOperator(call, null); - if (resolution == null) { - return false; - } - } catch (Exception e) { - return false; - } - - return true; - } - - // Returns true if the given type supports the operations necessary to be the point type of an interval - // (i.e. comparison, successor, and predecessor) - public boolean isPointType(DataType type) { - return supportsOperator("System", "LessOrEqual", type, type) && supportsOperator("System", "Successor", type); - } - - public OperatorResolution resolveOperator(CallContext callContext, ConversionMap conversionMap) { - OperatorEntry entry = getEntry(callContext.getOperatorName()); - List results = entry.resolve(callContext, this, conversionMap); - - // Score each resolution and return the lowest score - // Duplicate scores indicate ambiguous match - OperatorResolution result = null; - if (results != null) { - int lowestScore = Integer.MAX_VALUE; - List lowestScoringResults = new ArrayList<>(); - for (OperatorResolution resolution : results) { - Iterator operands = resolution - .getOperator() - .getSignature() - .getOperandTypes() - .iterator(); - Iterator callOperands = - callContext.getSignature().getOperandTypes().iterator(); - Iterator conversions = resolution.hasConversions() - ? resolution.getConversions().iterator() - : null; - int score = ConversionMap.ConversionScore.ExactMatch.score(); - while (operands.hasNext()) { - DataType operand = operands.next(); - DataType callOperand = callOperands.next(); - Conversion conversion = conversions != null ? conversions.next() : null; - score += ConversionMap.getConversionScore(callOperand, operand, conversion); - } - - resolution.setScore(score); - - if (score < lowestScore) { - lowestScore = score; - lowestScoringResults.clear(); - lowestScoringResults.add(resolution); - } else if (score == lowestScore) { - lowestScoringResults.add(resolution); - } - } - - if (lowestScoringResults.size() > 1) { - int lowestTypeScore = Integer.MAX_VALUE; - List lowestTypeScoringResults = new ArrayList<>(); - for (OperatorResolution resolution : lowestScoringResults) { - int typeScore = ConversionMap.ConversionScore.ExactMatch.score(); - for (DataType operand : - resolution.getOperator().getSignature().getOperandTypes()) { - typeScore += ConversionMap.getTypePrecedenceScore(operand); - } - - if (typeScore < lowestTypeScore) { - lowestTypeScore = typeScore; - lowestTypeScoringResults.clear(); - lowestTypeScoringResults.add(resolution); - } else if (typeScore == lowestTypeScore) { - lowestTypeScoringResults.add(resolution); - } - } - - lowestScoringResults = lowestTypeScoringResults; - } - - if (lowestScoringResults.size() > 1) { - if (callContext.getMustResolve()) { - // ERROR: - StringBuilder message = new StringBuilder("Call to operator ") - .append(callContext.getOperatorName()) - .append(callContext.getSignature()) - .append(" is ambiguous with: "); - for (OperatorResolution resolution : lowestScoringResults) { - message.append("\n - ") - .append(resolution.getOperator().getName()) - .append(resolution.getOperator().getSignature()); - } - throw new IllegalArgumentException(message.toString()); - } else { - return null; - } - } else { - result = lowestScoringResults.get(0); - } - } - - return result; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorResolution.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorResolution.java deleted file mode 100644 index 507429091..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/OperatorResolution.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.ArrayList; -import java.util.List; -import org.hl7.elm.r1.VersionedIdentifier; - -public class OperatorResolution { - public OperatorResolution() {} - - public OperatorResolution(Operator operator, Conversion[] conversions) { - this.operator = operator; - if (conversions != null) { - setConversions(conversions); - } - } - - private Operator operator; - - public Operator getOperator() { - return operator; - } - - public void setOperator(Operator operator) { - this.operator = operator; - } - - private boolean allowFluent = false; - - public boolean getAllowFluent() { - return allowFluent; - } - - public void setAllowFluent(boolean allowFluent) { - this.allowFluent = allowFluent; - } - - /* - The versioned identifier (fully qualified, versioned, library identifier of the library in which the resolved operator - is defined. This is set by the library resolution to allow the calling context to understand the defined location - of the resolved operator. - */ - private VersionedIdentifier libraryIdentifier; - - public VersionedIdentifier getLibraryIdentifier() { - return libraryIdentifier; - } - - public void setLibraryIdentifier(VersionedIdentifier libraryIdentifier) { - this.libraryIdentifier = libraryIdentifier; - } - - /* - The local alias for the resolved library. This is set by the libraryBuilder to allow the invocation - to set the library alias if necessary. - */ - private String libraryName; - - public String getLibraryName() { - return libraryName; - } - - public void setLibraryName(String libraryName) { - this.libraryName = libraryName; - } - - private void ensureConversions() { - if (this.conversions == null) { - this.conversions = new ArrayList<>(); - } else { - this.conversions.clear(); - } - } - - private List conversions; - - public Iterable getConversions() { - return conversions; - } - - public void setConversions(Iterable conversions) { - ensureConversions(); - for (Conversion conversion : conversions) { - this.conversions.add(conversion); - } - } - - public void setConversions(Conversion[] conversions) { - ensureConversions(); - for (int i = 0; i < conversions.length; i++) { - this.conversions.add(conversions[i]); - } - } - - public boolean hasConversions() { - return this.conversions != null; - } - - private boolean operatorHasOverloads = false; - - public boolean getOperatorHasOverloads() { - return operatorHasOverloads; - } - - public void setOperatorHasOverloads() { - operatorHasOverloads = true; - } - - private int score; - - public int getScore() { - return score; - } - - public void setScore(int score) { - this.score = score; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/PropertyResolution.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/PropertyResolution.java deleted file mode 100644 index 6082d82fa..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/PropertyResolution.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.Map; -import org.hl7.cql.model.ClassTypeElement; -import org.hl7.cql.model.DataType; -import org.hl7.cql.model.SearchType; -import org.hl7.cql.model.TupleTypeElement; - -/** - * Created by Bryn on 4/19/2019. - */ -public class PropertyResolution { - private DataType type; - private String name; - private String targetMap; - private boolean isSearchValue = false; - - public PropertyResolution(ClassTypeElement e) { - this.type = e.getType(); - this.name = e.getName(); - if (e.getTarget() != null) { - this.targetMap = e.getTarget(); - } - } - - public PropertyResolution(TupleTypeElement e) { - this.type = e.getType(); - this.name = e.getName(); - } - - public PropertyResolution(SearchType s) { - this.type = s.getType(); - this.name = s.getName(); - this.isSearchValue = true; - } - - public PropertyResolution(DataType type, String name) { - this(type, name, null); - } - - public PropertyResolution(DataType type, String name, Map targetMaps) { - if (type == null) { - throw new IllegalArgumentException("type cannot be null"); - } - - if (name == null) { - throw new IllegalArgumentException("name cannot be null"); - } - this.type = type; - this.name = name; - - if (targetMaps != null && targetMaps.size() > 0) { - StringBuilder builder = new StringBuilder(); - for (Map.Entry entry : targetMaps.entrySet()) { - if (builder.length() > 0) { - builder.append(";"); - } - if (targetMaps.size() > 1) { - builder.append(entry.getKey().toString()); - builder.append(":"); - } - builder.append(entry.getValue()); - } - this.targetMap = builder.toString(); - } - } - - public DataType getType() { - return this.type; - } - - public String getName() { - return this.name; - } - - public String getTargetMap() { - return this.targetMap; - } - - public boolean isSearch() { - return this.isSearchValue; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/QueryContext.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/QueryContext.java deleted file mode 100644 index f7651a98e..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/QueryContext.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.Collection; -import java.util.HashMap; -import org.hl7.cql.model.DataType; -import org.hl7.cql.model.ListType; -import org.hl7.elm.r1.AliasedQuerySource; -import org.hl7.elm.r1.LetClause; - -public class QueryContext { - private final HashMap sources = new HashMap<>(); - private final HashMap lets = new HashMap<>(); - - private void internalAddQuerySource(AliasedQuerySource source) { - sources.put(source.getAlias(), source); - } - - // Adds a related (i.e. with or without) source, which does not change cardinality of the query - public void addRelatedQuerySource(AliasedQuerySource source) { - internalAddQuerySource(source); - } - - // Adds primary sources, which affect cardinality (any primary plural source results in a plural query) - public void addPrimaryQuerySources(Collection sources) { - for (AliasedQuerySource source : sources) { - internalAddQuerySource(source); - if (source.getResultType() instanceof ListType) { - isSingularValue = false; - } - } - } - - public Collection getQuerySources() { - return sources.values(); - } - - public void removeQuerySource(AliasedQuerySource source) { - sources.remove(source.getAlias()); - } - - public void removeQuerySources(Collection sources) { - for (AliasedQuerySource source : sources) { - removeQuerySource(source); - } - } - - public void addLetClauses(Collection lets) { - for (LetClause let : lets) { - addLetClause(let); - } - } - - public void addLetClause(LetClause let) { - lets.put(let.getIdentifier(), let); - } - - public void removeLetClause(LetClause let) { - lets.remove(let.getIdentifier()); - } - - public void removeLetClauses(Collection lets) { - for (LetClause let : lets) { - removeLetClause(let); - } - } - - public AliasedQuerySource resolveAlias(String identifier) { - return sources.get(identifier); - } - - public LetClause resolveLet(String identifier) { - return lets.get(identifier); - } - - private boolean isSingularValue = true; - - public boolean isSingular() { - return isSingularValue; - } - - private boolean inSourceClauseValue; - - public void enterSourceClause() { - inSourceClauseValue = true; - } - - public void exitSourceClause() { - inSourceClauseValue = false; - } - - public boolean inSourceClause() { - return inSourceClauseValue; - } - - private boolean inSortClauseValue; - - public void enterSortClause() { - inSortClauseValue = true; - } - - public void exitSortClause() { - inSortClauseValue = false; - } - - public boolean inSortClause() { - return inSortClauseValue; - } - - private boolean isImplicitValue; - - public boolean isImplicit() { - return isImplicitValue; - } - - public void setIsImplicit(boolean isImplicitValue) { - this.isImplicitValue = isImplicitValue; - } - - private DataType resultElementType; - - public DataType getResultElementType() { - return resultElementType; - } - - public void setResultElementType(DataType resultElementType) { - this.resultElementType = resultElementType; - } - - private boolean referencesSpecificContextValue; - - public boolean referencesSpecificContext() { - return referencesSpecificContextValue; - } - - public void referenceSpecificContext() { - referencesSpecificContextValue = true; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ResolvedIdentifierContext.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ResolvedIdentifierContext.java deleted file mode 100644 index 02ca5c1b2..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ResolvedIdentifierContext.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.Objects; -import java.util.Optional; -import java.util.StringJoiner; -import org.hl7.elm.r1.CodeDef; -import org.hl7.elm.r1.CodeSystemDef; -import org.hl7.elm.r1.ConceptDef; -import org.hl7.elm.r1.ContextDef; -import org.hl7.elm.r1.Element; -import org.hl7.elm.r1.ExpressionDef; -import org.hl7.elm.r1.OperandDef; -import org.hl7.elm.r1.ParameterDef; -import org.hl7.elm.r1.TupleElementDefinition; -import org.hl7.elm.r1.ValueSetDef; - -/** - * Context for resolved identifiers containing the identifier, the resolved element (if non-null) as well as the type - * of matching done to retrieve the element, whether case-sensitive or case-insensitive. - */ -public class ResolvedIdentifierContext { - private final String identifier; - private final Element nullableElement; - - private enum ResolvedIdentifierMatchType { - EXACT, - CASE_INSENSITIVE - } - - // TODO: enum instead? - private final ResolvedIdentifierMatchType matchType; - - public static ResolvedIdentifierContext exactMatch(String identifier, Element nullableElement) { - return new ResolvedIdentifierContext(identifier, nullableElement, ResolvedIdentifierMatchType.EXACT); - } - - public static ResolvedIdentifierContext caseInsensitiveMatch(String identifier, Element nullableElement) { - return new ResolvedIdentifierContext(identifier, nullableElement, ResolvedIdentifierMatchType.CASE_INSENSITIVE); - } - - private ResolvedIdentifierContext( - String identifier, Element nullableElement, ResolvedIdentifierMatchType matchType) { - this.identifier = identifier; - this.nullableElement = nullableElement; - this.matchType = matchType; - } - - public Optional getExactMatchElement() { - if (isExactMatch()) { - return Optional.ofNullable(nullableElement); - } - - return Optional.empty(); - } - - private boolean isExactMatch() { - return ResolvedIdentifierMatchType.EXACT == matchType; - } - - public Optional warnCaseInsensitiveIfApplicable() { - if (nullableElement != null && !isExactMatch()) { - return getName(nullableElement) - .map(name -> - String.format("Could not find identifier: [%s]. Did you mean [%s]?", identifier, name)); - } - - return Optional.empty(); - } - - public T resolveIdentifier(Class clazz) { - return getExactMatchElement().filter(clazz::isInstance).map(clazz::cast).orElse(null); - } - - public Optional getElementOfType(Class clazz) { - if (clazz.isInstance(nullableElement)) { - return Optional.of(clazz.cast(nullableElement)); - } - - return Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; - } - ResolvedIdentifierContext that = (ResolvedIdentifierContext) other; - return Objects.equals(identifier, that.identifier) - && Objects.equals(nullableElement, that.nullableElement) - && matchType == that.matchType; - } - - @Override - public int hashCode() { - return Objects.hash(identifier, nullableElement, matchType); - } - - @Override - public String toString() { - return new StringJoiner(", ", ResolvedIdentifierContext.class.getSimpleName() + "[", "]") - .add("identifier='" + identifier + "'") - .add("nullableElement=" + nullableElement) - .add("matchType=" + matchType) - .toString(); - } - - private static Optional getName(Element element) { - // TODO: consider other Elements that don't have getName() - if (element instanceof ExpressionDef) { - return Optional.of(((ExpressionDef) element).getName()); - } - - if (element instanceof ValueSetDef) { - return Optional.of(((ValueSetDef) element).getName()); - } - - if (element instanceof OperandDef) { - return Optional.of(((OperandDef) element).getName()); - } - - if (element instanceof TupleElementDefinition) { - return Optional.of(((TupleElementDefinition) element).getName()); - } - - if (element instanceof CodeDef) { - return Optional.of(((CodeDef) element).getName()); - } - - if (element instanceof ConceptDef) { - return Optional.of(((ConceptDef) element).getName()); - } - - if (element instanceof ParameterDef) { - return Optional.of(((ParameterDef) element).getName()); - } - - if (element instanceof CodeSystemDef) { - return Optional.of(((CodeSystemDef) element).getName()); - } - - if (element instanceof ContextDef) { - return Optional.of(((ContextDef) element).getName()); - } - - return Optional.empty(); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Signature.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Signature.java deleted file mode 100644 index 91b8e583e..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Signature.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.ArrayList; -import java.util.List; -import org.hl7.cql.model.ChoiceType; -import org.hl7.cql.model.DataType; -import org.hl7.cql.model.InstantiationContext; - -public class Signature { - public Signature(DataType... operandTypes) { - if (operandTypes == null) { - throw new IllegalArgumentException("operandTypes is null"); - } - - for (DataType operandType : operandTypes) { - if (operandType == null) { - throw new IllegalArgumentException("operandTypes in signatures cannot be null"); - } - - this.operandTypes.add(operandType); - } - } - - private List operandTypes = new ArrayList<>(); - - public Iterable getOperandTypes() { - return this.operandTypes; - } - - public int getSize() { - return operandTypes.size(); - } - - public boolean isSuperTypeOf(Signature other) { - if (operandTypes.size() == other.operandTypes.size()) { - for (int i = 0; i < operandTypes.size(); i++) { - if (!operandTypes.get(i).isSuperTypeOf(other.operandTypes.get(i))) { - return false; - } - } - - return true; - } - - return false; - } - - private boolean getHasChoices() { - for (DataType operandType : operandTypes) { - if (operandType instanceof ChoiceType) { - return true; - } - } - - return false; - } - - private boolean hasChoices; - private boolean calculatedHasChoices; - - public boolean containsChoices() { - if (!calculatedHasChoices) { - hasChoices = getHasChoices(); - calculatedHasChoices = true; - } - return hasChoices; - } - - public boolean isSubTypeOf(Signature other) { - if (operandTypes.size() == other.operandTypes.size()) { - for (int i = 0; i < operandTypes.size(); i++) { - if (!operandTypes.get(i).isSubTypeOf(other.operandTypes.get(i))) { - return false; - } - } - - return true; - } - - return false; - } - - public boolean isInstantiable(Signature callSignature, InstantiationContext context) { - if (operandTypes.size() == callSignature.operandTypes.size()) { - for (int i = 0; i < operandTypes.size(); i++) { - if (!operandTypes.get(i).isInstantiable(callSignature.operandTypes.get(i), context)) { - return false; - } - } - - return true; - } - - return false; - } - - public Signature instantiate(InstantiationContext context) { - DataType[] result = new DataType[operandTypes.size()]; - for (int i = 0; i < operandTypes.size(); i++) { - result[i] = operandTypes.get(i).instantiate(context); - } - - return new Signature(result); - } - - public boolean isConvertibleTo( - Signature other, - ConversionMap conversionMap, - OperatorMap operatorMap, - boolean allowPromotionAndDemotion, - Conversion[] conversions) { - if (operandTypes.size() == other.operandTypes.size()) { - for (int i = 0; i < operandTypes.size(); i++) { - if (!operandTypes.get(i).isSubTypeOf(other.operandTypes.get(i))) { - Conversion conversion = conversionMap.findConversion( - operandTypes.get(i), - other.operandTypes.get(i), - true, - allowPromotionAndDemotion, - operatorMap); - if (conversion != null) { - conversions[i] = conversion; - } else { - return false; - } - } - } - - return true; - } - - return false; - } - - @Override - public int hashCode() { - int result = 53; - for (DataType operandType : operandTypes) { - result += (39 * operandType.hashCode()); - } - - return result; - } - - @Override - public boolean equals(Object o) { - if (o instanceof Signature) { - Signature that = (Signature) o; - - if (this.operandTypes.size() == that.operandTypes.size()) { - for (int i = 0; i < this.operandTypes.size(); i++) { - if (!(this.operandTypes.get(i).equals(that.operandTypes.get(i)))) { - return false; - } - } - - return true; - } - } - - return false; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("("); - for (int i = 0; i < operandTypes.size(); i++) { - if (i > 0) { - builder.append(","); - } - - builder.append(operandTypes.get(i).toString()); - } - builder.append(")"); - return builder.toString(); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/SystemLibraryHelper.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/SystemLibraryHelper.java deleted file mode 100644 index 562293119..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/SystemLibraryHelper.java +++ /dev/null @@ -1,2542 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.cqframework.cql.cql2elm.TypeBuilder; -import org.hl7.cql.model.*; -import org.hl7.elm.r1.FunctionDef; -import org.hl7.elm.r1.OperandDef; -import org.hl7.elm.r1.VersionedIdentifier; - -public class SystemLibraryHelper { - public static CompiledLibrary load(SystemModel systemModel, TypeBuilder tb) { - CompiledLibrary system = new CompiledLibrary(); - system.setIdentifier(new VersionedIdentifier().withId("System").withVersion("1.0")); - - // Logical Operators - add( - system, - tb, - new Operator( - "And", - new Signature(systemModel.getBoolean(), systemModel.getBoolean()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Or", - new Signature(systemModel.getBoolean(), systemModel.getBoolean()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Xor", - new Signature(systemModel.getBoolean(), systemModel.getBoolean()), - systemModel.getBoolean())); - add(system, tb, new Operator("Not", new Signature(systemModel.getBoolean()), systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Implies", - new Signature(systemModel.getBoolean(), systemModel.getBoolean()), - systemModel.getBoolean())); - - // Nullological Operators - add(system, tb, new Operator("IsNull", new Signature(systemModel.getAny()), systemModel.getBoolean())); - add(system, tb, new Operator("IsTrue", new Signature(systemModel.getBoolean()), systemModel.getBoolean())); - add(system, tb, new Operator("IsFalse", new Signature(systemModel.getBoolean()), systemModel.getBoolean())); - // Coalesce(list) - // Coalesce(T, T) - // Coalesce(T, T, T) - // Coalesce(T, T, T, T) - // Coalesce(T, T, T, T, T) - add( - system, - tb, - new GenericOperator( - "Coalesce", - new Signature(new ListType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T"))); - add( - system, - tb, - new GenericOperator( - "Coalesce", - new Signature(new TypeParameter("T"), new TypeParameter("T")), - new TypeParameter("T"), - new TypeParameter("T"))); - add( - system, - tb, - new GenericOperator( - "Coalesce", - new Signature(new TypeParameter("T"), new TypeParameter("T"), new TypeParameter("T")), - new TypeParameter("T"), - new TypeParameter("T"))); - add( - system, - tb, - new GenericOperator( - "Coalesce", - new Signature( - new TypeParameter("T"), - new TypeParameter("T"), - new TypeParameter("T"), - new TypeParameter("T")), - new TypeParameter("T"), - new TypeParameter("T"))); - add( - system, - tb, - new GenericOperator( - "Coalesce", - new Signature( - new TypeParameter("T"), - new TypeParameter("T"), - new TypeParameter("T"), - new TypeParameter("T"), - new TypeParameter("T")), - new TypeParameter("T"), - new TypeParameter("T"))); - - // Conversion Operators - // ToString(Boolean) : String - // ToString(Integer) : String - // ToString(Long) : String - // ToString(Decimal) : String - // ToString(DateTime) : String - // ToString(Date) : String - // ToString(Time) : String - // ToString(Quantity) : String - // ToString(Ratio) : String - // ToString(String) : String - Operator booleanToString = - new Operator("ToString", new Signature(systemModel.getBoolean()), systemModel.getString()); - add(system, tb, booleanToString); - add(system, tb, new Conversion(booleanToString, false)); - Operator integerToString = - new Operator("ToString", new Signature(systemModel.getInteger()), systemModel.getString()); - add(system, tb, integerToString); - add(system, tb, new Conversion(integerToString, false)); - Operator longToString = new Operator("ToString", new Signature(systemModel.getLong()), systemModel.getString()); - add(system, tb, longToString); - add(system, tb, new Conversion(longToString, false)); - Operator decimalToString = - new Operator("ToString", new Signature(systemModel.getDecimal()), systemModel.getString()); - add(system, tb, decimalToString); - add(system, tb, new Conversion(decimalToString, false)); - Operator dateTimeToString = - new Operator("ToString", new Signature(systemModel.getDateTime()), systemModel.getString()); - add(system, tb, dateTimeToString); - add(system, tb, new Conversion(dateTimeToString, false)); - Operator dateToString = new Operator("ToString", new Signature(systemModel.getDate()), systemModel.getString()); - add(system, tb, dateToString); - add(system, tb, new Conversion(dateToString, false)); - Operator timeToString = new Operator("ToString", new Signature(systemModel.getTime()), systemModel.getString()); - add(system, tb, timeToString); - add(system, tb, new Conversion(timeToString, false)); - Operator quantityToString = - new Operator("ToString", new Signature(systemModel.getQuantity()), systemModel.getString()); - add(system, tb, quantityToString); - add(system, tb, new Conversion(quantityToString, false)); - Operator ratioToString = - new Operator("ToString", new Signature(systemModel.getRatio()), systemModel.getString()); - add(system, tb, ratioToString); - add(system, tb, new Conversion(ratioToString, false)); - // Operator stringToString = new Operator("ToString", new Signature(systemModel.getString()), - // systemModel.getString()); - // add(system, tb, stringToString); - // add(system, tb, new Conversion(stringToString, false)); - - // ToBoolean(Boolean) : Boolean - // ToBoolean(Integer) : Boolean - // ToBoolean(Decimal) : Boolean - // ToBoolean(Long) : Boolean - // ToBoolean(String) : Boolean - Operator stringToBoolean = - new Operator("ToBoolean", new Signature(systemModel.getString()), systemModel.getBoolean()); - add(system, tb, stringToBoolean); - add(system, tb, new Conversion(stringToBoolean, false)); - Operator integerToBoolean = - new Operator("ToBoolean", new Signature(systemModel.getInteger()), systemModel.getBoolean()); - add(system, tb, integerToBoolean); - add(system, tb, new Conversion(integerToBoolean, false)); - Operator decimalToBoolean = - new Operator("ToBoolean", new Signature(systemModel.getDecimal()), systemModel.getBoolean()); - add(system, tb, decimalToBoolean); - add(system, tb, new Conversion(decimalToBoolean, false)); - Operator longToBoolean = - new Operator("ToBoolean", new Signature(systemModel.getLong()), systemModel.getBoolean()); - add(system, tb, longToBoolean); - add(system, tb, new Conversion(longToBoolean, false)); - // Operator booleanToBoolean = new Operator("ToBoolean", new Signature(systemModel.getBoolean()), - // systemModel.getBoolean()); - // add(system, tb, booleanToBoolean); - // add(system, tb, new Conversion(booleanToBoolean, false)); - - // ToChars(String) : List(String) - Operator toChars = - new Operator("ToChars", new Signature(systemModel.getString()), new ListType(systemModel.getString())); - add(system, tb, toChars); - add(system, tb, new Conversion(toChars, false)); - - // ToInteger(String) : Integer - // ToInteger(Boolean) : Integer - // ToInteger(Long) : Integer - // ToInteger(Integer) : Integer - Operator stringToInteger = - new Operator("ToInteger", new Signature(systemModel.getString()), systemModel.getInteger()); - add(system, tb, stringToInteger); - add(system, tb, new Conversion(stringToInteger, false)); - Operator longToInteger = - new Operator("ToInteger", new Signature(systemModel.getLong()), systemModel.getInteger()); - add(system, tb, longToInteger); - add(system, tb, new Conversion(longToInteger, false)); - Operator booleanToInteger = - new Operator("ToInteger", new Signature(systemModel.getBoolean()), systemModel.getInteger()); - add(system, tb, booleanToInteger); - add(system, tb, new Conversion(booleanToInteger, false)); - // Operator integerToInteger = new Operator("ToInteger", new Signature(systemModel.getInteger()), - // systemModel.getInteger()); - // add(system, tb, integerToInteger); - // add(system, tb, new Conversion(integerToInteger, false)); - - // ToLong(Boolean) : Long - // ToLong(String) : Long - // ToLong(Integer) : Long - // ToLong(Long) : Long - Operator stringToLong = new Operator("ToLong", new Signature(systemModel.getString()), systemModel.getLong()); - add(system, tb, stringToLong); - add(system, tb, new Conversion(stringToLong, false)); - Operator integerToLong = new Operator("ToLong", new Signature(systemModel.getInteger()), systemModel.getLong()); - add(system, tb, integerToLong); - add(system, tb, new Conversion(integerToLong, true)); - // Operator longToLong = new Operator("ToLong", new Signature(systemModel.getLong()), systemModel.getLong()); - // add(system, tb, longToLong); - // add(system, tb, new Conversion(longToLong, false)); - Operator booleanToLong = new Operator("ToLong", new Signature(systemModel.getBoolean()), systemModel.getLong()); - add(system, tb, booleanToLong); - add(system, tb, new Conversion(booleanToLong, false)); - - // ToDecimal(Boolean) : Decimal - // ToDecimal(String) : Decimal - // ToDecimal(Integer) : Decimal - // ToDecimal(Long) : Decimal - // ToDecimal(Decimal) : Decimal - Operator stringToDecimal = - new Operator("ToDecimal", new Signature(systemModel.getString()), systemModel.getDecimal()); - add(system, tb, stringToDecimal); - add(system, tb, new Conversion(stringToDecimal, false)); - Operator integerToDecimal = - new Operator("ToDecimal", new Signature(systemModel.getInteger()), systemModel.getDecimal()); - add(system, tb, integerToDecimal); - add(system, tb, new Conversion(integerToDecimal, true)); - Operator longToDecimal = - new Operator("ToDecimal", new Signature(systemModel.getLong()), systemModel.getDecimal()); - add(system, tb, longToDecimal); - add(system, tb, new Conversion(longToDecimal, true)); - // Operator decimalToDecimal = new Operator("ToDecimal", new Signature(systemModel.getDecimal()), - // systemModel.getDecimal()); - // add(system, tb, decimalToDecimal); - // add(system, tb, new Conversion(decimalToDecimal, false)); - Operator booleanToDecimal = - new Operator("ToDecimal", new Signature(systemModel.getBoolean()), systemModel.getDecimal()); - add(system, tb, booleanToDecimal); - add(system, tb, new Conversion(booleanToDecimal, false)); - - // ToDateTime(String) : DateTime - // ToDateTime(Date) : DateTime - // ToDateTime(DateTime) : DateTime - Operator stringToDateTime = - new Operator("ToDateTime", new Signature(systemModel.getString()), systemModel.getDateTime()); - add(system, tb, stringToDateTime); - add(system, tb, new Conversion(stringToDateTime, false)); - Operator dateToDateTime = - new Operator("ToDateTime", new Signature(systemModel.getDate()), systemModel.getDateTime()); - add(system, tb, dateToDateTime); - add(system, tb, new Conversion(dateToDateTime, true)); - // Operator dateTimeToDateTime = new Operator("ToDateTime", new Signature(systemModel.getDateTime()), - // systemModel.getDateTime()); - // add(system, tb, dateTimeToDateTime); - // add(system, tb, new Conversion(dateTimeToDateTime, false)); - - // ToDate(DateTime) : Date - // ToDate(String) : Date - // ToDate(Date) : Date - Operator stringToDate = new Operator("ToDate", new Signature(systemModel.getString()), systemModel.getDate()); - add(system, tb, stringToDate); - add(system, tb, new Conversion(stringToDate, false)); - Operator dateTimeToDate = - new Operator("ToDate", new Signature(systemModel.getDateTime()), systemModel.getDate()); - add(system, tb, dateTimeToDate); - add(system, tb, new Conversion(dateTimeToDate, false)); - // Operator dateToDate = new Operator("ToDate", new Signature(systemModel.getDate()), systemModel.getDate()); - // add(system, tb, dateToDate); - // add(system, tb, new Conversion(dateToDate, false)); - - // ToTime(String) : Time - // ToTime(Time) : Time - Operator stringToTime = new Operator("ToTime", new Signature(systemModel.getString()), systemModel.getTime()); - add(system, tb, stringToTime); - add(system, tb, new Conversion(stringToTime, false)); - // Operator timeToTime = new Operator("ToTime", new Signature(systemModel.getTime()), systemModel.getTime()); - // add(system, tb, timeToTime); - // add(system, tb, new Conversion(timeToTime, false)); - - // ToQuantity(String) : Quantity - // ToQuantity(Integer) : Quantity - // ToQuantity(Ratio) : Quantity - // ToQuantity(Decimal) : Quantity - // ToQuantity(Quantity) : Quantity - Operator stringToQuantity = - new Operator("ToQuantity", new Signature(systemModel.getString()), systemModel.getQuantity()); - add(system, tb, stringToQuantity); - add(system, tb, new Conversion(stringToQuantity, false)); - Operator ratioToQuantity = - new Operator("ToQuantity", new Signature(systemModel.getRatio()), systemModel.getQuantity()); - add(system, tb, ratioToQuantity); - add(system, tb, new Conversion(ratioToQuantity, false)); - Operator integerToQuantity = - new Operator("ToQuantity", new Signature(systemModel.getInteger()), systemModel.getQuantity()); - add(system, tb, integerToQuantity); - add(system, tb, new Conversion(integerToQuantity, true)); - Operator decimalToQuantity = - new Operator("ToQuantity", new Signature(systemModel.getDecimal()), systemModel.getQuantity()); - add(system, tb, decimalToQuantity); - add(system, tb, new Conversion(decimalToQuantity, true)); - // Operator quantityToQuantity = new Operator("ToQuantity", new Signature(systemModel.getQuantity()), - // systemModel.getQuantity()); - // add(system, tb, quantityToQuantity); - // add(system, tb, new Conversion(quantityToQuantity, false)); - - // ToRatio(String) : Ratio - // ToRatio(Ratio) : Ratio - Operator stringToRatio = - new Operator("ToRatio", new Signature(systemModel.getString()), systemModel.getRatio()); - add(system, tb, stringToRatio); - add(system, tb, new Conversion(stringToRatio, false)); - // Operator ratioToRatio = new Operator("ToRatio", new Signature(systemModel.getRatio()), - // systemModel.getRatio()); - // add(system, tb, ratioToRatio); - // add(system, tb, new Conversion(ratioToRatio, false)); - - // ConvertsToBoolean(Any): Boolean - Operator convertsTo = - new Operator("ConvertsToBoolean", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToInteger(Any): Boolean - convertsTo = new Operator("ConvertsToInteger", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToLong(Any): Boolean - convertsTo = new Operator("ConvertsToLong", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToDecimal - convertsTo = new Operator("ConvertsToDecimal", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToDateTime - convertsTo = new Operator("ConvertsToDateTime", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToDate - convertsTo = new Operator("ConvertsToDate", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToTime - convertsTo = new Operator("ConvertsToTime", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToString - convertsTo = new Operator("ConvertsToString", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToQuantity - convertsTo = new Operator("ConvertsToQuantity", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - // ConvertsToRatio - convertsTo = new Operator("ConvertsToRatio", new Signature(systemModel.getAny()), systemModel.getBoolean()); - add(system, tb, convertsTo); - - // CanConvertQuantity - Operator canConvertToQuantity = new Operator( - "CanConvertQuantity", - new Signature(systemModel.getQuantity(), systemModel.getString()), - systemModel.getBoolean()); - add(system, tb, canConvertToQuantity); - - // ConvertQuantity - Operator convertToQuantity = new Operator( - "ConvertQuantity", - new Signature(systemModel.getQuantity(), systemModel.getString()), - systemModel.getQuantity()); - add(system, tb, convertToQuantity); - - // Comparison Operators - // Equal(T, T) : Boolean - // TypeParameter T = new TypeParameter("T", TypeParameter.TypeParameterConstraint.VALUE, null); - // add(system, tb, new GenericOperator("Equal", new Signature(T, T), systemModel.getBoolean(), T)); - // Equal(C, C) : Boolean - TypeParameter C = new TypeParameter("C", TypeParameter.TypeParameterConstraint.CLASS, null); - add(system, tb, new GenericOperator("Equal", new Signature(C, C), systemModel.getBoolean(), C)); - // Equal(R, R) : Boolean - TypeParameter R = new TypeParameter("R", TypeParameter.TypeParameterConstraint.TUPLE, null); - add(system, tb, new GenericOperator("Equal", new Signature(R, R), systemModel.getBoolean(), R)); - // Equal(H, H) : Boolean - TypeParameter H = new TypeParameter("H", TypeParameter.TypeParameterConstraint.CHOICE, null); - add(system, tb, new GenericOperator("Equal", new Signature(H, H), systemModel.getBoolean(), H)); - // Equal(Any, Any) : Boolean - // add(system, tb, new Operator("Equal", new Signature(systemModel.getAny(), systemModel.getAny()), - // systemModel.getBoolean())); - // Equivalent(T, T) : Boolean - // T = new TypeParameter("T", TypeParameter.TypeParameterConstraint.VALUE, null); - // add(system, tb, new GenericOperator("Equivalent", new Signature(T, T), systemModel.getBoolean(), T)); - // Equivalent(C, C) : Boolean - C = new TypeParameter("C", TypeParameter.TypeParameterConstraint.CLASS, null); - add(system, tb, new GenericOperator("Equivalent", new Signature(C, C), systemModel.getBoolean(), C)); - // Equivalent(R, R) : Boolean - R = new TypeParameter("R", TypeParameter.TypeParameterConstraint.TUPLE, null); - add(system, tb, new GenericOperator("Equivalent", new Signature(R, R), systemModel.getBoolean(), R)); - // Equivalent(H, H) : Boolean - H = new TypeParameter("H", TypeParameter.TypeParameterConstraint.CHOICE, null); - add(system, tb, new GenericOperator("Equivalent", new Signature(H, H), systemModel.getBoolean(), H)); - // Equivalent(Any, Any) : Boolean - // add(system, tb, new Operator("Equivalent", new Signature(systemModel.getAny(), systemModel.getAny()), - // systemModel.getBoolean())); - - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getBoolean(), systemModel.getBoolean()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getBoolean(), systemModel.getBoolean()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Less", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "LessOrEqual", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Greater", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "GreaterOrEqual", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getLong(), systemModel.getLong()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getLong(), systemModel.getLong()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Less", new Signature(systemModel.getLong(), systemModel.getLong()), systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "LessOrEqual", - new Signature(systemModel.getLong(), systemModel.getLong()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Greater", - new Signature(systemModel.getLong(), systemModel.getLong()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "GreaterOrEqual", - new Signature(systemModel.getLong(), systemModel.getLong()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Less", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "LessOrEqual", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Greater", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "GreaterOrEqual", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Less", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "LessOrEqual", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Greater", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "GreaterOrEqual", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Less", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "LessOrEqual", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Greater", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "GreaterOrEqual", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Less", new Signature(systemModel.getDate(), systemModel.getDate()), systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "LessOrEqual", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Greater", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "GreaterOrEqual", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Less", new Signature(systemModel.getTime(), systemModel.getTime()), systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "LessOrEqual", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Greater", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "GreaterOrEqual", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Less", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "LessOrEqual", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Greater", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "GreaterOrEqual", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getRatio(), systemModel.getRatio()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getRatio(), systemModel.getRatio()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getCode(), systemModel.getCode()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getCode(), systemModel.getCode()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equal", - new Signature(systemModel.getConcept(), systemModel.getConcept()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Equivalent", - new Signature(systemModel.getConcept(), systemModel.getConcept()), - systemModel.getBoolean())); - - // Arithmetic Operators - add(system, tb, new Operator("Abs", new Signature(systemModel.getInteger()), systemModel.getInteger())); - add(system, tb, new Operator("Abs", new Signature(systemModel.getLong()), systemModel.getLong())); - add(system, tb, new Operator("Abs", new Signature(systemModel.getDecimal()), systemModel.getDecimal())); - add(system, tb, new Operator("Abs", new Signature(systemModel.getQuantity()), systemModel.getQuantity())); - - add( - system, - tb, - new Operator( - "Add", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "Add", new Signature(systemModel.getLong(), systemModel.getLong()), systemModel.getLong())); - add( - system, - tb, - new Operator( - "Add", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "Add", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getQuantity())); - - add(system, tb, new Operator("Ceiling", new Signature(systemModel.getDecimal()), systemModel.getInteger())); - - add( - system, - tb, - new Operator( - "Divide", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getDecimal())); - // add(system, tb, new Operator("Divide", new Signature(systemModel.getQuantity(), systemModel.getDecimal()), - // systemModel.getQuantity())); - add( - system, - tb, - new Operator( - "Divide", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getQuantity())); - - add(system, tb, new Operator("Exp", new Signature(systemModel.getDecimal()), systemModel.getDecimal())); - - add(system, tb, new Operator("Floor", new Signature(systemModel.getDecimal()), systemModel.getInteger())); - - add( - system, - tb, - new Operator( - "HighBoundary", - new Signature(systemModel.getDecimal(), systemModel.getInteger()), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "HighBoundary", - new Signature(systemModel.getDate(), systemModel.getInteger()), - systemModel.getDate())); - add( - system, - tb, - new Operator( - "HighBoundary", - new Signature(systemModel.getDateTime(), systemModel.getInteger()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "HighBoundary", - new Signature(systemModel.getTime(), systemModel.getInteger()), - systemModel.getTime())); - - add( - system, - tb, - new Operator( - "Log", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getDecimal())); - - add( - system, - tb, - new Operator( - "LowBoundary", - new Signature(systemModel.getDecimal(), systemModel.getInteger()), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "LowBoundary", - new Signature(systemModel.getDate(), systemModel.getInteger()), - systemModel.getDate())); - add( - system, - tb, - new Operator( - "LowBoundary", - new Signature(systemModel.getDateTime(), systemModel.getInteger()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "LowBoundary", - new Signature(systemModel.getTime(), systemModel.getInteger()), - systemModel.getTime())); - - add(system, tb, new Operator("Ln", new Signature(systemModel.getDecimal()), systemModel.getDecimal())); - - // MaxValue() : T - // MinValue() : T - - add( - system, - tb, - new Operator( - "Modulo", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "Modulo", new Signature(systemModel.getLong(), systemModel.getLong()), systemModel.getLong())); - add( - system, - tb, - new Operator( - "Modulo", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "Modulo", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getQuantity())); - - add( - system, - tb, - new Operator( - "Multiply", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "Multiply", - new Signature(systemModel.getLong(), systemModel.getLong()), - systemModel.getLong())); - add( - system, - tb, - new Operator( - "Multiply", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "Multiply", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getQuantity())); - - add(system, tb, new Operator("Negate", new Signature(systemModel.getInteger()), systemModel.getInteger())); - add(system, tb, new Operator("Negate", new Signature(systemModel.getLong()), systemModel.getLong())); - add(system, tb, new Operator("Negate", new Signature(systemModel.getDecimal()), systemModel.getDecimal())); - add(system, tb, new Operator("Negate", new Signature(systemModel.getQuantity()), systemModel.getQuantity())); - - add(system, tb, new Operator("Precision", new Signature(systemModel.getDecimal()), systemModel.getInteger())); - add(system, tb, new Operator("Precision", new Signature(systemModel.getDate()), systemModel.getInteger())); - add(system, tb, new Operator("Precision", new Signature(systemModel.getDateTime()), systemModel.getInteger())); - add(system, tb, new Operator("Precision", new Signature(systemModel.getTime()), systemModel.getInteger())); - - add(system, tb, new Operator("Predecessor", new Signature(systemModel.getInteger()), systemModel.getInteger())); - add(system, tb, new Operator("Predecessor", new Signature(systemModel.getLong()), systemModel.getLong())); - add(system, tb, new Operator("Predecessor", new Signature(systemModel.getDecimal()), systemModel.getDecimal())); - add(system, tb, new Operator("Predecessor", new Signature(systemModel.getDate()), systemModel.getDate())); - add( - system, - tb, - new Operator("Predecessor", new Signature(systemModel.getDateTime()), systemModel.getDateTime())); - add(system, tb, new Operator("Predecessor", new Signature(systemModel.getTime()), systemModel.getTime())); - add( - system, - tb, - new Operator("Predecessor", new Signature(systemModel.getQuantity()), systemModel.getQuantity())); - - add( - system, - tb, - new Operator( - "Power", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "Power", new Signature(systemModel.getLong(), systemModel.getLong()), systemModel.getLong())); - add( - system, - tb, - new Operator( - "Power", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getDecimal())); - - add(system, tb, new Operator("Round", new Signature(systemModel.getDecimal()), systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "Round", - new Signature(systemModel.getDecimal(), systemModel.getInteger()), - systemModel.getDecimal())); - - add( - system, - tb, - new Operator( - "Subtract", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "Subtract", - new Signature(systemModel.getLong(), systemModel.getLong()), - systemModel.getLong())); - add( - system, - tb, - new Operator( - "Subtract", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "Subtract", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getQuantity())); - - add(system, tb, new Operator("Successor", new Signature(systemModel.getInteger()), systemModel.getInteger())); - add(system, tb, new Operator("Successor", new Signature(systemModel.getLong()), systemModel.getLong())); - add(system, tb, new Operator("Successor", new Signature(systemModel.getDecimal()), systemModel.getDecimal())); - add(system, tb, new Operator("Successor", new Signature(systemModel.getDate()), systemModel.getDate())); - add(system, tb, new Operator("Successor", new Signature(systemModel.getDateTime()), systemModel.getDateTime())); - add(system, tb, new Operator("Successor", new Signature(systemModel.getTime()), systemModel.getTime())); - add(system, tb, new Operator("Successor", new Signature(systemModel.getQuantity()), systemModel.getQuantity())); - - add(system, tb, new Operator("Truncate", new Signature(systemModel.getDecimal()), systemModel.getInteger())); - - add( - system, - tb, - new Operator( - "TruncatedDivide", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "TruncatedDivide", - new Signature(systemModel.getLong(), systemModel.getLong()), - systemModel.getLong())); - add( - system, - tb, - new Operator( - "TruncatedDivide", - new Signature(systemModel.getDecimal(), systemModel.getDecimal()), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "TruncatedDivide", - new Signature(systemModel.getQuantity(), systemModel.getQuantity()), - systemModel.getQuantity())); - - // String operators - add( - system, - tb, - new Operator( - "Add", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getString())); - add( - system, - tb, - new Operator("Combine", new Signature(new ListType(systemModel.getString())), systemModel.getString())); - add( - system, - tb, - new Operator( - "Combine", - new Signature(new ListType(systemModel.getString()), systemModel.getString()), - systemModel.getString())); - add( - system, - tb, - new Operator( - "Concatenate", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getString())); - add( - system, - tb, - new Operator( - "EndsWith", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Indexer", - new Signature(systemModel.getString(), systemModel.getInteger()), - systemModel.getString())); - add( - system, - tb, - new Operator( - "LastPositionOf", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getInteger())); - add(system, tb, new Operator("Length", new Signature(systemModel.getString()), systemModel.getInteger())); - add(system, tb, new Operator("Lower", new Signature(systemModel.getString()), systemModel.getString())); - add( - system, - tb, - new Operator( - "Matches", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "PositionOf", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "ReplaceMatches", - new Signature(systemModel.getString(), systemModel.getString(), systemModel.getString()), - systemModel.getString())); - add( - system, - tb, - new Operator( - "Split", - new Signature(systemModel.getString(), systemModel.getString()), - new ListType(systemModel.getString()))); - add( - system, - tb, - new Operator( - "SplitOnMatches", - new Signature(systemModel.getString(), systemModel.getString()), - new ListType(systemModel.getString()))); - add( - system, - tb, - new Operator( - "StartsWith", - new Signature(systemModel.getString(), systemModel.getString()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Substring", - new Signature(systemModel.getString(), systemModel.getInteger()), - systemModel.getString())); - add( - system, - tb, - new Operator( - "Substring", - new Signature(systemModel.getString(), systemModel.getInteger(), systemModel.getInteger()), - systemModel.getString())); - add(system, tb, new Operator("Upper", new Signature(systemModel.getString()), systemModel.getString())); - - // Date/Time Operators - add( - system, - tb, - new Operator( - "Add", - new Signature(systemModel.getDateTime(), systemModel.getQuantity()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "Add", new Signature(systemModel.getDate(), systemModel.getQuantity()), systemModel.getDate())); - add( - system, - tb, - new Operator( - "Add", new Signature(systemModel.getTime(), systemModel.getQuantity()), systemModel.getTime())); - add( - system, - tb, - new Operator( - "After", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "After", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "After", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Before", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Before", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Before", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add(system, tb, new Operator("DateTime", new Signature(systemModel.getInteger()), systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "DateTime", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "DateTime", - new Signature(systemModel.getInteger(), systemModel.getInteger(), systemModel.getInteger()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "DateTime", - new Signature( - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "DateTime", - new Signature( - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "DateTime", - new Signature( - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "DateTime", - new Signature( - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "DateTime", - new Signature( - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getDecimal()), - systemModel.getDateTime())); - add(system, tb, new Operator("Date", new Signature(systemModel.getInteger()), systemModel.getDate())); - add( - system, - tb, - new Operator( - "Date", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getDate())); - add( - system, - tb, - new Operator( - "Date", - new Signature(systemModel.getInteger(), systemModel.getInteger(), systemModel.getInteger()), - systemModel.getDate())); - add(system, tb, new Operator("DateFrom", new Signature(systemModel.getDateTime()), systemModel.getDate())); - add(system, tb, new Operator("TimeFrom", new Signature(systemModel.getDateTime()), systemModel.getTime())); - add( - system, - tb, - new Operator("TimezoneFrom", new Signature(systemModel.getDateTime()), systemModel.getDecimal())); - add( - system, - tb, - new Operator("TimezoneOffsetFrom", new Signature(systemModel.getDateTime()), systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "DateTimeComponentFrom", new Signature(systemModel.getDateTime()), systemModel.getInteger())); - add( - system, - tb, - new Operator("DateTimeComponentFrom", new Signature(systemModel.getDate()), systemModel.getInteger())); - add( - system, - tb, - new Operator("DateTimeComponentFrom", new Signature(systemModel.getTime()), systemModel.getInteger())); - add( - system, - tb, - new Operator( - "DifferenceBetween", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "DifferenceBetween", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "DifferenceBetween", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "DurationBetween", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "DurationBetween", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "DurationBetween", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getInteger())); - add(system, tb, new Operator("Now", new Signature(), systemModel.getDateTime())); - add(system, tb, new Operator("now", new Signature(), systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "SameAs", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SameAs", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SameAs", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SameOrAfter", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SameOrAfter", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SameOrAfter", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SameOrBefore", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SameOrBefore", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SameOrBefore", - new Signature(systemModel.getTime(), systemModel.getTime()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Subtract", - new Signature(systemModel.getDateTime(), systemModel.getQuantity()), - systemModel.getDateTime())); - add( - system, - tb, - new Operator( - "Subtract", - new Signature(systemModel.getDate(), systemModel.getQuantity()), - systemModel.getDate())); - add( - system, - tb, - new Operator( - "Subtract", - new Signature(systemModel.getTime(), systemModel.getQuantity()), - systemModel.getTime())); - add(system, tb, new Operator("Today", new Signature(), systemModel.getDate())); - add(system, tb, new Operator("today", new Signature(), systemModel.getDate())); - add(system, tb, new Operator("Time", new Signature(systemModel.getInteger()), systemModel.getTime())); - add( - system, - tb, - new Operator( - "Time", - new Signature(systemModel.getInteger(), systemModel.getInteger()), - systemModel.getTime())); - add( - system, - tb, - new Operator( - "Time", - new Signature(systemModel.getInteger(), systemModel.getInteger(), systemModel.getInteger()), - systemModel.getTime())); - add( - system, - tb, - new Operator( - "Time", - new Signature( - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger(), - systemModel.getInteger()), - systemModel.getTime())); - add(system, tb, new Operator("TimeOfDay", new Signature(), systemModel.getTime())); - add(system, tb, new Operator("timeOfDay", new Signature(), systemModel.getTime())); - - // Interval Operators - // After(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "After", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Before(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "Before", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Collapse(list>) : list> - // Collapse(list>, Quantity) : list> - add( - system, - tb, - new GenericOperator( - "Collapse", - new Signature( - new ListType(new IntervalType(new TypeParameter("T"))), systemModel.getQuantity()), - new ListType(new IntervalType(new TypeParameter("T"))), - new TypeParameter("T"))); - // Contains(interval, T) : Boolean - add( - system, - tb, - new GenericOperator( - "Contains", - new Signature(new IntervalType(new TypeParameter("T")), new TypeParameter("T")), - systemModel.getBoolean(), - new TypeParameter("T"))); - // End(interval) : T - add( - system, - tb, - new GenericOperator( - "End", - new Signature(new IntervalType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T"))); - // Ends(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "Ends", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Equal(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "Equal", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Equivalent(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "Equivalent", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Except(interval, interval) : interval - add( - system, - tb, - new GenericOperator( - "Except", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - new IntervalType(new TypeParameter("T")), - new TypeParameter("T"))); - // Expand(list>) : list> - // Expand(list>, Quantity) : list> - // Expand(interval) : List - // Expand(interval, Quantity) : list - add( - system, - tb, - new GenericOperator( - "Expand", - new Signature( - new ListType(new IntervalType(new TypeParameter("T"))), systemModel.getQuantity()), - new ListType(new IntervalType(new TypeParameter("T"))), - new TypeParameter("T"))); - add( - system, - tb, - new GenericOperator( - "Expand", - new Signature(new IntervalType(new TypeParameter("T")), systemModel.getQuantity()), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - // In(T, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "In", - new Signature(new TypeParameter("T"), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Includes(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "Includes", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // IncludedIn(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "IncludedIn", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Intersect(interval, interval) : interval - add( - system, - tb, - new GenericOperator( - "Intersect", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - new IntervalType(new TypeParameter("T")), - new TypeParameter("T"))); - // Meets(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "Meets", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // MeetsBefore(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "MeetsBefore", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // MeetsAfter(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "MeetsAfter", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Overlaps(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "Overlaps", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // OverlapsBefore(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "OverlapsBefore", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // OverlapsAfter(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "OverlapsAfter", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // PointFrom(interval) : T - GenericOperator pointFrom = new GenericOperator( - "PointFrom", - new Signature(new IntervalType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T")); - add(system, tb, pointFrom); - // ProperContains(interval, T) : Boolean - add( - system, - tb, - new GenericOperator( - "ProperContains", - new Signature(new IntervalType(new TypeParameter("T")), new TypeParameter("T")), - systemModel.getBoolean(), - new TypeParameter("T"))); - // ProperIn(T, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "ProperIn", - new Signature(new TypeParameter("T"), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // ProperIncludes(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "ProperIncludes", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // ProperIncludedIn(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "ProperIncludedIn", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // SameAs(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "SameAs", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // SameOrAfter(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "SameOrAfter", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // SameOrBefore(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "SameOrBefore", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Size(interval) : T - add( - system, - tb, - new GenericOperator( - "Size", - new Signature(new IntervalType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T"))); - // Start(interval) : T - add( - system, - tb, - new GenericOperator( - "Start", - new Signature(new IntervalType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T"))); - // Starts(interval, interval) : Boolean - add( - system, - tb, - new GenericOperator( - "Starts", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Union(interval, interval) : interval - add( - system, - tb, - new GenericOperator( - "Union", - new Signature( - new IntervalType(new TypeParameter("T")), new IntervalType(new TypeParameter("T"))), - new IntervalType(new TypeParameter("T")), - new TypeParameter("T"))); - // Width(interval) : T - add( - system, - tb, - new GenericOperator( - "Width", - new Signature(new IntervalType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T"))); - - // List Operators - // Contains(list, T) : Boolean - add( - system, - tb, - new GenericOperator( - "Contains", - new Signature(new ListType(new TypeParameter("T")), new TypeParameter("T")), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Distinct(list) : list - add( - system, - tb, - new GenericOperator( - "Distinct", - new Signature(new ListType(new TypeParameter("T"))), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - // Equal(list, list) : Boolean - add( - system, - tb, - new GenericOperator( - "Equal", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Equivalent(list, list) : Boolean - add( - system, - tb, - new GenericOperator( - "Equivalent", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Except(list, list) : list - add( - system, - tb, - new GenericOperator( - "Except", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - // Exists(list) : Boolean - add( - system, - tb, - new GenericOperator( - "Exists", - new Signature(new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Flatten(list>) : list - add( - system, - tb, - new GenericOperator( - "Flatten", - new Signature(new ListType(new ListType(new TypeParameter("T")))), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - // First(list) : T - add( - system, - tb, - new GenericOperator( - "First", - new Signature(new ListType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T"))); - // In(T, list) : Boolean - add( - system, - tb, - new GenericOperator( - "In", - new Signature(new TypeParameter("T"), new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Includes(list, list) : Boolean - add( - system, - tb, - new GenericOperator( - "Includes", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // IncludedIn(list, list) : Boolean - add( - system, - tb, - new GenericOperator( - "IncludedIn", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // Indexer(list, integer) : T - add( - system, - tb, - new GenericOperator( - "Indexer", - new Signature(new ListType(new TypeParameter("T")), systemModel.getInteger()), - new TypeParameter("T"), - new TypeParameter("T"))); - // IndexOf(list, T) : Integer - add( - system, - tb, - new GenericOperator( - "IndexOf", - new Signature(new ListType(new TypeParameter("T")), new TypeParameter("T")), - systemModel.getInteger(), - new TypeParameter("T"))); - // Intersect(list, list) : list - add( - system, - tb, - new GenericOperator( - "Intersect", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - // Last(list) : T - add( - system, - tb, - new GenericOperator( - "Last", - new Signature(new ListType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T"))); - // Length(list) : Integer - add( - system, - tb, - new GenericOperator( - "Length", - new Signature(new ListType(new TypeParameter("T"))), - systemModel.getInteger(), - new TypeParameter("T"))); - // ProperContains(list, T) : Boolean - add( - system, - tb, - new GenericOperator( - "ProperContains", - new Signature(new ListType(new TypeParameter("T")), new TypeParameter("T")), - systemModel.getBoolean(), - new TypeParameter("T"))); - // ProperIn(T, list) : Boolean - add( - system, - tb, - new GenericOperator( - "ProperIn", - new Signature(new TypeParameter("T"), new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // ProperIncludes(list, list) : Boolean - add( - system, - tb, - new GenericOperator( - "ProperIncludes", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // ProperIncludedIn(list, list) : Boolean - add( - system, - tb, - new GenericOperator( - "ProperIncludedIn", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - systemModel.getBoolean(), - new TypeParameter("T"))); - // SingletonFrom(list) : T - GenericOperator singletonFrom = new GenericOperator( - "SingletonFrom", - new Signature(new ListType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T")); - add(system, tb, singletonFrom); - //// NOTE: FHIRPath Implicit List Demotion - // Generic conversions turned out to be computationally expensive, so we added explicit list promotion/demotion - // in the conversion map directly instead. - // add(system, tb, new Conversion(singletonFrom, true)); - // Skip(list, Integer): list - add( - system, - tb, - new GenericOperator( - "Skip", - new Signature(new ListType(new TypeParameter("T")), systemModel.getInteger()), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - // Tail(list): list - add( - system, - tb, - new GenericOperator( - "Tail", - new Signature(new ListType(new TypeParameter("T"))), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - // Take(list, Integer): list - add( - system, - tb, - new GenericOperator( - "Take", - new Signature(new ListType(new TypeParameter("T")), systemModel.getInteger()), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - // Union(list, list) : list - add( - system, - tb, - new GenericOperator( - "Union", - new Signature(new ListType(new TypeParameter("T")), new ListType(new TypeParameter("T"))), - new ListType(new TypeParameter("T")), - new TypeParameter("T"))); - - // NOTE: FHIRPath Implicit List Promotion operator - // GenericOperator toList = new GenericOperator("List", new Signature(new TypeParameter("T")), new ListType(new - // TypeParameter("T")), new TypeParameter("T")); - // add(system, tb, toList); - // add(system, tb, new Conversion(toList, true)); - - // Aggregate Operators - add( - system, - tb, - new Operator( - "AllTrue", new Signature(new ListType(systemModel.getBoolean())), systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyTrue", new Signature(new ListType(systemModel.getBoolean())), systemModel.getBoolean())); - add( - system, - tb, - new Operator("Avg", new Signature(new ListType(systemModel.getDecimal())), systemModel.getDecimal())); - add( - system, - tb, - new Operator("Avg", new Signature(new ListType(systemModel.getQuantity())), systemModel.getQuantity())); - // Count(list) : Integer - add( - system, - tb, - new GenericOperator( - "Count", - new Signature(new ListType(new TypeParameter("T"))), - systemModel.getInteger(), - new TypeParameter("T"))); - //// Count(list) : Integer - // add(system, tb, new Operator("Count", new Signature(new ListType(systemModel.getAny())), - // systemModel.getInteger())); - add( - system, - tb, - new Operator( - "GeometricMean", - new Signature(new ListType(systemModel.getDecimal())), - systemModel.getDecimal())); - add( - system, - tb, - new Operator("Max", new Signature(new ListType(systemModel.getInteger())), systemModel.getInteger())); - add(system, tb, new Operator("Max", new Signature(new ListType(systemModel.getLong())), systemModel.getLong())); - add( - system, - tb, - new Operator("Max", new Signature(new ListType(systemModel.getDecimal())), systemModel.getDecimal())); - add( - system, - tb, - new Operator("Max", new Signature(new ListType(systemModel.getQuantity())), systemModel.getQuantity())); - add( - system, - tb, - new Operator("Max", new Signature(new ListType(systemModel.getDateTime())), systemModel.getDateTime())); - add(system, tb, new Operator("Max", new Signature(new ListType(systemModel.getDate())), systemModel.getDate())); - add(system, tb, new Operator("Max", new Signature(new ListType(systemModel.getTime())), systemModel.getTime())); - add( - system, - tb, - new Operator("Max", new Signature(new ListType(systemModel.getString())), systemModel.getString())); - add( - system, - tb, - new Operator("Min", new Signature(new ListType(systemModel.getInteger())), systemModel.getInteger())); - add(system, tb, new Operator("Min", new Signature(new ListType(systemModel.getLong())), systemModel.getLong())); - add( - system, - tb, - new Operator("Min", new Signature(new ListType(systemModel.getDecimal())), systemModel.getDecimal())); - add( - system, - tb, - new Operator("Min", new Signature(new ListType(systemModel.getQuantity())), systemModel.getQuantity())); - add( - system, - tb, - new Operator("Min", new Signature(new ListType(systemModel.getDateTime())), systemModel.getDateTime())); - add(system, tb, new Operator("Min", new Signature(new ListType(systemModel.getDate())), systemModel.getDate())); - add(system, tb, new Operator("Min", new Signature(new ListType(systemModel.getTime())), systemModel.getTime())); - add( - system, - tb, - new Operator("Min", new Signature(new ListType(systemModel.getString())), systemModel.getString())); - add( - system, - tb, - new Operator( - "Median", new Signature(new ListType(systemModel.getDecimal())), systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "Median", new Signature(new ListType(systemModel.getQuantity())), systemModel.getQuantity())); - // Mode(list) : T - add( - system, - tb, - new GenericOperator( - "Mode", - new Signature(new ListType(new TypeParameter("T"))), - new TypeParameter("T"), - new TypeParameter("T"))); - add( - system, - tb, - new Operator( - "PopulationStdDev", - new Signature(new ListType(systemModel.getDecimal())), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "PopulationStdDev", - new Signature(new ListType(systemModel.getQuantity())), - systemModel.getQuantity())); - add( - system, - tb, - new Operator( - "PopulationVariance", - new Signature(new ListType(systemModel.getDecimal())), - systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "PopulationVariance", - new Signature(new ListType(systemModel.getQuantity())), - systemModel.getQuantity())); - add( - system, - tb, - new Operator( - "Product", new Signature(new ListType(systemModel.getInteger())), systemModel.getInteger())); - add( - system, - tb, - new Operator("Product", new Signature(new ListType(systemModel.getLong())), systemModel.getLong())); - add( - system, - tb, - new Operator( - "Product", new Signature(new ListType(systemModel.getDecimal())), systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "Product", new Signature(new ListType(systemModel.getQuantity())), systemModel.getQuantity())); - add( - system, - tb, - new Operator( - "StdDev", new Signature(new ListType(systemModel.getDecimal())), systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "StdDev", new Signature(new ListType(systemModel.getQuantity())), systemModel.getQuantity())); - add( - system, - tb, - new Operator("Sum", new Signature(new ListType(systemModel.getInteger())), systemModel.getInteger())); - add(system, tb, new Operator("Sum", new Signature(new ListType(systemModel.getLong())), systemModel.getLong())); - add( - system, - tb, - new Operator("Sum", new Signature(new ListType(systemModel.getDecimal())), systemModel.getDecimal())); - add( - system, - tb, - new Operator("Sum", new Signature(new ListType(systemModel.getQuantity())), systemModel.getQuantity())); - add( - system, - tb, - new Operator( - "Variance", new Signature(new ListType(systemModel.getDecimal())), systemModel.getDecimal())); - add( - system, - tb, - new Operator( - "Variance", new Signature(new ListType(systemModel.getQuantity())), systemModel.getQuantity())); - - // Clinical - // ToConcept(Code) - Operator codeToConcept = - new Operator("ToConcept", new Signature(systemModel.getCode()), systemModel.getConcept()); - add(system, tb, codeToConcept); - add(system, tb, new Conversion(codeToConcept, true)); - // ToConcept(list) - Operator codesToConcept = - new Operator("ToConcept", new Signature(new ListType(systemModel.getCode())), systemModel.getConcept()); - add(system, tb, codesToConcept); - add(system, tb, new Conversion(codesToConcept, false)); - - add( - system, - tb, - new Operator("CalculateAge", new Signature(systemModel.getDateTime()), systemModel.getInteger())); - add(system, tb, new Operator("CalculateAge", new Signature(systemModel.getDate()), systemModel.getInteger())); - add( - system, - tb, - new Operator( - "CalculateAgeAt", - new Signature(systemModel.getDateTime(), systemModel.getDateTime()), - systemModel.getInteger())); - add( - system, - tb, - new Operator( - "CalculateAgeAt", - new Signature(systemModel.getDate(), systemModel.getDate()), - systemModel.getInteger())); - - add(system, tb, new Operator("InValueSet", new Signature(systemModel.getString()), systemModel.getBoolean())); - add(system, tb, new Operator("InValueSet", new Signature(systemModel.getCode()), systemModel.getBoolean())); - add(system, tb, new Operator("InValueSet", new Signature(systemModel.getConcept()), systemModel.getBoolean())); - - add( - system, - tb, - new Operator( - "InValueSet", - new Signature(systemModel.getString(), systemModel.getValueSet()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "InValueSet", - new Signature(systemModel.getCode(), systemModel.getValueSet()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "InValueSet", - new Signature(systemModel.getConcept(), systemModel.getValueSet()), - systemModel.getBoolean())); - - add( - system, - tb, - new Operator( - "AnyInValueSet", - new Signature(new ListType(systemModel.getString())), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyInValueSet", new Signature(new ListType(systemModel.getCode())), systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyInValueSet", - new Signature(new ListType(systemModel.getConcept())), - systemModel.getBoolean())); - - add( - system, - tb, - new Operator( - "AnyInValueSet", - new Signature(new ListType(systemModel.getString()), systemModel.getValueSet()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyInValueSet", - new Signature(new ListType(systemModel.getCode()), systemModel.getValueSet()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyInValueSet", - new Signature(new ListType(systemModel.getConcept()), systemModel.getValueSet()), - systemModel.getBoolean())); - - add(system, tb, new Operator("InCodeSystem", new Signature(systemModel.getString()), systemModel.getBoolean())); - add(system, tb, new Operator("InCodeSystem", new Signature(systemModel.getCode()), systemModel.getBoolean())); - add( - system, - tb, - new Operator("InCodeSystem", new Signature(systemModel.getConcept()), systemModel.getBoolean())); - - add( - system, - tb, - new Operator( - "InCodeSystem", - new Signature(systemModel.getString(), systemModel.getCodeSystem()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "InCodeSystem", - new Signature(systemModel.getCode(), systemModel.getCodeSystem()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "InCodeSystem", - new Signature(systemModel.getConcept(), systemModel.getCodeSystem()), - systemModel.getBoolean())); - - add( - system, - tb, - new Operator( - "AnyInCodeSystem", - new Signature(new ListType(systemModel.getString())), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyInCodeSystem", - new Signature(new ListType(systemModel.getCode())), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyInCodeSystem", - new Signature(new ListType(systemModel.getConcept())), - systemModel.getBoolean())); - - add( - system, - tb, - new Operator( - "AnyInCodeSystem", - new Signature(new ListType(systemModel.getString()), systemModel.getCodeSystem()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyInCodeSystem", - new Signature(new ListType(systemModel.getCode()), systemModel.getCodeSystem()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "AnyInCodeSystem", - new Signature(new ListType(systemModel.getConcept()), systemModel.getCodeSystem()), - systemModel.getBoolean())); - - Operator expandValueSet = new Operator( - "ExpandValueSet", new Signature(systemModel.getValueSet()), new ListType(systemModel.getCode())); - add(system, tb, expandValueSet); - add(system, tb, new Conversion(expandValueSet, true)); - - add( - system, - tb, - new Operator( - "Subsumes", - new Signature(systemModel.getCode(), systemModel.getCode()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "Subsumes", - new Signature(systemModel.getConcept(), systemModel.getConcept()), - systemModel.getBoolean())); - - add( - system, - tb, - new Operator( - "SubsumedBy", - new Signature(systemModel.getCode(), systemModel.getCode()), - systemModel.getBoolean())); - add( - system, - tb, - new Operator( - "SubsumedBy", - new Signature(systemModel.getConcept(), systemModel.getConcept()), - systemModel.getBoolean())); - - // Errors - // Message(source T, condition Boolean, code String, severity String, message String) T - add( - system, - tb, - new GenericOperator( - "Message", - new Signature( - new TypeParameter("T"), - systemModel.getBoolean(), - systemModel.getString(), - systemModel.getString(), - systemModel.getString()), - new TypeParameter("T"), - new TypeParameter("T"))); - - return system; - } - - private static void add(CompiledLibrary systemLibrary, TypeBuilder tb, Operator operator) { - // In the case that an operator is added directly, manufacture a FunctionDef so it can be referred to in ELM - // Analysis - FunctionDef fd = new FunctionDef(); - fd.setName(operator.getName()); - int n = 0; - for (DataType dataType : operator.getSignature().getOperandTypes()) { - n++; - OperandDef od = new OperandDef().withName(String.format("param%d", n)); - if (dataType instanceof NamedType) { - od.setOperandType(tb.dataTypeToQName(dataType)); - } else { - od.setOperandTypeSpecifier(tb.dataTypeToTypeSpecifier(dataType)); - } - od.setResultType(dataType); - fd.getOperand().add(od); - } - operator.setFunctionDef(fd); - - systemLibrary.add(fd, operator); - } - - private static void add(CompiledLibrary systemLibrary, TypeBuilder tb, Conversion conversion) { - systemLibrary.add(conversion); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/SystemModel.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/SystemModel.java deleted file mode 100644 index 74788dc68..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/SystemModel.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.cql.model.DataType; -import org.hl7.cql.model.SimpleType; -import org.hl7.elm_modelinfo.r1.ModelInfo; - -public class SystemModel extends Model { - public SystemModel(ModelInfo modelInfo) throws ClassNotFoundException { - super(modelInfo, null); - } - - public DataType getAny() { - return this.resolveTypeName("Any"); - } - - public DataType getBoolean() { - return this.resolveTypeName("Boolean"); - } - - public DataType getInteger() { - return this.resolveTypeName("Integer"); - } - - public DataType getLong() { - return this.resolveTypeName("Long"); - } - - public DataType getDecimal() { - return this.resolveTypeName("Decimal"); - } - - public DataType getString() { - return this.resolveTypeName("String"); - } - - public DataType getDateTime() { - return this.resolveTypeName("DateTime"); - } - - public DataType getDate() { - return this.resolveTypeName("Date"); - } - - public DataType getTime() { - return this.resolveTypeName("Time"); - } - - public DataType getQuantity() { - return this.resolveTypeName("Quantity"); - } - - public DataType getRatio() { - return this.resolveTypeName("Ratio"); - } - - public DataType getCode() { - return this.resolveTypeName("Code"); - } - - public DataType getConcept() { - return this.resolveTypeName("Concept"); - } - - public DataType getVocabulary() { - return this.resolveTypeName("Vocabulary"); - } - - public DataType getCodeSystem() { - return this.resolveTypeName("CodeSystem"); - } - - public DataType getValueSet() { - return this.resolveTypeName("ValueSet"); - } - - public DataType getVoid() { - return new SimpleType("Void"); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/TimingOperatorContext.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/TimingOperatorContext.java deleted file mode 100644 index 6fb05fcbe..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/TimingOperatorContext.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.elm.r1.Expression; - -public class TimingOperatorContext { - private Expression left; - private Expression right; - - public TimingOperatorContext() {} - - public TimingOperatorContext(Expression left, Expression right) { - this.left = left; - this.right = right; - } - - public Expression getLeft() { - return left; - } - - public void setLeft(Expression value) { - left = value; - } - - public Expression getRight() { - return right; - } - - public void setRight(Expression value) { - right = value; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ToClassOperator.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ToClassOperator.java deleted file mode 100644 index 1d9751128..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ToClassOperator.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.cql.model.DataType; -import org.hl7.cql.model.TypeParameter; - -public class ToClassOperator extends GenericOperator { - public ToClassOperator(String name, Signature signature, DataType resultType, TypeParameter... typeParameters) { - super(name, signature, resultType, typeParameters); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ToTupleOperator.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ToTupleOperator.java deleted file mode 100644 index 759ba0f09..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/ToTupleOperator.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import org.hl7.cql.model.DataType; -import org.hl7.cql.model.TypeParameter; - -public class ToTupleOperator extends GenericOperator { - public ToTupleOperator(String name, Signature signature, DataType resultType, TypeParameter... typeParameters) { - super(name, signature, resultType, typeParameters); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Version.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Version.java deleted file mode 100644 index ff8cfa94f..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/Version.java +++ /dev/null @@ -1,229 +0,0 @@ -package org.cqframework.cql.cql2elm.model; - -import java.util.Objects; -import java.util.regex.Pattern; - -/** - * Created by Bryn on 3/2/2017. - */ - -/** - * Implements a comparable version for use in comparing CQL artifact versions. - * Supports versions specified in filename strings according to the following pattern: - * [v]{{major}}(.|-){{minor}}(.|-){{patch}}(.|-){{build}} - * where major, minor, and patch are all required to be unsigned integers, and build is any string - * - * Examples: - * 1.0.0 -> major: 1, minor: 0, patch: 0 - * v1-0-0 -> major: 1, minor: 0, patch: 0 - * v1-0-0-SNAPSHOT -> major: 1, minor: 0, patch: 0, build: snapshot - * - * NOTE: Deliberately not using Apache ComparableVersion to a) avoid dependencies on Maven and b) allow for more - * flexible version strings used by MAT file naming conventions. - */ -public class Version implements Comparable { - - private String version; - - private Integer majorVersion; - private Integer minorVersion; - private Integer patchVersion; - private String buildVersion; - - private static Pattern isUnsignedInteger = Pattern.compile("[0-9]+"); - - private void setVersion(String version) { - this.version = version; - String[] parts = this.version.split("\\.|-"); - for (int i = 0; i < Math.max(parts.length, 4); i++) { - String part = i < parts.length ? parts[i] : ""; - if (part.startsWith("v")) { - part = part.substring(1); - } - switch (i) { - case 0: - if (isUnsignedInteger.matcher(part).matches()) { - majorVersion = Integer.parseInt(part); - } - break; - case 1: - if (isUnsignedInteger.matcher(part).matches()) { - minorVersion = Integer.parseInt(part); - } else { - return; - } - break; - case 2: - if (isUnsignedInteger.matcher(part).matches()) { - patchVersion = Integer.parseInt(part); - } else { - return; - } - break; - case 3: - buildVersion = part; - break; - default: - buildVersion += "-" + part; - break; - } - } - } - - public Version(String version) { - if (version == null) throw new IllegalArgumentException("Version can not be null"); - setVersion(version); - } - - public Integer getMajorVersion() { - return majorVersion; - } - - public Integer getMinorVersion() { - return minorVersion; - } - - public Integer getPatchVersion() { - return patchVersion; - } - - public String getBuildVersion() { - return buildVersion; - } - - private int compareTo(Version that, int level) { - if (that == null) return 1; - validateComparability(that); - - for (int i = 0; i < Math.max(level, 4); i++) { - switch (i) { - case 0: - { - int result = Integer.compare(this.majorVersion, that.majorVersion); - if (result != 0) { - return result; - } - } - break; - - case 1: - { - if (this.minorVersion == null && that.minorVersion == null) { - return 0; - } - if (this.minorVersion == null) { - return -1; - } - if (that.minorVersion == null) { - return 1; - } - int result = Integer.compare(this.minorVersion, that.minorVersion); - if (result != 0) { - return result; - } - } - break; - - case 2: - { - if (this.patchVersion == null && that.patchVersion == null) { - return 0; - } - if (this.patchVersion == null) { - return -1; - } - if (that.patchVersion == null) { - return 1; - } - int result = Integer.compare(this.patchVersion, that.patchVersion); - if (result != 0) { - return result; - } - } - break; - - case 3: { - if (this.buildVersion == null && that.buildVersion == null) { - return 0; - } - if (this.buildVersion == null) { - return -1; - } - if (that.buildVersion == null) { - return 1; - } - return this.buildVersion.compareToIgnoreCase(that.buildVersion); - } - } - } - return 0; - } - - @Override - public int compareTo(Version that) { - return compareTo(that, 4); - } - - public boolean compatibleWith(Version that) { - if (that == null) return false; - - if (!isComparable() || !that.isComparable()) { - return matchStrictly(that); - } - - return Objects.equals(this.majorVersion, that.majorVersion) && compareTo(that, 2) >= 0; - } - - public boolean matchStrictly(Version that) { - if (that != null) { - return this.version.equals(that.version); - } - return false; - } - - public boolean isComparable(int level) { - switch (level) { - case 0: { - return majorVersion != null; - } - case 1: { - return minorVersion != null; - } - case 2: { - return patchVersion != null; - } - case 3: { - return buildVersion != null; - } - } - return false; - } - - public boolean isComparable() { - return this.isComparable(2); - } - - private void validateComparability(Version that) { - if (!this.isComparable() || (that != null && !that.isComparable())) { - throw new IllegalArgumentException("The versions are not comparable"); - } - } - - @Override - public boolean equals(Object that) { - if (this == that) return true; - if (that == null) return false; - if (this.getClass() != that.getClass()) return false; - return this.compareTo((Version) that) == 0; - } - - @Override - public int hashCode() { - return this.version.hashCode(); - } - - @Override - public String toString() { - return this.version; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AbstractExpressionInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AbstractExpressionInvocation.java deleted file mode 100644 index 078876c17..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AbstractExpressionInvocation.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import org.cqframework.cql.cql2elm.model.Invocation; -import org.cqframework.cql.cql2elm.model.OperatorResolution; -import org.hl7.cql.model.DataType; -import org.hl7.elm.r1.Expression; - -/** - * The AbstractExpressionInvocation can be used to more simply make invocations for classes that only extend - * Expression. - */ -public abstract class AbstractExpressionInvocation implements Invocation { - public AbstractExpressionInvocation(Expression expression) { - if (expression == null) { - throw new IllegalArgumentException("expression is null."); - } - - this.expression = expression; - } - - protected Expression expression; - - @Override - public void setResultType(DataType resultType) { - expression.setResultType(resultType); - } - - @Override - public Expression getExpression() { - return expression; - } - - protected Expression assertAndGetSingleOperand(Iterable operands) { - Expression operand = null; - for (Expression o : operands) { - if (operand != null) { - throw new IllegalArgumentException("Unary operation expected."); - } - - operand = o; - } - - if (operand == null) { - throw new IllegalArgumentException("Unary operation expected."); - } - - return operand; - } - - private OperatorResolution resolution; - - public OperatorResolution getResolution() { - return resolution; - } - - public void setResolution(OperatorResolution resolution) { - this.resolution = resolution; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AggregateExpressionInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AggregateExpressionInvocation.java deleted file mode 100644 index bbbd335ba..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AggregateExpressionInvocation.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Collections; -import org.hl7.elm.r1.AggregateExpression; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.TypeSpecifier; - -public class AggregateExpressionInvocation extends AbstractExpressionInvocation { - public AggregateExpressionInvocation(AggregateExpression expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return Collections.singletonList(((AggregateExpression) expression).getSource()); - } - - @Override - public void setOperands(Iterable operands) { - ((AggregateExpression) expression).setSource(assertAndGetSingleOperand(operands)); - } - - @Override - public Iterable getSignature() { - return ((AggregateExpression) expression).getSignature(); - } - - @Override - public void setSignature(Iterable signature) { - for (TypeSpecifier typeSpecifier : signature) { - ((AggregateExpression) expression).getSignature().add(typeSpecifier); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AnyInCodeSystemInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AnyInCodeSystemInvocation.java deleted file mode 100644 index f5b8caf04..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AnyInCodeSystemInvocation.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.List; -import org.hl7.elm.r1.AnyInCodeSystem; -import org.hl7.elm.r1.Expression; - -/** - * Created by Bryn on 9/12/2018. - */ -public class AnyInCodeSystemInvocation extends OperatorExpressionInvocation { - public AnyInCodeSystemInvocation(AnyInCodeSystem expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - List result = new ArrayList<>(); - result.add(((AnyInCodeSystem) expression).getCodes()); - if (((AnyInCodeSystem) expression).getCodesystemExpression() != null) { - result.add(((AnyInCodeSystem) expression).getCodesystemExpression()); - } - return result; - } - - @Override - public void setOperands(Iterable operands) { - int i = 0; - for (Expression operand : operands) { - switch (i) { - case 0: - ((AnyInCodeSystem) expression).setCodes(operand); - break; - case 1: - ((AnyInCodeSystem) expression).setCodesystemExpression(operand); - break; - } - i++; - } - - if (i > 2) { - throw new IllegalArgumentException("Unary or binary operator expected"); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AnyInValueSetInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AnyInValueSetInvocation.java deleted file mode 100644 index fc4841faa..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/AnyInValueSetInvocation.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.List; -import org.hl7.elm.r1.AnyInValueSet; -import org.hl7.elm.r1.Expression; - -/** - * Created by Bryn on 9/12/2018. - */ -public class AnyInValueSetInvocation extends OperatorExpressionInvocation { - public AnyInValueSetInvocation(AnyInValueSet expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - List result = new ArrayList<>(); - result.add(((AnyInValueSet) expression).getCodes()); - if (((AnyInValueSet) expression).getValuesetExpression() != null) { - result.add(((AnyInValueSet) expression).getValuesetExpression()); - } - return result; - } - - @Override - public void setOperands(Iterable operands) { - int i = 0; - for (Expression operand : operands) { - switch (i) { - case 0: - ((AnyInValueSet) expression).setCodes(operand); - break; - case 1: - ((AnyInValueSet) expression).setValuesetExpression(operand); - break; - } - i++; - } - - if (i > 2) { - throw new IllegalArgumentException("Unary or binary operator expected"); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/BinaryExpressionInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/BinaryExpressionInvocation.java deleted file mode 100644 index 1e6d7ea9c..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/BinaryExpressionInvocation.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.List; -import org.hl7.elm.r1.BinaryExpression; -import org.hl7.elm.r1.Expression; - -public class BinaryExpressionInvocation extends OperatorExpressionInvocation { - public BinaryExpressionInvocation(BinaryExpression expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return ((BinaryExpression) expression).getOperand(); - } - - @Override - public void setOperands(Iterable operands) { - List expOperands = ((BinaryExpression) expression).getOperand(); - expOperands.clear(); - for (Expression operand : operands) { - expOperands.add(operand); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/CombineInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/CombineInvocation.java deleted file mode 100644 index a022e7467..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/CombineInvocation.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.Iterator; -import org.hl7.elm.r1.Combine; -import org.hl7.elm.r1.Expression; - -public class CombineInvocation extends OperatorExpressionInvocation { - public CombineInvocation(Combine expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - Combine combine = (Combine) expression; - ArrayList ops = new ArrayList<>(); - ops.add(combine.getSource()); - if (combine.getSeparator() != null) { - ops.add(combine.getSeparator()); - } - return ops; - } - - @Override - public void setOperands(Iterable operands) { - Iterator it = operands.iterator(); - if (!it.hasNext()) { - throw new IllegalArgumentException("Combine operation requires one or two operands."); - } - Combine combine = (Combine) expression; - combine.setSource(it.next()); - - if (it.hasNext()) { - combine.setSeparator(it.next()); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/ConvertInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/ConvertInvocation.java deleted file mode 100644 index 1954f9232..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/ConvertInvocation.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Collections; -import org.hl7.elm.r1.Convert; -import org.hl7.elm.r1.Expression; - -public class ConvertInvocation extends OperatorExpressionInvocation { - public ConvertInvocation(Convert expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return Collections.singletonList(((Convert) expression).getOperand()); - } - - @Override - public void setOperands(Iterable operands) { - ((Convert) expression).setOperand(assertAndGetSingleOperand(operands)); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/DateInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/DateInvocation.java deleted file mode 100644 index 863184773..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/DateInvocation.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.hl7.elm.r1.Date; -import org.hl7.elm.r1.Expression; - -public class DateInvocation extends OperatorExpressionInvocation { - public DateInvocation(Date expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - Date dt = (Date) expression; - List opList = Arrays.asList(dt.getYear(), dt.getMonth(), dt.getDay()); - // If the last expression is null, we should trim this down - int i; - for (i = 2; i > 0 && opList.get(i) == null; i--) - ; - return opList.subList(0, i + 1); - } - - @Override - public void setOperands(Iterable operands) { - ArrayList opList = new ArrayList<>(); - for (Expression operand : operands) { - opList.add(operand); - } - setDateFieldsFromOperands((Date) expression, opList); - } - - public static void setDateFieldsFromOperands(Date dt, List operands) { - if (operands.isEmpty() || operands.size() > 3) { - throw new IllegalArgumentException( - "Could not resolve call to system operator DateTime. Expected 1 - 3 arguments."); - } - dt.setYear(operands.get(0)); - if (operands.size() > 1) { - dt.setMonth(operands.get(1)); - } - if (operands.size() > 2) { - dt.setDay(operands.get(2)); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/DateTimeInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/DateTimeInvocation.java deleted file mode 100644 index 44faa29b7..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/DateTimeInvocation.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.hl7.elm.r1.DateTime; -import org.hl7.elm.r1.Expression; - -public class DateTimeInvocation extends OperatorExpressionInvocation { - public DateTimeInvocation(DateTime expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - DateTime dt = (DateTime) expression; - List opList = Arrays.asList( - dt.getYear(), - dt.getMonth(), - dt.getDay(), - dt.getHour(), - dt.getMinute(), - dt.getSecond(), - dt.getMillisecond(), - dt.getTimezoneOffset()); - // If the last expression is null, we should trim this down - int i; - for (i = 7; i > 0 && opList.get(i) == null; i--) - ; - return opList.subList(0, i + 1); - } - - @Override - public void setOperands(Iterable operands) { - ArrayList opList = new ArrayList<>(); - for (Expression operand : operands) { - opList.add(operand); - } - setDateTimeFieldsFromOperands((DateTime) expression, opList); - } - - public static void setDateTimeFieldsFromOperands(DateTime dt, List operands) { - if (operands.isEmpty() || operands.size() > 8) { - throw new IllegalArgumentException( - "Could not resolve call to system operator DateTime. Expected 1 - 8 arguments."); - } - dt.setYear(operands.get(0)); - if (operands.size() > 1) { - dt.setMonth(operands.get(1)); - } - if (operands.size() > 2) { - dt.setDay(operands.get(2)); - } - if (operands.size() > 3) { - dt.setHour(operands.get(3)); - } - if (operands.size() > 4) { - dt.setMinute(operands.get(4)); - } - if (operands.size() > 5) { - dt.setSecond(operands.get(5)); - } - if (operands.size() > 6) { - dt.setMillisecond(operands.get(6)); - } - if (operands.size() > 7) { - dt.setTimezoneOffset(operands.get(7)); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/FirstInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/FirstInvocation.java deleted file mode 100644 index 04c842e9f..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/FirstInvocation.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Collections; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.First; - -public class FirstInvocation extends OperatorExpressionInvocation { - public FirstInvocation(First expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return Collections.singletonList(((First) expression).getSource()); - } - - @Override - public void setOperands(Iterable operands) { - ((First) expression).setSource(assertAndGetSingleOperand(operands)); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/FunctionRefInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/FunctionRefInvocation.java deleted file mode 100644 index 4573cd790..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/FunctionRefInvocation.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.List; -import org.cqframework.cql.cql2elm.model.OperatorResolution; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.FunctionRef; -import org.hl7.elm.r1.TypeSpecifier; - -public class FunctionRefInvocation extends AbstractExpressionInvocation { - public FunctionRefInvocation(FunctionRef expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return ((FunctionRef) expression).getOperand(); - } - - @Override - public void setOperands(Iterable operands) { - List expOperands = ((FunctionRef) expression).getOperand(); - expOperands.clear(); - for (Expression operand : operands) { - expOperands.add(operand); - } - } - - @Override - public Iterable getSignature() { - return ((FunctionRef) expression).getSignature(); - } - - @Override - public void setSignature(Iterable signature) { - for (TypeSpecifier typeSpecifier : signature) { - ((FunctionRef) expression).getSignature().add(typeSpecifier); - } - } - - @Override - public void setResolution(OperatorResolution resolution) { - super.setResolution(resolution); - FunctionRef fr = (FunctionRef) expression; - if (resolution.getLibraryName() != null && !resolution.getLibraryName().equals(fr.getLibraryName())) { - fr.setLibraryName(resolution.getLibraryName()); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/InCodeSystemInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/InCodeSystemInvocation.java deleted file mode 100644 index 45e9de7f8..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/InCodeSystemInvocation.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.List; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.InCodeSystem; - -public class InCodeSystemInvocation extends OperatorExpressionInvocation { - public InCodeSystemInvocation(InCodeSystem expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - List result = new ArrayList<>(); - result.add(((InCodeSystem) expression).getCode()); - if (((InCodeSystem) expression).getCodesystemExpression() != null) { - result.add(((InCodeSystem) expression).getCodesystemExpression()); - } - return result; - } - - @Override - public void setOperands(Iterable operands) { - int i = 0; - for (Expression operand : operands) { - switch (i) { - case 0: - ((InCodeSystem) expression).setCode(operand); - break; - case 1: - ((InCodeSystem) expression).setCodesystemExpression(operand); - break; - } - i++; - } - - if (i > 2) { - throw new IllegalArgumentException("Unary or binary operator expected"); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/InValueSetInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/InValueSetInvocation.java deleted file mode 100644 index 2fa3b7118..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/InValueSetInvocation.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.List; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.InValueSet; - -public class InValueSetInvocation extends OperatorExpressionInvocation { - public InValueSetInvocation(InValueSet expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - List result = new ArrayList<>(); - result.add(((InValueSet) expression).getCode()); - if (((InValueSet) expression).getValuesetExpression() != null) { - result.add(((InValueSet) expression).getValuesetExpression()); - } - return result; - } - - @Override - public void setOperands(Iterable operands) { - int i = 0; - for (Expression operand : operands) { - switch (i) { - case 0: - ((InValueSet) expression).setCode(operand); - break; - case 1: - ((InValueSet) expression).setValuesetExpression(operand); - break; - } - i++; - } - - if (i > 2) { - throw new IllegalArgumentException("Unary or binary operator expected"); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/IndexOfInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/IndexOfInvocation.java deleted file mode 100644 index 4ffd572e8..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/IndexOfInvocation.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Arrays; -import java.util.Iterator; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.IndexOf; - -public class IndexOfInvocation extends OperatorExpressionInvocation { - public IndexOfInvocation(IndexOf expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - IndexOf indexOf = (IndexOf) expression; - return Arrays.asList(indexOf.getSource(), indexOf.getElement()); - } - - @Override - public void setOperands(Iterable operands) { - Iterator it = operands.iterator(); - if (!it.hasNext()) { - throw new IllegalArgumentException("IndexOf operation requires two operands."); - } - IndexOf indexOf = (IndexOf) expression; - indexOf.setSource(it.next()); - - if (!it.hasNext()) { - throw new IllegalArgumentException("IndexOf operation requires two operands."); - } - indexOf.setElement(it.next()); - - if (it.hasNext()) { - throw new IllegalArgumentException("IndexOf operation requires two operands."); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/LastInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/LastInvocation.java deleted file mode 100644 index a6cf44f40..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/LastInvocation.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Collections; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Last; - -public class LastInvocation extends OperatorExpressionInvocation { - public LastInvocation(Last expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return Collections.singletonList(((Last) expression).getSource()); - } - - @Override - public void setOperands(Iterable operands) { - ((Last) expression).setSource(assertAndGetSingleOperand(operands)); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/LastPositionOfInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/LastPositionOfInvocation.java deleted file mode 100644 index 8aa21797b..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/LastPositionOfInvocation.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Arrays; -import java.util.Iterator; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.LastPositionOf; - -public class LastPositionOfInvocation extends OperatorExpressionInvocation { - public LastPositionOfInvocation(LastPositionOf expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - LastPositionOf pos = (LastPositionOf) expression; - return Arrays.asList(pos.getPattern(), pos.getString()); - } - - @Override - public void setOperands(Iterable operands) { - Iterator it = operands.iterator(); - if (!it.hasNext()) { - throw new IllegalArgumentException("LastPositionOf operation requires two operands."); - } - LastPositionOf pos = (LastPositionOf) expression; - pos.setPattern(it.next()); - if (!it.hasNext()) { - throw new IllegalArgumentException("LastPositionOf operation requires two operands."); - } - pos.setString(it.next()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/MessageInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/MessageInvocation.java deleted file mode 100644 index 7e98c47a7..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/MessageInvocation.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Arrays; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Message; - -public class MessageInvocation extends OperatorExpressionInvocation { - public MessageInvocation(Message expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - Message message = (Message) expression; - return Arrays.asList( - message.getSource(), - message.getCondition(), - message.getCode(), - message.getSeverity(), - message.getMessage()); - } - - @Override - public void setOperands(Iterable operands) { - Message message = (Message) expression; - - int i = 0; - for (Expression operand : operands) { - switch (i) { - case 0: - message.setSource(operand); - break; - case 1: - message.setCondition(operand); - break; - case 2: - message.setCode(operand); - break; - case 3: - message.setSeverity(operand); - break; - case 4: - message.setMessage(operand); - break; - default: - throw new IllegalArgumentException("Message operation requires five operands."); - } - i++; - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/NaryExpressionInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/NaryExpressionInvocation.java deleted file mode 100644 index 811cdbbe3..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/NaryExpressionInvocation.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.List; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.NaryExpression; - -public class NaryExpressionInvocation extends OperatorExpressionInvocation { - public NaryExpressionInvocation(NaryExpression expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return ((NaryExpression) expression).getOperand(); - } - - @Override - public void setOperands(Iterable operands) { - List expOperands = ((NaryExpression) expression).getOperand(); - expOperands.clear(); - for (Expression operand : operands) { - expOperands.add(operand); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/OperatorExpressionInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/OperatorExpressionInvocation.java deleted file mode 100644 index bce0a9ba1..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/OperatorExpressionInvocation.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import org.hl7.elm.r1.OperatorExpression; -import org.hl7.elm.r1.TypeSpecifier; - -/** - * Created by Bryn on 4/12/2018. - */ -@SuppressWarnings("checkstyle:abstractclassname") -public abstract class OperatorExpressionInvocation extends AbstractExpressionInvocation { - public OperatorExpressionInvocation(OperatorExpression expression) { - super(expression); - } - - @Override - public Iterable getSignature() { - return ((OperatorExpression) expression).getSignature(); - } - - @Override - public void setSignature(Iterable signature) { - for (TypeSpecifier typeSpecifier : signature) { - ((OperatorExpression) expression).getSignature().add(typeSpecifier); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/PositionOfInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/PositionOfInvocation.java deleted file mode 100644 index d010f5645..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/PositionOfInvocation.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Arrays; -import java.util.Iterator; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.PositionOf; - -public class PositionOfInvocation extends OperatorExpressionInvocation { - public PositionOfInvocation(PositionOf expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - PositionOf pos = (PositionOf) expression; - return Arrays.asList(pos.getPattern(), pos.getString()); - } - - @Override - public void setOperands(Iterable operands) { - Iterator it = operands.iterator(); - if (!it.hasNext()) { - throw new IllegalArgumentException("PositionOf operation requires two operands."); - } - PositionOf pos = (PositionOf) expression; - pos.setPattern(it.next()); - if (!it.hasNext()) { - throw new IllegalArgumentException("PositionOf operation requires two operands."); - } - pos.setString(it.next()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/RoundInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/RoundInvocation.java deleted file mode 100644 index 8d34f6c1d..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/RoundInvocation.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.Iterator; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Round; - -public class RoundInvocation extends OperatorExpressionInvocation { - public RoundInvocation(Round expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - Round round = (Round) expression; - ArrayList ops = new ArrayList<>(); - ops.add(round.getOperand()); - if (round.getPrecision() != null) { - ops.add(round.getPrecision()); - } - return ops; - } - - @Override - public void setOperands(Iterable operands) { - Iterator it = operands.iterator(); - if (!it.hasNext()) { - throw new IllegalArgumentException("Round operation requires one or two operands."); - } - Round round = (Round) expression; - round.setOperand(it.next()); - - if (it.hasNext()) { - round.setPrecision(it.next()); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SkipInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SkipInvocation.java deleted file mode 100644 index cca653f7d..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SkipInvocation.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Slice; - -/** - * Created by Bryn on 5/17/2017. - */ -public class SkipInvocation extends OperatorExpressionInvocation { - public SkipInvocation(Slice expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - ArrayList result = new ArrayList<>(); - result.add(((Slice) expression).getSource()); - result.add(((Slice) expression).getStartIndex()); - return result; - } - - @Override - public void setOperands(Iterable operands) { - boolean first = true; - for (Expression operand : operands) { - if (first) { - ((Slice) expression).setSource(operand); - first = false; - } else { - ((Slice) expression).setStartIndex(operand); - } - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SplitInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SplitInvocation.java deleted file mode 100644 index a6ccf8f4e..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SplitInvocation.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Arrays; -import java.util.Iterator; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Split; - -public class SplitInvocation extends OperatorExpressionInvocation { - public SplitInvocation(Split expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - Split split = (Split) expression; - return Arrays.asList(split.getStringToSplit(), split.getSeparator()); - } - - @Override - public void setOperands(Iterable operands) { - Iterator it = operands.iterator(); - if (!it.hasNext()) { - throw new IllegalArgumentException("Split operation requires two operands."); - } - Split split = (Split) expression; - split.setStringToSplit(it.next()); - if (!it.hasNext()) { - throw new IllegalArgumentException("Split operation requires two operands."); - } - split.setSeparator(it.next()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SplitOnMatchesInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SplitOnMatchesInvocation.java deleted file mode 100644 index 688a2ac85..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SplitOnMatchesInvocation.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Arrays; -import java.util.Iterator; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.SplitOnMatches; - -public class SplitOnMatchesInvocation extends OperatorExpressionInvocation { - public SplitOnMatchesInvocation(SplitOnMatches expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - SplitOnMatches splitOnMatches = (SplitOnMatches) expression; - return Arrays.asList(splitOnMatches.getStringToSplit(), splitOnMatches.getSeparatorPattern()); - } - - @Override - public void setOperands(Iterable operands) { - Iterator it = operands.iterator(); - if (!it.hasNext()) { - throw new IllegalArgumentException("SplitOnMatches operation requires two operands."); - } - SplitOnMatches splitOnMatches = (SplitOnMatches) expression; - splitOnMatches.setStringToSplit(it.next()); - if (!it.hasNext()) { - throw new IllegalArgumentException("SplitOnMatches operation requires two operands."); - } - splitOnMatches.setSeparatorPattern(it.next()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SubstringInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SubstringInvocation.java deleted file mode 100644 index 1f63be1e7..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/SubstringInvocation.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.Iterator; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Substring; - -public class SubstringInvocation extends OperatorExpressionInvocation { - public SubstringInvocation(Substring expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - Substring substring = (Substring) expression; - ArrayList ops = new ArrayList<>(); - // Note: these casts to Expression are necessary because of bug in expression.xsd (DSTU comment #824) - ops.add((Expression) substring.getStringToSub()); - ops.add((Expression) substring.getStartIndex()); - if (substring.getLength() != null) { - ops.add((Expression) substring.getLength()); - } - return ops; - } - - @Override - public void setOperands(Iterable operands) { - Iterator it = operands.iterator(); - if (!it.hasNext()) { - throw new IllegalArgumentException("Substring operation requires two or three operands."); - } - Substring substring = (Substring) expression; - substring.setStringToSub(it.next()); - if (!it.hasNext()) { - throw new IllegalArgumentException("Substring operation requires two or three operands."); - } - substring.setStartIndex(it.next()); - if (it.hasNext()) { - substring.setLength(it.next()); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TailInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TailInvocation.java deleted file mode 100644 index 1765e9841..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TailInvocation.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Collections; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Slice; - -/** - * Created by Bryn on 5/17/2017. - */ -public class TailInvocation extends OperatorExpressionInvocation { - public TailInvocation(Slice expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return Collections.singletonList(((Slice) expression).getSource()); - } - - @Override - public void setOperands(Iterable operands) { - ((Slice) expression).setSource(assertAndGetSingleOperand(operands)); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TakeInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TakeInvocation.java deleted file mode 100644 index 0fc60171c..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TakeInvocation.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Slice; - -/** - * Created by Bryn on 5/17/2017. - */ -public class TakeInvocation extends OperatorExpressionInvocation { - public TakeInvocation(Slice expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - ArrayList result = new ArrayList<>(); - result.add(((Slice) expression).getSource()); - result.add(((Slice) expression).getEndIndex()); - return result; - } - - @Override - public void setOperands(Iterable operands) { - boolean first = true; - for (Expression operand : operands) { - if (first) { - ((Slice) expression).setSource(operand); - first = false; - } else { - ((Slice) expression).setEndIndex(operand); - } - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TernaryExpressionInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TernaryExpressionInvocation.java deleted file mode 100644 index c327c9d59..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TernaryExpressionInvocation.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.List; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.TernaryExpression; - -public class TernaryExpressionInvocation extends OperatorExpressionInvocation { - public TernaryExpressionInvocation(TernaryExpression expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return ((TernaryExpression) expression).getOperand(); - } - - @Override - public void setOperands(Iterable operands) { - List expOperands = ((TernaryExpression) expression).getOperand(); - expOperands.clear(); - for (Expression operand : operands) { - expOperands.add(operand); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TimeInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TimeInvocation.java deleted file mode 100644 index 1945f0f33..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/TimeInvocation.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.Time; - -public class TimeInvocation extends OperatorExpressionInvocation { - public TimeInvocation(Time expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - Time t = (Time) expression; - List opList = Arrays.asList(t.getHour(), t.getMinute(), t.getSecond(), t.getMillisecond()); - // If the last expression is null, we should trim this down - int i; - for (i = 3; i > 0 && opList.get(i) == null; i--) - ; - return opList.subList(0, i + 1); - } - - @Override - public void setOperands(Iterable operands) { - ArrayList opList = new ArrayList<>(); - for (Expression operand : operands) { - opList.add(operand); - } - setTimeFieldsFromOperands((Time) expression, opList); - } - - public static void setTimeFieldsFromOperands(Time t, List operands) { - if (operands.isEmpty() || operands.size() > 4) { - throw new IllegalArgumentException( - "Could not resolve call to system operator Time. Expected 1 - 4 arguments."); - } - - t.setHour(operands.get(0)); - if (operands.size() > 1) { - t.setMinute(operands.get(1)); - } - if (operands.size() > 2) { - t.setSecond(operands.get(2)); - } - if (operands.size() > 3) { - t.setMillisecond(operands.get(3)); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/UnaryExpressionInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/UnaryExpressionInvocation.java deleted file mode 100644 index f49709134..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/UnaryExpressionInvocation.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.Collections; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.UnaryExpression; - -public class UnaryExpressionInvocation extends OperatorExpressionInvocation { - public UnaryExpressionInvocation(UnaryExpression expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return Collections.singletonList(((UnaryExpression) expression).getOperand()); - } - - @Override - public void setOperands(Iterable operands) { - ((UnaryExpression) expression).setOperand(assertAndGetSingleOperand(operands)); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/ZeroOperandExpressionInvocation.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/ZeroOperandExpressionInvocation.java deleted file mode 100644 index d06410acd..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/invocation/ZeroOperandExpressionInvocation.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cqframework.cql.cql2elm.model.invocation; - -import java.util.ArrayList; -import org.hl7.elm.r1.Expression; -import org.hl7.elm.r1.OperatorExpression; - -public class ZeroOperandExpressionInvocation extends OperatorExpressionInvocation { - public ZeroOperandExpressionInvocation(OperatorExpression expression) { - super(expression); - } - - @Override - public Iterable getOperands() { - return new ArrayList<>(); - } - - @Override - public void setOperands(Iterable operands) { - if (operands.iterator().hasNext()) { - throw new IllegalArgumentException("Zero operand operation expected."); - } - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/serialization/LibraryWrapper.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/serialization/LibraryWrapper.java deleted file mode 100644 index 1382d7103..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/model/serialization/LibraryWrapper.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.cqframework.cql.cql2elm.model.serialization; - -import org.hl7.elm.r1.Library; - -public class LibraryWrapper { - private Library library; - - public Library getLibrary() { - return this.library; - } - - public void setLibrary(Library library) { - this.library = library; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.java deleted file mode 100644 index 4ddcc5d8c..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/BaseInfo.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; - -@SuppressWarnings("checkstyle:abstractclassname") -public class BaseInfo { - private String header; - private Interval headerInterval; - private ParseTree definition; - - public String getHeader() { - return header; - } - - public void setHeader(String header) { - this.header = header; - } - - public Interval getHeaderInterval() { - return headerInterval; - } - - public void setHeaderInterval(Interval headerInterval) { - this.headerInterval = headerInterval; - } - - public ParseTree getDefinition() { - return definition; - } - - public void setDefinition(ParseTree definition) { - this.definition = definition; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.java deleted file mode 100644 index 7e852e182..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodeDefinitionInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -/** - * Created by Bryn on 5/22/2016. - */ -public class CodeDefinitionInfo extends BaseInfo { - private String name; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public CodeDefinitionInfo withName(String value) { - setName(value); - return this; - } - - @Override - public cqlParser.CodeDefinitionContext getDefinition() { - return (cqlParser.CodeDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.CodeDefinitionContext value) { - super.setDefinition(value); - } - - public CodeDefinitionInfo withDefinition(cqlParser.CodeDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.java deleted file mode 100644 index 51358f77b..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CodesystemDefinitionInfo.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class CodesystemDefinitionInfo extends BaseInfo { - private String name; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - @Override - public cqlParser.CodesystemDefinitionContext getDefinition() { - return (cqlParser.CodesystemDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.CodesystemDefinitionContext value) { - super.setDefinition(value); - } - - public CodesystemDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public CodesystemDefinitionInfo withDefinition(cqlParser.CodesystemDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.java deleted file mode 100644 index d12590a77..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ConceptDefinitionInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -/** - * Created by Bryn on 5/22/2016. - */ -public class ConceptDefinitionInfo extends BaseInfo { - private String name; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - @Override - public cqlParser.ConceptDefinitionContext getDefinition() { - return (cqlParser.ConceptDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ConceptDefinitionContext value) { - super.setDefinition(value); - } - - public ConceptDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public ConceptDefinitionInfo withDefinition(cqlParser.ConceptDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.java deleted file mode 100644 index b77e77cdc..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ContextDefinitionInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class ContextDefinitionInfo extends BaseInfo { - private String context; - - public String getContext() { - return context; - } - - public void setContext(String context) { - this.context = context; - } - - @Override - public cqlParser.ContextDefinitionContext getDefinition() { - return (cqlParser.ContextDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ContextDefinitionContext definition) { - super.setDefinition(definition); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.java deleted file mode 100644 index 7e5d6821b..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessor.java +++ /dev/null @@ -1,330 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import java.util.ArrayList; -import java.util.List; -import org.antlr.v4.runtime.Recognizer; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.cqframework.cql.cql2elm.*; -import org.cqframework.cql.cql2elm.model.Model; -import org.cqframework.cql.gen.cqlLexer; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.cql.model.*; -import org.hl7.elm.r1.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CqlPreprocessor extends CqlPreprocessorElmCommonVisitor { - static final Logger logger = LoggerFactory.getLogger(CqlPreprocessor.class); - private int lastSourceIndex = -1; - - public CqlPreprocessor(LibraryBuilder libraryBuilder, TokenStream tokenStream) { - super(libraryBuilder, tokenStream); - } - - public LibraryInfo getLibraryInfo() { - return libraryInfo; - } - - private void processHeader(ParseTree ctx, BaseInfo info) { - Interval header = null; - org.antlr.v4.runtime.misc.Interval sourceInterval = ctx.getSourceInterval(); - int beforeDefinition = sourceInterval.a - 1; - if (beforeDefinition >= lastSourceIndex) { - header = new org.antlr.v4.runtime.misc.Interval(lastSourceIndex + 1, sourceInterval.a - 1); - lastSourceIndex = sourceInterval.b; - - info.setHeaderInterval(header); - info.setHeader(tokenStream.getText(header)); - } - } - - @Override - public Object visitLibrary(cqlParser.LibraryContext ctx) { - Object lastResult = null; - // NOTE: Need to set the library identifier here so the builder can begin the translation appropriately - VersionedIdentifier identifier = - new VersionedIdentifier().withId(libraryInfo.getLibraryName()).withVersion(libraryInfo.getVersion()); - if (libraryInfo.getNamespaceName() != null) { - identifier.setSystem(libraryBuilder.resolveNamespaceUri(libraryInfo.getNamespaceName(), true)); - } else if (libraryBuilder.getNamespaceInfo() != null) { - identifier.setSystem(libraryBuilder.getNamespaceInfo().getUri()); - } - libraryBuilder.setLibraryIdentifier(identifier); - libraryBuilder.beginTranslation(); - try { - // Loop through and call visit on each child (to ensure they are tracked) - for (int i = 0; i < ctx.getChildCount(); i++) { - ParseTree tree = ctx.getChild(i); - TerminalNode terminalNode = tree instanceof TerminalNode ? (TerminalNode) tree : null; - if (terminalNode != null && terminalNode.getSymbol().getType() == Recognizer.EOF) { - continue; - } - - Object childResult = visit(tree); - // Only set the last result if we received something useful - if (childResult != null) { - lastResult = childResult; - } - } - - // Return last result (consistent with super implementation and helps w/ testing) - return lastResult; - } finally { - libraryBuilder.endTranslation(); - } - } - - @Override - @SuppressWarnings("unchecked") - public Object visitLibraryDefinition(cqlParser.LibraryDefinitionContext ctx) { - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - libraryInfo.setLibraryName(identifiers.remove(identifiers.size() - 1)); - if (identifiers.size() > 0) { - libraryInfo.setNamespaceName(String.join(".", identifiers)); - } - if (ctx.versionSpecifier() != null) { - libraryInfo.setVersion((String) visit(ctx.versionSpecifier())); - } - libraryInfo.setDefinition(ctx); - processHeader(ctx, libraryInfo); - return super.visitLibraryDefinition(ctx); - } - - @Override - @SuppressWarnings("unchecked") - public Object visitIncludeDefinition(cqlParser.IncludeDefinitionContext ctx) { - IncludeDefinitionInfo includeDefinition = new IncludeDefinitionInfo(); - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - includeDefinition.setName(identifiers.remove(identifiers.size() - 1)); - if (identifiers.size() > 0) { - includeDefinition.setNamespaceName(String.join(".", identifiers)); - } - if (ctx.versionSpecifier() != null) { - includeDefinition.setVersion((String) visit(ctx.versionSpecifier())); - } - if (ctx.localIdentifier() != null) { - includeDefinition.setLocalName(parseString(ctx.localIdentifier())); - } else { - includeDefinition.setLocalName(includeDefinition.getName()); - } - includeDefinition.setDefinition(ctx); - processHeader(ctx, includeDefinition); - libraryInfo.addIncludeDefinition(includeDefinition); - return includeDefinition; - } - - @Override - @SuppressWarnings("unchecked") - public Object visitUsingDefinition(cqlParser.UsingDefinitionContext ctx) { - UsingDefinitionInfo usingDefinition = new UsingDefinitionInfo(); - List identifiers = (List) visit(ctx.qualifiedIdentifier()); - final String unqualifiedIdentifier = identifiers.remove(identifiers.size() - 1); - usingDefinition.setName(unqualifiedIdentifier); - if (identifiers.size() > 0) { - usingDefinition.setNamespaceName(String.join(".", identifiers)); - } - if (ctx.versionSpecifier() != null) { - usingDefinition.setVersion((String) visit(ctx.versionSpecifier())); - } - if (ctx.localIdentifier() != null) { - usingDefinition.setLocalName(parseString(ctx.localIdentifier())); - } else { - usingDefinition.setLocalName(usingDefinition.getName()); - } - usingDefinition.setDefinition(ctx); - processHeader(ctx, usingDefinition); - libraryInfo.addUsingDefinition(usingDefinition); - - final String namespaceName = !identifiers.isEmpty() - ? String.join(".", identifiers) - : libraryBuilder.isWellKnownModelName(unqualifiedIdentifier) - ? null - : (libraryBuilder.getNamespaceInfo() != null - ? libraryBuilder.getNamespaceInfo().getName() - : null); - - NamespaceInfo modelNamespace = null; - if (namespaceName != null) { - String namespaceUri = libraryBuilder.resolveNamespaceUri(namespaceName, true); - modelNamespace = new NamespaceInfo(namespaceName, namespaceUri); - } - - String localIdentifier = - ctx.localIdentifier() == null ? unqualifiedIdentifier : parseString(ctx.localIdentifier()); - if (!localIdentifier.equals(unqualifiedIdentifier)) { - throw new IllegalArgumentException(String.format( - "Local identifiers for models must be the same as the name of the model in this release of the translator (Model %s, Called %s)", - unqualifiedIdentifier, localIdentifier)); - } - - // This should only be called once, from this class, and not from Cql2ElmVisitor otherwise there will be - // duplicate errors sometimes - Model model = - getModel(modelNamespace, unqualifiedIdentifier, parseString(ctx.versionSpecifier()), localIdentifier); - - return usingDefinition; - } - - @Override - public Object visitCodesystemDefinition(cqlParser.CodesystemDefinitionContext ctx) { - CodesystemDefinitionInfo codesystemDefinition = new CodesystemDefinitionInfo(); - codesystemDefinition.setName(parseString(ctx.identifier())); - codesystemDefinition.setDefinition(ctx); - processHeader(ctx, codesystemDefinition); - libraryInfo.addCodesystemDefinition(codesystemDefinition); - return codesystemDefinition; - } - - @Override - public Object visitValuesetDefinition(cqlParser.ValuesetDefinitionContext ctx) { - ValuesetDefinitionInfo valuesetDefinition = new ValuesetDefinitionInfo(); - valuesetDefinition.setName(parseString(ctx.identifier())); - valuesetDefinition.setDefinition(ctx); - processHeader(ctx, valuesetDefinition); - libraryInfo.addValuesetDefinition(valuesetDefinition); - return valuesetDefinition; - } - - @Override - public Object visitCodeDefinition(cqlParser.CodeDefinitionContext ctx) { - CodeDefinitionInfo codeDefinition = new CodeDefinitionInfo(); - codeDefinition.setName(parseString(ctx.identifier())); - codeDefinition.setDefinition(ctx); - processHeader(ctx, codeDefinition); - libraryInfo.addCodeDefinition(codeDefinition); - return codeDefinition; - } - - @Override - public Object visitConceptDefinition(cqlParser.ConceptDefinitionContext ctx) { - ConceptDefinitionInfo conceptDefinition = new ConceptDefinitionInfo(); - conceptDefinition.setName(parseString(ctx.identifier())); - conceptDefinition.setDefinition(ctx); - processHeader(ctx, conceptDefinition); - libraryInfo.addConceptDefinition(conceptDefinition); - return conceptDefinition; - } - - @Override - public Object visitParameterDefinition(cqlParser.ParameterDefinitionContext ctx) { - ParameterDefinitionInfo parameterDefinition = new ParameterDefinitionInfo(); - parameterDefinition.setName(parseString(ctx.identifier())); - parameterDefinition.setDefinition(ctx); - processHeader(ctx, parameterDefinition); - libraryInfo.addParameterDefinition(parameterDefinition); - return parameterDefinition; - } - - @Override - public Object visitContextDefinition(cqlParser.ContextDefinitionContext ctx) { - String modelIdentifier = ctx.modelIdentifier() != null ? parseString(ctx.modelIdentifier()) : null; - String unqualifiedContext = parseString(ctx.identifier()); - if (modelIdentifier != null && !modelIdentifier.equals("")) { - setCurrentContext(modelIdentifier + "." + unqualifiedContext); - } else { - setCurrentContext(unqualifiedContext); - } - - ContextDefinitionInfo contextDefinition = new ContextDefinitionInfo(); - contextDefinition.setDefinition(ctx); - processHeader(ctx, contextDefinition); - libraryInfo.addContextDefinition(contextDefinition); - - if (!getImplicitContextCreated() && !unqualifiedContext.equals("Unfiltered")) { - ExpressionDefinitionInfo expressionDefinition = new ExpressionDefinitionInfo(); - expressionDefinition.setName(unqualifiedContext); - expressionDefinition.setContext(getCurrentContext()); - libraryInfo.addExpressionDefinition(expressionDefinition); - setImplicitContextCreated(true); - } - return getCurrentContext(); - } - - @Override - public Object visitExpressionDefinition(cqlParser.ExpressionDefinitionContext ctx) { - ExpressionDefinitionInfo expressionDefinition = new ExpressionDefinitionInfo(); - expressionDefinition.setName(parseString(ctx.identifier())); - expressionDefinition.setContext(getCurrentContext()); - expressionDefinition.setDefinition(ctx); - processHeader(ctx, expressionDefinition); - libraryInfo.addExpressionDefinition(expressionDefinition); - return expressionDefinition; - } - - @Override - public Object visitFunctionDefinition(cqlParser.FunctionDefinitionContext ctx) { - FunctionDefinitionInfo functionDefinition = new FunctionDefinitionInfo(); - functionDefinition.setName(parseString(ctx.identifierOrFunctionIdentifier())); - functionDefinition.setContext(getCurrentContext()); - functionDefinition.setDefinition(ctx); - processHeader(ctx, functionDefinition); - libraryInfo.addFunctionDefinition(functionDefinition); - return functionDefinition; - } - - @Override - public NamedTypeSpecifier visitNamedTypeSpecifier(cqlParser.NamedTypeSpecifierContext ctx) { - List qualifiers = parseQualifiers(ctx); - String modelIdentifier = getModelIdentifier(qualifiers); - String identifier = getTypeIdentifier(qualifiers, parseString(ctx.referentialOrTypeNameIdentifier())); - final String typeSpecifierKey = String.format("%s:%s", modelIdentifier, identifier); - - DataType resultType = libraryBuilder.resolveTypeName(modelIdentifier, identifier); - if (null == resultType) { - libraryBuilder.addNamedTypeSpecifierResult(typeSpecifierKey, ResultWithPossibleError.withError()); - throw new CqlCompilerException( - String.format("Could not find type for model: %s and name: %s", modelIdentifier, identifier)); - } - NamedTypeSpecifier result = of.createNamedTypeSpecifier().withName(libraryBuilder.dataTypeToQName(resultType)); - - // Fluent API would be nice here, but resultType isn't part of the model so... - result.setResultType(resultType); - - libraryBuilder.addNamedTypeSpecifierResult(typeSpecifierKey, ResultWithPossibleError.withTypeSpecifier(result)); - - return result; - } - - @Override - public Object visitTerminal(TerminalNode node) { - String text = node.getText(); - int tokenType = node.getSymbol().getType(); - if (cqlLexer.STRING == tokenType || cqlLexer.QUOTEDIDENTIFIER == tokenType) { - // chop off leading and trailing ' or " - text = text.substring(1, text.length() - 1); - } - - return text; - } - - @Override - public Object visitQualifiedIdentifier(cqlParser.QualifiedIdentifierContext ctx) { - // Return the list of qualified identifiers for resolution by the containing element - List identifiers = new ArrayList<>(); - for (cqlParser.QualifierContext qualifierContext : ctx.qualifier()) { - String qualifier = (String) visit(qualifierContext); - identifiers.add(qualifier); - } - - String identifier = parseString(ctx.identifier()); - identifiers.add(identifier); - return identifiers; - } - - @Override - public Object visitQualifiedIdentifierExpression(cqlParser.QualifiedIdentifierExpressionContext ctx) { - // Return the list of qualified identifiers for resolution by the containing element - List identifiers = new ArrayList<>(); - for (cqlParser.QualifierExpressionContext qualifierContext : ctx.qualifierExpression()) { - String qualifier = (String) visit(qualifierContext); - identifiers.add(qualifier); - } - - String identifier = parseString(ctx.referentialIdentifier()); - identifiers.add(identifier); - return identifiers; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java deleted file mode 100644 index 1267eb049..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java +++ /dev/null @@ -1,903 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import jakarta.xml.bind.JAXBElement; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Stack; -import javax.xml.namespace.QName; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.TerminalNode; -import org.apache.commons.lang3.tuple.Pair; -import org.cqframework.cql.cql2elm.*; -import org.cqframework.cql.cql2elm.model.Chunk; -import org.cqframework.cql.cql2elm.model.FunctionHeader; -import org.cqframework.cql.cql2elm.model.Model; -import org.cqframework.cql.elm.IdObjectFactory; -import org.cqframework.cql.elm.tracking.TrackBack; -import org.cqframework.cql.elm.tracking.Trackable; -import org.cqframework.cql.gen.cqlBaseVisitor; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.cql.model.*; -import org.hl7.cql_annotations.r1.Annotation; -import org.hl7.cql_annotations.r1.Narrative; -import org.hl7.cql_annotations.r1.Tag; -import org.hl7.elm.r1.*; - -/** - * Common functionality used by {@link CqlPreprocessor} and {@link Cql2ElmVisitor} - */ -public class CqlPreprocessorElmCommonVisitor extends cqlBaseVisitor { - protected final IdObjectFactory of; - protected final org.hl7.cql_annotations.r1.ObjectFactory af = new org.hl7.cql_annotations.r1.ObjectFactory(); - private boolean implicitContextCreated = false; - private String currentContext = "Unfiltered"; - protected Stack chunks = new Stack<>(); - protected final LibraryBuilder libraryBuilder; - protected final TokenStream tokenStream; - protected LibraryInfo libraryInfo = new LibraryInfo(); - private boolean annotate = false; - private boolean detailedErrors = false; - private boolean locate = false; - private boolean resultTypes = false; - private boolean dateRangeOptimization = false; - private boolean methodInvocation = true; - private boolean fromKeywordRequired = false; - - private final List expressions = new ArrayList<>(); - - public CqlPreprocessorElmCommonVisitor(LibraryBuilder libraryBuilder, TokenStream tokenStream) { - this.libraryBuilder = Objects.requireNonNull(libraryBuilder, "libraryBuilder required"); - this.tokenStream = Objects.requireNonNull(tokenStream, "tokenStream required"); - this.of = Objects.requireNonNull(libraryBuilder.getObjectFactory(), "libraryBuilder.objectFactory required"); - - // Don't talk to strangers. Except when you have to. - this.setCompilerOptions(libraryBuilder.getLibraryManager().getCqlCompilerOptions()); - } - - protected boolean getImplicitContextCreated() { - return this.implicitContextCreated; - } - - protected void setImplicitContextCreated(boolean implicitContextCreated) { - this.implicitContextCreated = implicitContextCreated; - } - - protected String getCurrentContext() { - return this.currentContext; - } - - protected void setCurrentContext(String currentContext) { - this.currentContext = currentContext; - } - - protected String saveCurrentContext(String currentContext) { - String saveContext = this.currentContext; - this.currentContext = currentContext; - return saveContext; - } - - @Override - public Object visit(ParseTree tree) { - Objects.requireNonNull(tree, "ParseTree required"); - boolean pushedChunk = pushChunk(tree); - Object o = null; - try { - // ERROR: - try { - o = super.visit(tree); - if (o instanceof Element) { - Element element = (Element) o; - if (element.getLocalId() == null) { - throw new CqlInternalException( - String.format( - "Internal translator error. 'localId' was not assigned for Element \"%s\"", - element.getClass().getName()), - getTrackBack(tree)); - } - } - } catch (CqlIncludeException e) { - CqlCompilerException translatorException = - new CqlCompilerException(e.getMessage(), getTrackBack(tree), e); - if (translatorException.getLocator() == null) { - throw translatorException; - } - libraryBuilder.recordParsingException(translatorException); - } catch (CqlCompilerException e) { - libraryBuilder.recordParsingException(e); - } catch (Exception e) { - CqlCompilerException ex = null; - if (e.getMessage() == null) { - ex = new CqlInternalException("Internal translator error.", getTrackBack(tree), e); - } else { - ex = new CqlSemanticException(e.getMessage(), getTrackBack(tree), e); - } - - Exception rootCause = libraryBuilder.determineRootCause(); - if (rootCause == null) { - rootCause = ex; - libraryBuilder.recordParsingException(ex); - libraryBuilder.setRootCause(rootCause); - } else { - if (detailedErrors) { - libraryBuilder.recordParsingException(ex); - } - } - o = of.createNull(); - } - - if (o instanceof Trackable && !(tree instanceof cqlParser.LibraryContext)) { - this.track((Trackable) o, tree); - } - if (o instanceof Expression) { - addExpression((Expression) o); - } - - return o; - } finally { - popChunk(tree, o, pushedChunk); - processTags(tree, o); - } - } - - @Override - public TupleElementDefinition visitTupleElementDefinition(cqlParser.TupleElementDefinitionContext ctx) { - TupleElementDefinition result = of.createTupleElementDefinition() - .withName(parseString(ctx.referentialIdentifier())) - .withElementType(parseTypeSpecifier(ctx.typeSpecifier())); - - return result; - } - - @Override - public Object visitTupleTypeSpecifier(cqlParser.TupleTypeSpecifierContext ctx) { - TupleType resultType = new TupleType(); - TupleTypeSpecifier typeSpecifier = of.createTupleTypeSpecifier(); - for (cqlParser.TupleElementDefinitionContext definitionContext : ctx.tupleElementDefinition()) { - TupleElementDefinition element = (TupleElementDefinition) visit(definitionContext); - resultType.addElement(new TupleTypeElement( - element.getName(), element.getElementType().getResultType())); - typeSpecifier.getElement().add(element); - } - - typeSpecifier.setResultType(resultType); - - return typeSpecifier; - } - - @Override - public ChoiceTypeSpecifier visitChoiceTypeSpecifier(cqlParser.ChoiceTypeSpecifierContext ctx) { - var typeSpecifiers = new ArrayList(); - var types = new ArrayList(); - for (cqlParser.TypeSpecifierContext typeSpecifierContext : ctx.typeSpecifier()) { - TypeSpecifier typeSpecifier = parseTypeSpecifier(typeSpecifierContext); - typeSpecifiers.add(typeSpecifier); - types.add(typeSpecifier.getResultType()); - } - ChoiceTypeSpecifier result = of.createChoiceTypeSpecifier().withChoice(typeSpecifiers); - ChoiceType choiceType = new ChoiceType(types); - result.setResultType(choiceType); - return result; - } - - @Override - public IntervalTypeSpecifier visitIntervalTypeSpecifier(cqlParser.IntervalTypeSpecifierContext ctx) { - IntervalTypeSpecifier result = - of.createIntervalTypeSpecifier().withPointType(parseTypeSpecifier(ctx.typeSpecifier())); - IntervalType intervalType = new IntervalType(result.getPointType().getResultType()); - result.setResultType(intervalType); - return result; - } - - @Override - public ListTypeSpecifier visitListTypeSpecifier(cqlParser.ListTypeSpecifierContext ctx) { - ListTypeSpecifier result = - of.createListTypeSpecifier().withElementType(parseTypeSpecifier(ctx.typeSpecifier())); - ListType listType = new ListType(result.getElementType().getResultType()); - result.setResultType(listType); - return result; - } - - public FunctionHeader parseFunctionHeader(cqlParser.FunctionDefinitionContext ctx) { - final FunctionDef fun = of.createFunctionDef() - .withAccessLevel(parseAccessModifier(ctx.accessModifier())) - .withName(parseString(ctx.identifierOrFunctionIdentifier())) - .withContext(getCurrentContext()); - - if (ctx.fluentModifier() != null) { - libraryBuilder.checkCompatibilityLevel("Fluent functions", "1.5"); - fun.setFluent(true); - } - - if (ctx.operandDefinition() != null) { - for (cqlParser.OperandDefinitionContext opdef : ctx.operandDefinition()) { - TypeSpecifier typeSpecifier = parseTypeSpecifier(opdef.typeSpecifier()); - fun.getOperand().add((OperandDef) of.createOperandDef() - .withName(parseString(opdef.referentialIdentifier())) - .withOperandTypeSpecifier(typeSpecifier) - .withResultType(typeSpecifier.getResultType())); - } - } - - final cqlParser.TypeSpecifierContext typeSpecifierContext = ctx.typeSpecifier(); - - if (typeSpecifierContext != null) { - return FunctionHeader.withReturnType(fun, parseTypeSpecifier(typeSpecifierContext)); - } - - return FunctionHeader.noReturnType(fun); - } - - protected TypeSpecifier parseTypeSpecifier(ParseTree pt) { - return pt == null ? null : (TypeSpecifier) visit(pt); - } - - protected AccessModifier parseAccessModifier(ParseTree pt) { - return pt == null ? AccessModifier.PUBLIC : (AccessModifier) visit(pt); - } - - protected List parseQualifiers(cqlParser.NamedTypeSpecifierContext ctx) { - List qualifiers = new ArrayList<>(); - if (ctx.qualifier() != null) { - for (cqlParser.QualifierContext qualifierContext : ctx.qualifier()) { - String qualifier = parseString(qualifierContext); - qualifiers.add(qualifier); - } - } - return qualifiers; - } - - protected Model getModel(NamespaceInfo modelNamespace, String modelName, String version, String localIdentifier) { - if (modelName == null) { - var defaultUsing = libraryInfo.getDefaultUsingDefinition(); - modelName = defaultUsing.getName(); - version = defaultUsing.getVersion(); - } - - var modelIdentifier = new ModelIdentifier().withId(modelName).withVersion(version); - if (modelNamespace != null) { - modelIdentifier.setSystem(modelNamespace.getUri()); - } - return libraryBuilder.getModel(modelIdentifier, localIdentifier); - } - - private boolean pushChunk(ParseTree tree) { - if (!isAnnotationEnabled()) { - return false; - } - - org.antlr.v4.runtime.misc.Interval sourceInterval = tree.getSourceInterval(); - - // An interval of i..i-1 indicates an empty interval at position i in the input stream, - if (sourceInterval.b < sourceInterval.a) { - return false; - } - - Chunk chunk = new Chunk().withInterval(sourceInterval); - chunks.push(chunk); - return true; - } - - private void popChunk(ParseTree tree, Object o, boolean pushedChunk) { - if (!pushedChunk) { - return; - } - - Chunk chunk = chunks.pop(); - if (o instanceof Element) { - Element element = (Element) o; - chunk.setElement(element); - - if (!(tree instanceof cqlParser.LibraryContext)) { - if (element instanceof UsingDef - || element instanceof IncludeDef - || element instanceof CodeSystemDef - || element instanceof ValueSetDef - || element instanceof CodeDef - || element instanceof ConceptDef - || element instanceof ParameterDef - || element instanceof ContextDef - || element instanceof ExpressionDef) { - Annotation a = getAnnotation(element); - if (a == null || a.getS() == null) { - // Add header information (comments prior to the definition) - BaseInfo definitionInfo = libraryInfo.resolveDefinition(tree); - if (definitionInfo != null && definitionInfo.getHeaderInterval() != null) { - Chunk headerChunk = new Chunk() - .withInterval(definitionInfo.getHeaderInterval()) - .withIsHeaderChunk(true); - Chunk newChunk = new Chunk() - .withInterval(new org.antlr.v4.runtime.misc.Interval( - headerChunk.getInterval().a, chunk.getInterval().b)); - newChunk.addChunk(headerChunk); - newChunk.setElement(chunk.getElement()); - for (Chunk c : chunk.getChunks()) { - newChunk.addChunk(c); - } - chunk = newChunk; - } - if (a == null) { - element.getAnnotation().add(buildAnnotation(chunk)); - } else { - addNarrativeToAnnotation(a, chunk); - } - } - } - } else { - if (libraryInfo.getDefinition() != null && libraryInfo.getHeaderInterval() != null) { - Chunk headerChunk = new Chunk() - .withInterval(libraryInfo.getHeaderInterval()) - .withIsHeaderChunk(true); - Chunk definitionChunk = - new Chunk().withInterval(libraryInfo.getDefinition().getSourceInterval()); - Chunk newChunk = new Chunk() - .withInterval(new org.antlr.v4.runtime.misc.Interval( - headerChunk.getInterval().a, definitionChunk.getInterval().b)); - newChunk.addChunk(headerChunk); - newChunk.addChunk(definitionChunk); - newChunk.setElement(chunk.getElement()); - chunk = newChunk; - Annotation a = getAnnotation(libraryBuilder.getLibrary()); - if (a == null) { - libraryBuilder.getLibrary().getAnnotation().add(buildAnnotation(chunk)); - } else { - addNarrativeToAnnotation(a, chunk); - } - } - } - } - - if (!chunks.isEmpty()) { - chunks.peek().addChunk(chunk); - } - } - - private void processTags(ParseTree tree, Object o) { - if (!libraryBuilder.isCompatibleWith("1.5")) { - return; - } - - if (!(o instanceof Element)) { - return; - } - - Element element = (Element) o; - if (!(tree instanceof cqlParser.LibraryContext)) { - if (element instanceof UsingDef - || element instanceof IncludeDef - || element instanceof CodeSystemDef - || element instanceof ValueSetDef - || element instanceof CodeDef - || element instanceof ConceptDef - || element instanceof ParameterDef - || element instanceof ContextDef - || element instanceof ExpressionDef) { - var tags = getTags(tree); - if (!tags.isEmpty()) { - Annotation a = getAnnotation(element); - if (a == null) { - a = buildAnnotation(); - element.getAnnotation().add(a); - } - // If the definition was processed as a forward declaration, the tag processing will already - // have occurred - // and just adding tags would duplicate them here. This doesn't account for the possibility - // that - // tags would be added for some other reason, but I didn't want the overhead of checking for - // existing - // tags, and there is currently nothing that would add tags other than being processed from - // comments - if (a.getT().isEmpty()) { - a.getT().addAll(tags); - } - } - } - } else { - if (libraryInfo.getDefinition() != null && libraryInfo.getHeaderInterval() != null) { - var tags = getTags(libraryInfo.getHeader()); - if (!tags.isEmpty()) { - Annotation a = getAnnotation(libraryBuilder.getLibrary()); - if (a == null) { - a = buildAnnotation(); - libraryBuilder.getLibrary().getAnnotation().add(a); - } - a.getT().addAll(tags); - } - } - } - } - - private List getTags(String header) { - if (header != null) { - header = parseComments(header); - return parseTags(header); - } - - return Collections.emptyList(); - } - - private List getTags(ParseTree tree) { - BaseInfo bi = libraryInfo.resolveDefinition(tree); - if (bi != null) { - return getTags(bi.getHeader()); - } - - return Collections.emptyList(); - } - - private List parseTags(String header) { - header = String.join("\n", Arrays.asList(header.trim().split("\n[ \t]*\\*[ \t\\*]*"))); - var tags = new ArrayList(); - - int startFrom = 0; - while (startFrom < header.length()) { - Pair tagNamePair = lookForTagName(header, startFrom); - if (tagNamePair != null) { - if (tagNamePair.getLeft().length() > 0 && isValidIdentifier(tagNamePair.getLeft())) { - Tag t = af.createTag().withName(tagNamePair.getLeft()); - startFrom = tagNamePair.getRight(); - Pair tagValuePair = lookForTagValue(header, startFrom); - if (tagValuePair != null) { - if (tagValuePair.getLeft().length() > 0) { - t = t.withValue(tagValuePair.getLeft()); - startFrom = tagValuePair.getRight(); - } - } - tags.add(t); - } else { - startFrom = tagNamePair.getRight(); - } - } else { // no name tag found, no need to traverse more - break; - } - } - return tags; - } - - private String parseComments(String header) { - List result = new ArrayList<>(); - if (header != null) { - header = header.replace("\r\n", "\n"); - String[] lines = header.split("\n"); - boolean inMultiline = false; - for (String line : lines) { - if (!inMultiline) { - int start = line.indexOf("/*"); - if (start >= 0) { - if (line.endsWith("*/")) { - result.add(line.substring(start + 2, line.length() - 2)); - } else { - result.add(line.substring(start + 2)); - } - inMultiline = true; - } else start = line.indexOf("//"); - if (start >= 0 && !inMultiline) { - result.add(line.substring(start + 2)); - } - } else { - int end = line.indexOf("*/"); - if (end >= 0) { - inMultiline = false; - if (end > 0) { - result.add(line.substring(0, end)); - } - } else { - result.add(line); - } - } - } - } - return String.join("\n", result); - } - - public boolean isAnnotationEnabled() { - return annotate; - } - - public void enableAnnotations() { - annotate = true; - } - - public void disableAnnotations() { - annotate = false; - } - - private Annotation buildAnnotation(Chunk chunk) { - Annotation annotation = af.createAnnotation(); - annotation.setS(buildNarrative(chunk)); - return annotation; - } - - private Annotation buildAnnotation() { - return af.createAnnotation(); - } - - private void addNarrativeToAnnotation(Annotation annotation, Chunk chunk) { - annotation.setS(buildNarrative(chunk)); - } - - private Narrative buildNarrative(Chunk chunk) { - Narrative narrative = af.createNarrative(); - if (chunk.getElement() != null) { - narrative.setR(chunk.getElement().getLocalId()); - } - - if (chunk.hasChunks()) { - Narrative currentNarrative = null; - for (Chunk childChunk : chunk.getChunks()) { - Narrative chunkNarrative = buildNarrative(childChunk); - if (hasChunks(chunkNarrative)) { - if (currentNarrative != null) { - narrative.getContent().add(wrapNarrative(currentNarrative)); - currentNarrative = null; - } - narrative.getContent().add(wrapNarrative(chunkNarrative)); - } else { - if (currentNarrative == null) { - currentNarrative = chunkNarrative; - } else { - currentNarrative.getContent().addAll(chunkNarrative.getContent()); - if (currentNarrative.getR() == null) { - currentNarrative.setR(chunkNarrative.getR()); - } - } - } - } - if (currentNarrative != null) { - narrative.getContent().add(wrapNarrative(currentNarrative)); - } - } else { - String chunkContent = tokenStream.getText(chunk.getInterval()); - if (chunk.isHeaderChunk()) { - chunkContent = stripLeading(chunkContent); - } - chunkContent = normalizeWhitespace(chunkContent); - narrative.getContent().add(chunkContent); - } - - return narrative; - } - - private boolean hasChunks(Narrative narrative) { - for (Serializable c : narrative.getContent()) { - if (!(c instanceof String)) { - return true; - } - } - return false; - } - - private TrackBack getTrackBack(ParseTree tree) { - if (tree instanceof ParserRuleContext) { - return getTrackBack((ParserRuleContext) tree); - } - if (tree instanceof TerminalNode) { - return getTrackBack((TerminalNode) tree); - } - return null; - } - - private TrackBack getTrackBack(ParserRuleContext ctx) { - return new TrackBack( - libraryBuilder.getLibraryIdentifier(), - ctx.getStart().getLine(), - ctx.getStart().getCharPositionInLine() + 1, // 1-based instead of 0-based - ctx.getStop().getLine(), - ctx.getStop().getCharPositionInLine() + ctx.getStop().getText().length() // 1-based instead of 0-based - ); - } - - private TrackBack track(Trackable trackable, ParseTree pt) { - TrackBack tb = getTrackBack(pt); - - if (tb != null) { - trackable.getTrackbacks().add(tb); - } - - if (trackable instanceof Element) { - decorate((Element) trackable, tb); - } - - return tb; - } - - private void decorate(Element element, TrackBack tb) { - if (locate && tb != null) { - element.setLocator(tb.toLocator()); - } - - if (resultTypes && element.getResultType() != null) { - if (element.getResultType() instanceof NamedType) { - element.setResultTypeName(libraryBuilder.dataTypeToQName(element.getResultType())); - } else { - element.setResultTypeSpecifier(libraryBuilder.dataTypeToTypeSpecifier(element.getResultType())); - } - } - } - - private Pair lookForTagName(String header, int startFrom) { - - if (startFrom >= header.length()) { - return null; - } - int start = header.indexOf("@", startFrom); - if (start < 0) { - return null; - } - int nextTagStart = header.indexOf("@", start + 1); - int nextColon = header.indexOf(":", start + 1); - - if (nextTagStart < 0) { // no next tag , no next colon - if (nextColon < 0) { - return Pair.of(header.substring(start + 1, header.length()).trim(), header.length()); - } - } else { - if (nextColon < 0 - || nextColon - > nextTagStart) { // (has next tag and no colon) or (has next tag and next colon belongs to - // next tag) - return Pair.of(header.substring(start + 1, nextTagStart).trim(), nextTagStart); - } - } - return Pair.of(header.substring(start + 1, nextColon).trim(), nextColon + 1); - } - - // this method returns Pair starting from startFrom - // can return null in cases. - // for @1980-12-01, it will potentially check to be treated as value date - // it looks for parameter in double quotes, e.g. @parameter: "Measurement Interval" [@2019,@2020] - public static Pair lookForTagValue(String header, int startFrom) { - - if (startFrom >= header.length()) { - return null; - } - int nextTag = header.indexOf('@', startFrom); - int nextStartDoubleQuote = header.indexOf("\"", startFrom); - if ((nextTag < 0 || nextTag > nextStartDoubleQuote) - && nextStartDoubleQuote > 0 - && (header.length() > (nextStartDoubleQuote + 1))) { - int nextEndDoubleQuote = header.indexOf("\"", nextStartDoubleQuote + 1); - if (nextEndDoubleQuote > 0) { - int parameterEnd = header.indexOf("\n", (nextStartDoubleQuote + 1)); - if (parameterEnd < 0) { - return Pair.of(header.substring(nextStartDoubleQuote), header.length()); - } else { - return Pair.of(header.substring(nextStartDoubleQuote, parameterEnd), parameterEnd); - } - } else { // branch where the 2nd double quote is missing - return Pair.of(header.substring(nextStartDoubleQuote), header.length()); - } - } - if (nextTag == startFrom - && !isStartingWithDigit(header, nextTag + 1)) { // starts with `@` and not potential date value - return Pair.of("", startFrom); - } else if (nextTag > 0) { // has some text before tag - String interimText = header.substring(startFrom, nextTag).trim(); - if (isStartingWithDigit(header, nextTag + 1)) { // next `@` is a date value - if (interimText.length() > 0 - && !interimText.equals(":")) { // interim text has value, regards interim text - return Pair.of(interimText, nextTag); - } else { - int nextSpace = header.indexOf(' ', nextTag); - int nextLine = header.indexOf("\n", nextTag); - int mul = nextSpace * nextLine; - int nextDelimeterIndex = header.length(); - - if (mul < 0) { - nextDelimeterIndex = Math.max(nextLine, nextSpace); - } else if (mul > 1) { - nextDelimeterIndex = Math.min(nextLine, nextSpace); - } - - return Pair.of(header.substring(nextTag, nextDelimeterIndex), nextDelimeterIndex); - } - } else { // next `@` is not date - return Pair.of(interimText, nextTag); - } - } - - return Pair.of(header.substring(startFrom).trim(), header.length()); - } - - public static Serializable wrapNarrative(Narrative narrative) { - /* - TODO: Should be able to collapse narrative if the span doesn't have an attribute - That's what this code is doing, but it doesn't work and I don't have time to debug it - if (narrative.getR() == null) { - StringBuilder content = new StringBuilder(); - boolean onlyStrings = true; - for (Serializable s : narrative.getContent()) { - if (s instanceof String) { - content.append((String)s); - } - else { - onlyStrings = false; - } - } - if (onlyStrings) { - return content.toString(); - } - } - */ - return new JAXBElement<>(new QName("urn:hl7-org:cql-annotations:r1", "s"), Narrative.class, narrative); - } - - public static boolean isValidIdentifier(String tagName) { - for (int i = 0; i < tagName.length(); i++) { - if (tagName.charAt(i) == '_') { - continue; - } - - if (i == 0) { - if (!Character.isLetter(tagName.charAt(i))) { - return false; - } - } else { - if (!Character.isLetterOrDigit(tagName.charAt(i))) { - return false; - } - } - } - - return true; - } - - public static String getTypeIdentifier(List qualifiers, String identifier) { - if (qualifiers.size() > 1) { - String result = null; - for (int i = 1; i < qualifiers.size(); i++) { - result = result == null ? qualifiers.get(i) : (result + "." + qualifiers.get(i)); - } - return result + "." + identifier; - } - - return identifier; - } - - public static String getModelIdentifier(List qualifiers) { - return qualifiers.size() > 0 ? qualifiers.get(0) : null; - } - - // TODO: Should just use String.stripLeading() but that is only available in 11+ - public static String stripLeading(String s) { - int index = 0; - while (index < s.length()) { - if (!Character.isWhitespace(s.charAt(index))) { - break; - } - index++; - } - if (index == s.length()) { - return ""; - } - return s.substring(index); - } - - private void addExpression(Expression expression) { - expressions.add(expression); - } - - private Annotation getAnnotation(Element element) { - for (var o : element.getAnnotation()) { - if (o instanceof Annotation) { - return (Annotation) o; - } - } - - return null; - } - - protected String parseString(ParseTree pt) { - return StringEscapeUtils.unescapeCql(pt == null ? null : (String) visit(pt)); - } - - public static String normalizeWhitespace(String input) { - return input.replace("\r\n", "\n"); - } - - public static boolean isStartingWithDigit(String header, int index) { - return (index < header.length()) && Character.isDigit(header.charAt(index)); - } - - public void enableLocators() { - locate = true; - } - - public boolean locatorsEnabled() { - return locate; - } - - public void disableLocators() { - locate = false; - } - - public void enableResultTypes() { - resultTypes = true; - } - - public void disableResultTypes() { - resultTypes = false; - } - - public boolean resultTypesEnabled() { - return resultTypes; - } - - public void enableDateRangeOptimization() { - dateRangeOptimization = true; - } - - public void disableDateRangeOptimization() { - dateRangeOptimization = false; - } - - public boolean getDateRangeOptimization() { - return dateRangeOptimization; - } - - public void enableDetailedErrors() { - detailedErrors = true; - } - - public void disableDetailedErrors() { - detailedErrors = false; - } - - public boolean isDetailedErrorsEnabled() { - return detailedErrors; - } - - public void enableMethodInvocation() { - methodInvocation = true; - } - - public void disableMethodInvocation() { - methodInvocation = false; - } - - public boolean isMethodInvocationEnabled() { - return methodInvocation; - } - - public boolean isFromKeywordRequired() { - return fromKeywordRequired; - } - - public void enableFromKeywordRequired() { - fromKeywordRequired = true; - } - - public void disableFromKeywordRequired() { - fromKeywordRequired = false; - } - - private void setCompilerOptions(CqlCompilerOptions options) { - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableDateRangeOptimization)) { - this.enableDateRangeOptimization(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableAnnotations)) { - this.enableAnnotations(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableLocators)) { - this.enableLocators(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableResultTypes)) { - this.enableResultTypes(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableDetailedErrors)) { - this.enableDetailedErrors(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.DisableMethodInvocation)) { - this.disableMethodInvocation(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.RequireFromKeyword)) { - this.enableFromKeywordRequired(); - } - libraryBuilder.setCompatibilityLevel(options.getCompatibilityLevel()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.java deleted file mode 100644 index 8155d865a..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ExpressionDefinitionInfo.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class ExpressionDefinitionInfo extends BaseInfo { - private String name; - private String context; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public String getContext() { - return context; - } - - public void setContext(String value) { - context = value; - } - - @Override - public cqlParser.ExpressionDefinitionContext getDefinition() { - return (cqlParser.ExpressionDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ExpressionDefinitionContext value) { - super.setDefinition(value); - } - - public ExpressionDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public ExpressionDefinitionInfo withDefinition(cqlParser.ExpressionDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.java deleted file mode 100644 index 663e28f3f..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/FunctionDefinitionInfo.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import java.util.Objects; -import java.util.StringJoiner; -import org.cqframework.cql.cql2elm.model.FunctionHeader; -import org.cqframework.cql.gen.cqlParser; - -public class FunctionDefinitionInfo extends BaseInfo { - private String name; - private String context; - private FunctionHeader functionHeader; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public void setPreCompileOutput(FunctionHeader functionHeader) { - this.functionHeader = functionHeader; - } - - public FunctionHeader getPreCompileOutput() { - return this.functionHeader; - } - - public String getContext() { - return context; - } - - public void setContext(String value) { - context = value; - } - - @Override - public cqlParser.FunctionDefinitionContext getDefinition() { - return (cqlParser.FunctionDefinitionContext) super.getDefinition(); - } - - public FunctionDefinitionInfo withName(String value) { - setName(value); - return this; - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (other == null || getClass() != other.getClass()) return false; - FunctionDefinitionInfo that = (FunctionDefinitionInfo) other; - return Objects.equals(name, that.name) - && Objects.equals(context, that.context) - && Objects.equals(functionHeader, that.functionHeader); - } - - @Override - public int hashCode() { - return Objects.hash(name, context, functionHeader); - } - - @Override - public String toString() { - return new StringJoiner(", ", FunctionDefinitionInfo.class.getSimpleName() + "[", "]") - .add("name='" + name + "'") - .add("context='" + context + "'") - .add("preCompileOutput=" + functionHeader) - .toString(); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.java deleted file mode 100644 index b6f3d0a50..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/IncludeDefinitionInfo.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class IncludeDefinitionInfo extends BaseInfo { - private String namespaceName; - private String name; - private String version; - private String localName; - - public String getNamespaceName() { - return namespaceName; - } - - public void setNamespaceName(String namespaceName) { - this.namespaceName = namespaceName; - } - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public String getVersion() { - return version; - } - - public void setVersion(String value) { - version = value; - } - - public String getLocalName() { - return localName; - } - - public void setLocalName(String value) { - localName = value; - } - - public IncludeDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public IncludeDefinitionInfo withVersion(String value) { - setVersion(value); - return this; - } - - public IncludeDefinitionInfo withLocalName(String value) { - setLocalName(value); - return this; - } - - @Override - public cqlParser.IncludeDefinitionContext getDefinition() { - return (cqlParser.IncludeDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.IncludeDefinitionContext value) { - super.setDefinition(value); - } - - public IncludeDefinitionInfo withDefinition(cqlParser.IncludeDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.java deleted file mode 100644 index 6f1caad4d..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/LibraryInfo.java +++ /dev/null @@ -1,315 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import java.util.*; -import org.antlr.v4.runtime.misc.Interval; -import org.antlr.v4.runtime.tree.ParseTree; -import org.cqframework.cql.cql2elm.ResultWithPossibleError; -import org.cqframework.cql.gen.cqlParser; -import org.hl7.elm.r1.OperandDef; - -public class LibraryInfo extends BaseInfo { - private String namespaceName; - private String libraryName; - private String version; - - private UsingDefinitionInfo preferredUsingDefinition; - private final Map usingDefinitions; - private final Map includeDefinitions; - private final Map codesystemDefinitions; - private final Map valuesetDefinitions; - private final Map codeDefinitions; - private final Map conceptDefinitions; - private final Map parameterDefinitions; - private final Map expressionDefinitions; - private final Map> functionDefinitions; - private final List contextDefinitions; - private final Map definitions; - - public LibraryInfo() { - usingDefinitions = new LinkedHashMap<>(); - includeDefinitions = new LinkedHashMap<>(); - codesystemDefinitions = new LinkedHashMap<>(); - valuesetDefinitions = new LinkedHashMap<>(); - codeDefinitions = new LinkedHashMap<>(); - conceptDefinitions = new LinkedHashMap<>(); - parameterDefinitions = new LinkedHashMap<>(); - expressionDefinitions = new LinkedHashMap<>(); - functionDefinitions = new LinkedHashMap<>(); - contextDefinitions = new ArrayList<>(); - definitions = new HashMap<>(); - } - - public String getNamespaceName() { - return namespaceName; - } - - public void setNamespaceName(String namespaceName) { - this.namespaceName = namespaceName; - } - - public String getLibraryName() { - return libraryName; - } - - public void setLibraryName(String libraryName) { - this.libraryName = libraryName; - } - - public LibraryInfo withLibraryName(String value) { - setLibraryName(value); - return this; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public LibraryInfo withVersion(String value) { - setVersion(value); - return this; - } - - private void addDefinition(BaseInfo definition) { - if (definition != null && definition.getDefinition() != null) { - Interval sourceInterval = definition.getDefinition().getSourceInterval(); - if (sourceInterval != null) { - definitions.put(sourceInterval, definition); - } - } - } - - @Override - public cqlParser.LibraryDefinitionContext getDefinition() { - return (cqlParser.LibraryDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.LibraryDefinitionContext value) { - super.setDefinition(value); - addDefinition(this); - } - - public LibraryInfo withDefinition(cqlParser.LibraryDefinitionContext value) { - setDefinition(value); - return this; - } - - public void addUsingDefinition(UsingDefinitionInfo usingDefinition) { - // First using definition encountered is "preferred", meaning it will resolve as the default model info - if (preferredUsingDefinition == null) { - preferredUsingDefinition = usingDefinition; - } - usingDefinitions.put(usingDefinition.getName(), usingDefinition); - addDefinition(usingDefinition); - } - - public UsingDefinitionInfo resolveModelReference(String identifier) { - return usingDefinitions.get(identifier); - } - - public UsingDefinitionInfo getDefaultUsingDefinition() { - return preferredUsingDefinition; - } - - public String getDefaultModelName() { - UsingDefinitionInfo usingDefinitionInfo = getDefaultUsingDefinition(); - if (usingDefinitionInfo == null) { - throw new IllegalArgumentException( - "Could not determine a default model because no usings have been defined."); - } - - return usingDefinitionInfo.getName(); - } - - public void addIncludeDefinition(IncludeDefinitionInfo includeDefinition) { - includeDefinitions.put(includeDefinition.getLocalName(), includeDefinition); - addDefinition(includeDefinition); - } - - public IncludeDefinitionInfo resolveLibraryReference(String identifier) { - return includeDefinitions.get(identifier); - } - - public String resolveLibraryName(String identifier) { - IncludeDefinitionInfo includeDefinition = resolveLibraryReference(identifier); - if (includeDefinition != null) { - return includeDefinition.getLocalName(); - } - - return null; - } - - public void addParameterDefinition(ParameterDefinitionInfo parameterDefinition) { - parameterDefinitions.put(parameterDefinition.getName(), parameterDefinition); - addDefinition(parameterDefinition); - } - - public ParameterDefinitionInfo resolveParameterReference(String identifier) { - return parameterDefinitions.get(identifier); - } - - public String resolveParameterName(String identifier) { - ParameterDefinitionInfo parameterDefinition = resolveParameterReference(identifier); - if (parameterDefinition != null) { - return parameterDefinition.getName(); - } - - return null; - } - - public void addCodesystemDefinition(CodesystemDefinitionInfo codesystemDefinition) { - codesystemDefinitions.put(codesystemDefinition.getName(), codesystemDefinition); - addDefinition(codesystemDefinition); - } - - public CodesystemDefinitionInfo resolveCodesystemReference(String identifier) { - return codesystemDefinitions.get(identifier); - } - - public void addValuesetDefinition(ValuesetDefinitionInfo valuesetDefinition) { - valuesetDefinitions.put(valuesetDefinition.getName(), valuesetDefinition); - addDefinition(valuesetDefinition); - } - - public ValuesetDefinitionInfo resolveValuesetReference(String identifier) { - return valuesetDefinitions.get(identifier); - } - - public String resolveValuesetName(String identifier) { - ValuesetDefinitionInfo valuesetDefinition = resolveValuesetReference(identifier); - if (valuesetDefinition != null) { - return valuesetDefinition.getName(); - } - - return null; - } - - public void addCodeDefinition(CodeDefinitionInfo codeDefinition) { - codeDefinitions.put(codeDefinition.getName(), codeDefinition); - addDefinition(codeDefinition); - } - - public CodeDefinitionInfo resolveCodeReference(String identifier) { - return codeDefinitions.get(identifier); - } - - public void addConceptDefinition(ConceptDefinitionInfo conceptDefinition) { - conceptDefinitions.put(conceptDefinition.getName(), conceptDefinition); - addDefinition(conceptDefinition); - } - - public ConceptDefinitionInfo resolveConceptReference(String identifier) { - return conceptDefinitions.get(identifier); - } - - public void addExpressionDefinition(ExpressionDefinitionInfo letStatement) { - expressionDefinitions.put(letStatement.getName(), letStatement); - addDefinition(letStatement); - } - - public ExpressionDefinitionInfo resolveExpressionReference(String identifier) { - return expressionDefinitions.get(identifier); - } - - public String resolveExpressionName(String identifier) { - ExpressionDefinitionInfo letStatement = resolveExpressionReference(identifier); - if (letStatement != null) { - return letStatement.getName(); - } - - return null; - } - - public void addFunctionDefinition(FunctionDefinitionInfo functionDefinition) { - List infos = functionDefinitions.get(functionDefinition.getName()); - if (infos == null) { - infos = new ArrayList(); - functionDefinitions.put(functionDefinition.getName(), infos); - } - infos.add(functionDefinition); - addDefinition(functionDefinition); - } - - public Iterable resolveFunctionReference(String identifier) { - return functionDefinitions.get(identifier); - } - - public String resolveFunctionName(String identifier) { - Iterable functionDefinitions = resolveFunctionReference(identifier); - for (FunctionDefinitionInfo functionInfo : functionDefinitions) { - return functionInfo.getName(); - } - - return null; - } - - public void addContextDefinition(ContextDefinitionInfo contextDefinition) { - contextDefinitions.add(contextDefinition); - addDefinition(contextDefinition); - } - - public ContextDefinitionInfo resolveContext(cqlParser.ContextDefinitionContext ctx) { - for (ContextDefinitionInfo cd : contextDefinitions) { - if (ctx.getSourceInterval().equals(cd.getDefinition().getSourceInterval())) { - return cd; - } - } - - return null; - } - - public BaseInfo resolveDefinition(ParseTree pt) { - return definitions.get(pt.getSourceInterval()); - } - - private static boolean isFunctionDefInfoAlreadyPresent( - ResultWithPossibleError existingFunctionDefInfo, - ResultWithPossibleError functionDefinition) { - // equals/hashCode only goes so far because we don't control the entire class hierarchy - return matchesFunctionDefInfos(existingFunctionDefInfo, functionDefinition); - } - - private static boolean matchesFunctionDefInfos( - ResultWithPossibleError existingInfo, - ResultWithPossibleError newInfo) { - if (existingInfo == null) { - return false; - } - - if (existingInfo.hasError() || newInfo.hasError()) { - return existingInfo.hasError() && newInfo.hasError(); - } - - final List existingOperands = existingInfo - .getUnderlyingResultIfExists() - .getPreCompileOutput() - .getFunctionDef() - .getOperand(); - final List newOperands = newInfo.getUnderlyingResultIfExists() - .getPreCompileOutput() - .getFunctionDef() - .getOperand(); - - if (existingOperands.size() != newOperands.size()) { - return false; - } - - for (int index = 0; index < existingOperands.size(); index++) { - final OperandDef existingOperand = existingOperands.get(index); - final OperandDef newOperand = newOperands.get(index); - - if (!matchesOperands(existingOperand, newOperand)) { - return false; - } - } - - return true; - } - - private static boolean matchesOperands(OperandDef existingOperand, OperandDef newOperand) { - return existingOperand.getResultType().equals(newOperand.getResultType()); - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.java deleted file mode 100644 index ea6a37396..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ParameterDefinitionInfo.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class ParameterDefinitionInfo extends BaseInfo { - private String name; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - @Override - public cqlParser.ParameterDefinitionContext getDefinition() { - return (cqlParser.ParameterDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ParameterDefinitionContext value) { - super.setDefinition(value); - } - - public ParameterDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public ParameterDefinitionInfo withDefinition(cqlParser.ParameterDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.java deleted file mode 100644 index ddcdfb43f..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/UsingDefinitionInfo.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.cqframework.cql.gen.cqlParser; - -public class UsingDefinitionInfo extends BaseInfo { - private String namespaceName; - private String name; - private String version; - private String localName; - - public String getNamespaceName() { - return namespaceName; - } - - public void setNamespaceName(String value) { - namespaceName = value; - } - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - public String getVersion() { - return version; - } - - public void setVersion(String value) { - version = value; - } - - public String getLocalName() { - return localName; - } - - public void setLocalName(String value) { - localName = value; - } - - public UsingDefinitionInfo withNamespaceName(String value) { - setNamespaceName(value); - return this; - } - - public UsingDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public UsingDefinitionInfo withVersion(String value) { - setVersion(value); - return this; - } - - public UsingDefinitionInfo withLocalName(String value) { - setLocalName(value); - return this; - } - - @Override - public cqlParser.UsingDefinitionContext getDefinition() { - return (cqlParser.UsingDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.UsingDefinitionContext value) { - super.setDefinition(value); - } - - public UsingDefinitionInfo withDefinition(cqlParser.UsingDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.java deleted file mode 100644 index c52fc4243..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/ValuesetDefinitionInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.cqframework.cql.cql2elm.preprocessor; - -import org.antlr.v4.runtime.misc.Interval; -import org.cqframework.cql.gen.cqlParser; - -public class ValuesetDefinitionInfo extends BaseInfo { - private String name; - private String header; - private Interval headerInterval; - - public String getName() { - return name; - } - - public void setName(String value) { - name = value; - } - - @Override - public cqlParser.ValuesetDefinitionContext getDefinition() { - return (cqlParser.ValuesetDefinitionContext) super.getDefinition(); - } - - public void setDefinition(cqlParser.ValuesetDefinitionContext value) { - super.setDefinition(value); - } - - public ValuesetDefinitionInfo withName(String value) { - setName(value); - return this; - } - - public ValuesetDefinitionInfo withDefinition(cqlParser.ValuesetDefinitionContext value) { - setDefinition(value); - return this; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/quick/FhirLibrarySourceProvider.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/quick/FhirLibrarySourceProvider.java deleted file mode 100644 index f2b646230..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/quick/FhirLibrarySourceProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.cqframework.cql.cql2elm.quick; - -import java.io.InputStream; -import org.cqframework.cql.cql2elm.LibrarySourceProvider; -import org.hl7.cql.model.NamespaceAware; -import org.hl7.cql.model.NamespaceInfo; -import org.hl7.cql.model.NamespaceManager; -import org.hl7.elm.r1.VersionedIdentifier; - -/** - * Created by Bryn on 3/28/2017. - */ -public class FhirLibrarySourceProvider implements LibrarySourceProvider, NamespaceAware { - - private final String namespaceName = "FHIR"; - private final String namespaceUri = "http://hl7.org/fhir"; - - @Override - public InputStream getLibrarySource(VersionedIdentifier libraryIdentifier) { - InputStream result = FhirLibrarySourceProvider.class.getResourceAsStream( - String.format("/org/hl7/fhir/%s-%s.cql", libraryIdentifier.getId(), libraryIdentifier.getVersion())); - - if (result != null) { - // If the FHIRHelpers library is referenced in a namespace-enabled context, - // set the namespace to the FHIR namespace URI - if (namespaceManager != null && namespaceManager.hasNamespaces()) { - // If the context already has a namespace registered for FHIR, use that. - NamespaceInfo namespaceInfo = namespaceManager.getNamespaceInfoFromUri(namespaceUri); - if (namespaceInfo == null) { - namespaceInfo = new NamespaceInfo(namespaceName, namespaceUri); - namespaceManager.ensureNamespaceRegistered(namespaceInfo); - } - libraryIdentifier.setSystem(namespaceUri); - } - } - - return result; - } - - private NamespaceManager namespaceManager; - - @Override - public void setNamespaceManager(NamespaceManager namespaceManager) { - this.namespaceManager = namespaceManager; - } -} diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/targetmap/TargetMapVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/targetmap/TargetMapVisitor.java deleted file mode 100644 index 62216aba9..000000000 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/targetmap/TargetMapVisitor.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.cqframework.cql.cql2elm.targetmap; - -import org.cqframework.cql.gen.targetmapBaseVisitor; - -public class TargetMapVisitor extends targetmapBaseVisitor {} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CompilerOptions.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CompilerOptions.kt new file mode 100644 index 000000000..8292bac76 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CompilerOptions.kt @@ -0,0 +1,87 @@ +package org.cqframework.cql.cql2elm + +import java.util.* +import org.hl7.cql_annotations.r1.CqlToElmBase +import org.hl7.cql_annotations.r1.CqlToElmInfo +import org.hl7.elm.r1.Library + +/** This class provides functions for extracting and parsing CQL Compiler Options from a Library */ +object CompilerOptions { + /** + * Gets the compiler options used to generate an elm Library. + * + * Returns null if the compiler options could not be determined. (for example, the Library was + * translated without annotations) + * + * @param library The library to extracts the options from. + * @return The set of options used to translate the library. + */ + @JvmStatic + fun getCompilerOptions(library: Library): Set? { + if (library.annotation.isEmpty()) { + return null + } + val compilerOptions = getCompilerOptions(library.annotation) + return parseCompilerOptions(compilerOptions) + } + + private fun getCompilerOptions(annotations: List): String? { + for (base: CqlToElmBase in annotations) { + if (base is CqlToElmInfo) { + if (base.translatorOptions != null) { + return base.translatorOptions + } + } + } + return null + } + + /** + * Parses a string representing CQL compiler Options into an EnumSet. The string is expected to + * be a comma-delimited list of values from the CqlCompiler.Options enumeration. For example + * "EnableListPromotion, EnableListDemotion". + * + * @param compilerOptions the string to parse + * @return the set of options + */ + @JvmStatic + fun parseCompilerOptions(compilerOptions: String?): Set? { + if (compilerOptions.isNullOrEmpty()) { + return null + } + val optionSet: EnumSet = + EnumSet.noneOf(CqlCompilerOptions.Options::class.java) + val options = + compilerOptions.trim { it <= ' ' }.split(",".toRegex()).dropLastWhile { it.isEmpty() } + for (option in options) { + optionSet.add(CqlCompilerOptions.Options.valueOf(option)) + } + return optionSet + } + + /** + * Gets the compiler version used to generate an elm Library. + * + * Returns null if the compiled version could not be determined. (for example, the Library was + * compiled without annotations) + * + * @param library The library to extracts the compiler version from. + * @return The version of compiler used to compiler the library. + */ + @JvmStatic + fun getCompilerVersion(library: Library): String? { + if (library.annotation.isEmpty()) { + return null + } + return getCompilerVersion(library.annotation) + } + + private fun getCompilerVersion(annotations: List): String? { + for (o: CqlToElmBase in annotations) { + if (o is CqlToElmInfo) { + return o.translatorVersion + } + } + return null + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/Cql2ElmVisitor.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/Cql2ElmVisitor.kt new file mode 100755 index 000000000..14ddca105 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/Cql2ElmVisitor.kt @@ -0,0 +1,4481 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.math.BigDecimal +import java.util.* +import java.util.regex.Pattern +import org.antlr.v4.kotlinruntime.ParserRuleContext +import org.antlr.v4.kotlinruntime.Token +import org.antlr.v4.kotlinruntime.TokenStream +import org.antlr.v4.kotlinruntime.tree.ParseTree +import org.antlr.v4.kotlinruntime.tree.TerminalNode +import org.cqframework.cql.cql2elm.DataTypes.equal +import org.cqframework.cql.cql2elm.DataTypes.subTypeOf +import org.cqframework.cql.cql2elm.DataTypes.verifyCast +import org.cqframework.cql.cql2elm.DataTypes.verifyType +import org.cqframework.cql.cql2elm.LibraryBuilder.IdentifierScope +import org.cqframework.cql.cql2elm.model.* +import org.cqframework.cql.cql2elm.model.QueryContext +import org.cqframework.cql.cql2elm.model.invocation.* +import org.cqframework.cql.cql2elm.preprocessor.CqlPreprocessorElmCommonVisitor +import org.cqframework.cql.cql2elm.preprocessor.ExpressionDefinitionInfo +import org.cqframework.cql.cql2elm.preprocessor.LibraryInfo +import org.cqframework.cql.cql2elm.tracking.TrackBack +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.cqframework.cql.cql2elm.tracking.Trackable.trackbacks +import org.cqframework.cql.cql2elm.tracking.Trackable.withResultType +import org.cqframework.cql.gen.cqlLexer +import org.cqframework.cql.gen.cqlParser +import org.cqframework.cql.gen.cqlParser.* +import org.hl7.cql.model.* +import org.hl7.elm.r1.* +import org.hl7.elm_modelinfo.r1.ModelInfo +import org.slf4j.LoggerFactory + +@Suppress( + "LongMethod", + "LargeClass", + "CyclomaticComplexMethod", + "NestedBlockDepth", + "TooManyFunctions", + "ComplexCondition", + "TooGenericExceptionCaught", + "ReturnCount", + "ThrowsCount", + "MaxLineLength", + "ForbiddenComment", + "LoopWithTooManyJumpStatements", + "MagicNumber" +) +class Cql2ElmVisitor( + libraryBuilder: LibraryBuilder, + tokenStream: TokenStream, + libraryInfo: LibraryInfo +) : CqlPreprocessorElmCommonVisitor(libraryBuilder, tokenStream) { + private val systemMethodResolver = SystemMethodResolver(this, libraryBuilder) + private val definedExpressionDefinitions: MutableSet = HashSet() + private val forwards = Stack() + private val functionHeaders: MutableMap = HashMap() + private val functionHeadersByDef: MutableMap = HashMap() + private val functionDefinitions: MutableMap = + HashMap() + private val timingOperators = Stack() + val retrieves: MutableList = ArrayList() + val expressions: List = ArrayList() + private val contextDefinitions: MutableMap = HashMap() + + init { + this.libraryInfo = libraryInfo + } + + override fun defaultResult(): Any? { + return null + } + + private inline fun Any?.cast(): T { + return this as T + } + + override fun visitLibrary(ctx: LibraryContext): Any? { + var lastResult: Any? = null + + // Loop through and call visit on each child (to ensure they are tracked) + for (i in 0 until ctx.childCount) { + val tree = ctx.getChild(i) + val terminalNode = tree as? TerminalNode + if (terminalNode != null && terminalNode.symbol.type == Token.EOF) { + continue + } + val childResult = visit(tree!!) + // Only set the last result if we received something useful + if (childResult != null) { + lastResult = childResult + } + } + + // Return last result (consistent with super implementation and helps w/ + // testing) + return lastResult + } + + override fun visitLibraryDefinition(ctx: LibraryDefinitionContext): VersionedIdentifier { + val identifiers: MutableList = visit(ctx.qualifiedIdentifier()).cast() + val vid = + of.createVersionedIdentifier() + .withId(identifiers.removeAt(identifiers.size - 1)) + .withVersion(parseString(ctx.versionSpecifier())) + if (identifiers.isNotEmpty()) { + vid.system = libraryBuilder.resolveNamespaceUri(identifiers.joinToString("."), true) + } else if (libraryBuilder.namespaceInfo != null) { + vid.system = libraryBuilder.namespaceInfo.uri + } + libraryBuilder.libraryIdentifier = vid + return vid + } + + override fun visitUsingDefinition(ctx: UsingDefinitionContext): UsingDef? { + val identifiers: MutableList = visit(ctx.qualifiedIdentifier()).cast() + val unqualifiedIdentifier: String = identifiers.removeAt(identifiers.size - 1) + val namespaceName = + when { + identifiers.isNotEmpty() -> java.lang.String.join(".", identifiers) + libraryBuilder.isWellKnownModelName(unqualifiedIdentifier) -> null + libraryBuilder.namespaceInfo != null -> libraryBuilder.namespaceInfo.name + else -> null + } + var path: String? = null + var modelNamespace: NamespaceInfo? = null + if (namespaceName != null) { + val namespaceUri = libraryBuilder.resolveNamespaceUri(namespaceName, true) + path = NamespaceManager.getPath(namespaceUri, unqualifiedIdentifier) + modelNamespace = NamespaceInfo(namespaceName, namespaceUri!!) + } else { + path = unqualifiedIdentifier + } + val localIdentifier = + if (ctx.localIdentifier() == null) unqualifiedIdentifier + else parseString(ctx.localIdentifier())!! + require(localIdentifier == unqualifiedIdentifier) { + "Local identifiers for models must be the same as the name of the model in this release of the translator (Model $unqualifiedIdentifier, Called $localIdentifier)" + } + + // The model was already calculated by CqlPreprocessorVisitor + val usingDef = libraryBuilder.resolveUsingRef(localIdentifier) + libraryBuilder.pushIdentifier(localIdentifier, usingDef, IdentifierScope.GLOBAL) + return usingDef + } + + override fun getModel( + modelNamespace: NamespaceInfo?, + modelName: String?, + version: String?, + localIdentifier: String + ): Model { + val modelIdentifier = + ModelIdentifier(id = modelName!!, version = version, system = modelNamespace?.uri) + return libraryBuilder.getModel(modelIdentifier, localIdentifier) + } + + private fun getLibraryPath(namespaceName: String?, unqualifiedIdentifier: String): String { + if (namespaceName != null) { + val namespaceUri = libraryBuilder.resolveNamespaceUri(namespaceName, true) + return NamespaceManager.getPath(namespaceUri, unqualifiedIdentifier) + } + return unqualifiedIdentifier + } + + override fun visitIncludeDefinition(ctx: IncludeDefinitionContext): IncludeDef { + val identifiers: MutableList = visit(ctx.qualifiedIdentifier()).cast() + val unqualifiedIdentifier: String = identifiers.removeAt(identifiers.size - 1) + var namespaceName = + if (identifiers.isNotEmpty()) java.lang.String.join(".", identifiers) + else if (libraryBuilder.namespaceInfo != null) libraryBuilder.namespaceInfo.name + else null + var path = getLibraryPath(namespaceName, unqualifiedIdentifier) + var library = + of.createIncludeDef() + .withLocalIdentifier( + if (ctx.localIdentifier() == null) unqualifiedIdentifier + else parseString(ctx.localIdentifier()) + ) + .withPath(path) + .withVersion(parseString(ctx.versionSpecifier())) + + // TODO: This isn't great because it complicates the loading process (and + // results in the source being loaded + // twice in the general case) + // But the full fix is to introduce source resolution/caching to enable this + // layer to determine whether the + // library identifier resolved + // with the namespace + if (!libraryBuilder.canResolveLibrary(library)) { + namespaceName = + when { + identifiers.isNotEmpty() -> java.lang.String.join(".", identifiers) + libraryBuilder.isWellKnownLibraryName(unqualifiedIdentifier) -> null + libraryBuilder.namespaceInfo != null -> libraryBuilder.namespaceInfo.name + else -> null + } + path = getLibraryPath(namespaceName, unqualifiedIdentifier) + library = + of.createIncludeDef() + .withLocalIdentifier( + if (ctx.localIdentifier() == null) unqualifiedIdentifier + else parseString(ctx.localIdentifier()) + ) + .withPath(path) + .withVersion(parseString(ctx.versionSpecifier())) + } + libraryBuilder.addInclude(library) + libraryBuilder.pushIdentifier(library.localIdentifier!!, library, IdentifierScope.GLOBAL) + return library + } + + override fun visitParameterDefinition(ctx: ParameterDefinitionContext): ParameterDef { + val param = + of.createParameterDef() + .withAccessLevel(parseAccessModifier(ctx.accessModifier())) + .withName(parseString(ctx.identifier())) + .withDefault(parseLiteralExpression(ctx.expression())) + .withParameterTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())) + var paramType: DataType? = null + if (param.parameterTypeSpecifier != null) { + paramType = param.parameterTypeSpecifier!!.resultType + } + if (param.default != null) { + if (paramType != null) { + libraryBuilder.verifyType(param.default!!.resultType!!, paramType) + } else { + paramType = param.default!!.resultType + } + } + requireNotNull(paramType) { + "Could not determine parameter type for parameter ${param.name}." + } + param.resultType = paramType + if (param.default != null) { + param.default = libraryBuilder.ensureCompatible(param.default, paramType) + } + libraryBuilder.addParameter(param) + libraryBuilder.pushIdentifier(param.name!!, param, IdentifierScope.GLOBAL) + return param + } + + override fun visitTupleElementDefinition( + ctx: TupleElementDefinitionContext + ): TupleElementDefinition { + val result = + of.createTupleElementDefinition() + .withName(parseString(ctx.referentialIdentifier())) + .withElementType(parseTypeSpecifier(ctx.typeSpecifier())) + return result + } + + override fun visitAccessModifier(ctx: AccessModifierContext): AccessModifier { + return when (ctx.text.lowercase(Locale.getDefault())) { + "public" -> AccessModifier.PUBLIC + "private" -> AccessModifier.PRIVATE + else -> + throw IllegalArgumentException( + "Unknown access modifier ${ctx.text.lowercase(Locale.getDefault())}." + ) + } + } + + override fun visitCodesystemDefinition(ctx: CodesystemDefinitionContext): CodeSystemDef { + val cs = + of.createCodeSystemDef() + .withAccessLevel(parseAccessModifier(ctx.accessModifier())) + .withName(parseString(ctx.identifier())) + .withId(parseString(ctx.codesystemId())) + .withVersion(parseString(ctx.versionSpecifier())) + if (libraryBuilder.isCompatibleWith("1.5")) { + cs.resultType = libraryBuilder.resolveTypeName("System", "CodeSystem") + } else { + cs.resultType = ListType(libraryBuilder.resolveTypeName("System", "Code")!!) + } + libraryBuilder.addCodeSystem(cs) + libraryBuilder.pushIdentifier(cs.name!!, cs, IdentifierScope.GLOBAL) + return cs + } + + override fun visitCodesystemIdentifier(ctx: CodesystemIdentifierContext): CodeSystemRef { + val libraryName = parseString(ctx.libraryIdentifier()) + val name = parseString(ctx.identifier())!! + val def: CodeSystemDef? + if (libraryName != null) { + def = libraryBuilder.resolveLibrary(libraryName).resolveCodeSystemRef(name) + libraryBuilder.checkAccessLevel(libraryName, name, def!!.accessLevel!!) + } else { + def = libraryBuilder.resolveCodeSystemRef(name) + } + requireNotNull(def) { "Could not resolve reference to code system $name." } + return of.createCodeSystemRef() + .withLibraryName(libraryName) + .withName(name) + .withResultType(def.resultType) + } + + override fun visitCodeIdentifier(ctx: CodeIdentifierContext): CodeRef { + val libraryName = parseString(ctx.libraryIdentifier()) + val name = parseString(ctx.identifier())!! + val def: CodeDef? + if (libraryName != null) { + def = libraryBuilder.resolveLibrary(libraryName).resolveCodeRef(name) + libraryBuilder.checkAccessLevel(libraryName, name, def!!.accessLevel!!) + } else { + def = libraryBuilder.resolveCodeRef(name) + } + requireNotNull(def) { + // ERROR: + "Could not resolve reference to code $name." + } + return of.createCodeRef() + .withLibraryName(libraryName) + .withName(name) + .withResultType(def.resultType) + } + + override fun visitValuesetDefinition(ctx: ValuesetDefinitionContext): ValueSetDef { + val vs = + of.createValueSetDef() + .withAccessLevel(parseAccessModifier(ctx.accessModifier())) + .withName(parseString(ctx.identifier())) + .withId(parseString(ctx.valuesetId())) + .withVersion(parseString(ctx.versionSpecifier())) + if (ctx.codesystems() != null) { + for (codesystem in ctx.codesystems()!!.codesystemIdentifier()) { + val cs = + visit(codesystem) as CodeSystemRef? + ?: throw IllegalArgumentException( + "Could not resolve reference to code system ${codesystem.text}." + ) + vs.codeSystem.add(cs) + } + } + if (libraryBuilder.isCompatibleWith("1.5")) { + vs.resultType = libraryBuilder.resolveTypeName("System", "ValueSet") + } else { + vs.resultType = ListType(libraryBuilder.resolveTypeName("System", "Code")!!) + } + libraryBuilder.addValueSet(vs) + libraryBuilder.pushIdentifier(vs.name!!, vs, IdentifierScope.GLOBAL) + return vs + } + + override fun visitCodeDefinition(ctx: CodeDefinitionContext): CodeDef { + val cd = + of.createCodeDef() + .withAccessLevel(parseAccessModifier(ctx.accessModifier())) + .withName(parseString(ctx.identifier())) + .withId(parseString(ctx.codeId())) + cd.codeSystem = visit(ctx.codesystemIdentifier()) as CodeSystemRef? + if (ctx.displayClause() != null) { + cd.display = parseString(ctx.displayClause()!!.STRING()) + } + cd.resultType = libraryBuilder.resolveTypeName("Code") + libraryBuilder.addCode(cd) + libraryBuilder.pushIdentifier(cd.name!!, cd, IdentifierScope.GLOBAL) + return cd + } + + override fun visitConceptDefinition(ctx: ConceptDefinitionContext): ConceptDef { + val cd = + of.createConceptDef() + .withAccessLevel(parseAccessModifier(ctx.accessModifier())) + .withName(parseString(ctx.identifier())) + for (ci in ctx.codeIdentifier()) { + cd.code.add(visit(ci) as CodeRef) + } + if (ctx.displayClause() != null) { + cd.display = parseString(ctx.displayClause()!!.STRING()) + } + cd.resultType = libraryBuilder.resolveTypeName("Concept") + libraryBuilder.addConcept(cd) + return cd + } + + override fun visitNamedTypeSpecifier(ctx: NamedTypeSpecifierContext): NamedTypeSpecifier? { + val qualifiers = parseQualifiers(ctx) + val modelIdentifier = getModelIdentifier(qualifiers) + val identifier = + getTypeIdentifier(qualifiers, parseString(ctx.referentialOrTypeNameIdentifier())!!) + val retrievedResult = + libraryBuilder.getNamedTypeSpecifierResult("$modelIdentifier:$identifier") + if (retrievedResult != null) { + return if (retrievedResult.hasError()) { + null + } else retrievedResult.underlyingResultIfExists + } + val resultType = + libraryBuilder.resolveTypeName(modelIdentifier, identifier) + ?: throw CqlCompilerException( + "Could not find type for model: $modelIdentifier and name: $identifier", + getTrackBack(ctx) + ) + val result = + of.createNamedTypeSpecifier().withName(libraryBuilder.dataTypeToQName(resultType)) + + // Fluent API would be nice here, but resultType isn't part of the model so... + result.resultType = resultType + return result + } + + private fun isUnfilteredContext(contextName: String?): Boolean { + return contextName == "Unfiltered" || + libraryBuilder.isCompatibilityLevel3 && contextName == "Population" + } + + override fun visitContextDefinition(ctx: ContextDefinitionContext): Any { + val modelIdentifier: String? = parseString(ctx.modelIdentifier()) + val unqualifiedIdentifier = parseString(ctx.identifier())!! + currentContext = + if (modelIdentifier != null) "$modelIdentifier.$unqualifiedIdentifier" + else (unqualifiedIdentifier) + if (!isUnfilteredContext(unqualifiedIdentifier)) { + val modelContext: ModelContext? = + libraryBuilder.resolveContextName(modelIdentifier, unqualifiedIdentifier) + + // If this is the first time a context definition is encountered, construct a + // context definition: + // define = element of [] + var modelContextDefinition: Element? = contextDefinitions[modelContext!!.name] + if (modelContextDefinition == null) { + if (libraryBuilder.hasUsings()) { + val modelInfo: ModelInfo = + if (modelIdentifier == null) + libraryBuilder.getModel(libraryInfo.defaultModelName).modelInfo + else libraryBuilder.getModel(modelIdentifier).modelInfo + // String contextTypeName = modelContext.getName(); + // DataType contextType = libraryBuilder.resolveTypeName(modelInfo.getName(), + // contextTypeName); + val contextType: DataType = modelContext.type + modelContextDefinition = libraryBuilder.resolveParameterRef(modelContext.name) + if (modelContextDefinition != null) { + contextDefinitions[modelContext.name] = modelContextDefinition + } else { + val contextRetrieve: Retrieve = + of.createRetrieve() + .withDataType(libraryBuilder.dataTypeToQName(contextType)) + track(contextRetrieve, ctx) + contextRetrieve.resultType = ListType(contextType) + val contextClassIdentifier: String? = (contextType as ClassType).identifier + if (contextClassIdentifier != null) { + contextRetrieve.templateId = contextClassIdentifier + } + modelContextDefinition = + of.createExpressionDef() + .withName(unqualifiedIdentifier) + .withContext(currentContext) + .withExpression( + of.createSingletonFrom().withOperand(contextRetrieve) + ) + track(modelContextDefinition, ctx) + modelContextDefinition.expression!!.resultType = contextType + modelContextDefinition.resultType = contextType + libraryBuilder.addExpression(modelContextDefinition) + contextDefinitions[modelContext.name] = modelContextDefinition + } + } else { + modelContextDefinition = + of.createExpressionDef() + .withName(unqualifiedIdentifier) + .withContext(currentContext) + .withExpression(of.createNull()) + track(modelContextDefinition, ctx) + modelContextDefinition.expression!!.resultType = + libraryBuilder.resolveTypeName("System", "Any") + modelContextDefinition.resultType = + modelContextDefinition.expression!!.resultType + libraryBuilder.addExpression(modelContextDefinition) + contextDefinitions[modelContext.name] = modelContextDefinition + } + } + } + val contextDef: ContextDef = of.createContextDef().withName(currentContext) + track(contextDef, ctx) + if (libraryBuilder.isCompatibleWith("1.5")) { + libraryBuilder.addContext(contextDef) + } + return currentContext + } + + private fun isImplicitContextExpressionDef(def: ExpressionDef): Boolean { + for (e in contextDefinitions.values) { + if (def === e) { + return true + } + } + return false + } + + private fun removeImplicitContextExpressionDef(def: ExpressionDef) { + for ((key, value) in contextDefinitions) { + if (def === value) { + contextDefinitions.remove(key) + break + } + } + } + + private fun internalVisitExpressionDefinition( + ctx: ExpressionDefinitionContext + ): ExpressionDef? { + val identifier = parseString(ctx.identifier())!! + var def = libraryBuilder.resolveExpressionRef(identifier) + + // First time visiting this expression definition, create a lightweight ExpressionDef to be + // used to output a hiding warning message + // + // If it's the second time around, we'll be able to resolve it, + // and we can assume it's already on the hiding stack. + if (def == null) { + val hollowExpressionDef = + of.createExpressionDef().withName(identifier).withContext(currentContext) + libraryBuilder.pushIdentifier(identifier, hollowExpressionDef, IdentifierScope.GLOBAL) + } + if (def == null || isImplicitContextExpressionDef(def)) { + if (def != null && isImplicitContextExpressionDef(def)) { + libraryBuilder.removeExpression(def) + removeImplicitContextExpressionDef(def) + } + libraryBuilder.pushExpressionContext(currentContext) + try { + libraryBuilder.pushExpressionDefinition(identifier) + try { + def = + of.createExpressionDef() + .withAccessLevel(parseAccessModifier(ctx.accessModifier())) + .withName(identifier) + .withContext(currentContext) + .withExpression(visit(ctx.expression()) as Expression?) + if (def.expression != null) { + def.resultType = def.expression!!.resultType + } + libraryBuilder.addExpression(def) + } finally { + libraryBuilder.popExpressionDefinition() + } + } finally { + libraryBuilder.popExpressionContext() + } + } + return def + } + + override fun visitExpressionDefinition(ctx: ExpressionDefinitionContext): ExpressionDef? { + libraryBuilder.pushIdentifierScope() + return try { + val expressionDef = internalVisitExpressionDefinition(ctx) + if (forwards.isEmpty() || forwards.peek().name != expressionDef!!.name) { + require(!definedExpressionDefinitions.contains(expressionDef!!.name)) { + // ERROR: + "Identifier ${expressionDef.name} is already in use in this library." + } + + // Track defined expression definitions locally, otherwise duplicate expression + // definitions will be missed + // because they are + // overwritten by name when they are encountered by the preprocessor. + definedExpressionDefinitions.add(expressionDef.name!!) + } + expressionDef + } finally { + libraryBuilder.popIdentifierScope() + } + } + + override fun visitStringLiteral(ctx: StringLiteralContext): Literal { + val stringLiteral = libraryBuilder.createLiteral(parseString(ctx.STRING())) + // Literals are never actually pushed to the stack. This just emits a warning if + // the literal is hiding something + libraryBuilder.pushIdentifier(stringLiteral.value!!, stringLiteral) + return stringLiteral + } + + override fun visitSimpleStringLiteral(ctx: SimpleStringLiteralContext): Literal { + return libraryBuilder.createLiteral(parseString(ctx.STRING())) + } + + override fun visitBooleanLiteral(ctx: BooleanLiteralContext): Literal { + return libraryBuilder.createLiteral(java.lang.Boolean.valueOf(ctx.text)) + } + + override fun visitIntervalSelector(ctx: IntervalSelectorContext): Any { + return libraryBuilder.createInterval( + parseExpression(ctx.expression(0)), + ctx.getChild(1)!!.text == "[", + parseExpression(ctx.expression(1)), + ctx.getChild(5)!!.text == "]" + ) + } + + override fun visitTupleElementSelector(ctx: TupleElementSelectorContext): TupleElement { + val result = + of.createTupleElement() + .withName(parseString(ctx.referentialIdentifier())) + .withValue(parseExpression(ctx.expression())) + return result + } + + override fun visitTupleSelector(ctx: TupleSelectorContext): Tuple { + val tuple = of.createTuple() + val elements = mutableListOf() + for (elementContext in ctx.tupleElementSelector()) { + val element = visit(elementContext) as TupleElement + elements.add(TupleTypeElement(element.name!!, element.value!!.resultType!!)) + tuple.element.add(element) + } + tuple.resultType = TupleType(elements) + return tuple + } + + override fun visitInstanceElementSelector( + ctx: InstanceElementSelectorContext + ): InstanceElement { + val name = parseString(ctx.referentialIdentifier()) + val exp = parseExpression(ctx.expression()) + val result = of.createInstanceElement().withName(name).withValue(exp) + return result + } + + override fun visitInstanceSelector(ctx: InstanceSelectorContext): Instance { + val instance: Instance = of.createInstance() + val classTypeSpecifier = visitNamedTypeSpecifier(ctx.namedTypeSpecifier())!! + instance.classType = classTypeSpecifier.name + instance.resultType = classTypeSpecifier.resultType + for (elementContext in ctx.instanceElementSelector()) { + val element = visit(elementContext) as InstanceElement + val resolution: PropertyResolution? = + libraryBuilder.resolveProperty(classTypeSpecifier.resultType, element.name!!) + element.value = libraryBuilder.ensureCompatible(element.value, resolution!!.type) + element.name = resolution.name + require(resolution.targetMap == null) { + "Target Mapping in instance selectors not yet supported" + } + instance.element.add(element) + } + return instance + } + + override fun visitCodeSelector(ctx: CodeSelectorContext): Code { + val code = of.createCode() + code.code = parseString(ctx.STRING()) + code.system = visit(ctx.codesystemIdentifier()) as CodeSystemRef? + if (ctx.displayClause() != null) { + code.display = parseString(ctx.displayClause()!!.STRING()) + } + code.resultType = libraryBuilder.resolveTypeName("System", "Code") + return code + } + + override fun visitConceptSelector(ctx: ConceptSelectorContext): Concept { + val concept = of.createConcept() + if (ctx.displayClause() != null) { + concept.display = parseString(ctx.displayClause()!!.STRING()) + } + for (codeContext in ctx.codeSelector()) { + concept.code.add(visit(codeContext) as Code) + } + concept.resultType = libraryBuilder.resolveTypeName("System", "Concept") + return concept + } + + override fun visitListSelector(ctx: ListSelectorContext): org.hl7.elm.r1.List { + val elementTypeSpecifier = parseTypeSpecifier(ctx.typeSpecifier()) + val list = of.createList() + var listType: ListType? = null + if (elementTypeSpecifier != null) { + val listTypeSpecifier = + of.createListTypeSpecifier().withElementType(elementTypeSpecifier) + track(listTypeSpecifier, ctx.typeSpecifier()!!) + listType = ListType(elementTypeSpecifier.resultType!!) + listTypeSpecifier.resultType = listType + } + var elementType = elementTypeSpecifier?.resultType + var inferredElementType: DataType? = null + var initialInferredElementType: DataType? = null + val elements: MutableList = ArrayList() + for (elementContext in ctx.expression()) { + val element = + parseExpression(elementContext) + ?: @Suppress("TooGenericExceptionThrown") + throw RuntimeException("Element failed to parse") + if (elementType != null) { + libraryBuilder.verifyType(element.resultType!!, elementType) + } else { + if (initialInferredElementType == null) { + initialInferredElementType = element.resultType + inferredElementType = initialInferredElementType + } else { + // Once a list type is inferred as Any, keep it that way + // The only potential exception to this is if the element responsible for the + // inferred type of Any + // is a null + val compatibleType = + libraryBuilder.findCompatibleType(inferredElementType, element.resultType) + inferredElementType = + if ( + compatibleType != null && + (inferredElementType != + libraryBuilder.resolveTypeName("System", "Any") || + initialInferredElementType == + libraryBuilder.resolveTypeName("System", "Any")) + ) { + compatibleType + } else { + libraryBuilder.resolveTypeName("System", "Any") + } + } + } + elements.add(element) + } + if (elementType == null) { + elementType = inferredElementType ?: libraryBuilder.resolveTypeName("System", "Any") + } + for (element in elements) { + if (!elementType!!.isSuperTypeOf(element.resultType!!)) { + val conversion = + libraryBuilder.findConversion( + element.resultType!!, + elementType, + implicit = true, + allowPromotionAndDemotion = false + ) + if (conversion != null) { + list.element.add(libraryBuilder.convertExpression(element, conversion)) + } else { + list.element.add(element) + } + } else { + list.element.add(element) + } + } + if (listType == null) { + listType = ListType(elementType!!) + } + list.resultType = listType + return list + } + + override fun visitTimeLiteral(ctx: TimeLiteralContext): Time { + var input = ctx.text + if (input.startsWith("@")) { + input = input.substring(1) + } + val timePattern = Pattern.compile("T(\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?") + // -1-------2---3-------4---5-------6---7----------- + val matcher = timePattern.matcher(input) + return if (matcher.matches()) { + try { + val result = of.createTime() + val hour = matcher.group(1).toInt() + var minute = -1 + var second = -1 + var millisecond = -1 + require(hour in 0..24) { "Invalid hour in time literal ($hour)." } + result.hour = libraryBuilder.createLiteral(hour) + if (matcher.group(3) != null) { + minute = matcher.group(3).toInt() + require(!((minute < 0) || (minute >= 60) || (hour == 24 && minute > 0))) { + "Invalid minute in time literal ($minute)." + } + result.minute = libraryBuilder.createLiteral(minute) + } + if (matcher.group(5) != null) { + second = matcher.group(5).toInt() + require(!((second < 0) || (second >= 60) || (hour == 24 && second > 0))) { + "Invalid second in time literal ($second)." + } + result.second = libraryBuilder.createLiteral(second) + } + if (matcher.group(7) != null) { + millisecond = matcher.group(7).toInt() + require(hour == 24 && millisecond == 0 || millisecond >= 0) { + "Invalid millisecond in time literal ($millisecond)." + } + result.millisecond = libraryBuilder.createLiteral(millisecond) + } + result.resultType = libraryBuilder.resolveTypeName("System", "Time") + result + } catch (e: RuntimeException) { + throw IllegalArgumentException( + "Invalid time input ($input). Use ISO 8601 time representation (hh:mm:ss.fff).", + e + ) + } + } else { + @Suppress("UseRequire") + throw IllegalArgumentException( + "Invalid time input ($input). Use ISO 8601 time representation (hh:mm:ss.fff)." + ) + } + } + + private fun parseDateTimeLiteral(input: String): Expression { + /* + * DATETIME + * : '@' + * [0-9][0-9][0-9][0-9] // year + * ( + * ( + * '-'[0-9][0-9] // month + * ( + * ( + * '-'[0-9][0-9] // day + * ('T' TIMEFORMAT?)? + * ) + * | 'T' + * )? + * ) + * | 'T' + * )? + * ('Z' | ('+' | '-') [0-9][0-9]':'[0-9][0-9])? // timezone offset + * ; + */ + val dateTimePattern = + Pattern.compile( + "(\\d{4})(((-(\\d{2}))(((-(\\d{2}))((T)((\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?)?)?)|(T))?)|(T))?((Z)|(([+-])(\\d{2})(\\:(\\d{2}))))?" + ) + // 1-------234-5--------678-9--------11--11-------1---1-------1---1-------1---1-----------------2------2----22---22-----2-------2---2----------- + // ----------------------------------01--23-------4---5-------6---7-------8---9-----------------0------1----23---45-----6-------7---8----------- + + /* + * year - group 1 + * month - group 5 + * day - group 9 + * day dateTime indicator - group 11 + * hour - group 13 + * minute - group 15 + * second - group 17 + * millisecond - group 19 + * month dateTime indicator - group 20 + * year dateTime indicator - group 21 + * utc indicator - group 23 + * timezone offset polarity - group 25 + * timezone offset hour - group 26 + * timezone offset minute - group 28 + */ + + /* + * Pattern dateTimePattern = + * Pattern.compile( + * "(\\d{4})(-(\\d{2}))?(-(\\d{2}))?((Z)|(T((\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?)?((Z)|(([+-])(\\d{2})(\\:?(\\d{2}))?))?))?" + * ); + * //1-------2-3---------4-5---------67---8-91-------1---1-------1---1-------1-- + * -1-------------11---12-----2-------2----2--------------- + * //----------------------------------------0-------1---2-------3---4-------5-- + * -6-------------78---90-----1-------2----3--------------- + */ + val matcher = dateTimePattern.matcher(input) + return if (matcher.matches()) { + try { + val calendar = GregorianCalendar.getInstance() as GregorianCalendar + val result = of.createDateTime() + val year = matcher.group(1).toInt() + var month = -1 + var day = -1 + var hour = -1 + var minute = -1 + var second = -1 + var millisecond = -1 + result.year = libraryBuilder.createLiteral(year) + if (matcher.group(5) != null) { + month = matcher.group(5).toInt() + require(month in 0..12) { "Invalid month in date/time literal ($input)." } + result.month = libraryBuilder.createLiteral(month) + } + if (matcher.group(9) != null) { + day = matcher.group(9).toInt() + var maxDay = 31 + when (month) { + 2 -> maxDay = if (calendar.isLeapYear(year)) 29 else 28 + 4, + 6, + 9, + 11 -> maxDay = 30 + } + require(day in 0..maxDay) { "Invalid day in date/time literal ($input)." } + result.day = libraryBuilder.createLiteral(day) + } + if (matcher.group(13) != null) { + hour = matcher.group(13).toInt() + require(hour in 0..24) { "Invalid hour in date/time literal ($input)." } + result.hour = libraryBuilder.createLiteral(hour) + } + if (matcher.group(15) != null) { + minute = matcher.group(15).toInt() + require(minute in 0..60 && !(hour == 24 && minute > 0)) { + "Invalid minute in date/time literal ($input)." + } + result.minute = libraryBuilder.createLiteral(minute) + } + if (matcher.group(17) != null) { + second = matcher.group(17).toInt() + require(second in 0..60 && !(hour == 24 && second > 0)) { + "Invalid second in date/time literal ($input)." + } + result.second = libraryBuilder.createLiteral(second) + } + if (matcher.group(19) != null) { + millisecond = matcher.group(19).toInt() + require(millisecond >= 0 && !(hour == 24 && millisecond > 0)) { + "Invalid millisecond in date/time literal ($input)." + } + result.millisecond = libraryBuilder.createLiteral(millisecond) + } + if (matcher.group(23) != null && (matcher.group(23) == "Z")) { + result.timezoneOffset = libraryBuilder.createLiteral(0.0) + } + if (matcher.group(25) != null) { + val offsetPolarity = if ((matcher.group(25) == "+")) 1 else -1 + if (matcher.group(28) != null) { + val hourOffset = matcher.group(26).toInt() + require(hourOffset in 0..14) { + "Timezone hour offset is out of range in date/time literal ($input)." + } + val minuteOffset = matcher.group(28).toInt() + require(minuteOffset in 0..60 && !(hourOffset == 14 && minuteOffset > 0)) { + "Timezone minute offset is out of range in date/time literal ($input)." + } + result.timezoneOffset = + libraryBuilder.createLiteral( + (hourOffset + (minuteOffset.toDouble() / 60)) * offsetPolarity + ) + } else { + if (matcher.group(26) != null) { + val hourOffset = matcher.group(26).toInt() + require(hourOffset in 0..14) { + "Timezone hour offset is out of range in date/time literal ($input)." + } + result.timezoneOffset = + libraryBuilder.createLiteral( + (hourOffset * offsetPolarity).toDouble() + ) + } + } + } + if ( + (result.hour == null) && + (matcher.group(11) == null) && + (matcher.group(20) == null) && + (matcher.group(21) == null) + ) { + val date = of.createDate() + date.year = result.year + date.month = result.month + date.day = result.day + date.resultType = libraryBuilder.resolveTypeName("System", "Date") + return date + } + result.resultType = libraryBuilder.resolveTypeName("System", "DateTime") + result + } catch (e: RuntimeException) { + throw IllegalArgumentException( + "Invalid date-time input ($input)." + + " Use ISO 8601 date time representation (yyyy-MM-ddThh:mm:ss.fff(Z|(+/-hh:mm)).", + e + ) + } + } else + throw IllegalArgumentException( + "Invalid date-time input ($input)." + + " Use ISO 8601 date time representation (yyyy-MM-ddThh:mm:ss.fff(Z|+/-hh:mm))." + ) + } + + override fun visitDateLiteral(ctx: DateLiteralContext): Any { + var input = ctx.text + if (input.startsWith("@")) { + input = input.substring(1) + } + return parseDateTimeLiteral(input) + } + + override fun visitDateTimeLiteral(ctx: DateTimeLiteralContext): Any { + var input = ctx.text + if (input.startsWith("@")) { + input = input.substring(1) + } + return parseDateTimeLiteral(input) + } + + override fun visitNullLiteral(ctx: NullLiteralContext): Null { + val result = of.createNull() + result.resultType = libraryBuilder.resolveTypeName("System", "Any") + return result + } + + override fun visitNumberLiteral(ctx: NumberLiteralContext): Expression { + return libraryBuilder.createNumberLiteral(ctx.NUMBER().text) + } + + override fun visitSimpleNumberLiteral(ctx: SimpleNumberLiteralContext): Expression { + return libraryBuilder.createNumberLiteral(ctx.NUMBER().text) + } + + override fun visitLongNumberLiteral(ctx: LongNumberLiteralContext): Literal { + var input = ctx.LONGNUMBER().text + if (input.endsWith("L")) { + input = input.substring(0, input.length - 1) + } + return libraryBuilder.createLongNumberLiteral(input) + } + + private fun parseDecimal(value: String): BigDecimal { + return try { + BigDecimal(value) + } catch (@Suppress("SwallowedException") e: Exception) { + throw IllegalArgumentException("Could not parse number literal: $value") + } + } + + override fun visitQuantity(ctx: QuantityContext): Expression { + return if (ctx.unit() != null) { + libraryBuilder.createQuantity( + parseDecimal(ctx.NUMBER().text), + (parseString(ctx.unit()))!! + ) + } else { + libraryBuilder.createNumberLiteral(ctx.NUMBER().text) + } + } + + private fun getQuantity(source: Expression?): Quantity { + if (source is Literal) { + return libraryBuilder.createQuantity(parseDecimal(source.value!!), "1") + } else if (source is Quantity) { + return source + } + throw IllegalArgumentException("Could not create quantity from source expression.") + } + + override fun visitRatio(ctx: RatioContext): Ratio { + val numerator = getQuantity(visit(ctx.quantity(0)!!) as Expression?) + val denominator = getQuantity(visit(ctx.quantity(1)!!) as Expression?) + return libraryBuilder.createRatio(numerator, denominator) + } + + override fun visitNotExpression(ctx: NotExpressionContext): Not { + val result = of.createNot().withOperand(parseExpression(ctx.expression())) + libraryBuilder.resolveUnaryCall("System", "Not", result) + return result + } + + override fun visitExistenceExpression(ctx: ExistenceExpressionContext): Exists { + val result = of.createExists().withOperand(parseExpression(ctx.expression())) + libraryBuilder.resolveUnaryCall("System", "Exists", result) + return result + } + + override fun visitMultiplicationExpressionTerm( + ctx: MultiplicationExpressionTermContext + ): BinaryExpression { + val exp: BinaryExpression? + val operatorName: String? + when (ctx.getChild(1)!!.text) { + "*" -> { + exp = of.createMultiply() + operatorName = "Multiply" + } + "/" -> { + exp = of.createDivide() + operatorName = "Divide" + } + "div" -> { + exp = of.createTruncatedDivide() + operatorName = "TruncatedDivide" + } + "mod" -> { + exp = of.createModulo() + operatorName = "Modulo" + } + else -> + throw IllegalArgumentException("Unsupported operator: ${ctx.getChild(1)!!.text}.") + } + exp.withOperand( + parseExpression(ctx.expressionTerm(0))!!, + parseExpression(ctx.expressionTerm(1))!! + ) + libraryBuilder.resolveBinaryCall("System", operatorName, (exp)) + return exp + } + + override fun visitPowerExpressionTerm(ctx: PowerExpressionTermContext): Power { + val power = + of.createPower() + .withOperand( + parseExpression(ctx.expressionTerm(0))!!, + parseExpression(ctx.expressionTerm(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "Power", power) + return power + } + + override fun visitPolarityExpressionTerm(ctx: PolarityExpressionTermContext): Any? { + if (ctx.getChild(0)!!.text == "+") { + return visit(ctx.expressionTerm()) + } + val result = of.createNegate().withOperand(parseExpression(ctx.expressionTerm())) + libraryBuilder.resolveUnaryCall("System", "Negate", result) + return result + } + + override fun visitAdditionExpressionTerm(ctx: AdditionExpressionTermContext): Expression { + var exp: Expression? + val operatorName: String? + when (ctx.getChild(1)!!.text) { + "+" -> { + exp = of.createAdd() + operatorName = "Add" + } + "-" -> { + exp = of.createSubtract() + operatorName = "Subtract" + } + "&" -> { + exp = of.createConcatenate() + operatorName = "Concatenate" + } + else -> + throw IllegalArgumentException("Unsupported operator: ${ctx.getChild(1)!!.text}.") + } + if (exp is BinaryExpression) { + exp.withOperand( + parseExpression(ctx.expressionTerm(0))!!, + parseExpression(ctx.expressionTerm(1))!! + ) + libraryBuilder.resolveBinaryCall("System", operatorName, (exp as BinaryExpression?)!!) + if (exp.resultType === libraryBuilder.resolveTypeName("System", "String")) { + val concatenate: Concatenate = of.createConcatenate() + concatenate.operand.addAll(exp.operand) + concatenate.resultType = exp.resultType + exp = concatenate + } + } else { + val concatenate = exp as Concatenate? + concatenate!!.withOperand( + parseExpression(ctx.expressionTerm(0))!!, + parseExpression(ctx.expressionTerm(1))!! + ) + for (i in concatenate.operand.indices) { + val operand: Expression = concatenate.operand[i] + val empty: Literal = libraryBuilder.createLiteral("") + val params: ArrayList = ArrayList() + params.add(operand) + params.add(empty) + val coalesce = libraryBuilder.resolveFunction("System", "Coalesce", params)!! + concatenate.operand[i] = coalesce + } + libraryBuilder.resolveNaryCall("System", operatorName, concatenate) + } + return exp + } + + override fun visitPredecessorExpressionTerm(ctx: PredecessorExpressionTermContext): Any { + return libraryBuilder.buildPredecessor(parseExpression(ctx.expressionTerm())) + } + + override fun visitSuccessorExpressionTerm(ctx: SuccessorExpressionTermContext): Any { + return libraryBuilder.buildSuccessor(parseExpression(ctx.expressionTerm())) + } + + override fun visitElementExtractorExpressionTerm( + ctx: ElementExtractorExpressionTermContext + ): SingletonFrom { + val result = of.createSingletonFrom().withOperand(parseExpression(ctx.expressionTerm())) + libraryBuilder.resolveUnaryCall("System", "SingletonFrom", result) + return result + } + + override fun visitPointExtractorExpressionTerm( + ctx: PointExtractorExpressionTermContext + ): PointFrom { + val result = of.createPointFrom().withOperand(parseExpression(ctx.expressionTerm())) + libraryBuilder.resolveUnaryCall("System", "PointFrom", result) + return result + } + + override fun visitTypeExtentExpressionTerm(ctx: TypeExtentExpressionTermContext): Any { + val extent = parseString(ctx.getChild(0)) + val targetType = parseTypeSpecifier(ctx.namedTypeSpecifier()) + return when (extent) { + "minimum" -> { + libraryBuilder.buildMinimum(targetType!!.resultType) + } + "maximum" -> { + libraryBuilder.buildMaximum(targetType!!.resultType) + } + else -> throw IllegalArgumentException("Unknown extent: $extent") + } + } + + override fun visitTimeBoundaryExpressionTerm(ctx: TimeBoundaryExpressionTermContext): Any { + val result: UnaryExpression? + val operatorName: String? + if (ctx.getChild(0)!!.text == "start") { + result = of.createStart().withOperand(parseExpression(ctx.expressionTerm())) + operatorName = "Start" + } else { + result = of.createEnd().withOperand(parseExpression(ctx.expressionTerm())) + operatorName = "End" + } + libraryBuilder.resolveUnaryCall("System", operatorName, result) + return result + } + + private fun parseComparableDateTimePrecision(dateTimePrecision: String): DateTimePrecision? { + return parseDateTimePrecision( + dateTimePrecision, + precisionRequired = true, + allowWeeks = false + ) + } + + private fun parseComparableDateTimePrecision( + dateTimePrecision: String?, + precisionRequired: Boolean + ): DateTimePrecision? { + return parseDateTimePrecision(dateTimePrecision, precisionRequired, false) + } + + private fun parseDateTimePrecision( + dateTimePrecision: String?, + precisionRequired: Boolean = true, + allowWeeks: Boolean = true + ): DateTimePrecision? { + if (dateTimePrecision == null) { + require(!precisionRequired) { "dateTimePrecision is null" } + return null + } + return when (dateTimePrecision) { + "a", + "year", + "years" -> DateTimePrecision.YEAR + "mo", + "month", + "months" -> DateTimePrecision.MONTH + "wk", + "week", + "weeks" -> { + require(allowWeeks) { "Week precision cannot be used for comparisons." } + DateTimePrecision.WEEK + } + "d", + "day", + "days" -> DateTimePrecision.DAY + "h", + "hour", + "hours" -> DateTimePrecision.HOUR + "min", + "minute", + "minutes" -> DateTimePrecision.MINUTE + "s", + "second", + "seconds" -> DateTimePrecision.SECOND + "ms", + "millisecond", + "milliseconds" -> DateTimePrecision.MILLISECOND + else -> throw IllegalArgumentException("Unknown precision '$dateTimePrecision'.") + } + } + + override fun visitTimeUnitExpressionTerm(ctx: TimeUnitExpressionTermContext): Any { + val component = ctx.dateTimeComponent().text + val result: UnaryExpression? + val operatorName: String? + when (component) { + "date" -> { + result = of.createDateFrom().withOperand(parseExpression(ctx.expressionTerm())) + operatorName = "DateFrom" + } + "time" -> { + result = of.createTimeFrom().withOperand(parseExpression(ctx.expressionTerm())) + operatorName = "TimeFrom" + } + "timezone" -> { + require(libraryBuilder.isCompatibilityLevel3) { + "Timezone keyword is only valid in 1.3 or lower" + } + result = of.createTimezoneFrom().withOperand(parseExpression(ctx.expressionTerm())) + operatorName = "TimezoneFrom" + } + "timezoneoffset" -> { + result = + of.createTimezoneOffsetFrom().withOperand(parseExpression(ctx.expressionTerm())) + operatorName = "TimezoneOffsetFrom" + } + "year", + "month", + "day", + "hour", + "minute", + "second", + "millisecond" -> { + result = + of.createDateTimeComponentFrom() + .withOperand(parseExpression(ctx.expressionTerm())) + .withPrecision(parseDateTimePrecision(component)) + operatorName = "DateTimeComponentFrom" + } + "week" -> + throw IllegalArgumentException("Date/time values do not have a week component.") + else -> throw IllegalArgumentException("Unknown precision '$component'.") + } + libraryBuilder.resolveUnaryCall("System", operatorName, result) + return result + } + + override fun visitDurationExpressionTerm(ctx: DurationExpressionTermContext): DurationBetween { + // duration in days of X <=> days between start of X and end of X + val operand = parseExpression(ctx.expressionTerm()) + val start = of.createStart().withOperand(operand) + libraryBuilder.resolveUnaryCall("System", "Start", start) + val end = of.createEnd().withOperand(operand) + libraryBuilder.resolveUnaryCall("System", "End", end) + val result = + of.createDurationBetween() + .withPrecision(parseDateTimePrecision(ctx.pluralDateTimePrecision().text)) + .withOperand(start, end) + libraryBuilder.resolveBinaryCall("System", "DurationBetween", result) + return result + } + + override fun visitDifferenceExpressionTerm( + ctx: DifferenceExpressionTermContext + ): DifferenceBetween { + // difference in days of X <=> difference in days between start of X and end of + // X + val operand = parseExpression(ctx.expressionTerm()) + val start = of.createStart().withOperand(operand) + libraryBuilder.resolveUnaryCall("System", "Start", start) + val end = of.createEnd().withOperand(operand) + libraryBuilder.resolveUnaryCall("System", "End", end) + val result = + of.createDifferenceBetween() + .withPrecision(parseDateTimePrecision(ctx.pluralDateTimePrecision().text)) + .withOperand(start, end) + libraryBuilder.resolveBinaryCall("System", "DifferenceBetween", result) + return result + } + + override fun visitBetweenExpression(ctx: BetweenExpressionContext): Expression { + // X properly? between Y and Z + val first = parseExpression(ctx.expression())!! + val second = parseExpression(ctx.expressionTerm(0))!! + val third = parseExpression(ctx.expressionTerm(1)) + val isProper = ctx.getChild(0)!!.text == "properly" + return if (first.resultType is IntervalType) { + val result = + if (isProper) of.createProperIncludedIn() + else + of.createIncludedIn() + .withOperand( + first, + libraryBuilder.createInterval(second, true, third, true) + ) + libraryBuilder.resolveBinaryCall( + "System", + if (isProper) "ProperIncludedIn" else "IncludedIn", + result + ) + result + } else { + val result: BinaryExpression = + of.createAnd() + .withOperand( + (if (isProper) of.createGreater() else of.createGreaterOrEqual()) + .withOperand(first, second), + (if (isProper) of.createLess() else of.createLessOrEqual()).withOperand( + first, + third!! + ) + ) + libraryBuilder.resolveBinaryCall( + "System", + if (isProper) "Greater" else "GreaterOrEqual", + (result.operand[0] as BinaryExpression) + ) + libraryBuilder.resolveBinaryCall( + "System", + if (isProper) "Less" else "LessOrEqual", + (result.operand[1] as BinaryExpression) + ) + libraryBuilder.resolveBinaryCall("System", "And", result) + result + } + } + + override fun visitDurationBetweenExpression(ctx: DurationBetweenExpressionContext): Any { + val result: BinaryExpression = + of.createDurationBetween() + .withPrecision(parseDateTimePrecision(ctx.pluralDateTimePrecision().text)) + .withOperand( + parseExpression(ctx.expressionTerm(0))!!, + parseExpression(ctx.expressionTerm(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "DurationBetween", result) + return result + } + + override fun visitDifferenceBetweenExpression(ctx: DifferenceBetweenExpressionContext): Any { + val result: BinaryExpression = + of.createDifferenceBetween() + .withPrecision(parseDateTimePrecision(ctx.pluralDateTimePrecision().text)) + .withOperand( + parseExpression(ctx.expressionTerm(0))!!, + parseExpression(ctx.expressionTerm(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "DifferenceBetween", result) + return result + } + + override fun visitWidthExpressionTerm(ctx: WidthExpressionTermContext): Any { + val result: UnaryExpression = + of.createWidth().withOperand(parseExpression(ctx.expressionTerm())) + libraryBuilder.resolveUnaryCall("System", "Width", result) + return result + } + + override fun visitParenthesizedTerm(ctx: ParenthesizedTermContext): Expression? { + return parseExpression(ctx.expression()) + } + + override fun visitMembershipExpression(ctx: MembershipExpressionContext): Any { + val operator: String = ctx.getChild(1)!!.text + when (operator) { + "in" -> + if (ctx.dateTimePrecisionSpecifier() != null) { + val inExpression: In = + of.createIn() + .withPrecision( + parseComparableDateTimePrecision( + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + ) + ) + .withOperand( + parseExpression(ctx.expression(0))!!, + parseExpression(ctx.expression(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "In", inExpression) + return inExpression + } else { + val left: Expression? = parseExpression(ctx.expression(0)) + val right: Expression? = parseExpression(ctx.expression(1)) + return libraryBuilder.resolveIn((left)!!, (right)!!) + } + "contains" -> + if (ctx.dateTimePrecisionSpecifier() != null) { + val contains: Contains = + of.createContains() + .withPrecision( + parseComparableDateTimePrecision( + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + ) + ) + .withOperand( + parseExpression(ctx.expression(0))!!, + parseExpression(ctx.expression(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "Contains", contains) + return contains + } else { + val left = parseExpression(ctx.expression(0))!! + val right = parseExpression(ctx.expression(1))!! + if (left is ValueSetRef) { + val inValueSet: InValueSet = + of.createInValueSet() + .withCode(right) + .withValueset(left as ValueSetRef?) + .withValuesetExpression(left) + libraryBuilder.resolveCall( + "System", + "InValueSet", + InValueSetInvocation(inValueSet) + ) + return inValueSet + } + if (left is CodeSystemRef) { + val inCodeSystem: InCodeSystem = + of.createInCodeSystem() + .withCode(right) + .withCodesystem(left as CodeSystemRef?) + .withCodesystemExpression(left) + libraryBuilder.resolveCall( + "System", + "InCodeSystem", + InCodeSystemInvocation(inCodeSystem) + ) + return inCodeSystem + } + val contains: Contains = of.createContains().withOperand(left, right) + libraryBuilder.resolveBinaryCall("System", "Contains", contains) + return contains + } + } + throw IllegalArgumentException("Unknown operator: $operator") + } + + override fun visitAndExpression(ctx: AndExpressionContext): And { + val and = + of.createAnd() + .withOperand( + parseExpression(ctx.expression(0))!!, + parseExpression(ctx.expression(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "And", and) + return and + } + + override fun visitOrExpression(ctx: OrExpressionContext): Expression { + return if (ctx.getChild(1)!!.text == "xor") { + val xor = + of.createXor() + .withOperand( + parseExpression(ctx.expression(0))!!, + parseExpression(ctx.expression(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "Xor", xor) + xor + } else { + val or = + of.createOr() + .withOperand( + parseExpression(ctx.expression(0))!!, + parseExpression(ctx.expression(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "Or", or) + or + } + } + + override fun visitImpliesExpression(ctx: ImpliesExpressionContext): Implies { + val implies = + of.createImplies() + .withOperand( + parseExpression(ctx.expression(0))!!, + parseExpression(ctx.expression(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "Implies", implies) + return implies + } + + override fun visitInFixSetExpression(ctx: InFixSetExpressionContext): Expression { + val operator = ctx.getChild(1)!!.text + val left = parseExpression(ctx.expression(0)) + val right = parseExpression(ctx.expression(1)) + when (operator) { + "|", + "union" -> return libraryBuilder.resolveUnion(left!!, right!!) + "intersect" -> return libraryBuilder.resolveIntersect(left!!, right!!) + "except" -> return libraryBuilder.resolveExcept(left!!, right!!) + } + return of.createNull() + } + + override fun visitEqualityExpression(ctx: EqualityExpressionContext): Expression { + val operator = parseString(ctx.getChild(1)) + return if (operator == "~" || operator == "!~") { + val equivalent = + of.createEquivalent() + .withOperand( + parseExpression(ctx.expression(0))!!, + parseExpression(ctx.expression(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "Equivalent", equivalent) + if ("~" != parseString(ctx.getChild(1))) { + track(equivalent, ctx) + val not = of.createNot().withOperand(equivalent) + libraryBuilder.resolveUnaryCall("System", "Not", not) + return not + } + equivalent + } else { + val equal = + of.createEqual() + .withOperand( + parseExpression(ctx.expression(0))!!, + parseExpression(ctx.expression(1))!! + ) + libraryBuilder.resolveBinaryCall("System", "Equal", equal) + if ("=" != parseString(ctx.getChild(1))) { + track(equal, ctx) + val not = of.createNot().withOperand(equal) + libraryBuilder.resolveUnaryCall("System", "Not", not) + return not + } + equal + } + } + + override fun visitInequalityExpression(ctx: InequalityExpressionContext): BinaryExpression { + val exp: BinaryExpression + val operatorName: String + when (parseString(ctx.getChild(1))) { + "<=" -> { + operatorName = "LessOrEqual" + exp = of.createLessOrEqual() + } + "<" -> { + operatorName = "Less" + exp = of.createLess() + } + ">" -> { + operatorName = "Greater" + exp = of.createGreater() + } + ">=" -> { + operatorName = "GreaterOrEqual" + exp = of.createGreaterOrEqual() + } + else -> throw IllegalArgumentException("Unknown operator: ${ctx.getChild(1)!!.text}") + } + exp.withOperand(parseExpression(ctx.expression(0))!!, parseExpression(ctx.expression(1))!!) + libraryBuilder.resolveBinaryCall("System", operatorName, exp) + return exp + } + + override fun visitQualifiedIdentifier(ctx: QualifiedIdentifierContext): List { + // Return the list of qualified identifiers for resolution by the containing + // element + val identifiers: MutableList = ArrayList() + for (qualifierContext in ctx.qualifier()) { + val qualifier = parseString(qualifierContext) + identifiers.add(qualifier) + } + val identifier = parseString(ctx.identifier()) + identifiers.add(identifier) + return identifiers + } + + override fun visitQualifiedIdentifierExpression( + ctx: QualifiedIdentifierExpressionContext + ): List { + // Return the list of qualified identifiers for resolution by the containing + // element + val identifiers: MutableList = ArrayList() + for (qualifierContext in ctx.qualifierExpression()) { + val qualifier = parseString(qualifierContext) + identifiers.add(qualifier) + } + val identifier = parseString(ctx.referentialIdentifier()) + identifiers.add(identifier) + return identifiers + } + + override fun visitSimplePathReferentialIdentifier( + ctx: SimplePathReferentialIdentifierContext + ): String? { + return visit(ctx.referentialIdentifier()) as String? + } + + override fun visitSimplePathQualifiedIdentifier( + ctx: SimplePathQualifiedIdentifierContext + ): String { + return visit(ctx.simplePath()).toString() + "." + visit(ctx.referentialIdentifier()) + } + + override fun visitSimplePathIndexer(ctx: SimplePathIndexerContext): String { + return visit(ctx.simplePath()).toString() + "[" + visit(ctx.simpleLiteral()) + "]" + } + + override fun visitTermExpression(ctx: TermExpressionContext): Any? { + val result = super.visitTermExpression(ctx) + require(result !is LibraryRef) { + "Identifier ${(result as LibraryRef).libraryName} is a library and cannot be used as an expression." + } + return result + } + + override fun visitTerminal(node: TerminalNode): Any? { + var text = node.text + val tokenType = node.symbol.type + if (Token.EOF == tokenType) { + return null + } + if ( + cqlLexer.Tokens.STRING == tokenType || + cqlLexer.Tokens.QUOTEDIDENTIFIER == tokenType || + cqlLexer.Tokens.DELIMITEDIDENTIFIER == tokenType + ) { + // chop off leading and trailing ', ", or ` + text = text.substring(1, text.length - 1) + + // This is an alternate style of escaping that was removed when we switched to + // industry-standard escape + // sequences + // if (cqlLexer.STRING == tokenType) { + // text = text.replace("''", "'"); + // } + // else { + // text = text.replace("\"\"", "\""); + // } + } + return text + } + + override fun visitConversionExpressionTerm(ctx: ConversionExpressionTermContext): Any? { + if (ctx.typeSpecifier() != null) { + val targetType: TypeSpecifier? = parseTypeSpecifier(ctx.typeSpecifier()) + val operand: Expression? = parseExpression(ctx.expression()) + if (!equal(operand!!.resultType, targetType!!.resultType)) { + val conversion: Conversion = + libraryBuilder.findConversion( + operand.resultType!!, + targetType.resultType!!, + implicit = false, + allowPromotionAndDemotion = true + ) + ?: // ERROR: + throw IllegalArgumentException( + "Could not resolve conversion from type ${operand.resultType} to type ${targetType.resultType}." + ) + return libraryBuilder.convertExpression((operand), conversion) + } + return operand + } else { + var targetUnit: String? = parseString(ctx.unit()) + targetUnit = libraryBuilder.ensureUcumUnit((targetUnit)!!) + val operand = parseExpression(ctx.expression())!! + val unitOperand: Expression = libraryBuilder.createLiteral(targetUnit) + track(unitOperand, ctx.unit()!!) + val convertQuantity: ConvertQuantity = + of.createConvertQuantity().withOperand(operand, unitOperand) + track(convertQuantity, ctx) + return libraryBuilder.resolveBinaryCall("System", "ConvertQuantity", convertQuantity) + } + } + + override fun visitTypeExpression(ctx: TypeExpressionContext): Expression { + // NOTE: These don't use the buildIs or buildAs because those start with a + // DataType, rather than a TypeSpecifier + if (ctx.getChild(1)!!.text == "is") { + val isExpression = + of.createIs() + .withOperand(parseExpression(ctx.expression())) + .withIsTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())) + isExpression.resultType = libraryBuilder.resolveTypeName("System", "Boolean") + return isExpression + } + val asExpression = + of.createAs() + .withOperand(parseExpression(ctx.expression())) + .withAsTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())) + .withStrict(false) + val targetType = asExpression.asTypeSpecifier!!.resultType + verifyCast(targetType, asExpression.operand!!.resultType) + asExpression.resultType = targetType + return asExpression + } + + override fun visitCastExpression(ctx: CastExpressionContext): As { + // NOTE: This doesn't use buildAs because it starts with a DataType, rather than + // a TypeSpecifier + val asExpression = + of.createAs() + .withOperand(parseExpression(ctx.expression())) + .withAsTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())) + .withStrict(true) + val targetType = asExpression.asTypeSpecifier!!.resultType + verifyCast(targetType, asExpression.operand!!.resultType) + asExpression.resultType = targetType + return asExpression + } + + override fun visitBooleanExpression(ctx: BooleanExpressionContext): Expression { + var exp: UnaryExpression? + val left = visit(ctx.expression()) as Expression? + val lastChild = ctx.getChild(ctx.childCount - 1)!!.text + val nextToLast = ctx.getChild(ctx.childCount - 2)!!.text + when (lastChild) { + "null" -> { + exp = of.createIsNull().withOperand(left) + libraryBuilder.resolveUnaryCall("System", "IsNull", exp) + } + "true" -> { + exp = of.createIsTrue().withOperand(left) + libraryBuilder.resolveUnaryCall("System", "IsTrue", exp) + } + "false" -> { + exp = of.createIsFalse().withOperand(left) + libraryBuilder.resolveUnaryCall("System", "IsFalse", exp) + } + else -> throw IllegalArgumentException("Unknown boolean test predicate $lastChild.") + } + if ("not" == nextToLast) { + track(exp, ctx) + exp = of.createNot().withOperand(exp) + libraryBuilder.resolveUnaryCall("System", "Not", exp) + } + + return exp + } + + override fun visitTimingExpression(ctx: TimingExpressionContext): Any? { + val left = parseExpression(ctx.expression(0)) + val right = parseExpression(ctx.expression(1)) + Objects.requireNonNull(left, "left expression of timing operator can not be null") + Objects.requireNonNull(right, "right expression of timing operator can not be null") + val timingOperatorContext = TimingOperatorContext(left!!, right!!) + timingOperators.push(timingOperatorContext) + return try { + visit(ctx.intervalOperatorPhrase()) + } finally { + timingOperators.pop() + } + } + + override fun visitConcurrentWithIntervalOperatorPhrase( + ctx: ConcurrentWithIntervalOperatorPhraseContext + ): BinaryExpression { + // ('starts' | 'ends' | 'occurs')? 'same' dateTimePrecision? (relativeQualifier + // | 'as') ('start' | 'end')? + val timingOperator: TimingOperatorContext = timingOperators.peek() + val firstChild: ParseTree = ctx.getChild(0)!! + if (("starts" == firstChild.text)) { + val start: Start = of.createStart().withOperand(timingOperator.left) + track(start, firstChild) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.left = start + } + if (("ends" == firstChild.text)) { + val end: End = of.createEnd().withOperand(timingOperator.left) + track(end, firstChild) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.left = end + } + val lastChild: ParseTree = ctx.getChild(ctx.childCount - 1)!! + if (("start" == lastChild.text)) { + val start: Start = of.createStart().withOperand(timingOperator.right) + track(start, lastChild) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.right = start + } + if (("end" == lastChild.text)) { + val end: End = of.createEnd().withOperand(timingOperator.right) + track(end, lastChild) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.right = end + } + val operatorName: String? + var operator: BinaryExpression? + var allowPromotionAndDemotion = false + if (ctx.relativeQualifier() == null) { + operator = + if (ctx.dateTimePrecision() != null) { + of.createSameAs() + .withPrecision( + parseComparableDateTimePrecision(ctx.dateTimePrecision()!!.text) + ) + } else { + of.createSameAs() + } + operatorName = "SameAs" + } else { + when (ctx.relativeQualifier()!!.text) { + "or after" -> { + operator = + if (ctx.dateTimePrecision() != null) { + of.createSameOrAfter() + .withPrecision( + parseComparableDateTimePrecision(ctx.dateTimePrecision()!!.text) + ) + } else { + of.createSameOrAfter() + } + operatorName = "SameOrAfter" + allowPromotionAndDemotion = true + } + "or before" -> { + operator = + if (ctx.dateTimePrecision() != null) { + of.createSameOrBefore() + .withPrecision( + parseComparableDateTimePrecision(ctx.dateTimePrecision()!!.text) + ) + } else { + of.createSameOrBefore() + } + operatorName = "SameOrBefore" + allowPromotionAndDemotion = true + } + else -> + throw IllegalArgumentException( + "Unknown relative qualifier: '${ctx.relativeQualifier()!!.text}'." + ) + } + } + operator = operator.withOperand(timingOperator.left, timingOperator.right) + libraryBuilder.resolveBinaryCall( + "System", + operatorName, + operator, + true, + allowPromotionAndDemotion + ) + return operator + } + + override fun visitIncludesIntervalOperatorPhrase( + ctx: IncludesIntervalOperatorPhraseContext + ): Any? { + // 'properly'? 'includes' dateTimePrecisionSpecifier? ('start' | 'end')? + var isProper = false + var isRightPoint = false + val timingOperator = timingOperators.peek() + for (pt in ctx.children!!) { + if ("properly" == pt.text) { + isProper = true + continue + } + if ("start" == pt.text) { + val start = of.createStart().withOperand(timingOperator.right) + track(start, pt) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.right = start + isRightPoint = true + continue + } + if ("end" == pt.text) { + val end = of.createEnd().withOperand(timingOperator.right) + track(end, pt) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.right = end + isRightPoint = true + continue + } + } + val dateTimePrecision = + if (ctx.dateTimePrecisionSpecifier() != null) + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + else null + + // If the right is not convertible to an interval or list + // if (!isRightPoint && + // !(timingOperator.getRight().getResultType() instanceof IntervalType + // || timingOperator.getRight().getResultType() instanceof ListType)) { + // isRightPoint = true; + // } + if (isRightPoint) { + return if (isProper) { + libraryBuilder.resolveProperContains( + timingOperator.left, + timingOperator.right, + parseComparableDateTimePrecision(dateTimePrecision, false) + ) + } else + libraryBuilder.resolveContains( + timingOperator.left, + timingOperator.right, + parseComparableDateTimePrecision(dateTimePrecision, false) + ) + } + return if (isProper) { + libraryBuilder.resolveProperIncludes( + timingOperator.left, + timingOperator.right, + parseComparableDateTimePrecision(dateTimePrecision, false) + ) + } else + libraryBuilder.resolveIncludes( + timingOperator.left, + timingOperator.right, + parseComparableDateTimePrecision(dateTimePrecision, false) + ) + } + + override fun visitIncludedInIntervalOperatorPhrase( + ctx: IncludedInIntervalOperatorPhraseContext + ): Any? { + // ('starts' | 'ends' | 'occurs')? 'properly'? ('during' | 'included in') + // dateTimePrecisionSpecifier? + var isProper = false + var isLeftPoint = false + val timingOperator = timingOperators.peek() + for (pt in ctx.children!!) { + if ("starts" == pt.text) { + val start = of.createStart().withOperand(timingOperator.left) + track(start, pt) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.left = start + isLeftPoint = true + continue + } + if ("ends" == pt.text) { + val end = of.createEnd().withOperand(timingOperator.left) + track(end, pt) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.left = end + isLeftPoint = true + continue + } + if ("properly" == pt.text) { + isProper = true + continue + } + } + val dateTimePrecision = + if (ctx.dateTimePrecisionSpecifier() != null) + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + else null + + // If the left is not convertible to an interval or list + // if (!isLeftPoint && + // !(timingOperator.getLeft().getResultType() instanceof IntervalType + // || timingOperator.getLeft().getResultType() instanceof ListType)) { + // isLeftPoint = true; + // } + if (isLeftPoint) { + return if (isProper) { + libraryBuilder.resolveProperIn( + timingOperator.left, + timingOperator.right, + parseComparableDateTimePrecision(dateTimePrecision, false) + ) + } else + libraryBuilder.resolveIn( + timingOperator.left, + timingOperator.right, + parseComparableDateTimePrecision(dateTimePrecision, false) + ) + } + return if (isProper) { + libraryBuilder.resolveProperIncludedIn( + timingOperator.left, + timingOperator.right, + parseComparableDateTimePrecision(dateTimePrecision, false) + ) + } else + libraryBuilder.resolveIncludedIn( + timingOperator.left, + timingOperator.right, + parseComparableDateTimePrecision(dateTimePrecision, false) + ) + } + + override fun visitBeforeOrAfterIntervalOperatorPhrase( + ctx: BeforeOrAfterIntervalOperatorPhraseContext + ): Expression { + // ('starts' | 'ends' | 'occurs')? quantityOffset? ('before' | 'after') + // dateTimePrecisionSpecifier? ('start' | + // 'end')? + + // duration before/after + // A starts 3 days before start B + // * start of A same day as start of B - 3 days + // A starts 3 days after start B + // * start of A same day as start of B + 3 days + + // or more/less duration before/after + // A starts 3 days or more before start B + // * start of A <= start of B - 3 days + // A starts 3 days or more after start B + // * start of A >= start of B + 3 days + // A starts 3 days or less before start B + // * start of A in [start of B - 3 days, start of B) and B is not null + // A starts 3 days or less after start B + // * start of A in (start of B, start of B + 3 days] and B is not null + + // less/more than duration before/after + // A starts more than 3 days before start B + // * start of A < start of B - 3 days + // A starts more than 3 days after start B + // * start of A > start of B + 3 days + // A starts less than 3 days before start B + // * start of A in (start of B - 3 days, start of B) + // A starts less than 3 days after start B + // * start of A in (start of B, start of B + 3 days) + val timingOperator = timingOperators.peek() + var isBefore = false + var isInclusive = false + for (child in ctx.children!!) { + if ("starts" == child.text) { + val start = of.createStart().withOperand(timingOperator.left) + track(start, child) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.left = start + continue + } + if ("ends" == child.text) { + val end = of.createEnd().withOperand(timingOperator.left) + track(end, child) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.left = end + continue + } + if ("start" == child.text) { + val start = of.createStart().withOperand(timingOperator.right) + track(start, child) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.right = start + continue + } + if ("end" == child.text) { + val end = of.createEnd().withOperand(timingOperator.right) + track(end, child) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.right = end + continue + } + } + for (child in ctx.temporalRelationship().children!!) { + if ("before" == child.text) { + isBefore = true + continue + } + if ("on or" == child.text || "or on" == child.text) { + isInclusive = true + continue + } + } + val dateTimePrecision = + if (ctx.dateTimePrecisionSpecifier() != null) + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + else null + if (ctx.quantityOffset() == null) { + return if (isInclusive) { + if (isBefore) { + val sameOrBefore = + of.createSameOrBefore() + .withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + sameOrBefore.precision = parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall( + "System", + "SameOrBefore", + sameOrBefore, + mustResolve = true, + allowPromotionAndDemotion = true + ) + sameOrBefore + } else { + val sameOrAfter = + of.createSameOrAfter() + .withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + sameOrAfter.precision = parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall( + "System", + "SameOrAfter", + sameOrAfter, + mustResolve = true, + allowPromotionAndDemotion = true + ) + sameOrAfter + } + } else { + if (isBefore) { + val before = + of.createBefore().withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + before.precision = parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall( + "System", + "Before", + before, + mustResolve = true, + allowPromotionAndDemotion = true + ) + before + } else { + val after = + of.createAfter().withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + after.precision = parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall( + "System", + "After", + after, + mustResolve = true, + allowPromotionAndDemotion = true + ) + after + } + } + } else { + val quantity = visit(ctx.quantityOffset()!!.quantity()!!) as Quantity + if (timingOperator.left.resultType is IntervalType) { + if (isBefore) { + val end = of.createEnd().withOperand(timingOperator.left) + track(end, timingOperator.left) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.left = end + } else { + val start = of.createStart().withOperand(timingOperator.left) + track(start, timingOperator.left) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.left = start + } + } + if (timingOperator.right.resultType is IntervalType) { + if (isBefore) { + val start = of.createStart().withOperand(timingOperator.right) + track(start, timingOperator.right) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.right = start + } else { + val end = of.createEnd().withOperand(timingOperator.right) + track(end, timingOperator.right) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.right = end + } + } + if ( + ctx.quantityOffset()!!.offsetRelativeQualifier() == null && + ctx.quantityOffset()!!.exclusiveRelativeQualifier() == null + ) { + // Use a SameAs + // For a Before, subtract the quantity from the right operand + // For an After, add the quantity to the right operand + if (isBefore) { + val subtract = of.createSubtract().withOperand(timingOperator.right, quantity) + track(subtract, timingOperator.right) + libraryBuilder.resolveBinaryCall("System", "Subtract", subtract) + timingOperator.right = subtract + } else { + val add = of.createAdd().withOperand(timingOperator.right, quantity) + track(add, timingOperator.right) + libraryBuilder.resolveBinaryCall("System", "Add", add) + timingOperator.right = add + } + val sameAs = + of.createSameAs().withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + sameAs.precision = parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall("System", "SameAs", sameAs) + return sameAs + } else { + val isOffsetInclusive = ctx.quantityOffset()!!.offsetRelativeQualifier() != null + val qualifier = + if (ctx.quantityOffset()!!.offsetRelativeQualifier() != null) + ctx.quantityOffset()!!.offsetRelativeQualifier()!!.text + else ctx.quantityOffset()!!.exclusiveRelativeQualifier()!!.text + when (qualifier) { + "more than", + "or more" -> // For More Than/Or More, Use a + // Before/After/SameOrBefore/SameOrAfter + // For a Before, subtract the quantity from the right operand + // For an After, add the quantity to the right operand + return if (isBefore) { + val subtract = + of.createSubtract().withOperand(timingOperator.right, quantity) + track(subtract, timingOperator.right) + libraryBuilder.resolveBinaryCall("System", "Subtract", subtract) + timingOperator.right = subtract + if (!isOffsetInclusive) { + val before = + of.createBefore() + .withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + before.precision = + parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall( + "System", + "Before", + before, + mustResolve = true, + allowPromotionAndDemotion = true + ) + before + } else { + val sameOrBefore = + of.createSameOrBefore() + .withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + sameOrBefore.precision = + parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall( + "System", + "SameOrBefore", + sameOrBefore, + mustResolve = true, + allowPromotionAndDemotion = true + ) + sameOrBefore + } + } else { + val add = of.createAdd().withOperand(timingOperator.right, quantity) + track(add, timingOperator.right) + libraryBuilder.resolveBinaryCall("System", "Add", add) + timingOperator.right = add + if (!isOffsetInclusive) { + val after = + of.createAfter() + .withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + after.precision = + parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall( + "System", + "After", + after, + mustResolve = true, + allowPromotionAndDemotion = true + ) + after + } else { + val sameOrAfter = + of.createSameOrAfter() + .withOperand(timingOperator.left, timingOperator.right) + if (dateTimePrecision != null) { + sameOrAfter.precision = + parseComparableDateTimePrecision(dateTimePrecision) + } + libraryBuilder.resolveBinaryCall( + "System", + "SameOrAfter", + sameOrAfter, + mustResolve = true, + allowPromotionAndDemotion = true + ) + sameOrAfter + } + } + "less than", + "or less" -> { + // For Less Than/Or Less, Use an In + // For Before, construct an interval from right - quantity to right + // For After, construct an interval from right to right + quantity + val lowerBound: Expression? + val upperBound: Expression? + val right = timingOperator.right + if (isBefore) { + lowerBound = of.createSubtract().withOperand(right, quantity) + track(lowerBound, right) + libraryBuilder.resolveBinaryCall( + "System", + "Subtract", + (lowerBound as BinaryExpression?)!! + ) + upperBound = right + } else { + lowerBound = right + upperBound = of.createAdd().withOperand(right, quantity) + track(upperBound, right) + libraryBuilder.resolveBinaryCall( + "System", + "Add", + (upperBound as BinaryExpression?)!! + ) + } + + // 3 days or less before -> [B - 3 days, B) + // less than 3 days before -> (B - 3 days, B) + // 3 days or less after -> (B, B + 3 days] + // less than 3 days after -> (B, B + 3 days) + val interval = + if (isBefore) + libraryBuilder.createInterval( + lowerBound, + isOffsetInclusive, + upperBound, + isInclusive + ) + else + libraryBuilder.createInterval( + lowerBound, + isInclusive, + upperBound, + isOffsetInclusive + ) + track(interval, ctx.quantityOffset()!!) + val inExpression = of.createIn().withOperand(timingOperator.left, interval) + if (dateTimePrecision != null) { + inExpression.precision = + parseComparableDateTimePrecision(dateTimePrecision) + } + track(inExpression, ctx.quantityOffset()!!) + libraryBuilder.resolveBinaryCall("System", "In", inExpression) + + // if the offset or comparison is inclusive, add a null check for B to + // ensure + // correct + // interpretation + if (isOffsetInclusive || isInclusive) { + val nullTest = of.createIsNull().withOperand(right) + track(nullTest, ctx.quantityOffset()!!) + libraryBuilder.resolveUnaryCall("System", "IsNull", nullTest) + val notNullTest = of.createNot().withOperand(nullTest) + track(notNullTest, ctx.quantityOffset()!!) + libraryBuilder.resolveUnaryCall("System", "Not", notNullTest) + val and = of.createAnd().withOperand(inExpression, notNullTest) + track(and, ctx.quantityOffset()!!) + libraryBuilder.resolveBinaryCall("System", "And", and) + return and + } + + // Otherwise, return the constructed in + return inExpression + } + } + } + } + throw IllegalArgumentException("Unable to resolve interval operator phrase.") + } + + override fun visitWithinIntervalOperatorPhrase( + ctx: WithinIntervalOperatorPhraseContext + ): Expression { + // ('starts' | 'ends' | 'occurs')? 'properly'? 'within' quantityLiteral 'of' + // ('start' | 'end')? + // A starts within 3 days of start B + // * start of A in [start of B - 3 days, start of B + 3 days] and start B is not + // null + // A starts within 3 days of B + // * start of A in [start of B - 3 days, end of B + 3 days] + val timingOperator = timingOperators.peek() + var isProper = false + for (child in ctx.children!!) { + if ("starts" == child.text) { + val start = of.createStart().withOperand(timingOperator.left) + track(start, child) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.left = start + continue + } + if ("ends" == child.text) { + val end = of.createEnd().withOperand(timingOperator.left) + track(end, child) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.left = end + continue + } + if ("start" == child.text) { + val start = of.createStart().withOperand(timingOperator.right) + track(start, child) + libraryBuilder.resolveUnaryCall("System", "Start", start) + timingOperator.right = start + continue + } + if ("end" == child.text) { + val end = of.createEnd().withOperand(timingOperator.right) + track(end, child) + libraryBuilder.resolveUnaryCall("System", "End", end) + timingOperator.right = end + continue + } + if ("properly" == child.text) { + isProper = true + continue + } + } + val quantity = visit(ctx.quantity()) as Quantity + var lowerBound: Expression? + var upperBound: Expression? + var initialBound: Expression? = null + if (timingOperator.right.resultType is IntervalType) { + lowerBound = of.createStart().withOperand(timingOperator.right) + track(lowerBound, ctx.quantity()) + libraryBuilder.resolveUnaryCall("System", "Start", lowerBound) + upperBound = of.createEnd().withOperand(timingOperator.right) + track(upperBound, ctx.quantity()) + libraryBuilder.resolveUnaryCall("System", "End", upperBound) + } else { + lowerBound = timingOperator.right + upperBound = timingOperator.right + initialBound = lowerBound + } + lowerBound = of.createSubtract().withOperand(lowerBound, quantity) + track(lowerBound, ctx.quantity()) + libraryBuilder.resolveBinaryCall("System", "Subtract", (lowerBound as BinaryExpression?)!!) + upperBound = of.createAdd().withOperand(upperBound, quantity) + track(upperBound, ctx.quantity()) + libraryBuilder.resolveBinaryCall("System", "Add", (upperBound as BinaryExpression?)!!) + val interval = libraryBuilder.createInterval(lowerBound, !isProper, upperBound, !isProper) + track(interval, ctx.quantity()) + val inExpression = of.createIn().withOperand(timingOperator.left, interval) + libraryBuilder.resolveBinaryCall("System", "In", inExpression) + + // if the within is not proper and the interval is being constructed from a + // single point, add a null check for + // that point to ensure correct interpretation + if (!isProper && initialBound != null) { + val nullTest = of.createIsNull().withOperand(initialBound) + track(nullTest, ctx.quantity()) + libraryBuilder.resolveUnaryCall("System", "IsNull", nullTest) + val notNullTest = of.createNot().withOperand(nullTest) + track(notNullTest, ctx.quantity()) + libraryBuilder.resolveUnaryCall("System", "Not", notNullTest) + val and = of.createAnd().withOperand(inExpression, notNullTest) + track(and, ctx.quantity()) + libraryBuilder.resolveBinaryCall("System", "And", and) + return and + } + + // Otherwise, return the constructed in + return inExpression + } + + override fun visitMeetsIntervalOperatorPhrase(ctx: MeetsIntervalOperatorPhraseContext): Any { + val operatorName: String? + val operator: BinaryExpression + val dateTimePrecision = + if (ctx.dateTimePrecisionSpecifier() != null) + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + else null + if (ctx.childCount == 1 + if (dateTimePrecision == null) 0 else 1) { + operator = + if (dateTimePrecision != null) + of.createMeets() + .withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) + else of.createMeets() + operatorName = "Meets" + } else { + if ("before" == ctx.getChild(1)!!.text) { + operator = + if (dateTimePrecision != null) + of.createMeetsBefore() + .withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) + else of.createMeetsBefore() + operatorName = "MeetsBefore" + } else { + operator = + if (dateTimePrecision != null) + of.createMeetsAfter() + .withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) + else of.createMeetsAfter() + operatorName = "MeetsAfter" + } + } + operator.withOperand(timingOperators.peek().left, timingOperators.peek().right) + libraryBuilder.resolveBinaryCall("System", operatorName, operator) + return operator + } + + override fun visitOverlapsIntervalOperatorPhrase( + ctx: OverlapsIntervalOperatorPhraseContext + ): Any { + val operatorName: String? + val operator: BinaryExpression + val dateTimePrecision = + if (ctx.dateTimePrecisionSpecifier() != null) + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + else null + if (ctx.childCount == 1 + if (dateTimePrecision == null) 0 else 1) { + operator = + if (dateTimePrecision != null) + of.createOverlaps() + .withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) + else of.createOverlaps() + operatorName = "Overlaps" + } else { + if ("before" == ctx.getChild(1)!!.text) { + operator = + if (dateTimePrecision != null) + of.createOverlapsBefore() + .withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) + else of.createOverlapsBefore() + operatorName = "OverlapsBefore" + } else { + operator = + if (dateTimePrecision != null) + of.createOverlapsAfter() + .withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) + else of.createOverlapsAfter() + operatorName = "OverlapsAfter" + } + } + operator.withOperand(timingOperators.peek().left, timingOperators.peek().right) + libraryBuilder.resolveBinaryCall("System", operatorName, operator) + return operator + } + + override fun visitStartsIntervalOperatorPhrase( + ctx: StartsIntervalOperatorPhraseContext + ): Starts { + val dateTimePrecision = + if (ctx.dateTimePrecisionSpecifier() != null) + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + else null + val starts = + (if (dateTimePrecision != null) + of.createStarts() + .withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) + else of.createStarts()) + .withOperand(timingOperators.peek().left, timingOperators.peek().right) + libraryBuilder.resolveBinaryCall("System", "Starts", starts) + return starts + } + + override fun visitEndsIntervalOperatorPhrase(ctx: EndsIntervalOperatorPhraseContext): Ends { + val dateTimePrecision = + if (ctx.dateTimePrecisionSpecifier() != null) + ctx.dateTimePrecisionSpecifier()!!.dateTimePrecision().text + else null + val ends = + (if (dateTimePrecision != null) + of.createEnds() + .withPrecision(parseComparableDateTimePrecision(dateTimePrecision)) + else of.createEnds()) + .withOperand(timingOperators.peek().left, timingOperators.peek().right) + libraryBuilder.resolveBinaryCall("System", "Ends", ends) + return ends + } + + fun resolveIfThenElse(ifObject: If): Expression { + ifObject.condition = + libraryBuilder.ensureCompatible( + ifObject.condition, + libraryBuilder.resolveTypeName("System", "Boolean") + ) + val resultType: DataType? = + libraryBuilder.ensureCompatibleTypes( + ifObject.then!!.resultType, + ifObject.`else`!!.resultType!! + ) + ifObject.resultType = resultType + ifObject.then = libraryBuilder.ensureCompatible(ifObject.then, resultType) + ifObject.`else` = (libraryBuilder.ensureCompatible(ifObject.`else`, resultType)) + return ifObject + } + + override fun visitIfThenElseExpressionTerm(ctx: IfThenElseExpressionTermContext): Any { + val ifObject = + of.createIf() + .withCondition(parseExpression(ctx.expression(0))) + .withThen(parseExpression(ctx.expression(1))) + .withElse(parseExpression(ctx.expression(2))) + return resolveIfThenElse(ifObject) + } + + override fun visitCaseExpressionTerm(ctx: CaseExpressionTermContext): Any { + val result: Case = of.createCase() + var hitElse = false + var resultType: DataType? = null + for (pt: ParseTree in ctx.children!!) { + if (("else" == pt.text)) { + hitElse = true + continue + } + if (pt is ExpressionContext) { + if (hitElse) { + result.`else` = (parseExpression(pt)) + resultType = + libraryBuilder.ensureCompatibleTypes( + resultType, + result.`else`!!.resultType!! + ) + } else { + result.comparand = parseExpression(pt) + } + } + if (pt is CaseExpressionItemContext) { + val caseItem = visit(pt) as CaseItem + if (result.comparand != null) { + libraryBuilder.verifyType( + caseItem.`when`!!.resultType!!, + result.comparand!!.resultType!! + ) + } else { + verifyType( + caseItem.`when`!!.resultType, + libraryBuilder.resolveTypeName("System", "Boolean") + ) + } + resultType = + if (resultType == null) { + caseItem.then!!.resultType + } else { + libraryBuilder.ensureCompatibleTypes( + resultType, + caseItem.then!!.resultType!! + ) + } + result.caseItem.add(caseItem) + } + } + for (caseItem: CaseItem? in result.caseItem) { + if (result.comparand != null) { + caseItem!!.`when` = + (libraryBuilder.ensureCompatible( + caseItem.`when`, + result.comparand!!.resultType + )) + } + caseItem!!.then = libraryBuilder.ensureCompatible(caseItem.then, resultType) + } + result.`else` = (libraryBuilder.ensureCompatible(result.`else`, resultType)) + result.resultType = resultType + return result + } + + override fun visitCaseExpressionItem(ctx: CaseExpressionItemContext): CaseItem { + return of.createCaseItem() + .withWhen(parseExpression(ctx.expression(0))) + .withThen(parseExpression(ctx.expression(1))) + } + + override fun visitAggregateExpressionTerm(ctx: AggregateExpressionTermContext): Expression { + when (ctx.getChild(0)!!.text) { + "distinct" -> { + val distinct = of.createDistinct().withOperand(parseExpression(ctx.expression())) + libraryBuilder.resolveUnaryCall("System", "Distinct", distinct) + return distinct + } + "flatten" -> { + val flatten = of.createFlatten().withOperand(parseExpression(ctx.expression())) + libraryBuilder.resolveUnaryCall("System", "Flatten", flatten) + return flatten + } + } + throw IllegalArgumentException("Unknown aggregate operator ${ctx.getChild(0)!!.text}.") + } + + override fun visitSetAggregateExpressionTerm(ctx: SetAggregateExpressionTermContext): Any { + val source = parseExpression(ctx.expression(0))!! + + // If `per` is not set, it will remain `null as System.Quantity`. + var per: Expression? = + libraryBuilder.buildNull(libraryBuilder.resolveTypeName("System", "Quantity")) + if (ctx.dateTimePrecision() != null) { + per = + libraryBuilder.createQuantity( + BigDecimal.valueOf(1.0), + (parseString(ctx.dateTimePrecision()))!! + ) + } else if (ctx.expression().size > 1) { + per = parseExpression(ctx.expression(1)) + } else { + // Determine per quantity based on point type of the intervals involved + if (source.resultType is ListType) { + val listType: ListType = source.resultType as ListType + if (listType.elementType is IntervalType) { + val intervalType: IntervalType = listType.elementType as IntervalType + val pointType: DataType = intervalType.pointType + + // TODO: Test this... + // // Successor(MinValue) - MinValue + // MinValue minimum = libraryBuilder.buildMinimum(pointType); + // track(minimum, ctx); + // + // Expression successor = libraryBuilder.buildSuccessor(minimum); + // track(successor, ctx); + // + // minimum = libraryBuilder.buildMinimum(pointType); + // track(minimum, ctx); + // + // Subtract subtract = of.createSubtract().withOperand(successor, minimum); + // libraryBuilder.resolveBinaryCall("System", "Subtract", subtract); + // per = subtract; + } + } + } + when (ctx.getChild(0)!!.text) { + "expand" -> { + val expand: Expand = of.createExpand().withOperand(source, per!!) + libraryBuilder.resolveBinaryCall("System", "Expand", expand) + return expand + } + "collapse" -> { + val collapse: Collapse = of.createCollapse().withOperand(source, per!!) + libraryBuilder.resolveBinaryCall("System", "Collapse", collapse) + return collapse + } + } + throw IllegalArgumentException("Unknown aggregate set operator ${ctx.getChild(0)!!.text}.") + } + + override fun visitRetrieve(ctx: RetrieveContext): Expression? { + libraryBuilder.checkLiteralContext() + val qualifiers: List = parseQualifiers(ctx.namedTypeSpecifier()) + val model: String? = getModelIdentifier(qualifiers) + val label: String = + getTypeIdentifier( + qualifiers, + (parseString(ctx.namedTypeSpecifier().referentialOrTypeNameIdentifier()))!! + ) + val dataType = libraryBuilder.resolveTypeName(model, label) + + requireNotNull(dataType) { "Could not resolve type name $label." } + require(dataType is ClassType && dataType.isRetrievable) { + "Specified data type $label does not support retrieval." + } + + val classType: ClassType = dataType + // BTR -> The original intent of this code was to have the retrieve return the + // base type, and use the + // "templateId" + // element of the retrieve to communicate the "positive" or "negative" profile + // to the data access layer. + // However, because this notion of carrying the "profile" through a type is not + // general, it causes + // inconsistencies + // when using retrieve results with functions defined in terms of the same type + // (see GitHub Issue #131). + // Based on the discussion there, the retrieve will now return the declared + // type, whether it is a profile or + // not. + // ProfileType profileType = dataType instanceof ProfileType ? + // (ProfileType)dataType : null; + // NamedType namedType = profileType == null ? classType : + // (NamedType)classType.getBaseType(); + val namedType: NamedType = classType + val modelInfo: ModelInfo = libraryBuilder.getModel(namedType.namespace).modelInfo + val useStrictRetrieveTyping: Boolean = + modelInfo.isStrictRetrieveTyping() != null && modelInfo.isStrictRetrieveTyping()!! + var codePath: String? = null + var property: Property? = null + var propertyException: CqlCompilerException? = null + var terminology: Expression? = null + var codeComparator: String? = null + if (ctx.terminology() != null) { + if (ctx.codePath() != null) { + val identifiers: String? = visit(ctx.codePath()!!) as String? + codePath = identifiers + } else if (classType.primaryCodePath != null) { + codePath = classType.primaryCodePath + } + if (codePath == null) { + // ERROR: + // WARNING: + propertyException = + CqlSemanticException( + "Retrieve has a terminology target but does not specify a code path and the type of the retrieve does not have a primary code path defined.", + if (useStrictRetrieveTyping) CqlCompilerException.ErrorSeverity.Error + else CqlCompilerException.ErrorSeverity.Warning, + getTrackBack(ctx) + ) + libraryBuilder.recordParsingException(propertyException) + } else { + try { + val codeType: DataType? = + libraryBuilder.resolvePath(namedType as DataType, codePath) + property = of.createProperty().withPath(codePath) + property.resultType = codeType + } catch (e: Exception) { + // ERROR: + // WARNING: + propertyException = + CqlSemanticException( + "Could not resolve code path $codePath for the type of the retrieve ${namedType.name}.", + if (useStrictRetrieveTyping) CqlCompilerException.ErrorSeverity.Error + else CqlCompilerException.ErrorSeverity.Warning, + getTrackBack(ctx), + e + ) + libraryBuilder.recordParsingException(propertyException) + } + } + if (ctx.terminology()!!.qualifiedIdentifierExpression() != null) { + val identifiers: List = visit(ctx.terminology()!!).cast() + terminology = resolveQualifiedIdentifier(identifiers) + track(terminology, ctx.terminology()!!.qualifiedIdentifierExpression()!!) + } else { + terminology = parseExpression(ctx.terminology()!!.expression()) + } + codeComparator = + if (ctx.codeComparator() != null) visit(ctx.codeComparator()!!) as String? else null + } + var result: Expression? = null + + // Only expand a choice-valued code path if no comparator is specified + // Otherwise, a code comparator will always choose a specific representation + val hasFHIRHelpers: Boolean = libraryInfo.resolveLibraryName("FHIRHelpers") != null + if ((property != null) && property.resultType is ChoiceType && (codeComparator == null)) { + for (propertyType: DataType? in (property.resultType as ChoiceType).types) { + if ( + (hasFHIRHelpers && + propertyType is NamedType && + ((propertyType as NamedType).simpleName == "Reference") && + (namedType.simpleName == "MedicationRequest" || + namedType.simpleName == "MedicationAdministration" || + namedType.simpleName == "MedicationDispense" || + namedType.simpleName == "MedicationStatement")) + ) { + // TODO: This is a model-specific special case to support QICore + // This functionality needs to be generalized to a retrieve mapping in the model + // info + // But that requires a model info change (to represent references, right now the + // model info only + // includes context relationships) + // The reference expands to [MedicationRequest] MR with [Medication] M such that + // M.id = + // Last(Split(MR.medication.reference, '/')) and M.code in + val mrRetrieve: Retrieve = + buildRetrieve( + ctx, + useStrictRetrieveTyping, + namedType, + classType, + null, + null, + null, + null, + null, + null + ) + retrieves.add(mrRetrieve) + mrRetrieve.resultType = ListType(namedType as DataType) + val mDataType: DataType? = libraryBuilder.resolveTypeName(model, "Medication") + val mClassType = mDataType as ClassType + val mNamedType: NamedType = mClassType + val mRetrieve: Retrieve = + buildRetrieve( + ctx, + useStrictRetrieveTyping, + mNamedType, + mClassType, + null, + null, + null, + null, + null, + null + ) + retrieves.add(mRetrieve) + mRetrieve.resultType = ListType(mDataType) + val q: Query = of.createQuery() + val aqs: AliasedQuerySource = + of.createAliasedQuerySource().withExpression(mrRetrieve).withAlias("MR") + track(aqs, ctx) + aqs.resultType = aqs.expression!!.resultType + q.source.add(aqs) + track(q, ctx) + q.resultType = aqs.resultType + val w: With = of.createWith().withExpression(mRetrieve).withAlias("M") + track(w, ctx) + w.resultType = w.expression!!.resultType + q.relationship.add(w) + val idPath = "id" + val idType: DataType? = libraryBuilder.resolvePath(mDataType, idPath) + val idProperty: Property = + libraryBuilder.buildProperty("M", idPath, false, idType) + val refPath = "medication.reference" + val refType: DataType? = libraryBuilder.resolvePath(dataType, refPath) + val refProperty: Property = + libraryBuilder.buildProperty("MR", refPath, false, refType) + val split: Split = + of.createSplit() + .withStringToSplit(refProperty) + .withSeparator(libraryBuilder.createLiteral("/")) + libraryBuilder.resolveCall("System", "Split", SplitInvocation(split)) + val last: Last = of.createLast().withSource(split) + libraryBuilder.resolveCall("System", "Last", LastInvocation(last)) + val e: Equal = of.createEqual().withOperand(idProperty, last) + libraryBuilder.resolveBinaryCall("System", "Equal", e) + + // Apply target mapping if this is a profile-informed model info + if (equal(idType, libraryBuilder.resolveTypeName("System", "String"))) { + idProperty.path = "id.value" + } + if (equal(refType, libraryBuilder.resolveTypeName("System", "String"))) { + refProperty.path = "medication.reference.value" + } + val mCodeType: DataType? = + libraryBuilder.resolvePath(mNamedType as DataType?, "code") + val mProperty = libraryBuilder.buildProperty("M", "code", false, mCodeType) + var mCodeProperty: Expression = mProperty + + // Apply target mapping if this is a profile-informed model info + if (equal(mCodeType, libraryBuilder.resolveTypeName("System", "Concept"))) { + val toConcept = + of.createFunctionRef() + .withLibraryName("FHIRHelpers") + .withName("ToConcept") + .withOperand(mCodeProperty) + toConcept.resultType = mCodeType + mCodeProperty = toConcept + } + + var mCodeComparator = "~" + if (terminology!!.resultType is ListType) { + mCodeComparator = "in" + } else if (libraryBuilder.isCompatibleWith("1.5")) { + mCodeComparator = + if ( + terminology.resultType!!.isSubTypeOf( + libraryBuilder.resolveTypeName("System", "Vocabulary")!! + ) + ) + "in" + else "~" + } + val terminologyComparison: Expression = + if ((mCodeComparator == "in")) { + libraryBuilder.resolveIn(mCodeProperty, (terminology)) + } else { + val equivalent: BinaryExpression = + of.createEquivalent().withOperand(mCodeProperty, terminology) + libraryBuilder.resolveBinaryCall("System", "Equivalent", equivalent) + equivalent + } + val a: And = of.createAnd().withOperand(e, terminologyComparison) + libraryBuilder.resolveBinaryCall("System", "And", a) + w.withSuchThat(a) + result = + if (result == null) { + q + } else { + track(q, ctx) + libraryBuilder.resolveUnion(result, q) + } + } else { + val retrieve: Retrieve = + buildRetrieve( + ctx, + useStrictRetrieveTyping, + namedType, + classType, + codePath, + codeComparator, + property, + propertyType, + propertyException, + terminology + ) + retrieves.add(retrieve) + retrieve.resultType = ListType(namedType as DataType) + result = + if (result == null) { + retrieve + } else { + // Should only include the result if it resolved appropriately with the + // codeComparator + // Allowing it to go through for now + // if (retrieve.getCodeProperty() != null && + // retrieve.getCodeComparator() != + // null && + // retrieve.getCodes() != null) { + track(retrieve, ctx) + libraryBuilder.resolveUnion(result, retrieve) + // } + } + } + } + } else { + val retrieve: Retrieve = + buildRetrieve( + ctx, + useStrictRetrieveTyping, + namedType, + classType, + codePath, + codeComparator, + property, + property?.resultType, + propertyException, + terminology + ) + retrieves.add(retrieve) + retrieve.resultType = ListType(namedType as DataType) + result = retrieve + } + return result + } + + @Suppress("LongParameterList") + private fun buildRetrieve( + ctx: RetrieveContext, + useStrictRetrieveTyping: Boolean, + namedType: NamedType?, + classType: ClassType, + codePath: String?, + codeComparator: String?, + property: Property?, + propertyType: DataType?, + propertyException: Exception?, + terminology: Expression? + ): Retrieve { + var codeComparator: String? = codeComparator + val retrieve: Retrieve = + of.createRetrieve() + .withDataType(libraryBuilder.dataTypeToQName(namedType as DataType?)) + .withTemplateId(classType.identifier) + .withCodeProperty(codePath) + if (ctx.contextIdentifier() != null) { + val identifiers: List = visit(ctx.contextIdentifier()!!).cast() + val contextExpression: Expression? = resolveQualifiedIdentifier(identifiers) + retrieve.context = contextExpression + } + if (terminology != null) { + // Resolve the terminology target using an in or ~ operator + try { + if (codeComparator == null) { + codeComparator = "~" + if (terminology.resultType is ListType) { + codeComparator = "in" + } else if (libraryBuilder.isCompatibleWith("1.5")) { + codeComparator = + if ( + (propertyType != null && + propertyType.isSubTypeOf( + libraryBuilder.resolveTypeName("System", "Vocabulary")!! + )) + ) { + if ( + terminology.resultType!!.isSubTypeOf( + libraryBuilder.resolveTypeName("System", "Vocabulary")!! + ) + ) + "~" + else "contains" + } else { + if ( + terminology.resultType!!.isSubTypeOf( + libraryBuilder.resolveTypeName("System", "Vocabulary")!! + ) + ) + "in" + else "~" + } + } + } + if (property == null) { + throw (propertyException)!! + } + when (codeComparator) { + "in" -> { + when ( + val inExpression: Expression = + libraryBuilder.resolveIn(property, terminology) + ) { + is In -> { + retrieve.codes = inExpression.operand[1] + } + is InValueSet -> { + retrieve.codes = inExpression.valueset + } + is InCodeSystem -> { + retrieve.codes = inExpression.codesystem + } + is AnyInValueSet -> { + retrieve.codes = inExpression.valueset + } + is AnyInCodeSystem -> { + retrieve.codes = inExpression.codesystem + } + else -> { + // ERROR: + // WARNING: + libraryBuilder.recordParsingException( + CqlSemanticException( + "Unexpected membership operator ${inExpression.javaClass.simpleName} in retrieve", + if (useStrictRetrieveTyping) + CqlCompilerException.ErrorSeverity.Error + else CqlCompilerException.ErrorSeverity.Warning, + getTrackBack(ctx) + ) + ) + } + } + } + "contains" -> { + val contains: Expression = + libraryBuilder.resolveContains(property, terminology) + if (contains is Contains) { + retrieve.codes = contains.operand[1] + } + // TODO: Introduce support for the contains operator to make this possible + // to + // support with a + // retrieve (direct-reference code negation) + // ERROR: + libraryBuilder.recordParsingException( + CqlSemanticException( + "Terminology resolution using contains is not supported at this time. Use a where clause with an in operator instead.", + if (useStrictRetrieveTyping) + CqlCompilerException.ErrorSeverity.Error + else CqlCompilerException.ErrorSeverity.Warning, + getTrackBack(ctx) + ) + ) + } + "~" -> { + + // Resolve with equivalent to verify the type of the target + val equivalent: BinaryExpression = + of.createEquivalent().withOperand(property, terminology) + libraryBuilder.resolveBinaryCall("System", "Equivalent", equivalent) + + // Automatically promote to a list for use in the retrieve target + if ( + !(equivalent.operand[1].resultType is ListType || + (libraryBuilder.isCompatibleWith("1.5") && + equivalent.operand[1] + .resultType!! + .isSubTypeOf( + libraryBuilder.resolveTypeName("System", "Vocabulary")!! + ))) + ) { + retrieve.codes = libraryBuilder.resolveToList(equivalent.operand[1]) + } else { + retrieve.codes = equivalent.operand[1] + } + } + "=" -> { + + // Resolve with equality to verify the type of the source and target + val equal: BinaryExpression = + of.createEqual().withOperand(property, terminology) + libraryBuilder.resolveBinaryCall("System", "Equal", equal) + + // Automatically promote to a list for use in the retrieve target + if ( + !(equal.operand[1].resultType is ListType || + (libraryBuilder.isCompatibleWith("1.5") && + equal.operand[1] + .resultType!! + .isSubTypeOf( + libraryBuilder.resolveTypeName("System", "Vocabulary")!! + ))) + ) { + retrieve.codes = libraryBuilder.resolveToList(equal.operand[1]) + } else { + retrieve.codes = equal.operand[1] + } + } + else -> // ERROR: + // WARNING: + libraryBuilder.recordParsingException( + CqlSemanticException( + "Unknown code comparator $codeComparator in retrieve", + if (useStrictRetrieveTyping) + CqlCompilerException.ErrorSeverity.Error + else CqlCompilerException.ErrorSeverity.Warning, + getTrackBack(ctx.codeComparator()!!) + ) + ) + } + retrieve.codeComparator = codeComparator + + // Verify that the type of the terminology target is a List + // Due to implicit conversion defined by specific models, the resolution path + // above may result in a + // List + // In that case, convert to a list of code (Union the Code elements of the + // Concepts in the list) + if ( + ((retrieve.codes != null) && + (retrieve.codes!!.resultType != null) && + retrieve.codes!!.resultType is ListType && + ((retrieve.codes!!.resultType as ListType).elementType == + libraryBuilder.resolveTypeName("System", "Concept"))) + ) { + if (retrieve.codes is ToList) { + // ToList will always have a single argument + val toList: ToList = retrieve.codes as ToList + // If that argument is a ToConcept, replace the ToList argument with the + // code + // (skip the implicit + // conversion, the data access layer is responsible for it) + if (toList.operand is ToConcept) { + toList.operand = (toList.operand as ToConcept).operand + } else { + // Otherwise, access the codes property of the resulting Concept + val codesAccessor: Expression = + libraryBuilder.buildProperty( + toList.operand, + "codes", + false, + toList.operand!!.resultType + ) + retrieve.codes = codesAccessor + } + } else { + // WARNING: + libraryBuilder.recordParsingException( + CqlSemanticException( + "Terminology target is a list of concepts, but expects a list of codes", + CqlCompilerException.ErrorSeverity.Warning, + getTrackBack(ctx) + ) + ) + } + } + } catch (e: Exception) { + // If something goes wrong attempting to resolve, just set to the expression and + // report it as a warning, + // it shouldn't prevent translation unless the modelinfo indicates strict + // retrieve typing + if ( + ((libraryBuilder.isCompatibleWith("1.5") && + !(terminology.resultType!!.isSubTypeOf( + libraryBuilder.resolveTypeName("System", "Vocabulary")!! + ))) || + (!libraryBuilder.isCompatibleWith("1.5") && + terminology.resultType !is ListType)) + ) { + retrieve.codes = libraryBuilder.resolveToList(terminology) + } else { + retrieve.codes = terminology + } + retrieve.codeComparator = codeComparator + // ERROR: + // WARNING: + libraryBuilder.recordParsingException( + CqlSemanticException( + "Could not resolve membership operator for terminology target of the retrieve.", + if (useStrictRetrieveTyping) CqlCompilerException.ErrorSeverity.Error + else CqlCompilerException.ErrorSeverity.Warning, + getTrackBack(ctx), + e + ) + ) + } + } + return retrieve + } + + override fun visitSourceClause(ctx: SourceClauseContext): Any { + val hasFrom = "from" == ctx.getChild(0)!!.text + require(!(!hasFrom && isFromKeywordRequired)) { + "The from keyword is required for queries." + } + val sources: MutableList = ArrayList() + for (source in ctx.aliasedQuerySource()) { + require(!(sources.isNotEmpty() && !hasFrom)) { + "The from keyword is required for multi-source queries." + } + sources.add(visit(source) as AliasedQuerySource?) + } + return sources + } + + override fun visitQuery(ctx: cqlParser.QueryContext): Query { + val queryContext = QueryContext() + libraryBuilder.pushQueryContext(queryContext) + var sources: List? = null + return try { + queryContext.enterSourceClause() + try { + sources = visit(ctx.sourceClause()).cast() + } finally { + queryContext.exitSourceClause() + } + queryContext.addPrimaryQuerySources(sources!!) + for (source: AliasedQuerySource in sources) { + libraryBuilder.pushIdentifier(source.alias!!, source) + } + + // If we are evaluating a population-level query whose source ranges over any + // patient-context expressions, + // then references to patient context expressions within the iteration clauses + // of the query can be accessed + // at the patient, rather than the population, context. + val expressionContextPushed = false + /* + * TODO: Address the issue of referencing multiple context expressions within a + * query (or even expression in general) + * if (libraryBuilder.inUnfilteredContext() && + * queryContext.referencesSpecificContext()) { + * libraryBuilder.pushExpressionContext("Patient"); + * expressionContextPushed = true; + * } + */ + var dfcx: List = emptyList() + try { + dfcx = if (ctx.letClause() != null) visit(ctx.letClause()!!).cast() else dfcx + for (letClause: LetClause in dfcx) { + libraryBuilder.pushIdentifier(letClause.identifier!!, letClause) + } + val qicx: MutableList = ArrayList() + for (queryInclusionClauseContext in ctx.queryInclusionClause()) { + qicx.add(visit(queryInclusionClauseContext) as RelationshipClause) + } + var where = + if (ctx.whereClause() != null) visit(ctx.whereClause()!!) as Expression? + else null + if (dateRangeOptimization && where != null) { + for (aqs: AliasedQuerySource in sources) { + where = optimizeDateRangeInQuery(where, aqs) + } + } + var ret = + if (ctx.returnClause() != null) visit(ctx.returnClause()!!) as ReturnClause? + else null + val agg = + if (ctx.aggregateClause() != null) + visit(ctx.aggregateClause()!!) as AggregateClause? + else null + if (agg == null && ret == null && sources.size > 1) { + ret = of.createReturnClause().withDistinct(true) + val returnExpression = of.createTuple() + val elements = mutableListOf() + for (aqs: AliasedQuerySource in sources) { + val element = + of.createTupleElement() + .withName(aqs.alias) + .withValue(of.createAliasRef().withName(aqs.alias)) + val sourceType = + if (aqs.resultType is ListType) (aqs.resultType as ListType).elementType + else aqs.resultType + element.value!!.resultType = + sourceType // Doesn't use the fluent API to avoid casting + elements.add(TupleTypeElement(element.name!!, element.value!!.resultType!!)) + returnExpression.element.add(element) + } + val returnType = TupleType(elements) + returnExpression.resultType = + if (queryContext.isSingular) returnType else ListType(returnType) + ret.expression = returnExpression + ret.resultType = returnExpression.resultType + } + queryContext.removeQuerySources(sources) + queryContext.removeLetClauses(dfcx) + val queryResultType: DataType? = + if (agg != null) { + agg.resultType + } else if (ret != null) { + ret.resultType + } else { + sources[0].resultType + } + var sort: SortClause? = null + if (agg == null) { + queryContext.resultElementType = + if (queryContext.isSingular) null + else (queryResultType as ListType?)!!.elementType + if (ctx.sortClause() != null) { + require(!queryContext.isSingular) { + "Sort clause cannot be used in a singular query." + } + queryContext.enterSortClause() + try { + sort = visit(ctx.sortClause()!!) as SortClause + // Validate that the sort can be performed based on the existence of + // comparison + // operators + // for all types involved + for (sortByItem: SortByItem? in sort.by) { + if (sortByItem is ByDirection) { + // validate that there is a comparison operator defined for the + // result element + // type + // of the query context + libraryBuilder.verifyComparable( + queryContext.resultElementType!! + ) + } else { + libraryBuilder.verifyComparable(sortByItem!!.resultType!!) + } + } + } finally { + queryContext.exitSortClause() + } + } + } else { + require(ctx.sortClause() == null) { + "Sort clause cannot be used in an aggregate query." + } + } + val query = + of.createQuery() + .withSource(sources) + .withLet(dfcx) + .withRelationship(qicx) + .withWhere(where) + .withReturn(ret) + .withAggregate(agg) + .withSort(sort) + query.resultType = queryResultType + query + } finally { + if (expressionContextPushed) { + libraryBuilder.popExpressionContext() + } + for (letClause in dfcx) { + libraryBuilder.popIdentifier() + } + } + } finally { + libraryBuilder.popQueryContext() + if (sources != null) { + for (source in sources) { + libraryBuilder.popIdentifier() + } + } + } + } + // TODO: Expand this optimization to work the DateLow/DateHigh property + // attributes + /** + * Some systems may wish to optimize performance by restricting retrieves with available date + * ranges. Specifying date ranges in a retrieve was removed from the CQL grammar, but it is + * still possible to extract date ranges from the where clause and put them in the Retrieve in + * ELM. The `optimizeDateRangeInQuery` method attempts to do this automatically. If optimization + * is possible, it will remove the corresponding "during" from the where clause and insert the + * date range into the Retrieve. + * + * @param aqs the AliasedQuerySource containing the ClinicalRequest to possibly refactor a date + * range into. + * @param where the Where clause to search for potential date range optimizations + * @return the where clause with optimized "durings" removed, or `null` if there is no longer a + * Where clause after optimization. + */ + fun optimizeDateRangeInQuery(where: Expression?, aqs: AliasedQuerySource): Expression? { + var where = where + if (aqs.expression is Retrieve) { + val retrieve = aqs.expression as Retrieve + val alias = aqs.alias + if ( + (where is IncludedIn || where is In) && + attemptDateRangeOptimization(where as BinaryExpression, retrieve, alias!!) + ) { + where = null + } else if (where is And && attemptDateRangeOptimization(where, retrieve, alias!!)) { + // Now optimize out the trues from the Ands + where = consolidateAnd(where) + } + } + return where + } + + /** + * Test a `BinaryExpression` expression and determine if it is suitable to be refactored into + * the `Retrieve` as a date range restriction. If so, adjust the `Retrieve` accordingly and + * return `true`. + * + * @param during the `BinaryExpression` expression to potentially refactor into the `Retrieve` + * @param retrieve the `Retrieve` to add qualifying date ranges to (if applicable) + * @param alias the alias of the `Retrieve` in the query. + * @return `true` if the date range was set in the `Retrieve`; `false` otherwise. + */ + private fun attemptDateRangeOptimization( + during: BinaryExpression, + retrieve: Retrieve, + alias: String + ): Boolean { + if (retrieve.dateProperty != null || retrieve.dateRange != null) { + return false + } + val left = during.operand[0] + val right = during.operand[1] + val propertyPath = getPropertyPath(left, alias) + if (propertyPath != null && isRHSEligibleForDateRangeOptimization(right)) { + retrieve.dateProperty = propertyPath + retrieve.dateRange = right + return true + } + return false + } + + /** + * Collapse a property path expression back to it's qualified form for use as the path attribute + * of the retrieve. + * + * @param reference the `Expression` to collapse + * @param alias the alias of the `Retrieve` in the query. + * @return The collapsed path operands (or sub-operands) were modified; `false` otherwise. + */ + private fun getPropertyPath(reference: Expression, alias: String): String? { + var ref = reference + ref = getConversionReference(ref) + ref = getChoiceSelection(ref) + if (ref is Property) { + val property = ref + if (alias == property.scope) { + return property.path + } else if (property.source != null) { + val subPath = getPropertyPath(property.source!!, alias) + if (subPath != null) { + return "$subPath.${property.path}" + } + } + } + return null + } + + /** + * If this is a conversion operator, return the argument of the conversion, on the grounds that + * the date range optimization should apply through a conversion (i.e. it is an order-preserving + * conversion) + * + * @param reference the `Expression` to examine + * @return The argument to the conversion operator if there was one, otherwise, the given + * `reference` + */ + private fun getConversionReference(reference: Expression): Expression { + if (reference is FunctionRef) { + val functionRef: FunctionRef = reference + if ( + (functionRef.operand.size == 1) && + (functionRef.resultType != null) && + (functionRef.operand[0].resultType != null) + ) { + val o: Operator? = + libraryBuilder.conversionMap.getConversionOperator( + functionRef.operand[0].resultType!!, + functionRef.resultType!! + ) + if ( + ((o != null) && + (o.libraryName != null) && + (o.libraryName == functionRef.libraryName) && + (o.name == functionRef.name)) + ) { + return functionRef.operand[0] + } + } + } + return reference + } + + /** + * If this is a choice selection, return the argument of the choice selection, on the grounds + * that the date range optimization should apply through the cast (i.e. it is an + * order-preserving cast) + * + * @param reference the `Expression` to examine + * @return The argument to the choice selection (i.e. As) if there was one, otherwise, the given + * `reference` + */ + private fun getChoiceSelection(reference: Expression): Expression { + if (reference is As) { + if (reference.operand != null && reference.operand!!.resultType is ChoiceType) { + return reference.operand!! + } + } + return reference + } + + /** + * Test an `And` expression and determine if it contains any operands (first-level or nested + * deeper) than are `IncludedIn` expressions that can be refactored into a `Retrieve`. If so, + * adjust the `Retrieve` accordingly and reset the corresponding operand to a literal `true`. + * This `and` branch containing a `true` can be further consolidated later. + * + * @param and the `And` expression containing operands to potentially refactor into the + * `Retrieve` + * @param retrieve the `Retrieve` to add qualifying date ranges to (if applicable) + * @param alias the alias of the `Retrieve` in the query. + * @return `true` if the date range was set in the `Retrieve` and the `And` operands (or + * sub-operands) were modified; `false` otherwise. + */ + private fun attemptDateRangeOptimization(and: And, retrieve: Retrieve, alias: String): Boolean { + if (retrieve.dateProperty != null || retrieve.dateRange != null) { + return false + } + for (i in and.operand.indices) { + val operand = and.operand[i] + if ( + (operand is IncludedIn || operand is In) && + attemptDateRangeOptimization(operand as BinaryExpression, retrieve, alias) + ) { + // Replace optimized part in And with true -- to be optimized out later + and.operand[i] = libraryBuilder.createLiteral(true) + return true + } else if (operand is And && attemptDateRangeOptimization(operand, retrieve, alias)) { + return true + } + } + return false + } + + /** + * If any branches in the `And` tree contain a `true`, refactor it out. + * + * @param and the `And` tree to attempt to consolidate + * @return the potentially consolidated `And` + */ + private fun consolidateAnd(and: And): Expression { + var result: Expression = and + val lhs = and.operand[0] + val rhs = and.operand[1] + when { + isBooleanLiteral(lhs, true) -> result = rhs + isBooleanLiteral(rhs, true) -> result = lhs + lhs is And -> and.operand[0] = consolidateAnd(lhs) + rhs is And -> and.operand[1] = consolidateAnd(rhs) + } + return result + } + + /** + * Determine if the right-hand side of an `IncludedIn` expression can be refactored into the + * date range of a `Retrieve`. Currently, refactoring is only supported when the RHS is a + * literal DateTime interval, a literal DateTime, a parameter representing a DateTime interval + * or a DateTime, or an expression reference representing a DateTime interval or a DateTime. + * + * @param rhs the right-hand side of the `IncludedIn` to test for potential optimization + * @return `true` if the RHS supports refactoring to a `Retrieve`, `false` otherwise. + */ + private fun isRHSEligibleForDateRangeOptimization(rhs: Expression): Boolean { + return (rhs.resultType!!.isSubTypeOf( + libraryBuilder.resolveTypeName("System", "DateTime")!! + ) || + rhs.resultType!!.isSubTypeOf( + IntervalType(libraryBuilder.resolveTypeName("System", "DateTime")!!) + )) + + // BTR: The only requirement for the optimization is that the expression be of + // type DateTime or + // Interval + // Whether or not the expression can be statically evaluated (literal, in the + // loose sense of the word) is really + // a function of the engine in determining the "initial" data requirements, + // versus subsequent data requirements + // Element targetElement = rhs; + // if (rhs instanceof ParameterRef) { + // String paramName = ((ParameterRef) rhs).getName(); + // for (ParameterDef def : getLibrary().getParameters().getDef()) { + // if (paramName.equals(def.getName())) { + // targetElement = def.getParameterTypeSpecifier(); + // if (targetElement == null) { + // targetElement = def.getDefault(); + // } + // break; + // } + // } + // } else if (rhs instanceof ExpressionRef && !(rhs instanceof FunctionRef)) { + // // TODO: Support forward declaration, if necessary + // String expName = ((ExpressionRef) rhs).getName(); + // for (ExpressionDef def : getLibrary().getStatements().getDef()) { + // if (expName.equals(def.getName())) { + // targetElement = def.getExpression(); + // } + // } + // } + // + // boolean isEligible = false; + // if (targetElement instanceof DateTime) { + // isEligible = true; + // } else if (targetElement instanceof Interval) { + // Interval ivl = (Interval) targetElement; + // isEligible = (ivl.getLow() != null && ivl.getLow() instanceof DateTime) || + // (ivl.getHigh() != null + // && ivl.getHigh() instanceof DateTime); + // } else if (targetElement instanceof IntervalTypeSpecifier) { + // IntervalTypeSpecifier spec = (IntervalTypeSpecifier) targetElement; + // isEligible = isDateTimeTypeSpecifier(spec.getPointType()); + // } else if (targetElement instanceof NamedTypeSpecifier) { + // isEligible = isDateTimeTypeSpecifier(targetElement); + // } + // return isEligible; + } + + override fun visitLetClause(ctx: LetClauseContext): Any { + val letClauseItems: MutableList = ArrayList() + for (letClauseItem in ctx.letClauseItem()) { + letClauseItems.add(visit(letClauseItem) as LetClause?) + } + return letClauseItems + } + + override fun visitLetClauseItem(ctx: LetClauseItemContext): LetClause { + val letClause = + of.createLetClause() + .withExpression(parseExpression(ctx.expression())) + .withIdentifier(parseString(ctx.identifier())) + letClause.resultType = letClause.expression!!.resultType + libraryBuilder.peekQueryContext().addLetClause(letClause) + return letClause + } + + override fun visitAliasedQuerySource(ctx: AliasedQuerySourceContext): AliasedQuerySource { + val source = + of.createAliasedQuerySource() + .withExpression(parseExpression(ctx.querySource())) + .withAlias(parseString(ctx.alias())) + source.resultType = source.expression!!.resultType + return source + } + + override fun visitWithClause(ctx: WithClauseContext): Any { + val aqs = visit(ctx.aliasedQuerySource()) as AliasedQuerySource + libraryBuilder.peekQueryContext().addRelatedQuerySource(aqs) + return try { + val expression = visit(ctx.expression()) as Expression + verifyType(expression.resultType, libraryBuilder.resolveTypeName("System", "Boolean")) + val result: RelationshipClause = of.createWith() + result.withExpression(aqs.expression).withAlias(aqs.alias).withSuchThat(expression) + result.resultType = aqs.resultType + result + } finally { + libraryBuilder.peekQueryContext().removeQuerySource(aqs) + } + } + + override fun visitWithoutClause(ctx: WithoutClauseContext): Any { + val aqs = visit(ctx.aliasedQuerySource()) as AliasedQuerySource + libraryBuilder.peekQueryContext().addRelatedQuerySource(aqs) + return try { + val expression = visit(ctx.expression()) as Expression + verifyType(expression.resultType, libraryBuilder.resolveTypeName("System", "Boolean")) + val result: RelationshipClause = of.createWithout() + result.withExpression(aqs.expression).withAlias(aqs.alias).withSuchThat(expression) + result.resultType = aqs.resultType + result + } finally { + libraryBuilder.peekQueryContext().removeQuerySource(aqs) + } + } + + override fun visitWhereClause(ctx: WhereClauseContext): Any { + val result = visit(ctx.expression()) as Expression + verifyType(result.resultType, libraryBuilder.resolveTypeName("System", "Boolean")) + return result + } + + override fun visitReturnClause(ctx: ReturnClauseContext): ReturnClause { + val returnClause = of.createReturnClause() + if (ctx.getChild(1) is TerminalNode) { + when (ctx.getChild(1)!!.text) { + "all" -> returnClause.distinct = false + "distinct" -> returnClause.distinct = true + } + } + returnClause.expression = parseExpression(ctx.expression()) + returnClause.resultType = + if (libraryBuilder.peekQueryContext().isSingular) returnClause.expression!!.resultType + else ListType(returnClause.expression!!.resultType!!) + return returnClause + } + + override fun visitStartingClause(ctx: StartingClauseContext): Any? { + if (ctx.simpleLiteral() != null) { + return visit(ctx.simpleLiteral()!!) + } + if (ctx.quantity() != null) { + return visit(ctx.quantity()!!) + } + return if (ctx.expression() != null) { + visit(ctx.expression()!!) + } else null + } + + override fun visitAggregateClause(ctx: AggregateClauseContext): AggregateClause { + libraryBuilder.checkCompatibilityLevel("Aggregate query clause", "1.5") + val aggregateClause = of.createAggregateClause() + if (ctx.getChild(1) is TerminalNode) { + when (ctx.getChild(1)!!.text) { + "all" -> aggregateClause.distinct = false + "distinct" -> aggregateClause.distinct = true + } + } + if (ctx.startingClause() != null) { + aggregateClause.starting = parseExpression(ctx.startingClause()) + } + + // If there is a starting, that's the type of the var + // If there's not a starting, push an Any and then attempt to evaluate (might + // need a type hint here) + aggregateClause.identifier = parseString(ctx.identifier()) + val accumulator: Expression = + if (aggregateClause.starting != null) { + libraryBuilder.buildNull(aggregateClause.starting!!.resultType) + } else { + libraryBuilder.buildNull(libraryBuilder.resolveTypeName("System", "Any")) + } + val letClause = + of.createLetClause() + .withExpression(accumulator) + .withIdentifier(aggregateClause.identifier) + letClause.resultType = letClause.expression!!.resultType + libraryBuilder.peekQueryContext().addLetClause(letClause) + aggregateClause.expression = parseExpression(ctx.expression()) + aggregateClause.resultType = aggregateClause.expression!!.resultType + if (aggregateClause.starting == null) { + accumulator.resultType = aggregateClause.resultType + aggregateClause.starting = accumulator + } + return aggregateClause + } + + override fun visitSortDirection(ctx: SortDirectionContext): SortDirection { + return SortDirection.fromValue(ctx.text) + } + + private fun parseSortDirection(ctx: SortDirectionContext?): SortDirection { + return if (ctx != null) { + visitSortDirection(ctx) + } else SortDirection.ASC + } + + override fun visitSortByItem(ctx: SortByItemContext): SortByItem { + val sortExpression = parseExpression(ctx.expressionTerm())!! + return if (sortExpression is IdentifierRef) { + of.createByColumn() + .withPath(sortExpression.name) + .withDirection(parseSortDirection(ctx.sortDirection())) + .withResultType(sortExpression.resultType) + } else + of.createByExpression() + .withExpression(sortExpression) + .withDirection(parseSortDirection(ctx.sortDirection())) + .withResultType(sortExpression.resultType) + } + + override fun visitSortClause(ctx: SortClauseContext): SortClause { + if (ctx.sortDirection() != null) { + return of.createSortClause() + .withBy( + of.createByDirection().withDirection(parseSortDirection(ctx.sortDirection())) + ) + } + val sortItems: MutableList = ArrayList() + for (sortByItemContext in ctx.sortByItem()) { + sortItems.add(visit(sortByItemContext) as SortByItem) + } + return of.createSortClause().withBy(sortItems) + } + + override fun visitQuerySource(ctx: QuerySourceContext): Any? { + return if (ctx.expression() != null) { + visit(ctx.expression()!!) + } else if (ctx.retrieve() != null) { + visit(ctx.retrieve()!!) + } else { + val identifiers: List = visit(ctx.qualifiedIdentifierExpression()!!).cast() + resolveQualifiedIdentifier(identifiers) + } + } + + override fun visitIndexedExpressionTerm(ctx: IndexedExpressionTermContext): Indexer { + val indexer = + of.createIndexer() + .withOperand( + parseExpression(ctx.expressionTerm())!!, + parseExpression(ctx.expression())!! + ) + + // TODO: Support zero-based indexers as defined by the isZeroBased attribute + libraryBuilder.resolveBinaryCall("System", "Indexer", indexer) + return indexer + } + + override fun visitInvocationExpressionTerm(ctx: InvocationExpressionTermContext): Expression? { + val left = parseExpression(ctx.expressionTerm())!! + libraryBuilder.pushExpressionTarget(left) + return try { + visit(ctx.qualifiedInvocation()) as Expression? + } finally { + libraryBuilder.popExpressionTarget() + } + } + + override fun visitExternalConstant(ctx: ExternalConstantContext): Expression? { + return libraryBuilder.resolveIdentifier(ctx.text, true) + } + + override fun visitThisInvocation(ctx: ThisInvocationContext): Expression? { + return libraryBuilder.resolveIdentifier(ctx.text, true) + } + + override fun visitMemberInvocation(ctx: MemberInvocationContext): Expression? { + val identifier = parseString(ctx.referentialIdentifier())!! + return resolveMemberIdentifier(identifier) + } + + override fun visitQualifiedMemberInvocation( + ctx: QualifiedMemberInvocationContext + ): Expression? { + val identifier = parseString(ctx.referentialIdentifier())!! + return resolveMemberIdentifier(identifier) + } + + private fun resolveQualifiedIdentifier(identifiers: List): Expression? { + var current: Expression? = null + for (identifier in identifiers) { + current = + if (current == null) { + resolveIdentifier(identifier) + } else { + libraryBuilder.resolveAccessor(current, identifier) + } + } + return current + } + + private fun resolveMemberIdentifier(identifier: String): Expression? { + if (libraryBuilder.hasExpressionTarget()) { + val target = libraryBuilder.popExpressionTarget() + return try { + libraryBuilder.resolveAccessor(target, identifier) + } finally { + libraryBuilder.pushExpressionTarget(target) + } + } + return resolveIdentifier(identifier) + } + + private fun resolveIdentifier(identifier: String): Expression? { + // If the identifier cannot be resolved in the library builder, check for + // forward declarations for expressions + // and parameters + var result = libraryBuilder.resolveIdentifier(identifier, false) + if (result == null) { + val expressionInfo = libraryInfo.resolveExpressionReference(identifier) + if (expressionInfo != null) { + val saveContext = saveCurrentContext(expressionInfo.context) + try { + val saveChunks = chunks + chunks = Stack() + forwards.push(expressionInfo) + try { + requireNotNull(expressionInfo.definition) { + // ERROR: + "Could not validate reference to expression ${expressionInfo.name} because its definition contains errors." + } + + // Have to call the visit to get the outer processing to occur + visit(expressionInfo.definition) + } finally { + chunks = saveChunks + forwards.pop() + } + } finally { + currentContext = saveContext + } + } + val parameterInfo = libraryInfo.resolveParameterReference(identifier) + if (parameterInfo != null) { + visitParameterDefinition(parameterInfo.definition) + } + result = libraryBuilder.resolveIdentifier(identifier, true) + } + return result + } + + private fun ensureSystemFunctionName(libraryName: String?, functionName: String): String { + if (libraryName == null || libraryName == "System") { + // Because these functions can be both a keyword and the name of a method, they + // can be resolved by the + // parser as a function, instead of as the keyword-based parser rule. In this + // case, the function + // name needs to be translated to the System function name in order to resolve. + return when (functionName) { + "contains" -> "Contains" + "distinct" -> "Distinct" + "exists" -> "Exists" + "in" -> "In" + "not" -> "Not" + else -> functionName + } + } + return functionName + } + + private fun resolveFunction( + libraryName: String?, + functionName: String, + paramList: ParamListContext? + ): Expression? { + val expressions: MutableList = ArrayList() + if (paramList?.expression() != null) { + for (expressionContext in paramList.expression()) { + expressions.add(visit(expressionContext) as Expression) + } + } + return resolveFunction( + libraryName, + functionName, + expressions, + mustResolve = true, + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + + @Suppress("LongParameterList") + fun resolveFunction( + libraryName: String?, + functionName: String, + expressions: List, + mustResolve: Boolean, + allowPromotionAndDemotion: Boolean, + allowFluent: Boolean + ): Expression? { + var name = functionName + if (allowFluent) { + libraryBuilder.checkCompatibilityLevel("Fluent functions", "1.5") + } + name = ensureSystemFunctionName(libraryName, name) + + // 1. Ensure all overloads of the function are registered with the operator map + // 2. Resolve the function, allowing for the case that operator map is a + // skeleton + // 3. If the resolution from the operator map is a skeleton, compile the + // function body to determine the result + // type + + // Find all functionDefinitionInfo instances with the given name + // register each functionDefinitionInfo + if (libraryName == null || libraryName == "" || libraryName == libraryInfo.libraryName) { + val fdis = libraryInfo.resolveFunctionReference(name) + if (fdis != null) { + for ((_, context, definition) in fdis) { + val saveContext = saveCurrentContext(context) + try { + registerFunctionDefinition(definition) + } finally { + currentContext = saveContext + } + } + } + } + val result = + libraryBuilder.resolveFunction( + libraryName, + name, + expressions, + mustResolve, + allowPromotionAndDemotion, + allowFluent + ) + if ( + result is FunctionRefInvocation && + result.resolution != null && + (result.resolution!!.operator.libraryName == null || + (result.resolution!!.operator.libraryName == + libraryBuilder.compiledLibrary.identifier!!.id)) + ) { + val op = result.resolution!!.operator + val fh = getFunctionHeader(op) + if (!fh.isCompiled) { + val ctx = getFunctionDefinitionContext(fh) + val saveContext = saveCurrentContext(fh.functionDef.context!!) + val saveChunks = chunks + chunks = Stack() + try { + val fd = compileFunctionDefinition(ctx) + op.resultType = fd.resultType + result.resultType = op.resultType + } finally { + currentContext = saveContext + chunks = saveChunks + } + } + } + if (mustResolve) { + // Extra internal error handling, these should never be hit if the two-phase + // operator compile is working as + // expected + require(result != null) { "Internal error: could not resolve function" } + require(result.expression.resultType != null) { + "Internal error: could not determine result type" + } + } + return result?.expression + } + + private fun resolveFunctionOrQualifiedFunction( + identifier: String, + paramListCtx: ParamListContext? + ): Expression? { + if (libraryBuilder.hasExpressionTarget()) { + val target: Expression = libraryBuilder.popExpressionTarget() + try { + // If the target is a library reference, resolve as a standard qualified call + if (target is LibraryRef) { + return resolveFunction(target.libraryName, identifier, paramListCtx) + } + + // NOTE: FHIRPath method invocation + // If the target is an expression, resolve as a method invocation + if (isMethodInvocationEnabled) { + return systemMethodResolver.resolveMethod( + target, + identifier, + paramListCtx, + true + ) + } + if (!isMethodInvocationEnabled) { + throw CqlCompilerException( + "The identifier $identifier could not be resolved as an invocation because method-style invocation is disabled.", + CqlCompilerException.ErrorSeverity.Error + ) + } + throw IllegalArgumentException( + "Invalid invocation target: ${target.javaClass.name}" + ) + } finally { + libraryBuilder.pushExpressionTarget(target) + } + } + + // If we are in an implicit $this context, the function may be resolved as a + // method invocation + val thisRef: Expression? = libraryBuilder.resolveIdentifier("\$this", false) + if (thisRef != null) { + val result: Expression? = + systemMethodResolver.resolveMethod(thisRef, identifier, paramListCtx, false) + if (result != null) { + return result + } + } + + // If we are in an implicit context (i.e. a context named the same as a + // parameter), the function may be resolved + // as a method invocation + val parameterRef: ParameterRef? = libraryBuilder.resolveImplicitContext() + if (parameterRef != null) { + val result: Expression? = + systemMethodResolver.resolveMethod(parameterRef, identifier, paramListCtx, false) + if (result != null) { + return result + } + } + + // If there is no target, resolve as a system function + return resolveFunction(null, identifier, paramListCtx) + } + + override fun visitFunction(ctx: FunctionContext): Expression? { + return resolveFunctionOrQualifiedFunction( + parseString(ctx.referentialIdentifier())!!, + ctx.paramList() + ) + } + + override fun visitQualifiedFunction(ctx: QualifiedFunctionContext): Expression? { + return resolveFunctionOrQualifiedFunction( + parseString(ctx.identifierOrFunctionIdentifier())!!, + ctx.paramList() + ) + } + + override fun visitFunctionBody(ctx: FunctionBodyContext): Any? { + return visit(ctx.expression()) + } + + private fun getFunctionHeader(ctx: FunctionDefinitionContext): FunctionHeader { + var fh = functionHeaders[ctx] + if (fh == null) { + val saveChunks = chunks + chunks = Stack() + fh = + try { + // Have to call the visit to allow the outer processing to occur + parseFunctionHeader(ctx) + } finally { + chunks = saveChunks + } + functionHeaders[ctx] = fh + functionDefinitions[fh] = ctx + functionHeadersByDef[fh!!.functionDef] = fh + } + return fh + } + + private fun getFunctionDef(op: Operator): FunctionDef? { + var target: FunctionDef? = null + val st: MutableList = ArrayList() + for (dt in op.signature.operandTypes) { + st.add(dt) + } + val fds = libraryBuilder.compiledLibrary.resolveFunctionRef(op.name, st) + for (fd in fds) { + if (fd!!.operand.size == op.signature.size) { + val signatureTypes = op.signature.operandTypes.iterator() + var signaturesMatch = true + for (i in fd.operand.indices) { + if (!equal(fd.operand[i].resultType, signatureTypes.next())) { + signaturesMatch = false + } + } + if (signaturesMatch) { + check(target == null) { + "Internal error attempting to resolve function header for ${op.name}" + } + target = fd + } + } + } + return target + } + + private fun getFunctionHeaderByDef(fd: FunctionDef): FunctionHeader? { + // Shouldn't need to do this, something about the hashCode implementation of + // FunctionDef is throwing this off, + // Don't have time to investigate right now, this should work fine, could + // potentially be improved + for ((key, value) in functionHeadersByDef) { + if (key === fd) { + return value + } + } + return null + } + + private fun getFunctionHeader(op: Operator): FunctionHeader { + val fd = + getFunctionDef(op) + ?: throw IllegalArgumentException( + "Could not resolve function header for operator ${op.name}" + ) + return getFunctionHeaderByDef(fd) + ?: throw IllegalArgumentException( + "Could not resolve function header for operator ${op.name}" + ) + } + + private fun getFunctionDefinitionContext(fh: FunctionHeader): FunctionDefinitionContext { + return functionDefinitions[fh] + ?: throw IllegalArgumentException( + "Could not resolve function definition context for function header ${fh.functionDef.name}" + ) + } + + private fun registerFunctionDefinition(ctx: FunctionDefinitionContext) { + val fh = getFunctionHeader(ctx) + if (!libraryBuilder.compiledLibrary.contains(fh.functionDef)) { + libraryBuilder.addExpression(fh.functionDef) + } + } + + private fun compileFunctionDefinition(ctx: FunctionDefinitionContext): FunctionDef { + val fh: FunctionHeader = getFunctionHeader(ctx) + val functionDef: FunctionDef = fh.functionDef + val resultType: TypeSpecifier? = fh.resultType + val op: Operator = + libraryBuilder.resolveFunctionDefinition(fh.functionDef) + ?: throw IllegalArgumentException( + "Internal error: Could not resolve operator map entry for function header ${fh.mangledName}" + ) + libraryBuilder.pushIdentifier(functionDef.name!!, functionDef, IdentifierScope.GLOBAL) + val operand = op.functionDef!!.operand as List + for (operandDef: OperandDef in operand) { + libraryBuilder.pushIdentifier(operandDef.name!!, operandDef) + } + try { + if (ctx.functionBody() != null) { + libraryBuilder.beginFunctionDef(functionDef) + try { + libraryBuilder.pushExpressionContext(currentContext) + try { + libraryBuilder.pushExpressionDefinition(fh.mangledName) + try { + functionDef.expression = parseExpression(ctx.functionBody()) + } finally { + libraryBuilder.popExpressionDefinition() + } + } finally { + libraryBuilder.popExpressionContext() + } + } finally { + libraryBuilder.endFunctionDef() + } + if ( + (resultType != null) && + (functionDef.expression != null) && + (functionDef.expression!!.resultType != null) + ) { + require(subTypeOf(functionDef.expression!!.resultType, resultType.resultType)) { + // ERROR: + "Function ${functionDef.name} has declared return type ${resultType.resultType} but the function body returns incompatible type ${functionDef.expression!!.resultType}." + } + } + functionDef.resultType = functionDef.expression!!.resultType + op.resultType = functionDef.resultType + } else { + functionDef.external = true + requireNotNull(resultType) { + // ERROR: + "Function ${functionDef.name} is marked external but does not declare a return type." + } + functionDef.resultType = resultType.resultType + op.resultType = functionDef.resultType + } + functionDef.context = currentContext + fh.isCompiled = true + return functionDef + } finally { + for (operandDef: OperandDef? in operand) { + try { + libraryBuilder.popIdentifier() + } catch (e: Exception) { + log.warn("Error popping identifier", e) + } + } + // Intentionally do _not_ pop the function name, it needs to remain in global scope! + } + } + + override fun visitFunctionDefinition(ctx: FunctionDefinitionContext): Any { + libraryBuilder.pushIdentifierScope() + return try { + registerFunctionDefinition(ctx) + compileFunctionDefinition(ctx) + } finally { + libraryBuilder.popIdentifierScope() + } + } + + private fun parseLiteralExpression(pt: ParseTree?): Expression? { + libraryBuilder.pushLiteralContext() + return try { + parseExpression(pt) + } finally { + libraryBuilder.popLiteralContext() + } + } + + private fun parseExpression(pt: ParseTree?): Expression? { + return if (pt == null) null else visit(pt) as Expression? + } + + private fun isBooleanLiteral(expression: Expression, bool: Boolean?): Boolean { + var ret = false + if (expression is Literal) { + ret = + (expression.valueType == + libraryBuilder.dataTypeToQName( + libraryBuilder.resolveTypeName("System", "Boolean") + )) + if (ret && bool != null) { + ret = bool == java.lang.Boolean.valueOf(expression.value) + } + } + return ret + } + + private fun getTrackBack(tree: ParseTree): TrackBack? { + if (tree is ParserRuleContext) { + return getTrackBack(tree) + } + return if (tree is TerminalNode) { + getTrackBack(tree) + } else null + } + + private fun getTrackBack(node: TerminalNode): TrackBack { + return TrackBack( + libraryBuilder.libraryIdentifier, + node.symbol.line, + node.symbol.charPositionInLine + 1, // 1-based instead of 0-based + node.symbol.line, + node.symbol.charPositionInLine + node.symbol.text!!.length + ) + } + + private fun getTrackBack(ctx: ParserRuleContext): TrackBack { + return TrackBack( + libraryBuilder.libraryIdentifier, + ctx.start?.line ?: 0, + ctx.start?.charPositionInLine?.inc() ?: 0, // 1-based instead of 0-based + ctx.stop?.line ?: 0, + (ctx.stop?.charPositionInLine ?: 0) + + (ctx.stop?.text?.length ?: 0) // 1-based instead of 0-based + ) + } + + private fun decorate(element: Element, tb: TrackBack?) { + if (locatorsEnabled() && tb != null) { + element.locator = tb.toLocator() + } + if (resultTypesEnabled() && element.resultType != null) { + if (element.resultType is NamedType) { + element.resultTypeName = libraryBuilder.dataTypeToQName(element.resultType) + } else { + element.resultTypeSpecifier = + libraryBuilder.dataTypeToTypeSpecifier(element.resultType) + } + } + } + + private fun track(trackable: Element?, pt: ParseTree): TrackBack? { + val tb = getTrackBack(pt) + if (tb != null) { + trackable!!.trackbacks.add(tb) + } + if (trackable is Element) { + decorate(trackable, tb) + } + return tb + } + + private fun track(element: Element?, from: Element): TrackBack? { + val tb = if (from.trackbacks.isNotEmpty()) from.trackbacks[0] else null + if (tb != null) { + element!!.trackbacks.add(tb) + } + if (element is Element) { + decorate(element, tb) + } + return tb + } + + companion object { + private val log = LoggerFactory.getLogger(Cql2ElmVisitor::class.java) + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCapability.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCapability.kt new file mode 100644 index 000000000..59a76fcc1 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCapability.kt @@ -0,0 +1,128 @@ +package org.cqframework.cql.cql2elm + +import java.util.* + +/* +Defines a language capability +*/ +class CqlCapability +@JvmOverloads +constructor( // A unique code identifying the capability + var code: String, // A short string providing a user-friendly display name for the capability + var display: + String, // A definition of the capability, including description of possible values for the + // capability + var definition: + String, // The version in which the capability was introduced, drawn from release versions, + // specifying as . + var sinceVersion: String = + "1.0", // The version in which the capability was removed, drawn from release versions, + // specifying as . + var upToVersion: String? = null +) { + + override fun equals(o: Any?): Boolean { + if (this === o) return true + if (o == null || javaClass != o.javaClass) return false + val that = o as CqlCapability + return code == that.code + } + + override fun hashCode(): Int { + return Objects.hash(code) + } + + override fun toString(): String { + return ("CqlCapability{" + + "code='" + + code + + '\'' + + ", display='" + + display + + '\'' + + ", definition='" + + definition + + '\'' + + '}') + } + + companion object { + @Suppress("MaxLineLength") + var capabilities: Set = + object : HashSet() { + init { + add( + CqlCapability( + "decimal-precision", + "Decimal Precision", + "Maximum number of digits of precision that can be represented in decimal values. Conformant implementations SHALL support at least 28 digits of precision for decimal values." + ) + ) + add( + CqlCapability( + "decimal-scale", + "Decimal Scale", + "Maximum number of digits of scale that can be represented in decimal values (i.e. the maximum number of digits after the decimal point). Conformant implementations SHALL support at least 8 digits of scale for decimal values." + ) + ) + add( + CqlCapability( + "datetime-precision", + "DateTime Precision", + "The maximum number of digits of precision that can be represented for DateTime values, where each numeric place, beginning with years, is counted as a single digit. Conformant implementations SHALL support at least 17 digits of precision for datetime values (YYYYMMDDHHmmss.fff)." + ) + ) + add( + CqlCapability( + "datetime-scale", + "DateTime Scale", + "The maximum number of digits of scale that can be represented in datetime values (i.e. the maximum number of digits after the decimal point in the seconds component). Conformant implementations SHALL support at least 3 digits of scale for datetime values." + ) + ) + add( + CqlCapability( + "ucum-unit-conversion", + "UCUM Unit Conversion", + "Whether or not the implementation supports conversion of Unified Code for Units of Measure (UCUM) units. Conformant implementations SHOULD support UCUM unit conversion." + ) + ) + add( + CqlCapability( + "regex-dialect", + "Regular Expression Dialect", + "The dialect of regular expressions used by the implementation. Conformant implementations SHOULD use the Perl Compatible Regular Expression (PCRE) dialect. Values for this feature should be drawn from the Name of the regular expression language list here: https://en.wikipedia.org/wiki/Comparison_of_regular-expression_engines" + ) + ) + add( + CqlCapability( + "supported-data-model", + "Supported Data Model", + "A supported data model, specified as the URI of the model information." + ) + ) + add( + CqlCapability( + "supported-function", + "Supported Function", + "A supported function, specified as the fully qualified name of the function." + ) + ) + add( + CqlCapability( + "unfiltered-context-retrieve", + "Unfiltered Context Retrieve", + "Whether or not the implementation supports evaluating retrieves in the unfiltered context." + ) + ) + add( + CqlCapability( + "related-context-retrieve", + "Related Context Retrieve", + "Whether or not the implementation supports related-context retrieves.", + "1.4" + ) + ) + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompiler.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompiler.kt new file mode 100644 index 000000000..f9d51fcdf --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompiler.kt @@ -0,0 +1,218 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.io.* +import java.util.* +import org.antlr.v4.kotlinruntime.* +import org.antlr.v4.kotlinruntime.tree.ParseTree +import org.cqframework.cql.cql2elm.elm.ElmEdit +import org.cqframework.cql.cql2elm.elm.ElmEditor +import org.cqframework.cql.cql2elm.elm.IElmEdit +import org.cqframework.cql.cql2elm.model.CompiledLibrary +import org.cqframework.cql.cql2elm.preprocessor.CqlPreprocessor +import org.cqframework.cql.cql2elm.tracking.TrackBack +import org.cqframework.cql.elm.IdObjectFactory +import org.cqframework.cql.gen.cqlLexer +import org.cqframework.cql.gen.cqlParser +import org.cqframework.cql.gen.cqlParser.LibraryContext +import org.hl7.cql.model.* +import org.hl7.elm.r1.* + +class CqlCompiler( + private val namespaceInfo: NamespaceInfo?, + sourceInfo: VersionedIdentifier?, + private val libraryManager: LibraryManager +) { + var library: Library? = null + private set + + var compiledLibrary: CompiledLibrary? = null + private set + + private var visitResult: Any? = null + private var retrieves: List? = null + var exceptions: MutableList? = null + var errors: MutableList? = null + var warnings: MutableList? = null + var messages: MutableList? = null + private var sourceInfo = + sourceInfo ?: VersionedIdentifier().withId("Anonymous").withSystem("text/cql") + + constructor(libraryManager: LibraryManager) : this(null, null, libraryManager) + + constructor( + namespaceInfo: NamespaceInfo?, + libraryManager: LibraryManager + ) : this(namespaceInfo, null, libraryManager) + + init { + if (namespaceInfo != null) { + libraryManager.namespaceManager.ensureNamespaceRegistered(namespaceInfo) + } + if ( + libraryManager.namespaceManager.hasNamespaces() && + libraryManager.librarySourceLoader is NamespaceAware + ) { + (libraryManager.librarySourceLoader as NamespaceAware).setNamespaceManager( + libraryManager.namespaceManager + ) + } + } + + fun toObject(): Any? { + return visitResult + } + + fun toRetrieves(): List? { + return retrieves + } + + val libraries: Map + get() { + val result = HashMap() + libraryManager.compiledLibraries.forEach { (id, compiledLibrary) -> + result[id] = compiledLibrary.library!! + } + return result + } + + private inner class CqlErrorListener( + private val builder: LibraryBuilder, + private val detailedErrors: Boolean + ) : BaseErrorListener() { + private fun extractLibraryIdentifier(parser: cqlParser): VersionedIdentifier? { + var context: RuleContext? = parser.context + while (context != null && context !is LibraryContext) { + context = context.getParent() + } + if (context is LibraryContext) { + val ldc = context.libraryDefinition() + if (ldc?.qualifiedIdentifier() != null) { + return VersionedIdentifier() + .withId( + StringEscapeUtils.unescapeCql( + ldc.qualifiedIdentifier().identifier().text + ) + ) + } + } + return null + } + + override fun syntaxError( + recognizer: Recognizer<*, *>, + offendingSymbol: Any?, + line: Int, + charPositionInLine: Int, + msg: String, + e: RecognitionException? + ) { + var libraryIdentifier = builder.libraryIdentifier + if (libraryIdentifier == null) { + // Attempt to extract a libraryIdentifier from the currently parsed content + if (recognizer is cqlParser) { + libraryIdentifier = extractLibraryIdentifier(recognizer) + } + if (libraryIdentifier == null) { + libraryIdentifier = sourceInfo + } + } + val trackback = + TrackBack(libraryIdentifier, line, charPositionInLine, line, charPositionInLine) + if (detailedErrors) { + builder.recordParsingException(CqlSyntaxException(msg, trackback, e)) + builder.recordParsingException(CqlCompilerException(msg, trackback, e)) + } else { + if (offendingSymbol is CommonToken) { + builder.recordParsingException( + CqlSyntaxException("Syntax error at ${offendingSymbol.text}", trackback, e) + ) + } else { + builder.recordParsingException(CqlSyntaxException("Syntax error", trackback, e)) + } + } + } + } + + @Throws(IOException::class) + fun run(cqlFile: File): Library? { + return run(CharStreams.fromStream(FileInputStream(cqlFile))) + } + + fun run(cqlText: String): Library? { + return run(CharStreams.fromString(cqlText)) + } + + @Throws(IOException::class) + fun run(inputStream: InputStream): Library? { + return run(CharStreams.fromStream(inputStream)) + } + + fun run(charStream: CharStream): Library? { + exceptions = ArrayList() + errors = ArrayList() + warnings = ArrayList() + messages = ArrayList() + val options = libraryManager.cqlCompilerOptions.options + val builder = LibraryBuilder(namespaceInfo, libraryManager, IdObjectFactory()) + val errorListener = + CqlErrorListener( + builder, + options.contains(CqlCompilerOptions.Options.EnableDetailedErrors) + ) + + // Phase 1: Lexing + val lexer = cqlLexer(charStream) + lexer.removeErrorListeners() + lexer.addErrorListener(errorListener) + val tokens = CommonTokenStream(lexer) + + // Phase 2: Parsing (the lexer is actually streaming, so Phase 1 and 2 happen together) + val parser = cqlParser(tokens) + parser.buildParseTree = true + parser.removeErrorListeners() // Clear the default console listener + parser.addErrorListener(errorListener) + val tree: ParseTree = parser.library() + + // Phase 3: preprocess the parse tree (generates the LibraryInfo with + // header information for definitions) + val preprocessor = CqlPreprocessor(builder, tokens) + preprocessor.visit(tree) + + // Phase 4: generate the ELM (the ELM is generated with full type information that can be + // used + // for validation, optimization, rewriting, debugging, etc.) + val visitor = Cql2ElmVisitor(builder, tokens, preprocessor.libraryInfo) + visitResult = visitor.visit(tree) + library = builder.library + + // Phase 5: ELM optimization/reduction (this is where result types, annotations, etc. are + // removed + // and there will probably be a lot of other optimizations that happen here in the future) + val edits = + allNonNull( + (if (!options.contains(CqlCompilerOptions.Options.EnableAnnotations)) + ElmEdit.REMOVE_ANNOTATION + else null), + (if (!options.contains(CqlCompilerOptions.Options.EnableResultTypes)) + ElmEdit.REMOVE_RESULT_TYPE + else null), + (if (!options.contains(CqlCompilerOptions.Options.EnableLocators)) + ElmEdit.REMOVE_LOCATOR + else null) + ) + ElmEditor(edits).edit(library!!) + compiledLibrary = builder.compiledLibrary + retrieves = visitor.retrieves + exceptions?.addAll(builder.exceptions) + errors?.addAll(builder.errors) + warnings?.addAll(builder.warnings) + messages?.addAll(builder.messages) + return library + } + + private fun allNonNull(vararg ts: IElmEdit?): List { + return ts.filterNotNull() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompilerException.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompilerException.kt new file mode 100644 index 000000000..acd7058e6 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompilerException.kt @@ -0,0 +1,52 @@ +package org.cqframework.cql.cql2elm + +import org.cqframework.cql.cql2elm.tracking.TrackBack + +open class CqlCompilerException +@JvmOverloads +constructor( + message: String?, + val severity: ErrorSeverity = ErrorSeverity.Error, + @field:Transient val locator: TrackBack? = null, + cause: Throwable? = null +) : RuntimeException(message, cause) { + enum class ErrorSeverity { + Info, + Warning, + Error + } + + constructor( + message: String?, + cause: Throwable? + ) : this(message, ErrorSeverity.Error, null, cause) + + constructor( + message: String?, + severity: ErrorSeverity, + cause: Throwable? + ) : this(message, severity, null, cause) + + constructor( + message: String?, + locator: TrackBack? + ) : this(message, ErrorSeverity.Error, locator, null) + + constructor( + message: String?, + locator: TrackBack?, + cause: Throwable? + ) : this(message, ErrorSeverity.Error, locator, cause) + + companion object { + @JvmStatic + fun hasErrors(exceptions: List): Boolean { + for (exception in exceptions) { + if (exception.severity == ErrorSeverity.Error) { + return true + } + } + return false + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompilerOptions.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompilerOptions.kt new file mode 100644 index 000000000..f498189bc --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlCompilerOptions.kt @@ -0,0 +1,377 @@ +package org.cqframework.cql.cql2elm + +import java.util.* +import org.cqframework.cql.cql2elm.LibraryBuilder.SignatureLevel + +/** translation options for Cql source files */ +class CqlCompilerOptions { + enum class Options { + EnableDateRangeOptimization, + EnableAnnotations, + EnableLocators, + EnableResultTypes, + EnableDetailedErrors, + DisableListTraversal, + DisableListDemotion, + DisableListPromotion, + EnableIntervalDemotion, + EnableIntervalPromotion, + DisableMethodInvocation, + RequireFromKeyword, + DisableDefaultModelInfoLoad + } + + val options: EnumSet = EnumSet.noneOf(Options::class.java) + /** + * Return instance of CqlTranslatorOptions validateUnits boolean + * + * @return + */ + /** + * Set new validateUnits boolean + * + * @param validateUnits + */ + var validateUnits: Boolean = true + /** + * Return instance of CqlTranslatorOptions verifyOnly boolean + * + * @return + */ + /** + * Set new verifyOnly boolean + * + * @param verifyOnly + */ + var verifyOnly: Boolean = false + /** + * Return instance of CqlTranslatorOptions enableCqlOnly boolean + * + * @return + */ + /** + * Set new enableCqlOnly boolean + * + * @param enableCqlOnly + */ + var enableCqlOnly: Boolean = false + /** + * Return instance of CqlTranslatorOptions compatibilityLevel + * + * @return + */ + /** + * Set new compatibilityLevel + * + * @param compatibilityLevel + */ + var compatibilityLevel: String = "1.5" + /** + * Return instance of CqlTranslatorOptions errorLevel (CqlTranslatorException.ErrorSeverity) + * + * @return + */ + /** + * Set new errorLevel (CqlTranslatorException.ErrorSeverity) + * + * @param errorLevel + */ + var errorLevel: CqlCompilerException.ErrorSeverity? = CqlCompilerException.ErrorSeverity.Info + /** + * Return instance of CqlTranslatorOptions signatureLevel (LibraryBuilder.SignatureLevel) + * + * @return + */ + /** + * Set new signatureLevel (LibraryBuilder.SignatureLevel) + * + * @param signatureLevel + */ + var signatureLevel: SignatureLevel = SignatureLevel.Overloads + /** + * Return instance of CqlTranslatorOptions analyzeDataRequirements boolean + * + * @return + */ + /** + * Set new analyzeDataRequirements boolean + * + * @param analyzeDataRequirements + */ + var analyzeDataRequirements: Boolean = false + /** + * Return instance of CqlTranslatorOptions collapseDataRequirements boolean + * + * @return + */ + /** + * Set new collapseDataRequirements boolean + * + * @param collapseDataRequirements + */ + var collapseDataRequirements: Boolean = false + + constructor() + + /** + * Constructor with arbitrary number of options utilizing default ErrorSeverity (Info) and + * SignatureLevel (None) + * + * @param options + */ + @Suppress("SpreadOperator") + constructor( + vararg options: Options + ) : this(CqlCompilerException.ErrorSeverity.Info, SignatureLevel.None, *options) + + @Suppress("SpreadOperator") + constructor( + errorLevel: CqlCompilerException.ErrorSeverity?, + vararg options: Options + ) : this(errorLevel, SignatureLevel.None, *options) + + /** + * Constructor with defined ErrorSeverity, SignatureLevel, and arbitrary number of options. + * + * @param errorLevel + * @param signatureLevel + * @param options + */ + @Suppress("SpreadOperator") + constructor( + errorLevel: CqlCompilerException.ErrorSeverity?, + signatureLevel: SignatureLevel, + vararg options: Options + ) { + setOptions(*options) + this.errorLevel = errorLevel + this.signatureLevel = signatureLevel + } + + @Suppress("LongParameterList") + /** + * Constructor using defined SignatureLevel, and Compatibility Level, boolean set to true + * denotes addition of predefined option + * + * @param dateRangeOptimizations boolean + * @param annotations boolean + * @param locators boolean + * @param resultTypes boolean + * @param verifyOnly boolean + * @param detailedErrors boolean + * @param errorLevel boolean + * @param disableListTraversal boolean + * @param disableListDemotion boolean + * @param disableListPromotion boolean + * @param enableIntervalDemotion boolean + * @param enableIntervalPromotion boolean + * @param disableMethodInvocation boolean + * @param requireFromKeyword boolean + * @param validateUnits boolean + * @param signatureLevel LibraryBuilder.SignatureLevel + * @param compatibilityLevel String + */ + constructor( + dateRangeOptimizations: Boolean, + annotations: Boolean, + locators: Boolean, + resultTypes: Boolean, + verifyOnly: Boolean, + detailedErrors: Boolean, + errorLevel: CqlCompilerException.ErrorSeverity?, + disableListTraversal: Boolean, + disableListDemotion: Boolean, + disableListPromotion: Boolean, + enableIntervalDemotion: Boolean, + enableIntervalPromotion: Boolean, + disableMethodInvocation: Boolean, + requireFromKeyword: Boolean, + validateUnits: Boolean, + disableDefaultModelInfoLoad: Boolean, + signatureLevel: SignatureLevel, + compatibilityLevel: String + ) { + this.verifyOnly = verifyOnly + this.errorLevel = errorLevel + this.signatureLevel = signatureLevel + this.validateUnits = validateUnits + this.compatibilityLevel = compatibilityLevel + if (dateRangeOptimizations) { + options.add(Options.EnableDateRangeOptimization) + } + if (annotations) { + options.add(Options.EnableAnnotations) + } + if (locators) { + options.add(Options.EnableLocators) + } + if (resultTypes) { + options.add(Options.EnableResultTypes) + } + if (detailedErrors) { + options.add(Options.EnableDetailedErrors) + } + if (disableListTraversal) { + options.add(Options.DisableListTraversal) + } + if (disableListDemotion) { + options.add(Options.DisableListDemotion) + } + if (disableListPromotion) { + options.add(Options.DisableListPromotion) + } + if (enableIntervalDemotion) { + options.add(Options.EnableIntervalDemotion) + } + if (enableIntervalPromotion) { + options.add(Options.EnableIntervalPromotion) + } + if (disableMethodInvocation) { + options.add(Options.DisableMethodInvocation) + } + if (requireFromKeyword) { + options.add(Options.RequireFromKeyword) + } + if (disableDefaultModelInfoLoad) { + options.add(Options.DisableDefaultModelInfoLoad) + } + } + + /** + * Set arbitrary number of options + * + * @param options + */ + fun setOptions(vararg options: Options) { + for (option: Options in options) { + this.options.add(option) + } + } + + /** + * Return this instance of CqlTranslatorOptions using new collection of arbitrary number of + * options + * + * @param options + * @return + */ + fun withOptions(vararg options: Options): CqlCompilerOptions { + setOptions(*options) + return this + } + + /** + * Return this instance of CqlTranslatorOptions with addition of newly assigned + * compatibilityLevel + * + * @param compatibilityLevel + * @return + */ + fun withCompatibilityLevel(compatibilityLevel: String): CqlCompilerOptions { + this.compatibilityLevel = compatibilityLevel + return this + } + + /** + * Return this instance of CqlTranslatorOptions with addition of newly assigned verifyOnly + * boolean + * + * @param verifyOnly + * @return + */ + fun withVerifyOnly(verifyOnly: Boolean): CqlCompilerOptions { + this.verifyOnly = verifyOnly + return this + } + + /** + * Return this instance of CqlTranslatorOptions with addition of newly assigned validateUnits + * boolean + * + * @param validateUnits + * @return + */ + fun withValidateUnits(validateUnits: Boolean): CqlCompilerOptions { + this.validateUnits = validateUnits + return this + } + + /** + * Return this instance of CqlTranslatorOptions with addition of newly assigned errorLevel + * (CqlTranslatorException.ErrorSeverity) + * + * @param errorLevel + * @return + */ + fun withErrorLevel(errorLevel: CqlCompilerException.ErrorSeverity?): CqlCompilerOptions { + this.errorLevel = errorLevel + return this + } + + /** + * Return this instance of CqlTranslatorOptions with addition of newly assigned signatureLevel + * (LibraryBuilder.SignatureLevel) + * + * @param signatureLevel + * @return + */ + fun withSignatureLevel(signatureLevel: SignatureLevel): CqlCompilerOptions { + this.signatureLevel = signatureLevel + return this + } + + /** + * Return this instance of CqlTranslatorOptions with addition of newly assigned + * collapseDataRequirements boolean + * + * @param collapseDataRequirements + * @return + */ + fun withCollapseDataRequirements(collapseDataRequirements: Boolean): CqlCompilerOptions { + this.collapseDataRequirements = collapseDataRequirements + return this + } + + /** + * git Return this instance of CqlTranslatorOptions with addition of newly assigned + * analyzedDataRequirements boolean + * + * @param analyzeDataRequirements + * @return + */ + fun withAnalyzeDataRequirements(analyzeDataRequirements: Boolean): CqlCompilerOptions { + this.analyzeDataRequirements = analyzeDataRequirements + return this + } + + override fun toString(): String { + val translatorOptions: StringBuilder = StringBuilder() + for (option: Options in options) { + if (translatorOptions.isNotEmpty()) { + translatorOptions.append(",") + } + translatorOptions.append(option.name) + } + return translatorOptions.toString() + } + + companion object { + /** + * Returns default translator options: EnableAnnotations EnableLocators DisableListDemotion + * DisableListPromotion ErrorSeverity.Info SignatureLevel.None + * + * @return + */ + @JvmStatic + fun defaultOptions(): CqlCompilerOptions { + // Default options based on recommended settings: + // http://build.fhir.org/ig/HL7/cqf-measures/using-cql.html#translation-to-elm + val result = CqlCompilerOptions() + result.options.add(Options.EnableAnnotations) + result.options.add(Options.EnableLocators) + result.options.add(Options.DisableListDemotion) + result.options.add(Options.DisableListPromotion) + return result + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlIncludeException.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlIncludeException.kt new file mode 100644 index 000000000..ed3fad465 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlIncludeException.kt @@ -0,0 +1,35 @@ +package org.cqframework.cql.cql2elm + +class CqlIncludeException : RuntimeException { + var librarySystem: String? + private set + + var libraryId: String + private set + + var versionId: String? + private set + + constructor( + message: String?, + librarySystem: String?, + libraryId: String, + versionId: String? + ) : super(message) { + this.librarySystem = librarySystem + this.libraryId = libraryId + this.versionId = versionId + } + + constructor( + message: String?, + librarySystem: String, + libraryId: String, + versionId: String?, + cause: Throwable? + ) : super(message, cause) { + this.librarySystem = librarySystem + this.libraryId = libraryId + this.versionId = versionId + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlInternalException.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlInternalException.kt new file mode 100644 index 000000000..4458fc36c --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlInternalException.kt @@ -0,0 +1,7 @@ +package org.cqframework.cql.cql2elm + +import org.cqframework.cql.cql2elm.tracking.TrackBack + +/** Created by Bryn on 5/20/2017. */ +class CqlInternalException(message: String?, locator: TrackBack? = null, cause: Throwable? = null) : + CqlCompilerException(message, ErrorSeverity.Error, locator, cause) diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlSemanticException.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlSemanticException.kt new file mode 100644 index 000000000..5e8406c61 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlSemanticException.kt @@ -0,0 +1,27 @@ +package org.cqframework.cql.cql2elm + +import org.cqframework.cql.cql2elm.tracking.TrackBack + +/** Created by Bryn on 3/27/2017. */ +class CqlSemanticException : CqlCompilerException { + constructor(message: String?) : super(message) + + constructor( + message: String?, + severity: ErrorSeverity, + locator: TrackBack? + ) : super(message, severity, locator) + + constructor( + message: String?, + locator: TrackBack?, + cause: Throwable? + ) : super(message, locator, cause) + + constructor( + message: String?, + severity: ErrorSeverity, + locator: TrackBack?, + cause: Throwable? + ) : super(message, severity, locator, cause) +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlSyntaxException.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlSyntaxException.kt new file mode 100644 index 000000000..0f23747db --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlSyntaxException.kt @@ -0,0 +1,7 @@ +package org.cqframework.cql.cql2elm + +import org.cqframework.cql.cql2elm.tracking.TrackBack + +/** Created by Bryn on 3/27/2017. */ +class CqlSyntaxException(message: String?, locator: TrackBack?, cause: Throwable?) : + CqlCompilerException(message, locator, cause) diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlTranslator.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlTranslator.kt new file mode 100644 index 000000000..ba91c64dd --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlTranslator.kt @@ -0,0 +1,238 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.io.* +import org.antlr.v4.kotlinruntime.CharStream +import org.antlr.v4.kotlinruntime.CharStreams +import org.cqframework.cql.cql2elm.model.CompiledLibrary +import org.cqframework.cql.elm.serializing.ElmLibraryWriterFactory +import org.hl7.cql.model.* +import org.hl7.elm.r1.* + +class CqlTranslator( + namespaceInfo: NamespaceInfo?, + sourceInfo: VersionedIdentifier?, + `is`: CharStream, + libraryManager: LibraryManager +) { + enum class Format { + XML, + JSON, + COFFEE + } + + private val compiler = CqlCompiler(namespaceInfo, sourceInfo, libraryManager) + + init { + compiler.run(`is`) + } + + private fun toXml(library: Library): String { + return try { + convertToXml(library) + } catch (e: IOException) { + throw IllegalArgumentException("Could not convert library to XML.", e) + } + } + + private fun toJson(library: Library): String { + return try { + convertToJson(library) + } catch (e: IOException) { + throw IllegalArgumentException( + "Could not convert library to JSON using JAXB serializer.", + e + ) + } + } + + fun toXml(): String { + return toXml(compiler.library!!) + } + + fun toJson(): String { + return toJson(compiler.library!!) + } + + fun toELM(): Library? { + return compiler.library + } + + val translatedLibrary: CompiledLibrary? + get() = compiler.compiledLibrary + + fun toObject(): Any? { + return compiler.toObject() + } + + fun toRetrieves(): List? { + return compiler.toRetrieves() + } + + val libraries: Map + get() = compiler.libraries + + val exceptions: List? + // public Map getLibrariesAsXML() { + get() = compiler.exceptions + + val errors: List? + get() = compiler.errors + + val warnings: List? + get() = compiler.warnings + + val messages: List? + get() = compiler.messages + + @Suppress("TooManyFunctions") + companion object { + + @JvmStatic + fun fromText(cqlText: String, libraryManager: LibraryManager): CqlTranslator { + return CqlTranslator(null, null, CharStreams.fromString(cqlText), libraryManager) + } + + @JvmStatic + @Throws(IOException::class) + fun fromStream( + namespaceInfo: NamespaceInfo?, + cqlStream: InputStream, + libraryManager: LibraryManager + ): CqlTranslator { + return CqlTranslator( + namespaceInfo, + null, + CharStreams.fromStream(cqlStream), + libraryManager + ) + } + + @JvmStatic + @Throws(IOException::class) + fun fromStream(cqlStream: InputStream, libraryManager: LibraryManager): CqlTranslator { + return CqlTranslator(null, null, CharStreams.fromStream(cqlStream), libraryManager) + } + + @JvmStatic + @Throws(IOException::class) + fun fromStream( + namespaceInfo: NamespaceInfo?, + sourceInfo: VersionedIdentifier?, + cqlStream: InputStream, + libraryManager: LibraryManager + ): CqlTranslator { + return CqlTranslator( + namespaceInfo, + sourceInfo, + CharStreams.fromStream(cqlStream), + libraryManager + ) + } + + @JvmStatic + @Throws(IOException::class) + fun fromFile(cqlFileName: String, libraryManager: LibraryManager): CqlTranslator { + return CqlTranslator( + null, + getSourceInfo(cqlFileName), + CharStreams.fromStream(FileInputStream(cqlFileName)), + libraryManager + ) + } + + @JvmStatic + @Throws(IOException::class) + fun fromFile( + namespaceInfo: NamespaceInfo?, + cqlFileName: String, + libraryManager: LibraryManager + ): CqlTranslator { + return CqlTranslator( + namespaceInfo, + getSourceInfo(cqlFileName), + CharStreams.fromStream(FileInputStream(cqlFileName)), + libraryManager + ) + } + + @JvmStatic + @Throws(IOException::class) + fun fromFile(cqlFile: File, libraryManager: LibraryManager): CqlTranslator { + return CqlTranslator( + null, + getSourceInfo(cqlFile), + CharStreams.fromStream(FileInputStream(cqlFile)), + libraryManager + ) + } + + @JvmStatic + @Throws(IOException::class) + fun fromFile( + namespaceInfo: NamespaceInfo?, + cqlFile: File, + libraryManager: LibraryManager + ): CqlTranslator { + return CqlTranslator( + namespaceInfo, + getSourceInfo(cqlFile), + CharStreams.fromStream(FileInputStream(cqlFile)), + libraryManager + ) + } + + @JvmStatic + @Throws(IOException::class) + fun fromFile( + namespaceInfo: NamespaceInfo?, + sourceInfo: VersionedIdentifier?, + cqlFile: File, + libraryManager: LibraryManager + ): CqlTranslator { + return CqlTranslator( + namespaceInfo, + sourceInfo, + CharStreams.fromStream(FileInputStream(cqlFile)), + libraryManager + ) + } + + private fun getSourceInfo(cqlFileName: String): VersionedIdentifier { + return getSourceInfo(File(cqlFileName)) + } + + private fun getSourceInfo(cqlFile: File): VersionedIdentifier { + var name = cqlFile.name + val extensionIndex = name.lastIndexOf('.') + if (extensionIndex > 0) { + name = name.substring(0, extensionIndex) + } + val system: String? = + try { + cqlFile.canonicalPath + } catch (@Suppress("SwallowedException") e: IOException) { + cqlFile.absolutePath + } + return VersionedIdentifier().withId(name).withSystem(system) + } + + @Throws(IOException::class) + fun convertToXml(library: Library): String { + val writer = StringWriter() + ElmLibraryWriterFactory.getWriter(LibraryContentType.XML.mimeType()) + .write(library, writer) + return writer.buffer.toString() + } + + @JvmStatic + @Throws(IOException::class) + fun convertToJson(library: Library): String { + val writer = StringWriter() + ElmLibraryWriterFactory.getWriter(LibraryContentType.JSON.mimeType()) + .write(library, writer) + return writer.buffer.toString() + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlTranslatorOptions.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlTranslatorOptions.kt new file mode 100644 index 000000000..6a4712926 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/CqlTranslatorOptions.kt @@ -0,0 +1,33 @@ +package org.cqframework.cql.cql2elm + +import java.util.* + +class CqlTranslatorOptions { + enum class Format { + XML, + JSON, + COFFEE + } + + var cqlCompilerOptions: CqlCompilerOptions? = null + var formats: Set? = null + + fun withCqlCompilerOptions(cqlCompilerOptions: CqlCompilerOptions?): CqlTranslatorOptions { + this.cqlCompilerOptions = cqlCompilerOptions + return this + } + + fun withFormats(formats: Set?): CqlTranslatorOptions { + this.formats = formats + return this + } + + companion object { + @JvmStatic + fun defaultOptions(): CqlTranslatorOptions { + return CqlTranslatorOptions() + .withCqlCompilerOptions(CqlCompilerOptions.defaultOptions()) + .withFormats(EnumSet.of(Format.XML)) + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DataTypes.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DataTypes.kt new file mode 100644 index 000000000..dd0eebe57 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DataTypes.kt @@ -0,0 +1,44 @@ +package org.cqframework.cql.cql2elm + +import org.hl7.cql.model.DataType + +private const val UNKNOWN = "" + +object DataTypes { + fun verifyType(actualType: DataType?, expectedType: DataType?) { + require(subTypeOf(actualType, expectedType)) { + // ERROR: + "Expected an expression of type '${expectedType?.toLabel() ?: UNKNOWN}'," + + "but found an expression of type '${actualType?.toLabel() ?: UNKNOWN}'." + } + } + + fun verifyCast(targetType: DataType?, sourceType: DataType?) { + // Casting can be used for compatible types as well as subtypes and supertypes + require( + subTypeOf(targetType, sourceType) || + superTypeOf(targetType, sourceType) || + compatibleWith(sourceType, targetType) + ) { + // ERROR: + "Expression of type '${sourceType?.toLabel() ?: UNKNOWN}'" + + " cannot be cast as a value of type '${targetType?.toLabel() ?: UNKNOWN}'." + } + } + + fun equal(a: DataType?, b: DataType?): Boolean { + return a != null && b != null && a == b + } + + private fun compatibleWith(a: DataType?, b: DataType?): Boolean { + return a != null && b != null && a.isCompatibleWith(b) + } + + fun subTypeOf(a: DataType?, b: DataType?): Boolean { + return a != null && b != null && a.isSubTypeOf(b) + } + + private fun superTypeOf(a: DataType?, b: DataType?): Boolean { + return a != null && b != null && a.isSuperTypeOf(b) + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultLibrarySourceLoader.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultLibrarySourceLoader.kt new file mode 100644 index 000000000..3353e4f24 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultLibrarySourceLoader.kt @@ -0,0 +1,86 @@ +package org.cqframework.cql.cql2elm + +import java.io.InputStream +import java.nio.file.Path +import kotlin.collections.ArrayList +import org.hl7.cql.model.NamespaceAware +import org.hl7.cql.model.NamespaceManager +import org.hl7.elm.r1.VersionedIdentifier + +/** + * Used by LibraryManager to manage a set of library source providers that resolve library includes + * within CQL. Package private since it's not intended to be used outside the context of the + * instantiating LibraryManager instance. + */ +internal class DefaultLibrarySourceLoader : LibrarySourceLoader, NamespaceAware, PathAware { + private val providers: MutableList = ArrayList() + private var initialized: Boolean = false + + override fun registerProvider(provider: LibrarySourceProvider) { + if (namespaceManager != null && provider is NamespaceAware) { + provider.setNamespaceManager(namespaceManager!!) + } + if (path != null && provider is PathAware) { + provider.setPath(path!!) + } + providers.add(provider) + } + + private var path: Path? = null + + override fun setPath(path: Path) { + require(path.toFile().isDirectory) { "path '$path' is not a valid directory" } + + this.path = path + for (provider in getProviders()) { + if (provider is PathAware) { + provider.setPath(path) + } + } + } + + override fun clearProviders() { + providers.clear() + initialized = false + } + + private fun getProviders(): List { + if (!initialized) { + initialized = true + val it: Iterator = LibrarySourceProviderFactory.providers(false) + while (it.hasNext()) { + val provider: LibrarySourceProvider = it.next() + registerProvider(provider) + } + } + return providers + } + + override fun getLibrarySource(libraryIdentifier: VersionedIdentifier): InputStream { + var source: InputStream? = null + for (provider: LibrarySourceProvider in getProviders()) { + val localSource: InputStream? = provider.getLibrarySource(libraryIdentifier) + if (localSource != null) { + require(source == null) { + "Multiple sources found for library ${libraryIdentifier.id}, version ${libraryIdentifier.version}." + } + source = localSource + } + } + requireNotNull(source) { + "Could not load source for library ${libraryIdentifier.id}, version ${libraryIdentifier.version}." + } + return source + } + + private var namespaceManager: NamespaceManager? = null + + override fun setNamespaceManager(namespaceManager: NamespaceManager) { + this.namespaceManager = namespaceManager + for (provider: LibrarySourceProvider? in getProviders()) { + if (provider is NamespaceAware) { + (provider as NamespaceAware).setNamespaceManager(namespaceManager) + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultLibrarySourceProvider.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultLibrarySourceProvider.kt new file mode 100644 index 000000000..506110290 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultLibrarySourceProvider.kt @@ -0,0 +1,121 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.io.* +import java.nio.file.Path +import org.cqframework.cql.cql2elm.model.Version +import org.hl7.elm.r1.VersionedIdentifier + +// NOTE: This implementation is naive and assumes library file names will always take the form: +// [-].cql +// And further that will never contain dashes, and that will always be of the +// form +// [.[.]] +// Usage outside these boundaries will result in errors or incorrect behavior. +class DefaultLibrarySourceProvider(path: Path) : LibrarySourceProvider, PathAware { + private var path: Path? = null + + init { + this.setPath(path) + } + + override fun setPath(path: Path) { + require(path.toFile().isDirectory) { "path '$path' is not a valid directory" } + this.path = path + } + + @Suppress("CyclomaticComplexMethod", "NestedBlockDepth", "LongMethod") + override fun getLibrarySource(libraryIdentifier: VersionedIdentifier): InputStream? { + val currentPath = path + if (currentPath != null) { + val libraryName: String = libraryIdentifier.id!! + val libraryPath: Path = + currentPath.resolve( + "$libraryName${ + if (libraryIdentifier.version != null) ("-" + libraryIdentifier.version) + else "" + }.cql" + ) + var libraryFile: File? = libraryPath.toFile() + if (libraryFile?.exists() != true) { + val filter = FilenameFilter { _, name -> + name.startsWith(libraryName) && name.endsWith(".cql") + } + var mostRecentFile: File? = null + var mostRecent: Version? = null + val requestedVersion: Version? = + if (libraryIdentifier.version == null) null + else Version(libraryIdentifier.version!!) + for (file: File in currentPath.toFile().listFiles(filter)!!) { + var fileName: String = file.name + val indexOfExtension: Int = fileName.lastIndexOf(".") + if (indexOfExtension >= 0) { + fileName = fileName.substring(0, indexOfExtension) + } + val indexOfVersionSeparator: Int = fileName.indexOf("-") + if (indexOfVersionSeparator >= 0) { + val version = Version(fileName.substring(indexOfVersionSeparator + 1)) + // If the file has a version, make sure it is compatible with the version we + // are looking for + if ( + (indexOfVersionSeparator == libraryName.length && + requestedVersion == null || + version.compatibleWith(requestedVersion)) + ) { + @Suppress("ComplexCondition") + if ( + (mostRecent == null || + ((version.isComparable) && + (mostRecent.isComparable) && + (version > mostRecent))) + ) { + mostRecent = version + mostRecentFile = file + } else if (version.matchStrictly(mostRecent)) { + mostRecent = version + mostRecentFile = file + } + } + } else { + // If the file is named correctly, but has no version, consider it the most + // recent version + if ((fileName == libraryName) && mostRecent == null) { + mostRecentFile = file + } + } + } + + // Do not throw, allow the loader to throw, just report null + // if (mostRecentFile == null) { + // throw new IllegalArgumentException(String.format("Could not resolve most + // recent source library for + // library %s.", libraryIdentifier.getId())); + // } + libraryFile = mostRecentFile + } + try { + if (libraryFile != null) { + return FileInputStream(libraryFile) + } + } catch (e: FileNotFoundException) { + throw IllegalArgumentException( + "Could not load source for library ${libraryIdentifier.id}.", + e + ) + } + } + return null + } + + override fun getLibraryContent( + libraryIdentifier: VersionedIdentifier, + type: LibraryContentType + ): InputStream? { + if (LibraryContentType.CQL == type) { + return getLibrarySource(libraryIdentifier) + } + + return null + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultModelInfoProvider.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultModelInfoProvider.kt new file mode 100644 index 000000000..3eaae83f5 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/DefaultModelInfoProvider.kt @@ -0,0 +1,116 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.io.* +import java.nio.file.Path +import kotlinx.io.asSource +import kotlinx.io.buffered +import org.cqframework.cql.cql2elm.model.Version +import org.hl7.cql.model.ModelIdentifier +import org.hl7.cql.model.ModelInfoProvider +import org.hl7.elm_modelinfo.r1.ModelInfo +import org.hl7.elm_modelinfo.r1.serializing.ModelInfoReaderFactory + +// NOTE: This implementation assumes modelinfo file names will always take the form: +// -modelinfo[-].cql +// And further that will never contain dashes, and that will always be of the +// form +// [.[.]] +// Usage outside these boundaries will result in errors or incorrect behavior. +class DefaultModelInfoProvider() : ModelInfoProvider, PathAware { + constructor(path: Path) : this() { + this.setPath(path) + } + + private var path: Path? = null + + override fun setPath(path: Path) { + require(path.toFile().isDirectory) { "path '$path' is not a valid directory" } + this.path = path + } + + @Suppress("UnusedPrivateMember") + private fun checkPath() { + require(!(path == null || path!!.equals(""))) { + "Path is required for DefaultModelInfoProvider implementation" + } + } + + @Suppress("CyclomaticComplexMethod", "NestedBlockDepth", "LongMethod") + override fun load(modelIdentifier: ModelIdentifier): ModelInfo? { + val currentPath = path + if (currentPath != null) { + val modelName = modelIdentifier.id + val modelVersion = modelIdentifier.version + val modelPath = + currentPath.resolve( + "${modelName.lowercase()}-modelinfo${if (modelVersion != null) "-$modelVersion" else ""}.xml" + ) + var modelFile = modelPath.toFile() + if (!modelFile.exists()) { + val filter = FilenameFilter { _, name -> + name.startsWith(modelName.lowercase() + "-modelinfo") && name.endsWith(".xml") + } + var mostRecentFile: File? = null + var mostRecent: Version? = null + try { + val requestedVersion = if (modelVersion == null) null else Version(modelVersion) + for (file in currentPath.toFile().listFiles(filter)!!) { + var fileName = file.name + val indexOfExtension = fileName.lastIndexOf(".") + if (indexOfExtension >= 0) { + fileName = fileName.substring(0, indexOfExtension) + } + val fileNameComponents = + fileName + .split("-".toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + @Suppress("MagicNumber") + if (fileNameComponents.size == 3) { + val version = Version(fileNameComponents[2]) + if ( + requestedVersion == null || version.compatibleWith(requestedVersion) + ) { + @Suppress("ComplexCondition") + if ( + mostRecent == null || + version.isComparable && + mostRecent.isComparable && + version > mostRecent + ) { + mostRecent = version + mostRecentFile = file + } else if (version.matchStrictly(mostRecent)) { + mostRecent = version + mostRecentFile = file + } + } + } else { + if (mostRecent == null) { + mostRecentFile = file + } + } + } + modelFile = mostRecentFile!! + } catch (@Suppress("SwallowedException") e: IllegalArgumentException) { + // do nothing, if the version can't be understood as a semantic version, don't + // allow unspecified + // version resolution + } + } + try { + val inputStream: InputStream = FileInputStream(modelFile) + return ModelInfoReaderFactory.getReader("application/xml") + ?.read(inputStream.asSource().buffered()) + } catch (e: IOException) { + throw IllegalArgumentException( + "Could not load definition for model info ${modelIdentifier.id}.", + e + ) + } + } + return null + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/IdentifierContext.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/IdentifierContext.kt new file mode 100644 index 000000000..cdfb695d8 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/IdentifierContext.kt @@ -0,0 +1,33 @@ +package org.cqframework.cql.cql2elm + +import java.util.* +import org.hl7.elm.r1.Element + +/** + * Simple POJO using for identifier hider that maintains the identifier and Trackable type of the + * construct being evaluated. + */ +class IdentifierContext(val identifier: String, val trackableSubclass: Class?) { + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + val that = other as IdentifierContext + return identifier == that.identifier && trackableSubclass == that.trackableSubclass + } + + override fun hashCode(): Int { + return Objects.hash(identifier, trackableSubclass) + } + + override fun toString(): String { + return StringJoiner(", ", IdentifierContext::class.java.simpleName + "[", "]") + .add("identifier='$identifier'") + .add("elementSubclass=$trackableSubclass") + .toString() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryBuilder.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryBuilder.kt new file mode 100644 index 000000000..98214d638 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryBuilder.kt @@ -0,0 +1,3815 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.math.BigDecimal +import java.util.* +import javax.xml.namespace.QName +import org.cqframework.cql.cql2elm.model.* +import org.cqframework.cql.cql2elm.model.SystemLibraryHelper.load +import org.cqframework.cql.cql2elm.model.invocation.* +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.cqframework.cql.cql2elm.tracking.Trackable.trackbacks +import org.cqframework.cql.cql2elm.tracking.Trackable.withResultType +import org.cqframework.cql.elm.IdObjectFactory +import org.hl7.cql.model.* +import org.hl7.cql_annotations.r1.* +import org.hl7.cql_annotations.r1.ObjectFactory +import org.hl7.elm.r1.* + +private const val FP_THIS = "\$this" + +/** Created by Bryn on 12/29/2016. */ +@Suppress("LargeClass", "TooManyFunctions", "ForbiddenComment", "ReturnCount", "MaxLineLength") +class LibraryBuilder( + @JvmField + val namespaceInfo: NamespaceInfo?, // Note: allowed to be null, implies global namespace + val libraryManager: LibraryManager, + val objectFactory: IdObjectFactory +) { + enum class SignatureLevel { + /* + Indicates signatures will never be included in operator invocations + */ + None, + + /* + Indicates signatures will only be included in invocations if the declared signature of the resolve operator is different from the invocation signature + */ + Differing, + + /* + Indicates signatures will only be included in invocations if the function has multiple overloads with the same number of arguments as the invocation + */ + Overloads, + + /* + Indicates signatures will always be included in invocations + */ + All + } + + constructor( + libraryManager: LibraryManager, + objectFactory: IdObjectFactory + ) : this(null, libraryManager, objectFactory) + + // Only exceptions of severity Error + val errors: MutableList = ArrayList() + + // Only exceptions of severity Warning + val warnings: MutableList = ArrayList() + + // Only exceptions of severity Info + val messages: MutableList = ArrayList() + + // All exceptions + val exceptions: MutableList = ArrayList() + + private val models: MutableMap = LinkedHashMap() + private val nameTypeSpecifiers: + MutableMap> = + HashMap() + private val libraries: MutableMap = LinkedHashMap() + private val systemFunctionResolver: SystemFunctionResolver = SystemFunctionResolver(this) + private val expressionContext = Stack() + private val expressionDefinitions = ExpressionDefinitionContextStack() + private val functionDefs = Stack() + private val globalIdentifiers: Deque = ArrayDeque() + private val localIdentifierStack = Stack>() + private var literalContext = 0 + private var typeSpecifierContext = 0 + private val modelManager: ModelManager = libraryManager.modelManager + var defaultModel: Model? = null + private set(model) { + // The default model is the first model that is not System + if (field == null && model?.modelInfo?.name != "System") { + field = model + } + } + + var library: Library = + objectFactory + .createLibrary() + .withSchemaIdentifier( + objectFactory + .createVersionedIdentifier() + .withId("urn:hl7-org:elm") // TODO: Pull this from the ELM library namespace + .withVersion("r1") + ) + @JvmField var compiledLibrary = CompiledLibrary() + @JvmField val conversionMap = ConversionMap() + private val af = ObjectFactory() + private var listTraversal = true + private val options: CqlCompilerOptions = libraryManager.cqlCompilerOptions + private val cqlToElmInfo = af.createCqlToElmInfo() + private val typeBuilder = TypeBuilder(objectFactory, modelManager) + + fun enableListTraversal() { + listTraversal = true + } + + private fun setCompilerOptions(options: CqlCompilerOptions) { + if (options.options.contains(CqlCompilerOptions.Options.DisableListTraversal)) { + listTraversal = false + } + if (options.options.contains(CqlCompilerOptions.Options.DisableListDemotion)) { + conversionMap.isListDemotionEnabled = false + } + if (options.options.contains(CqlCompilerOptions.Options.DisableListPromotion)) { + conversionMap.isListPromotionEnabled = false + } + if (options.options.contains(CqlCompilerOptions.Options.EnableIntervalDemotion)) { + conversionMap.isIntervalDemotionEnabled = true + } + if (options.options.contains(CqlCompilerOptions.Options.EnableIntervalPromotion)) { + conversionMap.isIntervalPromotionEnabled = true + } + compatibilityLevel = options.compatibilityLevel + cqlToElmInfo.translatorOptions = options.toString() + cqlToElmInfo.signatureLevel = options.signatureLevel.name + } + + var compatibilityLevel: String? = null + set(compatibilityLevel) { + field = compatibilityLevel + if (compatibilityLevel != null) { + compatibilityVersion = Version(compatibilityLevel) + } + } + + val isCompatibilityLevel3: Boolean + get() = ("1.3" == compatibilityLevel) + + val isCompatibilityLevel4: Boolean + get() = ("1.4" == compatibilityLevel) + + private var compatibilityVersion: Version? = null + + init { + cqlToElmInfo.translatorVersion = + LibraryBuilder::class.java.getPackage().implementationVersion + library.annotation.add(cqlToElmInfo) + setCompilerOptions(options) + compiledLibrary.library = library + } + + fun isCompatibleWith(sinceCompatibilityLevel: String?): Boolean { + if (compatibilityVersion == null) { + // No compatibility version is specified, assume latest functionality + return true + } + require(!sinceCompatibilityLevel.isNullOrEmpty()) { + "Internal Translator Error: compatibility level is required to perform a compatibility check" + } + val sinceVersion = Version(sinceCompatibilityLevel) + return compatibilityVersion!!.compatibleWith(sinceVersion) + } + + fun checkCompatibilityLevel(featureName: String?, sinceCompatibilityLevel: String?) { + require(!featureName.isNullOrEmpty()) { + "Internal Translator Error: feature name is required to perform a compatibility check" + } + require(isCompatibleWith(sinceCompatibilityLevel)) { + "Feature $featureName was introduced in version $sinceCompatibilityLevel and so cannot be used at compatibility level $compatibilityLevel" + } + } + + /* + A "well-known" model name is one that is allowed to resolve without a namespace in a namespace-aware context + */ + fun isWellKnownModelName(unqualifiedIdentifier: String?): Boolean { + return if (namespaceInfo == null) { + false + } else modelManager.isWellKnownModelName(unqualifiedIdentifier) + } + + /* + A "well-known" library name is a library name that is allowed to resolve without a namespace in a namespace-aware context + */ + fun isWellKnownLibraryName(unqualifiedIdentifier: String?): Boolean { + return if (namespaceInfo == null) { + false + } else libraryManager.isWellKnownLibraryName(unqualifiedIdentifier) + } + + private fun loadModel(modelIdentifier: ModelIdentifier): Model { + val model = modelManager.resolveModel(modelIdentifier) + loadConversionMap(model) + return model + } + + fun getModel(modelIdentifier: ModelIdentifier, localIdentifier: String): Model { + var model: Model? = models[localIdentifier] + if (model == null) { + model = loadModel(modelIdentifier) + defaultModel = model + models[localIdentifier] = model + // Add the model using def to the output + buildUsingDef(modelIdentifier, model, localIdentifier) + } + require( + !(modelIdentifier.version != null && modelIdentifier.version != model.modelInfo.version) + ) { + "Could not load model information for model ${modelIdentifier.id}, version ${modelIdentifier.version} because version ${model.modelInfo.version} is already loaded." + } + return model + } + + fun getNamedTypeSpecifierResult( + namedTypeSpecifierIdentifier: String + ): ResultWithPossibleError? { + return nameTypeSpecifiers[namedTypeSpecifierIdentifier] + } + + fun addNamedTypeSpecifierResult( + namedTypeSpecifierIdentifier: String, + namedTypeSpecifierResult: ResultWithPossibleError + ) { + if (!nameTypeSpecifiers.containsKey(namedTypeSpecifierIdentifier)) { + nameTypeSpecifiers[namedTypeSpecifierIdentifier] = namedTypeSpecifierResult + } + } + + private fun loadConversionMap(model: Model?) { + for (conversion in model!!.getConversions()) { + conversionMap.add(conversion) + } + } + + private fun buildUsingDef( + modelIdentifier: ModelIdentifier, + model: Model?, + localIdentifier: String + ): UsingDef { + val usingDef = + objectFactory + .createUsingDef() + .withLocalIdentifier(localIdentifier) + .withVersion(modelIdentifier.version) + .withUri(model!!.modelInfo.url) + // TODO: Needs to write xmlns and schemalocation to the resulting ELM XML document... + addUsing(usingDef) + return usingDef + } + + fun hasUsings(): Boolean { + for (model in models.values) { + if (model!!.modelInfo.name != "System") { + return true + } + } + return false + } + + private fun addUsing(usingDef: UsingDef) { + if (library.usings == null) { + library.usings = objectFactory.createLibraryUsings() + } + library.usings!!.def.add(usingDef) + compiledLibrary.add(usingDef) + } + + @Suppress("NestedBlockDepth") + private fun resolveLabel(modelName: String?, label: String): ClassType? { + var result: ClassType? = null + if (modelName == null || (modelName == "")) { + for (model: Model? in models.values) { + val modelResult: ClassType? = model!!.resolveLabel(label) + if (modelResult != null) { + require(result == null) { + "Label $label is ambiguous between ${(result as ClassType).label} and ${modelResult.label}." + } + result = modelResult + } + } + } else { + result = getModel(modelName).resolveLabel(label) + } + return result + } + + @Suppress("NestedBlockDepth") + fun resolveContextName(modelName: String?, contextName: String): ModelContext? { + // Attempt to resolve as a label first + var result: ModelContext? = null + if (modelName == null || (modelName == "")) { + // Attempt to resolve in the default model if one is available + if (defaultModel != null) { + val modelResult: ModelContext? = defaultModel!!.resolveContextName(contextName) + if (modelResult != null) { + return modelResult + } + } + + // Otherwise, resolve across all models and throw for ambiguous resolution + for (model: Model? in models.values) { + val modelResult: ModelContext? = model!!.resolveContextName(contextName) + if (modelResult != null) { + require(result == null) { + "Context name $contextName is ambiguous between ${(result as ModelContext).name} and ${modelResult.name}." + } + result = modelResult + } + } + } else { + result = getModel(modelName).resolveContextName(contextName) + } + return result + } + + fun resolveTypeName(typeName: String): DataType? { + return resolveTypeName(null, typeName) + } + + @Suppress("NestedBlockDepth") + fun resolveTypeName(modelName: String?, typeName: String): DataType? { + // Attempt to resolve as a label first + var result: DataType? = resolveLabel(modelName, typeName) + if (result == null) { + if (modelName == null || (modelName == "")) { + // Attempt to resolve in the default model if one is available + if (defaultModel != null) { + val modelResult: DataType? = defaultModel!!.resolveTypeName(typeName) + if (modelResult != null) { + return modelResult + } + } + + // Otherwise, resolve across all models and throw for ambiguous resolution + for (model: Model? in models.values) { + val modelResult: DataType? = model!!.resolveTypeName(typeName) + if (modelResult != null) { + require(result == null) { + "Type name $typeName is ambiguous between ${(result as NamedType).name} and ${(modelResult as NamedType).name}." + } + result = modelResult + } + } + } else { + result = getModel(modelName).resolveTypeName(typeName) + } + } + + // Types introduced in 1.5: Long, Vocabulary, ValueSet, CodeSystem + if (result != null && result is NamedType) { + when ((result as NamedType).name) { + "System.Long", + "System.Vocabulary", + "System.CodeSystem", + "System.ValueSet" -> // NOTE: This is a hack to allow the new ToValueSet operator in + // FHIRHelpers for + // backwards-compatibility + // The operator still cannot be used in 1.4, but the definition will compile. + // This really should be + // being done with preprocessor directives, + // but that's a whole other project in and of itself. + require(!(!isCompatibleWith("1.5") && !isFHIRHelpers(compiledLibrary))) { + "The type ${(result as NamedType).name} was introduced in CQL 1.5 and cannot be referenced at compatibility level $compatibilityLevel" + } + } + } + return result + } + + private fun isFHIRHelpers(library: CompiledLibrary?): Boolean { + return (library != null) && + (library.identifier != null) && + (library.identifier!!.id != null) && + (library.identifier!!.id == "FHIRHelpers") + } + + fun resolveTypeSpecifier(typeSpecifier: String?): DataType? { + requireNotNull(typeSpecifier) { "typeSpecifier is null" } + + // typeSpecifier: simpleTypeSpecifier | intervalTypeSpecifier | listTypeSpecifier + // simpleTypeSpecifier: (identifier '.')? identifier + // intervalTypeSpecifier: 'interval' '<' typeSpecifier '>' + // listTypeSpecifier: 'list' '<' typeSpecifier '>' + return when { + typeSpecifier.lowercase(Locale.getDefault()).startsWith("interval<") -> { + val pointType = + resolveTypeSpecifier( + typeSpecifier.substring( + typeSpecifier.indexOf('<') + 1, + typeSpecifier.lastIndexOf('>') + ) + ) + IntervalType(pointType!!) + } + else -> + if (typeSpecifier.lowercase(Locale.getDefault()).startsWith("list<")) { + val elementType = + resolveTypeName( + typeSpecifier.substring( + typeSpecifier.indexOf('<') + 1, + typeSpecifier.lastIndexOf('>') + ) + ) + ListType(elementType!!) + } else if (typeSpecifier.indexOf(".") >= 0) { + val modelName = typeSpecifier.substring(0, typeSpecifier.indexOf(".")) + val typeName = typeSpecifier.substring(typeSpecifier.indexOf(".") + 1) + resolveTypeName(modelName, typeName) + } else { + resolveTypeName(typeSpecifier) + } + } + } + + fun resolveUsingRef(modelName: String): UsingDef? { + return compiledLibrary.resolveUsingRef(modelName) + } + + val systemModel: SystemModel + get() = // TODO: Support loading different versions of the system library + getModel(ModelIdentifier("System"), "System") as SystemModel + + fun getModel(modelName: String): Model { + val usingDef = resolveUsingRef(modelName) + if (usingDef == null && modelName == "FHIR") { + // Special case for FHIR-derived models that include FHIR Helpers + return modelManager.resolveModelByUri("http://hl7.org/fhir") + } + requireNotNull(usingDef) { "Could not resolve model name $modelName" } + return getModel(usingDef) + } + + private fun getModel(usingDef: UsingDef): Model { + return getModel( + ModelIdentifier( + id = NamespaceManager.getNamePart(usingDef.uri)!!, + system = NamespaceManager.getUriPart(usingDef.uri), + version = usingDef.version + ), + usingDef.localIdentifier!! + ) + } + + private fun loadSystemLibrary() { + val systemLibrary = load(systemModel, typeBuilder) + libraries[systemLibrary.identifier!!.id!!] = systemLibrary + loadConversionMap(systemLibrary) + } + + private fun loadConversionMap(library: CompiledLibrary) { + for (conversion in library.getConversions()) { + conversionMap.add(conversion) + } + } + + private val systemLibrary: CompiledLibrary + get() = resolveLibrary("System") + + fun resolveLibrary(identifier: String?): CompiledLibrary { + if (identifier != "System") { + checkLiteralContext() + } + return libraries[identifier] + ?: throw IllegalArgumentException("Could not resolve library name $identifier.") + } + + fun resolveNamespaceUri(namespaceName: String, mustResolve: Boolean): String? { + val namespaceUri = libraryManager.namespaceManager.resolveNamespaceUri(namespaceName) + require(!(namespaceUri == null && mustResolve)) { + "Could not resolve namespace name $namespaceName" + } + return namespaceUri + } + + private fun toErrorSeverity(severity: CqlCompilerException.ErrorSeverity): ErrorSeverity { + return when (severity) { + CqlCompilerException.ErrorSeverity.Info -> { + ErrorSeverity.INFO + } + CqlCompilerException.ErrorSeverity.Warning -> { + ErrorSeverity.WARNING + } + CqlCompilerException.ErrorSeverity.Error -> { + ErrorSeverity.ERROR + } + } + } + + private fun addException(e: CqlCompilerException) { + // Always add to the list of all exceptions + exceptions.add(e) + when (e.severity) { + CqlCompilerException.ErrorSeverity.Error -> { + errors.add(e) + } + CqlCompilerException.ErrorSeverity.Warning -> { + warnings.add(e) + } + CqlCompilerException.ErrorSeverity.Info -> { + messages.add(e) + } + } + } + + private fun shouldReport(errorSeverity: CqlCompilerException.ErrorSeverity): Boolean { + return when (options.errorLevel) { + CqlCompilerException.ErrorSeverity.Info -> + errorSeverity == CqlCompilerException.ErrorSeverity.Info || + errorSeverity == CqlCompilerException.ErrorSeverity.Warning || + errorSeverity == CqlCompilerException.ErrorSeverity.Error + CqlCompilerException.ErrorSeverity.Warning -> + (errorSeverity == CqlCompilerException.ErrorSeverity.Warning || + errorSeverity == CqlCompilerException.ErrorSeverity.Error) + CqlCompilerException.ErrorSeverity.Error -> + errorSeverity == CqlCompilerException.ErrorSeverity.Error + else -> throw IllegalArgumentException("Unknown error severity $errorSeverity") + } + } + + /** + * Record any errors while parsing in both the list of errors but also in the library itself so + * they can be processed easily by a remote client + * + * @param e the exception to record + */ + fun recordParsingException(e: CqlCompilerException) { + addException(e) + if (shouldReport(e.severity)) { + val err = af.createCqlToElmError() + err.message = e.message + err.errorType = + if (e is CqlSyntaxException) ErrorType.SYNTAX + else (if (e is CqlSemanticException) ErrorType.SEMANTIC else ErrorType.INTERNAL) + err.errorSeverity = toErrorSeverity(e.severity) + if (e.locator != null) { + if (e.locator.library != null) { + err.librarySystem = e.locator.library.system + err.libraryId = e.locator.library.id + err.libraryVersion = e.locator.library.version + } + err.startLine = e.locator.startLine + err.endLine = e.locator.endLine + err.startChar = e.locator.startChar + err.endChar = e.locator.endChar + } + if (e.cause != null && e.cause is CqlIncludeException) { + val incEx = e.cause as CqlIncludeException? + err.targetIncludeLibrarySystem = incEx!!.librarySystem + err.targetIncludeLibraryId = incEx.libraryId + err.targetIncludeLibraryVersionId = incEx.versionId + err.errorType = ErrorType.INCLUDE + } + library.annotation.add(err) + } + } + + fun beginTranslation() { + loadSystemLibrary() + } + + var libraryIdentifier: VersionedIdentifier? + get() = library.identifier + set(vid) { + library.identifier = vid + compiledLibrary.identifier = vid + } + + fun endTranslation() { + applyTargetModelMaps() + } + + fun canResolveLibrary(includeDef: IncludeDef): Boolean { + val libraryIdentifier = + VersionedIdentifier() + .withSystem(NamespaceManager.getUriPart(includeDef.path)) + .withId(NamespaceManager.getNamePart(includeDef.path)) + .withVersion(includeDef.version) + return libraryManager.canResolveLibrary(libraryIdentifier) + } + + fun addInclude(includeDef: IncludeDef) { + require(!(library.identifier == null || library.identifier!!.id == null)) { + "Unnamed libraries cannot reference other libraries." + } + if (library.includes == null) { + library.includes = objectFactory.createLibraryIncludes() + } + library.includes!!.def.add(includeDef) + compiledLibrary.add(includeDef) + val libraryIdentifier = + VersionedIdentifier() + .withSystem(NamespaceManager.getUriPart(includeDef.path)) + .withId(NamespaceManager.getNamePart(includeDef.path)) + .withVersion(includeDef.version) + val errors = ArrayList() + val referencedLibrary = libraryManager.resolveLibrary(libraryIdentifier, errors) + for (error in errors) { + recordParsingException(error) + } + + // Note that translation of a referenced library may result in implicit specification of the + // namespace + // In this case, the referencedLibrary will have a namespaceUri different from the currently + // resolved + // namespaceUri + // of the IncludeDef. + val currentNamespaceUri = NamespaceManager.getUriPart(includeDef.path) + @Suppress("ComplexCondition") + if ( + currentNamespaceUri == null && libraryIdentifier.system != null || + currentNamespaceUri != null && currentNamespaceUri != libraryIdentifier.system + ) { + includeDef.path = + NamespaceManager.getPath(libraryIdentifier.system, libraryIdentifier.id!!) + } + libraries[includeDef.localIdentifier!!] = referencedLibrary + loadConversionMap(referencedLibrary) + } + + fun addParameter(paramDef: ParameterDef) { + if (library.parameters == null) { + library.parameters = objectFactory.createLibraryParameters() + } + library.parameters!!.def.add(paramDef) + compiledLibrary.add(paramDef) + } + + fun addCodeSystem(cs: CodeSystemDef) { + if (library.codeSystems == null) { + library.codeSystems = objectFactory.createLibraryCodeSystems() + } + library.codeSystems!!.def.add(cs) + compiledLibrary.add(cs) + } + + fun addValueSet(vs: ValueSetDef) { + if (library.valueSets == null) { + library.valueSets = objectFactory.createLibraryValueSets() + } + library.valueSets!!.def.add(vs) + compiledLibrary.add(vs) + } + + fun addCode(cd: CodeDef) { + if (library.codes == null) { + library.codes = objectFactory.createLibraryCodes() + } + library.codes!!.def.add(cd) + compiledLibrary.add(cd) + } + + fun addConcept(cd: ConceptDef) { + if (library.concepts == null) { + library.concepts = objectFactory.createLibraryConcepts() + } + library.concepts!!.def.add(cd) + compiledLibrary.add(cd) + } + + fun addContext(cd: ContextDef) { + if (library.contexts == null) { + library.contexts = objectFactory.createLibraryContexts() + } + library.contexts!!.def.add(cd) + } + + fun addExpression(expDef: ExpressionDef) { + if (library.statements == null) { + library.statements = objectFactory.createLibraryStatements() + } + library.statements!!.def.add(expDef) + compiledLibrary.add(expDef) + } + + fun removeExpression(expDef: ExpressionDef) { + if (library.statements != null) { + library.statements!!.def.remove(expDef) + compiledLibrary.remove(expDef) + } + } + + fun resolve(identifier: String): ResolvedIdentifierContext { + return compiledLibrary.resolve(identifier) + } + + fun resolveIncludeRef(identifier: String): IncludeDef? { + return compiledLibrary.resolveIncludeRef(identifier) + } + + private fun resolveIncludeAlias(libraryIdentifier: VersionedIdentifier): String? { + return compiledLibrary.resolveIncludeAlias(libraryIdentifier) + } + + fun resolveCodeSystemRef(identifier: String): CodeSystemDef? { + return compiledLibrary.resolveCodeSystemRef(identifier) + } + + fun resolveValueSetRef(identifier: String): ValueSetDef? { + return compiledLibrary.resolveValueSetRef(identifier) + } + + fun resolveCodeRef(identifier: String): CodeDef? { + return compiledLibrary.resolveCodeRef(identifier) + } + + fun resolveConceptRef(identifier: String): ConceptDef? { + return compiledLibrary.resolveConceptRef(identifier) + } + + fun resolveParameterRef(identifier: String): ParameterDef? { + checkLiteralContext() + return compiledLibrary.resolveParameterRef(identifier) + } + + fun resolveExpressionRef(identifier: String): ExpressionDef? { + checkLiteralContext() + return compiledLibrary.resolveExpressionRef(identifier) + } + + fun findConversion( + fromType: DataType, + toType: DataType, + implicit: Boolean, + allowPromotionAndDemotion: Boolean + ): Conversion? { + return conversionMap.findConversion( + fromType, + toType, + implicit, + allowPromotionAndDemotion, + compiledLibrary.operatorMap + ) + } + + fun resolveUnaryCall( + libraryName: String?, + operatorName: String, + expression: UnaryExpression + ): Expression? { + return resolveCall( + libraryName, + operatorName, + UnaryExpressionInvocation(expression), + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + + fun resolveBinaryCall( + libraryName: String?, + operatorName: String, + expression: BinaryExpression + ): Expression? { + val invocation = resolveBinaryInvocation(libraryName, operatorName, expression) + return invocation?.expression + } + + @JvmOverloads + fun resolveBinaryInvocation( + libraryName: String?, + operatorName: String, + expression: BinaryExpression, + mustResolve: Boolean = true, + allowPromotionAndDemotion: Boolean = false + ): Invocation? { + return resolveInvocation( + libraryName, + operatorName, + BinaryExpressionInvocation(expression), + mustResolve, + allowPromotionAndDemotion, + false + ) + } + + fun resolveBinaryCall( + libraryName: String?, + operatorName: String, + expression: BinaryExpression, + mustResolve: Boolean, + allowPromotionAndDemotion: Boolean + ): Expression? { + val invocation = + resolveBinaryInvocation( + libraryName, + operatorName, + expression, + mustResolve, + allowPromotionAndDemotion + ) + return invocation?.expression + } + + fun resolveTernaryCall( + libraryName: String?, + operatorName: String, + expression: TernaryExpression + ): Expression? { + return resolveCall( + libraryName, + operatorName, + TernaryExpressionInvocation(expression), + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + + fun resolveNaryCall( + libraryName: String?, + operatorName: String, + expression: NaryExpression? + ): Expression? { + return resolveCall( + libraryName, + operatorName, + NaryExpressionInvocation(expression!!), + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + + fun resolveAggregateCall( + libraryName: String?, + operatorName: String, + expression: AggregateExpression + ): Expression? { + return resolveCall( + libraryName, + operatorName, + AggregateExpressionInvocation(expression), + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + + private inner class BinaryWrapper(var left: Expression, var right: Expression) + + @Suppress("NestedBlockDepth") + private fun normalizeListTypes(left: Expression, right: Expression): BinaryWrapper { + // for union of lists + // collect list of types in either side + // cast both operands to a choice type with all types + + // for intersect of lists + // collect list of types in both sides + // cast both operands to a choice type with all types + // TODO: cast the result to a choice type with only types in both sides + + // for difference of lists + // collect list of types in both sides + // cast both operands to a choice type with all types + // TODO: cast the result to the initial type of the left + var left = left + var right = right + if (left.resultType is ListType && right.resultType is ListType) { + val leftListType = left.resultType as ListType + val rightListType = right.resultType as ListType + @Suppress("ComplexCondition") + if ( + !(leftListType.isSuperTypeOf(rightListType) || + rightListType.isSuperTypeOf(leftListType)) && + !(leftListType.isCompatibleWith(rightListType) || + rightListType.isCompatibleWith(leftListType)) + ) { + val elementTypes: MutableSet = HashSet() + if (leftListType.elementType is ChoiceType) { + for (choice in (leftListType.elementType as ChoiceType).types) { + elementTypes.add(choice) + } + } else { + elementTypes.add(leftListType.elementType) + } + if (rightListType.elementType is ChoiceType) { + for (choice in (rightListType.elementType as ChoiceType).types) { + elementTypes.add(choice) + } + } else { + elementTypes.add(rightListType.elementType) + } + if (elementTypes.size > 1) { + val targetType = ListType(ChoiceType(elementTypes)) + left = + objectFactory + .createAs() + .withOperand(left) + .withAsTypeSpecifier(dataTypeToTypeSpecifier(targetType)) + left.resultType = targetType + right = + objectFactory + .createAs() + .withOperand(right) + .withAsTypeSpecifier(dataTypeToTypeSpecifier(targetType)) + right.resultType = targetType + } + } + } + return BinaryWrapper(left, right) + } + + fun resolveUnion(left: Expression, right: Expression): Expression { + // Create right-leaning bushy instead of left-deep + var left = left + var right = right + if (left is Union) { + val leftUnion = left + val leftUnionLeft = leftUnion.operand[0] + val leftUnionRight = leftUnion.operand[1] + if (leftUnionLeft is Union && leftUnionRight !is Union) { + left = leftUnionLeft + right = resolveUnion(leftUnionRight, right) + } + } + + // TODO: Take advantage of nary unions + val wrapper = normalizeListTypes(left, right) + val union = objectFactory.createUnion().withOperand(wrapper.left, wrapper.right) + resolveNaryCall("System", "Union", union) + return union + } + + fun resolveIntersect(left: Expression, right: Expression): Expression { + // Create right-leaning bushy instead of left-deep + var left = left + var right = right + if (left is Intersect) { + val leftIntersect = left + val leftIntersectLeft = leftIntersect.operand[0] + val leftIntersectRight = leftIntersect.operand[1] + if (leftIntersectLeft is Intersect && leftIntersectRight !is Intersect) { + left = leftIntersectLeft + right = resolveIntersect(leftIntersectRight, right) + } + } + + // TODO: Take advantage of nary intersect + val wrapper = normalizeListTypes(left, right) + val intersect = objectFactory.createIntersect().withOperand(wrapper.left, wrapper.right) + resolveNaryCall("System", "Intersect", intersect) + return intersect + } + + fun resolveExcept(left: Expression, right: Expression): Expression { + val wrapper = normalizeListTypes(left, right) + val except = objectFactory.createExcept().withOperand(wrapper.left, wrapper.right) + resolveNaryCall("System", "Except", except) + return except + } + + @Suppress("CyclomaticComplexMethod") + fun resolveIn(left: Expression, right: Expression): Expression { + @Suppress("ComplexCondition") + if ( + right is ValueSetRef || + (isCompatibleWith("1.5") && + right.resultType!!.isCompatibleWith(resolveTypeName("System", "ValueSet")!!) && + right.resultType != resolveTypeName("System", "Any")) + ) { + if (left.resultType is ListType) { + val anyIn = + objectFactory + .createAnyInValueSet() + .withCodes(left) + .withValueset(if (right is ValueSetRef) right else null) + .withValuesetExpression(if (right is ValueSetRef) null else right) + resolveCall("System", "AnyInValueSet", AnyInValueSetInvocation(anyIn)) + return anyIn + } + val inValueSet = + objectFactory + .createInValueSet() + .withCode(left) + .withValueset(if (right is ValueSetRef) right else null) + .withValuesetExpression(if (right is ValueSetRef) null else right) + resolveCall("System", "InValueSet", InValueSetInvocation(inValueSet)) + return inValueSet + } + @Suppress("ComplexCondition") + if ( + right is CodeSystemRef || + (isCompatibleWith("1.5") && + right.resultType!!.isCompatibleWith( + resolveTypeName("System", "CodeSystem")!! + ) && + right.resultType != resolveTypeName("System", "Any")) + ) { + if (left.resultType is ListType) { + val anyIn = + objectFactory + .createAnyInCodeSystem() + .withCodes(left) + .withCodesystem(if (right is CodeSystemRef) right else null) + .withCodesystemExpression(if (right is CodeSystemRef) null else right) + resolveCall("System", "AnyInCodeSystem", AnyInCodeSystemInvocation(anyIn)) + return anyIn + } + val inCodeSystem = + objectFactory + .createInCodeSystem() + .withCode(left) + .withCodesystem(if (right is CodeSystemRef) right else null) + .withCodesystemExpression(if (right is CodeSystemRef) null else right) + resolveCall("System", "InCodeSystem", InCodeSystemInvocation(inCodeSystem)) + return inCodeSystem + } + val inExpression = objectFactory.createIn().withOperand(left, right) + resolveBinaryCall("System", "In", inExpression) + return inExpression + } + + fun resolveContains(left: Expression, right: Expression): Expression { + // TODO: Add terminology overloads + val contains = objectFactory.createContains().withOperand(left, right) + resolveBinaryCall("System", "Contains", contains) + return contains + } + + fun resolveIn( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Expression? { + val result = resolveInInvocation(left, right, dateTimePrecision) + return result?.expression + } + + private fun resolveInInvocation( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Invocation? { + val inExpression = + objectFactory.createIn().withOperand(left, right).withPrecision(dateTimePrecision) + return resolveBinaryInvocation("System", "In", inExpression) + } + + fun resolveProperIn( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Expression? { + val result = resolveProperInInvocation(left, right, dateTimePrecision) + return result?.expression + } + + private fun resolveProperInInvocation( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Invocation? { + val properIn = + objectFactory.createProperIn().withOperand(left, right).withPrecision(dateTimePrecision) + return resolveBinaryInvocation("System", "ProperIn", properIn) + } + + fun resolveContains( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Expression? { + val result = resolveContainsInvocation(left, right, dateTimePrecision) + return result?.expression + } + + private fun resolveContainsInvocation( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Invocation? { + val contains = + objectFactory.createContains().withOperand(left, right).withPrecision(dateTimePrecision) + return resolveBinaryInvocation("System", "Contains", contains) + } + + fun resolveProperContains( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Expression? { + val result = resolveProperContainsInvocation(left, right, dateTimePrecision) + return result?.expression + } + + private fun resolveProperContainsInvocation( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Invocation? { + val properContains = + objectFactory + .createProperContains() + .withOperand(left, right) + .withPrecision(dateTimePrecision) + return resolveBinaryInvocation("System", "ProperContains", properContains) + } + + private fun getTypeScore(resolution: OperatorResolution?): Int { + var typeScore = ConversionMap.ConversionScore.ExactMatch.score + for (operand in resolution!!.operator.signature.operandTypes) { + typeScore += ConversionMap.getTypePrecedenceScore(operand) + } + return typeScore + } + + @Suppress("NestedBlockDepth") + private fun lowestScoringInvocation(primary: Invocation?, secondary: Invocation?): Expression? { + if (primary != null) { + if (secondary != null) { + if (secondary.resolution!!.score < primary.resolution!!.score) { + return secondary.expression + } else if (primary.resolution!!.score < secondary.resolution!!.score) { + return primary.expression + } + if (primary.resolution!!.score == secondary.resolution!!.score) { + val primaryTypeScore = getTypeScore(primary.resolution) + val secondaryTypeScore = getTypeScore(secondary.resolution) + return if (secondaryTypeScore < primaryTypeScore) { + secondary.expression + } else if (primaryTypeScore < secondaryTypeScore) { + primary.expression + } else { + // ERROR: + val message = + StringBuilder("Call to operator ") + .append(primary.resolution!!.operator.name) + .append("/") + .append(secondary.resolution!!.operator.name) + .append(" is ambiguous with: ") + .append("\n - ") + .append(primary.resolution!!.operator.name) + .append(primary.resolution!!.operator.signature) + .append("\n - ") + .append(secondary.resolution!!.operator.name) + .append(secondary.resolution!!.operator.signature) + throw IllegalArgumentException(message.toString()) + } + } + } + return primary.expression + } + return secondary?.expression + } + + fun resolveIncludes( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Expression? { + val includes = + objectFactory.createIncludes().withOperand(left, right).withPrecision(dateTimePrecision) + val includesInvocation = + resolveBinaryInvocation( + "System", + "Includes", + includes, + mustResolve = false, + allowPromotionAndDemotion = false + ) + val contains = + objectFactory.createContains().withOperand(left, right).withPrecision(dateTimePrecision) + val containsInvocation = + resolveBinaryInvocation( + "System", + "Contains", + contains, + mustResolve = false, + allowPromotionAndDemotion = false + ) + return lowestScoringInvocation(includesInvocation, containsInvocation) + ?: resolveBinaryCall("System", "Includes", includes) + + // Neither operator resolved, so force a resolve to throw + } + + fun resolveProperIncludes( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Expression? { + val properIncludes = + objectFactory + .createProperIncludes() + .withOperand(left, right) + .withPrecision(dateTimePrecision) + val properIncludesInvocation = + resolveBinaryInvocation( + "System", + "ProperIncludes", + properIncludes, + mustResolve = false, + allowPromotionAndDemotion = false + ) + val properContains = + objectFactory + .createProperContains() + .withOperand(left, right) + .withPrecision(dateTimePrecision) + val properContainsInvocation = + resolveBinaryInvocation( + "System", + "ProperContains", + properContains, + mustResolve = false, + allowPromotionAndDemotion = false + ) + return lowestScoringInvocation(properIncludesInvocation, properContainsInvocation) + ?: resolveBinaryCall("System", "ProperIncludes", properIncludes) + + // Neither operator resolved, so force a resolve to throw + } + + fun resolveIncludedIn( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Expression? { + val includedIn = + objectFactory + .createIncludedIn() + .withOperand(left, right) + .withPrecision(dateTimePrecision) + val includedInInvocation = + resolveBinaryInvocation( + "System", + "IncludedIn", + includedIn, + mustResolve = false, + allowPromotionAndDemotion = false + ) + val inExpression = + objectFactory.createIn().withOperand(left, right).withPrecision(dateTimePrecision) + val inInvocation = + resolveBinaryInvocation( + "System", + "In", + inExpression, + mustResolve = false, + allowPromotionAndDemotion = false + ) + return lowestScoringInvocation(includedInInvocation, inInvocation) + ?: resolveBinaryCall("System", "IncludedIn", includedIn) + + // Neither operator resolved, so force a resolve to throw + } + + fun resolveProperIncludedIn( + left: Expression, + right: Expression, + dateTimePrecision: DateTimePrecision? + ): Expression? { + val properIncludedIn = + objectFactory + .createProperIncludedIn() + .withOperand(left, right) + .withPrecision(dateTimePrecision) + val properIncludedInInvocation = + resolveBinaryInvocation( + "System", + "ProperIncludedIn", + properIncludedIn, + mustResolve = false, + allowPromotionAndDemotion = false + ) + val properIn = + objectFactory.createProperIn().withOperand(left, right).withPrecision(dateTimePrecision) + val properInInvocation = + resolveBinaryInvocation( + "System", + "ProperIn", + properIn, + mustResolve = false, + allowPromotionAndDemotion = false + ) + return lowestScoringInvocation(properIncludedInInvocation, properInInvocation) + ?: resolveBinaryCall("System", "ProperIncludedIn", properIncludedIn) + + // Neither operator resolved, so force a resolve to throw + } + + fun resolveCall( + libraryName: String?, + operatorName: String, + invocation: Invocation + ): Expression? { + return resolveCall( + libraryName, + operatorName, + invocation, + mustResolve = true, + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + + private fun resolveCall( + libraryName: String?, + operatorName: String, + invocation: Invocation, + allowPromotionAndDemotion: Boolean, + allowFluent: Boolean + ): Expression? { + return resolveCall( + libraryName, + operatorName, + invocation, + true, + allowPromotionAndDemotion, + allowFluent + ) + } + + @Suppress("LongParameterList") + private fun resolveCall( + libraryName: String?, + operatorName: String, + invocation: Invocation, + mustResolve: Boolean, + allowPromotionAndDemotion: Boolean, + allowFluent: Boolean + ): Expression? { + val result = + resolveInvocation( + libraryName, + operatorName, + invocation, + mustResolve, + allowPromotionAndDemotion, + allowFluent + ) + return result?.expression + } + + fun resolveInvocation( + libraryName: String?, + operatorName: String, + invocation: Invocation, + allowPromotionAndDemotion: Boolean, + allowFluent: Boolean + ): Invocation? { + return resolveInvocation( + libraryName, + operatorName, + invocation, + true, + allowPromotionAndDemotion, + allowFluent + ) + } + + @Suppress("LongParameterList") + private fun buildCallContext( + libraryName: String?, + operatorName: String, + operands: Iterable, + mustResolve: Boolean, + allowPromotionAndDemotion: Boolean, + allowFluent: Boolean + ): CallContext { + val dataTypes: MutableList = ArrayList() + for (operand in operands) { + require(!(operand == null || operand.resultType == null)) { + "Could not determine signature for invocation of operator ${if (libraryName == null) "" else "$libraryName."}$operatorName." + } + dataTypes.add(operand.resultType!!) + } + return CallContext( + libraryName, + operatorName, + allowPromotionAndDemotion, + allowFluent, + mustResolve, + dataTypes + ) + } + + @Suppress("LongParameterList", "LongMethod") + fun resolveInvocation( + libraryName: String?, + operatorName: String, + invocation: Invocation, + mustResolve: Boolean = true, + allowPromotionAndDemotion: Boolean = false, + allowFluent: Boolean = false + ): Invocation? { + val operands: Iterable = invocation.operands + val callContext = + buildCallContext( + libraryName, + operatorName, + operands, + mustResolve, + allowPromotionAndDemotion, + allowFluent + ) + val resolution = resolveCall(callContext) + if (resolution == null && !mustResolve) { + return null + } + checkOperator(callContext, resolution) + val convertedOperands: MutableList = ArrayList() + val operandIterator = operands.iterator() + val signatureTypes = resolution!!.operator.signature.operandTypes.iterator() + val conversionIterator = + if (resolution.hasConversions()) resolution.conversions.iterator() else null + while (operandIterator.hasNext()) { + var operand = operandIterator.next() + val conversion = conversionIterator?.next() + if (conversion != null) { + operand = convertExpression(operand, conversion) + } + val signatureType = signatureTypes.next() + operand = pruneChoices(operand, signatureType) + convertedOperands.add(operand) + } + invocation.operands = convertedOperands + @Suppress("ComplexCondition") + if ( + options.signatureLevel == SignatureLevel.All || + (options.signatureLevel == SignatureLevel.Differing && + resolution.operator.signature != callContext.signature) || + options.signatureLevel == SignatureLevel.Overloads && + resolution.operatorHasOverloads + ) { + invocation.signature = + dataTypesToTypeSpecifiers(resolution.operator.signature.operandTypes) + } else if (resolution.operatorHasOverloads && resolution.operator.libraryName != "System") { + // NOTE: Because system functions only deal with CQL system-defined types, and there is + // one and only one + // runtime representation of each system-defined type, there is no possibility of + // ambiguous overload + // resolution with system functions + // WARNING: + reportWarning( + """ + The function ${resolution.operator.libraryName}.${resolution.operator.name} has multiple overloads + and due to the SignatureLevel setting (${options.signatureLevel.name}), + the overload signature is not being included in the output. + This may result in ambiguous function resolution + at runtime, consider setting the SignatureLevel to Overloads or All + to ensure that the output includes sufficient + information to support correct overload selection at runtime. + """ + .trimIndent() + .replace("\n", " "), + invocation.expression + ) + } + invocation.resultType = resolution.operator.resultType + if (resolution.libraryIdentifier != null) { + resolution.libraryName = resolveIncludeAlias(resolution.libraryIdentifier!!) + } + invocation.resolution = resolution + return invocation + } + + private fun pruneChoices( + expression: Expression, + @Suppress("UnusedParameter") targetType: DataType + ): Expression { + // TODO: In theory, we could collapse expressions that are unnecessarily broad, given the + // targetType (type + // leading) + // This is a placeholder for where this functionality would be added in the future. + return expression + } + + fun resolveFunctionDefinition(fd: FunctionDef): Operator? { + val libraryName = compiledLibrary.identifier!!.id + val operatorName = fd.name + val dataTypes: MutableList = ArrayList() + for (operand in fd.operand) { + requireNotNull(operand.resultType) { + "Could not determine signature for invocation of operator ${if (libraryName == null) "" else "$libraryName."}$operatorName." + } + dataTypes.add(operand.resultType!!) + } + val callContext = + CallContext( + compiledLibrary.identifier!!.id, + fd.name!!, + false, + fd.fluent != null && fd.fluent!!, + false, + dataTypes + ) + // Resolve exact, no conversion map + return compiledLibrary.resolveCall(callContext, conversionMap)?.operator + } + + @Suppress("NestedBlockDepth") + private fun resolveCall(callContext: CallContext): OperatorResolution? { + var result: OperatorResolution? + if (callContext.libraryName.isNullOrEmpty()) { + result = compiledLibrary.resolveCall(callContext, conversionMap) + if (result == null) { + result = systemLibrary.resolveCall(callContext, conversionMap) + if (result == null && callContext.allowFluent) { + // attempt to resolve in each non-system included library, in order of + // inclusion, first resolution + // wins + for (lib in libraries.values) { + if (lib != systemLibrary) { + result = lib.resolveCall(callContext, conversionMap) + if (result != null) { + break + } + } + } + } + /* + // Implicit resolution is only allowed for the system library functions. + // Except for fluent functions, so consider whether we should have an ambiguous warnings for fluent function resolution? + for (CompiledLibrary library : libraries.values()) { + OperatorResolution libraryResult = library.resolveCall(callContext, libraryBuilder.getConversionMap()); + if (libraryResult != null) { + if (result != null) { + throw new IllegalArgumentException(String.format("Operator name %s is ambiguous between %s and %s.", + callContext.getOperatorName(), result.getOperator().getName(), libraryResult.getOperator().getName())); + } + + result = libraryResult; + } + } + */ + } + } else { + result = resolveLibrary(callContext.libraryName).resolveCall(callContext, conversionMap) + } + if (result != null) { + checkAccessLevel( + result.operator.libraryName, + result.operator.name, + result.operator.accessLevel + ) + } + return result + } + + private fun isInterFunctionAccess(f1: String, f2: String?): Boolean { + return if (f1.isNotBlank() && !f2.isNullOrBlank()) { + !f1.equals(f2, ignoreCase = true) + } else false + } + + private fun checkOperator(callContext: CallContext, resolution: OperatorResolution?) { + requireNotNull(resolution) { + // ERROR: + "Could not resolve call to operator ${callContext.operatorName} with signature ${callContext.signature}." + } + require(!(resolution.operator.fluent && !callContext.allowFluent)) { + "Operator ${callContext.operatorName} with signature ${callContext.signature} is a fluent function and can only be invoked with fluent syntax." + } + require( + !(callContext.allowFluent && !resolution.operator.fluent && !resolution.allowFluent) + ) { + "Invocation of operator ${callContext.operatorName} with signature ${callContext.signature} uses fluent syntax, but the operator is not defined as a fluent function." + } + } + + fun checkAccessLevel( + libraryName: String?, + objectName: String?, + accessModifier: AccessModifier + ) { + if ( + accessModifier == AccessModifier.PRIVATE && + isInterFunctionAccess(library.identifier!!.id!!, libraryName) + ) { + // ERROR: + throw CqlSemanticException( + "Identifier $objectName in library $libraryName is marked private and cannot be referenced from another library." + ) + } + } + + fun resolveFunction( + libraryName: String?, + functionName: String, + paramList: List + ): Expression? { + return resolveFunction( + libraryName, + functionName, + paramList, + mustResolve = true, + allowPromotionAndDemotion = false, + allowFluent = false + ) + ?.expression + } + + private fun buildFunctionRef( + libraryName: String?, + functionName: String, + paramList: Iterable + ): FunctionRef { + val functionRef = + objectFactory.createFunctionRef().withLibraryName(libraryName).withName(functionName) + for (param in paramList) { + functionRef.operand.add(param) + } + return functionRef + } + + @Suppress("LongParameterList") + fun resolveFunction( + libraryName: String?, + functionName: String, + paramList: List, + mustResolve: Boolean, + allowPromotionAndDemotion: Boolean, + allowFluent: Boolean + ): Invocation? { + var functionRef: FunctionRef? = buildFunctionRef(libraryName, functionName, paramList) + + // Attempt normal resolution, but don't require one + var invocation: Invocation = FunctionRefInvocation(functionRef!!) + functionRef = + resolveCall( + functionRef.libraryName, + functionRef.name!!, + invocation, + false, + allowPromotionAndDemotion, + allowFluent + ) + as FunctionRef? + if (functionRef != null) { + if ("System" == invocation.resolution!!.operator.libraryName) { + val systemFun = + buildFunctionRef( + libraryName, + functionName, + paramList + ) // Rebuild the fun from the original arguments, otherwise it will resolve with + // conversions in place + val systemFunctionInvocation = + systemFunctionResolver.resolveSystemFunction(systemFun) + if (systemFunctionInvocation != null) { + return systemFunctionInvocation + } + } else { + // If the invocation is to a local function or a function in a non-system library, + // check literal context + if (mustResolve) { + checkLiteralContext() + } + } + } + + // If it didn't resolve, there are two possibilities + // 1. It is a special system function resolution that only resolves with the + // systemFunctionResolver + // 2. It is an error condition that needs to be reported + if (functionRef == null) { + functionRef = buildFunctionRef(libraryName, functionName, paramList) + invocation = FunctionRefInvocation(functionRef) + if (!allowFluent) { + // Only attempt to resolve as a system function if + // this is not a fluent call, or it is a required resolution + val systemFunction = systemFunctionResolver.resolveSystemFunction(functionRef) + if (systemFunction != null) { + return systemFunction + } + checkLiteralContext() + } + functionRef = + resolveCall( + functionRef.libraryName, + functionRef.name!!, + invocation, + mustResolve, + allowPromotionAndDemotion, + allowFluent + ) + as FunctionRef? + if (functionRef == null) { + return null + } + } + return invocation + } + + fun verifyComparable(dataType: DataType) { + val left = objectFactory.createLiteral().withResultType(dataType) as Expression + val right = objectFactory.createLiteral().withResultType(dataType) as Expression + val comparison: BinaryExpression = objectFactory.createLess().withOperand(left, right) + resolveBinaryCall("System", "Less", comparison) + } + + @JvmOverloads + fun convertExpression( + expression: Expression, + targetType: DataType, + implicit: Boolean = true + ): Expression { + val conversion = findConversion(expression.resultType!!, targetType, implicit, false) + if (conversion != null) { + return convertExpression(expression, conversion) + } + DataTypes.verifyType(expression.resultType, targetType) + return expression + } + + private fun convertListExpression(expression: Expression?, conversion: Conversion): Expression { + val fromType: ListType = conversion.fromType as ListType + val toType: ListType = conversion.toType as ListType + return objectFactory + .createQuery() + .withSource( + objectFactory + .createAliasedQuerySource() + .withAlias("X") + .withExpression(expression) + .withResultType(fromType) + ) + .withReturn( + objectFactory + .createReturnClause() + .withDistinct(false) + .withExpression( + convertExpression( + objectFactory + .createAliasRef() + .withName("X") + .withResultType(fromType.elementType), + conversion.conversion!! + ) + ) + .withResultType(toType) + ) + .withResultType(toType) + } + + private fun reportWarning(message: String, expression: Element?) { + val trackback = + if (expression != null && expression.trackbacks.isNotEmpty()) expression.trackbacks[0] + else null + val warning = + CqlSemanticException(message, CqlCompilerException.ErrorSeverity.Warning, trackback) + recordParsingException(warning) + } + + private fun demoteListExpression(expression: Expression?, conversion: Conversion): Expression { + val fromType = conversion.fromType as ListType + val singletonFrom = objectFactory.createSingletonFrom().withOperand(expression) + singletonFrom.resultType = fromType.elementType + resolveUnaryCall("System", "SingletonFrom", singletonFrom) + // WARNING: + reportWarning("List-valued expression was demoted to a singleton.", expression) + return if (conversion.conversion != null) { + convertExpression(singletonFrom, conversion.conversion) + } else { + singletonFrom + } + } + + private fun promoteListExpression(expression: Expression, conversion: Conversion): Expression { + var expression = expression + if (conversion.conversion != null) { + expression = convertExpression(expression, conversion.conversion) + } + if (expression.resultType == resolveTypeName("System", "Boolean")) { + // WARNING: + reportWarning("Boolean-valued expression was promoted to a list.", expression) + } + return resolveToList(expression) + } + + fun resolveToList(expression: Expression?): Expression { + // Use a ToList operator here to avoid duplicate evaluation of the operand. + val toList = objectFactory.createToList().withOperand(expression) + toList.resultType = ListType(expression!!.resultType!!) + return toList + } + + private fun demoteIntervalExpression( + expression: Expression?, + conversion: Conversion + ): Expression { + val fromType = conversion.fromType as IntervalType + val pointFrom = objectFactory.createPointFrom().withOperand(expression) + pointFrom.resultType = fromType.pointType + resolveUnaryCall("System", "PointFrom", pointFrom) + // WARNING: + reportWarning("Interval-valued expression was demoted to a point.", expression) + return if (conversion.conversion != null) { + convertExpression(pointFrom, conversion.conversion) + } else { + pointFrom + } + } + + private fun promoteIntervalExpression( + expression: Expression, + conversion: Conversion + ): Expression { + var expression = expression + if (conversion.conversion != null) { + expression = convertExpression(expression, conversion.conversion) + } + return resolveToInterval(expression) + } + + // When promoting a point to an interval, if the point is null, the result is null, rather than + // constructing an + // interval + // with null boundaries + private fun resolveToInterval(expression: Expression?): Expression { + val condition = objectFactory.createIf() + condition.condition = buildIsNull(expression) + condition.then = buildNull(IntervalType(expression!!.resultType!!)) + val toInterval = + objectFactory + .createInterval() + .withLow(expression) + .withHigh(expression) + .withLowClosed(true) + .withHighClosed(true) + toInterval.resultType = IntervalType(expression.resultType!!) + condition.`else` = (toInterval) + condition.resultType = resolveTypeName("System", "Boolean") + return condition + } + + private fun convertIntervalExpression( + expression: Expression?, + conversion: Conversion + ): Expression { + val fromType: IntervalType = conversion.fromType as IntervalType + val toType: IntervalType = conversion.toType as IntervalType + return objectFactory + .createInterval() + .withLow( + convertExpression( + objectFactory + .createProperty() + .withSource(expression) + .withPath("low") + .withResultType(fromType.pointType), + conversion.conversion!! + ) + ) + .withLowClosedExpression( + objectFactory + .createProperty() + .withSource(expression) + .withPath("lowClosed") + .withResultType(resolveTypeName("System", "Boolean")) + ) + .withHigh( + convertExpression( + objectFactory + .createProperty() + .withSource(expression) + .withPath("high") + .withResultType(fromType.pointType), + conversion.conversion + ) + ) + .withHighClosedExpression( + objectFactory + .createProperty() + .withSource(expression) + .withPath("highClosed") + .withResultType(resolveTypeName("System", "Boolean")) + ) + .withResultType(toType) + } + + fun buildAs(expression: Expression?, asType: DataType?): As { + val result = objectFactory.createAs().withOperand(expression).withResultType(asType) + if (result.resultType is NamedType) { + result.asType = dataTypeToQName(result.resultType) + } else { + result.asTypeSpecifier = dataTypeToTypeSpecifier(result.resultType) + } + return result + } + + fun buildIs(expression: Expression?, isType: DataType?): Is { + val result = + objectFactory + .createIs() + .withOperand(expression) + .withResultType(resolveTypeName("System", "Boolean")) + if (isType is NamedType) { + result.isType = dataTypeToQName(isType) + } else { + result.isTypeSpecifier = dataTypeToTypeSpecifier(isType) + } + return result + } + + fun buildNull(nullType: DataType?): Null { + val result = objectFactory.createNull().withResultType(nullType) + if (nullType is NamedType) { + result.resultTypeName = dataTypeToQName(nullType) + } else { + result.resultTypeSpecifier = dataTypeToTypeSpecifier(nullType) + } + return result + } + + private fun buildIsNull(expression: Expression?): IsNull { + val isNull = objectFactory.createIsNull().withOperand(expression) + isNull.resultType = resolveTypeName("System", "Boolean") + return isNull + } + + private fun buildIsNotNull(expression: Expression?): Not { + val isNull = buildIsNull(expression) + val not = objectFactory.createNot().withOperand(isNull) + not.resultType = resolveTypeName("System", "Boolean") + return not + } + + fun buildMinimum(dataType: DataType?): MinValue { + val minimum = objectFactory.createMinValue() + minimum.valueType = dataTypeToQName(dataType) + minimum.resultType = dataType + return minimum + } + + fun buildMaximum(dataType: DataType?): MaxValue { + val maximum = objectFactory.createMaxValue() + maximum.valueType = dataTypeToQName(dataType) + maximum.resultType = dataType + return maximum + } + + fun buildPredecessor(source: Expression?): Expression { + val result = objectFactory.createPredecessor().withOperand(source) + resolveUnaryCall("System", "Predecessor", result) + return result + } + + fun buildSuccessor(source: Expression?): Expression { + val result = objectFactory.createSuccessor().withOperand(source) + resolveUnaryCall("System", "Successor", result) + return result + } + + @Suppress("LongMethod", "CyclomaticComplexMethod", "NestedBlockDepth") + fun convertExpression(expression: Expression, conversion: Conversion): Expression { + return if ( + conversion.isCast && + (conversion.fromType.isSuperTypeOf(conversion.toType) || + conversion.fromType.isCompatibleWith(conversion.toType)) + ) { + // Otherwise, the choice is narrowing and a run-time As is required (to use only the + // expected target + // types) + if ( + conversion.fromType is ChoiceType && + conversion.toType is ChoiceType && + (conversion.fromType).isSubSetOf(conversion.toType) + ) { + // conversion between compatible choice types requires no cast (i.e. + // choice can be + // safely passed to choice + return expression + } + val castedOperand = buildAs(expression, conversion.toType) + collapseTypeCase(castedOperand) + } else + @Suppress("ComplexCondition") + when { + conversion.isCast && + conversion.conversion != null && + (conversion.fromType.isSuperTypeOf(conversion.conversion.fromType) || + conversion.fromType.isCompatibleWith(conversion.conversion.fromType)) -> { + val castedOperand = buildAs(expression, conversion.conversion.fromType) + var result = convertExpression(castedOperand, conversion.conversion) + if (conversion.hasAlternativeConversions()) { + val caseResult = objectFactory.createCase() + caseResult.resultType = result.resultType + caseResult.caseItem.add( + objectFactory + .createCaseItem() + .withWhen(buildIs(expression, conversion.conversion.fromType)) + .withThen(result) + ) + for (alternative: Conversion in conversion.getAlternativeConversions()) { + caseResult.caseItem.add( + objectFactory + .createCaseItem() + .withWhen(buildIs(expression, alternative.fromType)) + .withThen( + convertExpression( + buildAs(expression, alternative.fromType), + alternative + ) + ) + ) + } + caseResult.withElse(buildNull(result.resultType)) + result = caseResult + } + result + } + conversion.isListConversion -> { + convertListExpression(expression, conversion) + } + conversion.isListDemotion -> { + demoteListExpression(expression, conversion) + } + conversion.isListPromotion -> { + promoteListExpression(expression, conversion) + } + conversion.isIntervalConversion -> { + convertIntervalExpression(expression, conversion) + } + conversion.isIntervalDemotion -> { + demoteIntervalExpression(expression, conversion) + } + conversion.isIntervalPromotion -> { + promoteIntervalExpression(expression, conversion) + } + conversion.operator != null -> { + val functionRef = + objectFactory + .createFunctionRef() + .withLibraryName(conversion.operator.libraryName) + .withName(conversion.operator.name) + .withOperand(expression) + val systemFunctionInvocation = + systemFunctionResolver.resolveSystemFunction(functionRef) + if (systemFunctionInvocation != null) { + return systemFunctionInvocation.expression + } + resolveCall( + functionRef.libraryName, + functionRef.name!!, + FunctionRefInvocation(functionRef), + allowPromotionAndDemotion = false, + allowFluent = false + ) + functionRef + } + else -> { + when (conversion.toType) { + resolveTypeName("System", "Boolean") -> { + objectFactory + .createToBoolean() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "Integer") -> { + objectFactory + .createToInteger() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "Long") -> { + objectFactory + .createToLong() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "Decimal") -> { + objectFactory + .createToDecimal() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "String") -> { + objectFactory + .createToString() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "Date") -> { + objectFactory + .createToDate() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "DateTime") -> { + objectFactory + .createToDateTime() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "Time") -> { + objectFactory + .createToTime() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "Quantity") -> { + objectFactory + .createToQuantity() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "Ratio") -> { + objectFactory + .createToRatio() + .withOperand(expression) + .withResultType(conversion.toType) + } + resolveTypeName("System", "Concept") -> { + objectFactory + .createToConcept() + .withOperand(expression) + .withResultType(conversion.toType) + } + else -> { + val convertedOperand = + objectFactory + .createConvert() + .withOperand(expression) + .withResultType(conversion.toType) + if (convertedOperand.resultType is NamedType) { + convertedOperand.toType = + dataTypeToQName(convertedOperand.resultType) + } else { + convertedOperand.toTypeSpecifier = + dataTypeToTypeSpecifier(convertedOperand.resultType) + } + convertedOperand + } + } + } + } + } + + /** + * If the operand to an As is a "type case", meaning a case expression whose only cases have the + * form: when X is T then X as T If one of the type cases is the same type as the As, the + * operand of the As can be set to the operand of the type case with the same type, optimizing + * the case as effectively a no-op + * + * @param asExpression + * @return + */ + @Suppress("NestedBlockDepth") + private fun collapseTypeCase(asExpression: As): Expression { + if (asExpression.operand is Case) { + val c = asExpression.operand as Case + if (isTypeCase(c)) { + for (ci in c.caseItem) { + if (DataTypes.equal(asExpression.resultType, ci.then!!.resultType)) { + return ci.then!! + } + } + } + } + return asExpression + } + + private fun isTypeCase(c: Case): Boolean { + if (c.comparand != null) { + return false + } + for (ci in c.caseItem) { + if (ci.`when` !is Is) { + return false + } + if (ci.then!!.resultType == null) { + return false + } + } + if (c.`else` !is Null) { + return false + } + return c.resultType is ChoiceType + } + + fun verifyType(actualType: DataType, expectedType: DataType) { + if (expectedType.isSuperTypeOf(actualType) || actualType.isCompatibleWith(expectedType)) { + return + } + val conversion = + findConversion( + actualType, + expectedType, + implicit = true, + allowPromotionAndDemotion = false + ) + if (conversion != null) { + return + } + DataTypes.verifyType(actualType, expectedType) + } + + fun findCompatibleType(first: DataType?, second: DataType?): DataType? { + if (first == null || second == null) { + return null + } + if (first == DataType.ANY) { + return second + } + if (second == DataType.ANY) { + return first + } + if (first.isSuperTypeOf(second) || second.isCompatibleWith(first)) { + return first + } + if (second.isSuperTypeOf(first) || first.isCompatibleWith(second)) { + return second + } + + // If either side is a choice type, don't allow conversions because they will incorrectly + // eliminate choices + // based on convertibility + if (!(first is ChoiceType || second is ChoiceType)) { + var conversion = + findConversion(second, first, implicit = true, allowPromotionAndDemotion = false) + if (conversion != null) { + return first + } + conversion = + findConversion(first, second, implicit = true, allowPromotionAndDemotion = false) + if (conversion != null) { + return second + } + } + return null + } + + fun ensureCompatibleTypes(first: DataType?, second: DataType): DataType? { + val compatibleType = findCompatibleType(first, second) + if (compatibleType != null) { + return compatibleType + } + if (first != null && !second.isSubTypeOf(first)) { + return ChoiceType(first, second) + } + + // The above construction of a choice type guarantees this will never be hit + DataTypes.verifyType(second, first) + return first + } + + fun ensureCompatible(expression: Expression?, targetType: DataType?): Expression { + if (targetType == null) { + return objectFactory.createNull() + } + return if (!targetType.isSuperTypeOf(expression!!.resultType!!)) { + convertExpression(expression, targetType, true) + } else expression + } + + fun enforceCompatible(expression: Expression?, targetType: DataType?): Expression { + if (targetType == null) { + return objectFactory.createNull() + } + return if (!targetType.isSuperTypeOf(expression!!.resultType!!)) { + convertExpression(expression, targetType, false) + } else expression + } + + private fun createLiteral(value: String?, type: String): Literal { + val resultType = resolveTypeName("System", type) + val result = + objectFactory + .createLiteral() + .withValue(value) + .withValueType(dataTypeToQName(resultType)) + result.resultType = resultType + return result + } + + fun createLiteral(string: String?): Literal { + return createLiteral(string, "String") + } + + fun createLiteral(bool: Boolean): Literal { + return createLiteral(bool.toString(), "Boolean") + } + + fun createLiteral(integer: Int): Literal { + return createLiteral(integer.toString(), "Integer") + } + + fun createLiteral(value: Double): Literal { + return createLiteral(value.toString(), "Decimal") + } + + fun createNumberLiteral(value: String): Literal { + val resultType = + resolveTypeName("System", if (value.contains(".")) "Decimal" else "Integer") + val result = + objectFactory + .createLiteral() + .withValue(value) + .withValueType(dataTypeToQName(resultType)) + result.resultType = resultType + return result + } + + fun createLongNumberLiteral(value: String?): Literal { + val resultType = resolveTypeName("System", "Long") + val result = + objectFactory + .createLiteral() + .withValue(value) + .withValueType(dataTypeToQName(resultType)) + result.resultType = resultType + return result + } + + private fun validateUnit(unit: String) { + when (unit) { + "year", + "years", + "month", + "months", + "week", + "weeks", + "day", + "days", + "hour", + "hours", + "minute", + "minutes", + "second", + "seconds", + "millisecond", + "milliseconds" -> {} + else -> validateUcumUnit(unit) + } + } + + fun ensureUcumUnit(unit: String): String { + when (unit) { + "year", + "years" -> return "a" + "month", + "months" -> return "mo" + "week", + "weeks" -> return "wk" + "day", + "days" -> return "d" + "hour", + "hours" -> return "h" + "minute", + "minutes" -> return "min" + "second", + "seconds" -> return "s" + "millisecond", + "milliseconds" -> return "ms" + else -> validateUcumUnit(unit) + } + return unit + } + + private fun validateUcumUnit(unit: String) { + val message = libraryManager.ucumService.validate(unit) + require(message == null) { message!! } + } + + fun createQuantity(value: BigDecimal?, unit: String): Quantity { + validateUnit(unit) + val result = objectFactory.createQuantity().withValue(value).withUnit(unit) + val resultType = resolveTypeName("System", "Quantity") + result.resultType = resultType + return result + } + + fun createRatio(numerator: Quantity?, denominator: Quantity?): Ratio { + val result = + objectFactory.createRatio().withNumerator(numerator).withDenominator(denominator) + val resultType = resolveTypeName("System", "Ratio") + result.resultType = resultType + return result + } + + fun createInterval( + low: Expression?, + lowClosed: Boolean, + high: Expression?, + highClosed: Boolean + ): Interval { + val result: Interval = + objectFactory + .createInterval() + .withLow(low) + .withLowClosed(lowClosed) + .withHigh(high) + .withHighClosed(highClosed) + val pointType: DataType? = + ensureCompatibleTypes(result.low!!.resultType, result.high!!.resultType!!) + result.resultType = IntervalType(pointType!!) + result.low = ensureCompatible(result.low, pointType) + result.high = ensureCompatible(result.high, pointType) + return result + } + + fun dataTypeToQName(type: DataType?): QName { + return typeBuilder.dataTypeToQName(type) + } + + private fun dataTypesToTypeSpecifiers(types: List): List { + return typeBuilder.dataTypesToTypeSpecifiers(types) + } + + fun dataTypeToTypeSpecifier(type: DataType?): TypeSpecifier { + return typeBuilder.dataTypeToTypeSpecifier(type) + } + + fun resolvePath(sourceType: DataType?, path: String): DataType? { + // TODO: This is using a naive implementation for now... needs full path support (but not + // full FluentPath + // support...) + var sourceType: DataType? = sourceType + val identifiers: Array = + path.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (i in identifiers.indices) { + val resolution: PropertyResolution? = resolveProperty(sourceType, identifiers[i]) + sourceType = resolution!!.type + // Actually, this doesn't matter for this call, we're just resolving the type... + // if (!resolution.getTargetMap().equals(identifiers[i])) { + // throw new IllegalArgumentException(String.format("Identifier %s references an + // element with a target + // mapping defined and cannot be resolved as part of a path", identifiers[i])); + // } + } + return sourceType + } + + // TODO: Support case-insensitive models + @Suppress("LongMethod", "CyclomaticComplexMethod", "NestedBlockDepth", "ThrowsCount") + @JvmOverloads + fun resolveProperty( + sourceType: DataType?, + identifier: String, + mustResolve: Boolean = true + ): PropertyResolution? { + var currentType: DataType? = sourceType + while (currentType != null) { + when { + currentType is ClassType -> { + val classType: ClassType = currentType + if (identifier.startsWith("?") && isCompatibleWith("1.5")) { + val searchPath: String = identifier.substring(1) + for (s: SearchType in classType.getSearches()) { + if ((s.name == searchPath)) { + return PropertyResolution(s) + } + } + } else { + for (e: ClassTypeElement in classType.elements) { + if ((e.name == identifier)) { + require(!e.prohibited) { + "Element ${e.name} cannot be referenced because it is marked prohibited in type ${(currentType as ClassType).name}." + } + return PropertyResolution(e) + } + } + } + } + currentType is TupleType -> { + for (e: TupleTypeElement in currentType.elements) { + if ((e.name == identifier)) { + return PropertyResolution(e) + } + } + } + currentType is IntervalType -> { + return when (identifier) { + "low", + "high" -> PropertyResolution(currentType.pointType, identifier) + "lowClosed", + "highClosed" -> + PropertyResolution((resolveTypeName("System", "Boolean"))!!, identifier) + else -> // ERROR: + throw IllegalArgumentException( + "Invalid interval property name $identifier." + ) + } + } + currentType is ChoiceType -> { + // TODO: Issue a warning if the property does not resolve against every type in + // the + // choice + + // Resolve the property against each type in the choice + val resultTypes: MutableSet = HashSet() + val resultTargetMaps: MutableMap = HashMap() + var name: String? = null + for (choice: DataType in currentType.types) { + val resolution: PropertyResolution? = + resolveProperty(choice, identifier, false) + if (resolution != null) { + resultTypes.add(resolution.type) + if (resolution.targetMap != null) { + if (resultTargetMaps.containsKey(resolution.type)) { + require( + resultTargetMaps[resolution.type] == resolution.targetMap + ) { + "Inconsistent target maps ${resultTargetMaps[resolution.type]} and ${resolution.targetMap} for choice type ${resolution.type}" + } + } else { + resultTargetMaps[resolution.type] = resolution.targetMap + } + } + if (name == null) { + name = resolution.name + } else + require(name == resolution.name) { + "Inconsistent property resolution for choice type $choice (was $name, is ${resolution.name})" + } + } + } + + // The result type is a choice of all the resolved types + if (resultTypes.size > 1) { + return PropertyResolution(ChoiceType(resultTypes), name!!, resultTargetMaps) + } + if (resultTypes.size == 1) { + return PropertyResolution( + resultTypes.iterator().next(), + name!!, + resultTargetMaps + ) + } + } + currentType is ListType && listTraversal -> { + // NOTE: FHIRPath path traversal support + // Resolve property as a list of items of property of the element type + val resolution: PropertyResolution? = + resolveProperty(currentType.elementType, identifier) + return PropertyResolution(ListType(resolution!!.type), (resolution.targetMap)!!) + } + } + if (currentType.baseType != DataType.ANY) { + currentType = currentType.baseType + } else { + break + } + } + require(!mustResolve) { + // ERROR: + "Member $identifier not found for type ${sourceType?.toLabel()}." + } + return null + } + + @Suppress("LongMethod", "CyclomaticComplexMethod", "ThrowsCount") + fun resolveIdentifier(identifier: String, mustResolve: Boolean): Expression? { + // An Identifier will always be: + // 1: The name of an alias + // 2: The name of a query define clause + // 3: The name of an expression + // 4: The name of a parameter + // 5: The name of a valueset + // 6: The name of a codesystem + // 7: The name of a code + // 8: The name of a concept + // 9: The name of a library + // 10: The name of a property on a specific context + // 11: An unresolved identifier error is thrown + + // In a type specifier context, return the identifier as a Literal for resolution as a type + // by the caller + if (inTypeSpecifierContext()) { + return this.createLiteral(identifier) + } + + // In the sort clause of a plural query, names may be resolved based on the result type of + // the query + val resultElement: Expression? = resolveQueryResultElement(identifier) + if (resultElement != null) { + return resultElement + } + + // In the case of a $this alias, names may be resolved as implicit property references + val thisElement: Expression? = resolveQueryThisElement(identifier) + if (thisElement != null) { + return thisElement + } + if ((identifier == "\$index")) { + val result: Iteration = objectFactory.createIteration() + result.resultType = resolveTypeName("System", "Integer") + return result + } + if ((identifier == "\$total")) { + val result: Total = objectFactory.createTotal() + result.resultType = + resolveTypeName( + "System", + "Decimal" + ) // TODO: This isn't right, but we don't set up a query for the Aggregate operator + // right + // now... + return result + } + val alias: AliasedQuerySource? = resolveAlias(identifier) + if (alias != null) { + val result: AliasRef = objectFactory.createAliasRef().withName(identifier) + if (alias.resultType is ListType) { + result.resultType = (alias.resultType as ListType).elementType + } else { + result.resultType = alias.resultType + } + return result + } + val let: LetClause? = resolveQueryLet(identifier) + if (let != null) { + val result: QueryLetRef = objectFactory.createQueryLetRef().withName(identifier) + result.resultType = let.resultType + return result + } + val operandRef: OperandRef? = resolveOperandRef(identifier) + if (operandRef != null) { + return operandRef + } + val resolvedIdentifierContext: ResolvedIdentifierContext = resolve(identifier) + val element = resolvedIdentifierContext.exactMatchElement + if (element != null) { + if (element is ExpressionDef) { + checkLiteralContext() + val expressionRef: ExpressionRef = + objectFactory.createExpressionRef().withName(element.name) + expressionRef.resultType = getExpressionDefResultType(element) + requireNotNull(expressionRef.resultType) { + // ERROR: + "Could not validate reference to expression ${expressionRef.name} because its definition contains errors." + } + return expressionRef + } + if (element is ParameterDef) { + checkLiteralContext() + val parameterRef: ParameterRef = + objectFactory.createParameterRef().withName(element.name) + parameterRef.resultType = element.resultType + requireNotNull(parameterRef.resultType) { + // ERROR: + "Could not validate reference to parameter ${parameterRef.name} because its definition contains errors." + } + return parameterRef + } + if (element is ValueSetDef) { + checkLiteralContext() + val valuesetRef: ValueSetRef = + objectFactory.createValueSetRef().withName(element.name) + valuesetRef.resultType = element.resultType + requireNotNull(valuesetRef.resultType) { + // ERROR: + "Could not validate reference to valueset ${valuesetRef.name} because its definition contains errors." + } + if (isCompatibleWith("1.5")) { + valuesetRef.preserve = true + } + return valuesetRef + } + if (element is CodeSystemDef) { + checkLiteralContext() + val codesystemRef: CodeSystemRef = + objectFactory.createCodeSystemRef().withName(element.name) + codesystemRef.resultType = element.resultType + requireNotNull(codesystemRef.resultType) { + // ERROR: + "Could not validate reference to codesystem ${codesystemRef.name} because its definition contains errors." + } + return codesystemRef + } + if (element is CodeDef) { + checkLiteralContext() + val codeRef: CodeRef = objectFactory.createCodeRef().withName(element.name) + codeRef.resultType = element.resultType + requireNotNull(codeRef.resultType) { + // ERROR: + "Could not validate reference to code ${codeRef.name} because its definition contains errors." + } + return codeRef + } + if (element is ConceptDef) { + checkLiteralContext() + val conceptRef: ConceptRef = objectFactory.createConceptRef().withName(element.name) + conceptRef.resultType = element.resultType + requireNotNull(conceptRef.resultType) { + // ERROR: + "Could not validate reference to concept ${conceptRef.name} because its definition contains error." + } + return conceptRef + } + if (element is IncludeDef) { + checkLiteralContext() + return LibraryRef(objectFactory.nextId(), element.localIdentifier) + } + } + + // If no other resolution occurs, and we are in a specific context, and there is a parameter + // with the same name + // as the context, + // the identifier may be resolved as an implicit property reference on that context. + val parameterRef: ParameterRef? = resolveImplicitContext() + if (parameterRef != null) { + val resolution: PropertyResolution? = + resolveProperty(parameterRef.resultType, identifier, false) + if (resolution != null) { + var contextAccessor: Expression? = + buildProperty( + parameterRef, + resolution.name, + resolution.isSearch, + resolution.type + ) + contextAccessor = applyTargetMap(contextAccessor, resolution.targetMap) + return contextAccessor + } + } + if (mustResolve) { + // ERROR: + var message = resolvedIdentifierContext.warnCaseInsensitiveIfApplicable() + if (message == null) { + message = "Could not resolve identifier $identifier in the current library." + } + + throw IllegalArgumentException(message) + } + return null + } + + /** + * An implicit context is one where the context has the same name as a parameter. Implicit + * contexts are used to allow FHIRPath expressions to resolve on the implicit context of the + * expression + * + * For example, in a Patient context, with a parameter of type Patient, the expression + * `birthDate` resolves to a property reference. + * + * @return A reference to the parameter providing the implicit context value + */ + fun resolveImplicitContext(): ParameterRef? { + if (!inLiteralContext() && inSpecificContext()) { + val resolvedIdentifierContext: ResolvedIdentifierContext = + resolve(currentExpressionContext()) + val optParameterDef: Optional = + resolvedIdentifierContext.getElementOfType(ParameterDef::class.java) + if (optParameterDef.isPresent) { + val contextParameter: ParameterDef = optParameterDef.get() + checkLiteralContext() + val parameterRef: ParameterRef = + objectFactory.createParameterRef().withName(contextParameter.name) + parameterRef.resultType = contextParameter.resultType + requireNotNull(parameterRef.resultType) { + // ERROR: + "Could not validate reference to parameter ${parameterRef.name} because its definition contains errors." + } + return parameterRef + } + } + return null + } + + fun buildProperty( + scope: String?, + path: String?, + isSearch: Boolean, + resultType: DataType? + ): Property { + return if (isSearch) { + val result = objectFactory.createSearch().withScope(scope).withPath(path) + result.resultType = resultType + result + } else { + val result = objectFactory.createProperty().withScope(scope).withPath(path) + result.resultType = resultType + result + } + } + + fun buildProperty( + source: Expression?, + path: String?, + isSearch: Boolean, + resultType: DataType? + ): Property { + return if (isSearch) { + val result = objectFactory.createSearch().withSource(source).withPath(path) + result.resultType = resultType + result + } else { + val result = objectFactory.createProperty().withSource(source).withPath(path) + result.resultType = resultType + result + } + } + + @Suppress("NestedBlockDepth") + private fun getModelMapping(sourceContext: Expression?): VersionedIdentifier? { + var result: VersionedIdentifier? = null + if (library.usings != null) { + for (usingDef in library.usings!!.def) { + val model = getModel(usingDef) + if (model.modelInfo.targetUrl != null) { + if (result != null) { + reportWarning( + "Duplicate mapped model ${model.modelInfo.name}:${model.modelInfo.targetUrl}${ + if (model.modelInfo.targetVersion != null) + "|" + model.modelInfo.targetVersion + else "" + }", + sourceContext + ) + } + result = + objectFactory + .createVersionedIdentifier() + .withId(model.modelInfo.name) + .withSystem(model.modelInfo.targetUrl) + .withVersion(model.modelInfo.targetVersion) + } + } + } + return result + } + + private fun ensureLibraryIncluded(libraryName: String, sourceContext: Expression?) { + var includeDef = compiledLibrary.resolveIncludeRef(libraryName) + if (includeDef == null) { + val modelMapping = getModelMapping(sourceContext) + var path = libraryName + if (namespaceInfo != null && modelMapping != null && modelMapping.system != null) { + path = NamespaceManager.getPath(modelMapping.system, path) + } + includeDef = + objectFactory.createIncludeDef().withLocalIdentifier(libraryName).withPath(path) + if (modelMapping != null) { + includeDef.version = modelMapping.version + } + compiledLibrary.add(includeDef) + } + } + + private fun applyTargetModelMaps() { + if (library.usings != null) { + for (usingDef in library.usings!!.def) { + val model = getModel(usingDef) + if (model.modelInfo.targetUrl != null) { + usingDef.uri = model.modelInfo.targetUrl + usingDef.version = model.modelInfo.targetVersion + } + } + } + } + + @Suppress("LongMethod", "CyclomaticComplexMethod", "NestedBlockDepth", "ThrowsCount") + fun applyTargetMap(source: Expression?, targetMap: String?): Expression? { + var targetMap: String? = targetMap + if (targetMap == null || (targetMap == "null")) { + return source + } + + // TODO: Consider whether the mapping should remove this in the ModelInfo...this is really a + // FHIR-specific + // hack... + // Remove any "choice" paths... + targetMap = targetMap.replace("[x]", "") + + // TODO: This only works for simple mappings, nested mappings will require the targetMap.g4 + // parser + // Supported target mapping syntax: + // %value. + // Resolves as a property accessor with the given source and as the path + // (%value) + // Resolves as a function ref with the given function name and the source as an operand + // :;:... + // Semi-colon delimited list of type names and associated maps + // Resolves as a case with whens for each type, with target mapping applied per the target + // map for that type + // %parent.[=,=,...]. + // Resolves as a replacement of the property on which it appears + // Replaces the path of the property on which it appears with the given qualified path, + // which then becomes the + // source of a query with a where clause with criteria built for each comparison in the + // indexer + // If there is a trailing qualified path, the query is wrapped in a singletonFrom and a + // property access + // Any other target map results in an exception + when { + targetMap.contains(";") -> { + val typeCases: Array = + targetMap.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val c: Case = objectFactory.createCase() + for (typeCase: String in typeCases) { + if (typeCase.isNotEmpty()) { + val splitIndex: Int = typeCase.indexOf(':') + require(splitIndex > 0) { "Malformed type case in targetMap $targetMap" } + val typeCaseElement: String = typeCase.substring(0, splitIndex) + val typeCaseType: DataType? = resolveTypeName(typeCaseElement) + val typeCaseMap: String = typeCase.substring(splitIndex + 1) + val ci: CaseItem = + objectFactory + .createCaseItem() + .withWhen( + objectFactory + .createIs() + .withOperand(applyTargetMap(source, typeCaseMap)) + .withIsType(dataTypeToQName(typeCaseType)) + ) + .withThen(applyTargetMap(source, typeCaseMap)) + ci.then!!.resultType = typeCaseType + c.caseItem.add(ci) + } + } + return when (c.caseItem.size) { + 0 -> { + buildNull(source!!.resultType) + } + 1 -> { + c.caseItem[0].then + } + else -> { + c.`else` = (buildNull(source!!.resultType)) + c.resultType = source.resultType + c + } + } + } + targetMap.contains("(") -> { + val invocationStart: Int = targetMap.indexOf("(") + val qualifiedFunctionName: String = targetMap.substring(0, invocationStart) + val nameParts: Array = + qualifiedFunctionName + .split("\\.".toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + var libraryName: String? = null + var functionName: String? = qualifiedFunctionName + if (nameParts.size == 2) { + libraryName = nameParts[0] + functionName = nameParts[1] + ensureLibraryIncluded(libraryName, source) + } + val functionArgument: String = + targetMap.substring(invocationStart + 1, targetMap.lastIndexOf(')')) + val argumentSource: Expression? = + if ((functionArgument == "%value")) source + else applyTargetMap(source, functionArgument) + + // NOTE: This is needed to work around the mapping for ToInterval + // FHIRHelpers defines multiple overloads of ToInterval, but the type mapping + // does not have the type of the source data type. + // All the mappings for ToInterval use FHIR.Period, so this is safe to assume + // In addition, no other FHIRHelpers functions use overloads (except ToString and + // ToDateTime, + // but those mappings expand the value element directly, rather than invoking the + // FHIRHelpers function) + var argumentSignature: TypeSpecifier? = null + if ( + options.signatureLevel != SignatureLevel.None && + qualifiedFunctionName == "FHIRHelpers.ToInterval" + ) { + // Force loading of the FHIR model, as it's an implicit + // dependency of the target mapping here. + var fhirVersion = "4.0.1" + val qiCoreModel = this.getModel("QICore") + val version = qiCoreModel.modelInfo.version + if (version == "3.3.0") { + fhirVersion = "4.0.0" + } else if (version!!.startsWith("3")) { + fhirVersion = "3.0.1" + } + + // Force the FHIR model to be loaded. + modelManager.resolveModel("FHIR", fhirVersion) + + val namedTypeSpecifier = + NamedTypeSpecifier() + .withName(dataTypeToQName(resolveTypeName("FHIR", "Period"))) + argumentSignature = namedTypeSpecifier + } + + when (argumentSource!!.resultType) { + is ListType -> { + val query: Query = + objectFactory + .createQuery() + .withSource( + objectFactory + .createAliasedQuerySource() + .withExpression(argumentSource) + .withAlias(FP_THIS) + ) + val fr: FunctionRef = + objectFactory + .createFunctionRef() + .withLibraryName(libraryName) + .withName(functionName) + .withOperand(objectFactory.createAliasRef().withName(FP_THIS)) + + if (argumentSignature != null) { + fr.signature.add(argumentSignature) + } + + // This doesn't quite work because the US.Core types aren't subtypes of FHIR + // types. + // resolveCall(libraryName, functionName, new FunctionRefInvocation(fr), + // false, + // false); + query.`return` = + (objectFactory + .createReturnClause() + .withDistinct(false) + .withExpression(fr)) + query.resultType = source!!.resultType + return query + } + else -> { + val fr: FunctionRef = + objectFactory + .createFunctionRef() + .withLibraryName(libraryName) + .withName(functionName) + .withOperand(argumentSource) + fr.resultType = source!!.resultType + + if (argumentSignature != null) { + fr.signature.add(argumentSignature) + } + + return fr + // This doesn't quite work because the US.Core types aren't subtypes of FHIR + // types, + // or they are defined as System types and not FHIR types + // return resolveCall(libraryName, functionName, new + // FunctionRefInvocation(fr), + // false, false); + } + } + } + targetMap.contains("[") -> { + val indexerStart: Int = targetMap.indexOf("[") + val indexerEnd: Int = targetMap.indexOf("]") + val indexer: String = targetMap.substring(indexerStart + 1, indexerEnd) + val indexerPath: String = targetMap.substring(0, indexerStart) + var result: Expression? = null + + // Apply sourcePaths to get to the indexer + val indexerPaths: Array = + indexerPath.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (path: String in indexerPaths) { + if ((path == "%parent")) { + require(source is Property) { + "Cannot expand target map $targetMap for non-property-accessor type ${source!!.javaClass.simpleName}" + } + val sourceProperty: Property = source + result = + sourceProperty.source + ?: sourceProperty.scope?.let { + resolveIdentifier(sourceProperty.scope!!, true) + } + requireNotNull(result) { + "Cannot resolve %%parent reference in targetMap $targetMap" + } + } else { + val p: Property = + objectFactory.createProperty().withSource(result).withPath(path) + result = p + } + } + + // Build a query with the current result as source and the indexer content as + // criteria + // in the where clause + val querySource: AliasedQuerySource = + objectFactory + .createAliasedQuerySource() + .withExpression(result) + .withAlias(FP_THIS) + var criteria: Expression? = null + for (indexerItem: String in + indexer.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) { + val indexerItems: Array = + indexerItem + .split("=".toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray() + require(indexerItems.size == 2) { + "Invalid indexer item $indexerItem in targetMap $targetMap" + } + var left: Expression? = null + for (path: String in + indexerItems[0] + .split("\\.".toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray()) { + left = + if (left == null) { + objectFactory.createProperty().withScope(FP_THIS).withPath(path) + } else { + objectFactory.createProperty().withSource(left).withPath(path) + } + + // HACK: Workaround the fact that we don't have type information for the + // mapping + // expansions... + if ((path == "coding")) { + left = + objectFactory + .createFirst() + .withSource(left) + .withResultType( + this.getModel("FHIR").resolveTypeName("FHIR.coding") + ) + } + if ((path == "url")) { + // HACK: This special cases FHIR model resolution + left.resultType = this.getModel("FHIR").resolveTypeName("FHIR.uri") + val ref: FunctionRef = + objectFactory + .createFunctionRef() + .withLibraryName("FHIRHelpers") + .withName("ToString") + .withOperand(left) + left = + resolveCall( + ref.libraryName, + ref.name!!, + FunctionRefInvocation(ref), + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + } + + // HACK: Workaround the fact that we don't have type information for the mapping + // expansions... + // These hacks will be removed when addressed by the model info + if ((indexerItems[0] == "code.coding.system")) { + // HACK: This special cases FHIR model resolution + left!!.resultType = this.getModel("FHIR").resolveTypeName("FHIR.uri") + val ref: FunctionRef = + objectFactory + .createFunctionRef() + .withLibraryName("FHIRHelpers") + .withName("ToString") + .withOperand(left) + left = + resolveCall( + ref.libraryName, + ref.name!!, + FunctionRefInvocation(ref), + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + if ((indexerItems[0] == "code.coding.code")) { + // HACK: This special cases FHIR model resolution + left!!.resultType = this.getModel("FHIR").resolveTypeName("FHIR.code") + val ref: FunctionRef = + objectFactory + .createFunctionRef() + .withLibraryName("FHIRHelpers") + .withName("ToString") + .withOperand(left) + left = + resolveCall( + ref.libraryName, + ref.name!!, + FunctionRefInvocation(ref), + allowPromotionAndDemotion = false, + allowFluent = false + ) + } + val rightValue: String = + indexerItems[1].substring(1, indexerItems[1].length - 1) + val right: Expression = + this.createLiteral(StringEscapeUtils.unescapeCql(rightValue)) + val criteriaItem: Expression = + objectFactory.createEqual().withOperand(left!!, right) + criteria = + if (criteria == null) { + criteriaItem + } else { + objectFactory.createAnd().withOperand(criteria, criteriaItem) + } + } + val query: Query = + objectFactory.createQuery().withSource(querySource).withWhere(criteria) + result = query + if (indexerEnd + 1 < targetMap.length) { + // There are additional paths following the indexer, apply them + var targetPath: String = targetMap.substring(indexerEnd + 1) + if (targetPath.startsWith(".")) { + targetPath = targetPath.substring(1) + } + if (targetPath.isNotEmpty()) { + query.`return` = + (objectFactory + .createReturnClause() + .withDistinct(false) + .withExpression( + objectFactory + .createProperty() + .withSource( + objectFactory.createAliasRef().withName(FP_THIS) + ) + .withPath(targetPath) + )) + } + + // The value reference should go inside the query, rather than being applied as + // a + // property outside of it + // for (String path : targetPath.split("\\.")) { + // result = of.createProperty().withSource(result).withPath(path); + // } + } + if (source!!.resultType !is ListType) { + // Use a singleton from since the source of the query is a list + result = objectFactory.createSingletonFrom().withOperand(result) + } + result.resultType = source.resultType + return result + } + targetMap.startsWith("%value.") -> { + val propertyName: String = targetMap.substring(@Suppress("MagicNumber") 7) + // If the source is a list, the mapping is expected to apply to every element in the + // list + // ((source $this return all $this.value) + if (source!!.resultType is ListType) { + val s: AliasedQuerySource = + objectFactory + .createAliasedQuerySource() + .withExpression(source) + .withAlias(FP_THIS) + val p: Property = + objectFactory.createProperty().withScope(FP_THIS).withPath(propertyName) + p.resultType = (source.resultType as ListType).elementType + val r: ReturnClause = + objectFactory.createReturnClause().withDistinct(false).withExpression(p) + val q: Query = objectFactory.createQuery().withSource(s).withReturn(r) + q.resultType = source.resultType + return q + } else { + val p: Property = + objectFactory.createProperty().withSource(source).withPath(propertyName) + p.resultType = source.resultType + return p + } + } + } + throw IllegalArgumentException("TargetMapping not implemented: $targetMap") + } + + @Suppress("LongMethod", "NestedBlockDepth", "CyclomaticComplexMethod") + fun resolveAccessor(left: Expression, memberIdentifier: String): Expression? { + // if left is a LibraryRef + // if right is an identifier + // right may be an ExpressionRef, a CodeSystemRef, a ValueSetRef, a CodeRef, a ConceptRef, + // or a ParameterRef -- + // need to resolve on the referenced library + // if left is an ExpressionRef + // if right is an identifier + // return a Property with the ExpressionRef as source and identifier as Path + // if left is a Property + // if right is an identifier + // modify the Property to append the identifier to the path + // if left is an AliasRef + // return a Property with a Path and no source, and Scope set to the Alias + // if left is an Identifier + // return a new Identifier with left as a qualifier + // else + // throws an error as an unresolved identifier + when { + left is LibraryRef -> { + val libraryName: String? = left.libraryName + val referencedLibrary: CompiledLibrary = resolveLibrary(libraryName) + val resolvedIdentifierContext: ResolvedIdentifierContext = + referencedLibrary.resolve(memberIdentifier) + val element = resolvedIdentifierContext.exactMatchElement + if (element != null) { + if (element is ExpressionDef) { + checkAccessLevel(libraryName, memberIdentifier, element.accessLevel!!) + val result: Expression = + objectFactory + .createExpressionRef() + .withLibraryName(libraryName) + .withName(memberIdentifier) + result.resultType = getExpressionDefResultType(element) + return result + } + if (element is ParameterDef) { + checkAccessLevel(libraryName, memberIdentifier, element.accessLevel!!) + val result: Expression = + objectFactory + .createParameterRef() + .withLibraryName(libraryName) + .withName(memberIdentifier) + result.resultType = element.resultType + return result + } + if (element is ValueSetDef) { + checkAccessLevel(libraryName, memberIdentifier, element.accessLevel!!) + val result: ValueSetRef = + objectFactory + .createValueSetRef() + .withLibraryName(libraryName) + .withName(memberIdentifier) + result.resultType = element.resultType + if (isCompatibleWith("1.5")) { + result.preserve = true + } + return result + } + if (element is CodeSystemDef) { + checkAccessLevel(libraryName, memberIdentifier, element.accessLevel!!) + val result: CodeSystemRef = + objectFactory + .createCodeSystemRef() + .withLibraryName(libraryName) + .withName(memberIdentifier) + result.resultType = element.resultType + return result + } + if (element is CodeDef) { + checkAccessLevel(libraryName, memberIdentifier, element.accessLevel!!) + val result: CodeRef = + objectFactory + .createCodeRef() + .withLibraryName(libraryName) + .withName(memberIdentifier) + result.resultType = element.resultType + return result + } + if (element is ConceptDef) { + checkAccessLevel(libraryName, memberIdentifier, element.accessLevel!!) + val result: ConceptRef = + objectFactory + .createConceptRef() + .withLibraryName(libraryName) + .withName(memberIdentifier) + result.resultType = element.resultType + return result + } + } + + // ERROR: + throw IllegalArgumentException( + "Could not resolve identifier $memberIdentifier in library ${referencedLibrary.identifier!!.id}." + ) + } + left is AliasRef -> { + val resolution: PropertyResolution? = + resolveProperty(left.resultType, memberIdentifier) + val result: Expression = + buildProperty( + left.name, + resolution!!.name, + resolution.isSearch, + resolution.type + ) + return applyTargetMap(result, resolution.targetMap) + } + left.resultType is ListType && listTraversal -> { + // NOTE: FHIRPath path traversal support + // Resolve property access of a list of items as a query: + // listValue.property ::= listValue X where X.property is not null return all + // X.property + val listType: ListType = left.resultType as ListType + val resolution: PropertyResolution? = + resolveProperty(listType.elementType, memberIdentifier) + var accessor: Expression? = + buildProperty( + objectFactory.createAliasRef().withName(FP_THIS), + resolution!!.name, + resolution.isSearch, + resolution.type + ) + accessor = applyTargetMap(accessor, resolution.targetMap) + val not: Expression = buildIsNotNull(accessor) + + // Recreate property, it needs to be accessed twice + accessor = + buildProperty( + objectFactory.createAliasRef().withName(FP_THIS), + resolution.name, + resolution.isSearch, + resolution.type + ) + accessor = applyTargetMap(accessor, resolution.targetMap) + val source: AliasedQuerySource = + objectFactory.createAliasedQuerySource().withExpression(left).withAlias(FP_THIS) + source.resultType = left.resultType + val query: Query = + objectFactory + .createQuery() + .withSource(source) + .withWhere(not) + .withReturn( + objectFactory + .createReturnClause() + .withDistinct(false) + .withExpression(accessor) + ) + query.resultType = ListType(accessor!!.resultType!!) + if (accessor.resultType is ListType) { + val result: Flatten = objectFactory.createFlatten().withOperand(query) + result.resultType = accessor.resultType + return result + } + return query + } + else -> { + val resolution: PropertyResolution? = + resolveProperty(left.resultType, memberIdentifier) + var result: Expression? = + buildProperty(left, resolution!!.name, resolution.isSearch, resolution.type) + result = applyTargetMap(result, resolution.targetMap) + return result + } + } + } + + private fun resolveQueryResultElement(identifier: String): Expression? { + if (inQueryContext()) { + val query = peekQueryContext() + if (query.inSortClause() && !query.isSingular) { + if (identifier == FP_THIS) { + val result = objectFactory.createIdentifierRef().withName(identifier) + result.resultType = query.resultElementType + return result + } + val resolution = resolveProperty(query.resultElementType, identifier, false) + if (resolution != null) { + val result = objectFactory.createIdentifierRef().withName(resolution.name) + result.resultType = resolution.type + return applyTargetMap(result, resolution.targetMap) + } + } + } + return null + } + + private fun resolveAlias(identifier: String): AliasedQuerySource? { + // Need to use a for loop to go through backwards, iteration on a Stack is bottom up + if (inQueryContext()) { + for (i in scope.queries.indices.reversed()) { + val source = scope.queries[i].resolveAlias(identifier) + if (source != null) { + return source + } + } + } + return null + } + + @Suppress("NestedBlockDepth") + private fun resolveQueryThisElement(identifier: String): Expression? { + if (inQueryContext()) { + val query = peekQueryContext() + if (query.isImplicit) { + val source = resolveAlias(FP_THIS) + if (source != null) { + val aliasRef = objectFactory.createAliasRef().withName(FP_THIS) + if (source.resultType is ListType) { + aliasRef.resultType = (source.resultType as ListType).elementType + } else { + aliasRef.resultType = source.resultType + } + val result = resolveProperty(aliasRef.resultType, identifier, false) + if (result != null) { + return resolveAccessor(aliasRef, identifier) + } + } + } + } + return null + } + + private fun resolveQueryLet(identifier: String): LetClause? { + // Need to use a for loop to go through backwards, iteration on a Stack is bottom up + if (inQueryContext()) { + for (i in scope.queries.indices.reversed()) { + val let = scope.queries[i].resolveLet(identifier) + if (let != null) { + return let + } + } + } + return null + } + + private fun resolveOperandRef(identifier: String): OperandRef? { + if (!functionDefs.empty()) { + for (operand in functionDefs.peek().operand) { + if (operand.name == identifier) { + return objectFactory + .createOperandRef() + .withName(identifier) + .withResultType(operand.resultType) + } + } + } + return null + } + + private fun getExpressionDefResultType(expressionDef: ExpressionDef): DataType? { + // If the current expression context is the same as the expression def context, return the + // expression def result + // type. + if ((currentExpressionContext() == expressionDef.context)) { + return expressionDef.resultType + } + + // If the current expression context is specific, a reference to an unfiltered context + // expression will indicate + // a full + // evaluation of the population context expression, and the result type is the same. + if (inSpecificContext()) { + return expressionDef.resultType + } + + // If the current expression context is unfiltered, a reference to a specific context + // expression will need to be + // performed for every context in the system, so the result type is promoted to a list (if + // it is not already). + if (inUnfilteredContext()) { + // If we are in the source clause of a query, indicate that the source references + // patient context + if (inQueryContext() && scope.queries.peek().inSourceClause()) { + scope.queries.peek().referencesSpecificContextValue = true + } + val resultType: DataType = expressionDef.resultType!! + return if (resultType !is ListType) { + ListType(resultType) + } else { + resultType + } + } + throw IllegalArgumentException( + "Invalid context reference from ${currentExpressionContext()} context to ${expressionDef.context} context." + ) + } + + enum class IdentifierScope { + GLOBAL, + LOCAL + } + + /** + * Add an identifier to the deque to indicate that we are considering it for consideration for + * identifier hiding and adding a compiler warning if this is the case. + * + * For example, if an alias within an expression body has the same name as a parameter, + * execution would have added the parameter identifier and the next execution would consider an + * alias with the same name, thus resulting in a warning. + * + * Exact case matching as well as case-insensitive matching are considered. If known, the type + * of the structure in question will be considered in crafting the warning message, as per the + * [Element] parameter. + * + * Also, special case function overloads so that only a single overloaded function name is taken + * into account. + * + * Default scope is [IdentifierScope.LOCAL] + * + * @param identifier The identifier belonging to the parameter, expression, function, alias, + * etc., to be evaluated. + * @param element The construct trackable, for example [ExpressionRef]. + */ + @JvmOverloads + fun pushIdentifier( + identifier: String, + element: Element?, + scope: IdentifierScope = IdentifierScope.LOCAL + ) { + val localMatch = + if (localIdentifierStack.isNotEmpty()) + findMatchingIdentifierContext(localIdentifierStack.peek(), identifier) + else Optional.empty() + val globalMatch = findMatchingIdentifierContext(globalIdentifiers, identifier) + if (globalMatch.isPresent || localMatch.isPresent) { + val matchedContext = if (globalMatch.isPresent) globalMatch.get() else localMatch.get() + val matchedOnFunctionOverloads = + matchedContext.trackableSubclass == FunctionDef::class.java && + element is FunctionDef + if (!matchedOnFunctionOverloads) { + reportWarning( + resolveWarningMessage(matchedContext.identifier, identifier, element), + element + ) + } + } + if (shouldAddIdentifierContext(element)) { + val trackableOrNull: Class? = element?.javaClass + // Sometimes the underlying Trackable doesn't resolve in the calling code + if (scope == IdentifierScope.GLOBAL) { + globalIdentifiers.push(IdentifierContext(identifier, trackableOrNull)) + } else { + localIdentifierStack.peek().push(IdentifierContext(identifier, trackableOrNull)) + } + } + } + + private fun findMatchingIdentifierContext( + identifierContext: Collection, + identifier: String + ): Optional { + return identifierContext + .stream() + .filter { innerContext: IdentifierContext -> (innerContext.identifier == identifier) } + .findFirst() + } + + /** + * Pop the last resolved identifier off the deque. This is needed in case of a context in which + * an identifier falls out of scope, for an example, an alias within an expression or function + * body. + */ + @JvmOverloads + fun popIdentifier(scope: IdentifierScope = IdentifierScope.LOCAL) { + if (scope == IdentifierScope.GLOBAL) { + globalIdentifiers.pop() + } else { + localIdentifierStack.peek().pop() + } + } + + fun pushIdentifierScope() { + localIdentifierStack.push(ArrayDeque()) + } + + fun popIdentifierScope() { + localIdentifierStack.pop() + } + + private fun shouldAddIdentifierContext(element: Element?): Boolean { + return element !is Literal + } + + private fun resolveWarningMessage( + matchedIdentifier: String?, + identifierParam: String, + element: Element? + ): String { + val elementString = lookupElementWarning(element) + return if (element is Literal) { + "You used a string literal: [$identifierParam] here that matches an identifier in scope: [$matchedIdentifier]. Did you mean to use the identifier instead?" + } else + "$elementString identifier [$identifierParam] is hiding another identifier of the same name." + } + + private inner class Scope { + val targets = Stack() + val queries = Stack() + } + + private inner class ExpressionDefinitionContext(val identifier: String) { + val scope: Scope = Scope() + var rootCause: Exception? = null + } + + private inner class ExpressionDefinitionContextStack : Stack() { + operator fun contains(identifier: String): Boolean { + for (i in 0 until elementCount) { + if (this.elementAt(i)?.identifier == identifier) { + return true + } + } + return false + } + } + + fun determineRootCause(): Exception? { + if (expressionDefinitions.isNotEmpty()) { + val currentContext = expressionDefinitions.peek() + if (currentContext != null) { + return currentContext.rootCause + } + } + return null + } + + fun setRootCause(rootCause: Exception?) { + if (expressionDefinitions.isNotEmpty()) { + val currentContext = expressionDefinitions.peek() + currentContext?.rootCause = rootCause + } + } + + fun pushExpressionDefinition(identifier: String) { + require(!expressionDefinitions.contains(identifier)) { + // ERROR: + "Cannot resolve reference to expression or function $identifier because it results in a circular reference." + } + expressionDefinitions.push(ExpressionDefinitionContext(identifier)) + } + + fun popExpressionDefinition() { + expressionDefinitions.pop() + } + + private fun hasScope(): Boolean { + return !expressionDefinitions.empty() + } + + private val scope: Scope + get() = expressionDefinitions.peek()?.scope!! + + fun pushExpressionContext(context: String?) { + require(context != null) { "Expression context cannot be null" } + expressionContext.push(context) + } + + fun popExpressionContext() { + check(!expressionContext.empty()) { "Expression context stack is empty." } + expressionContext.pop() + } + + private fun currentExpressionContext(): String { + check(!expressionContext.empty()) { "Expression context stack is empty." } + return expressionContext.peek() + } + + private fun inSpecificContext(): Boolean { + return !inUnfilteredContext() + } + + private fun inUnfilteredContext(): Boolean { + return currentExpressionContext() == "Unfiltered" || + isCompatibilityLevel3 && currentExpressionContext() == "Population" + } + + private fun inQueryContext(): Boolean { + return hasScope() && scope.queries.isNotEmpty() + } + + fun pushQueryContext(context: QueryContext) { + scope.queries.push(context) + } + + fun popQueryContext(): QueryContext { + return scope.queries.pop() + } + + fun peekQueryContext(): QueryContext { + return scope.queries.peek() + } + + fun pushExpressionTarget(target: Expression) { + scope.targets.push(target) + } + + fun popExpressionTarget(): Expression { + return scope.targets.pop() + } + + fun hasExpressionTarget(): Boolean { + return hasScope() && scope.targets.isNotEmpty() + } + + fun beginFunctionDef(functionDef: FunctionDef) { + functionDefs.push(functionDef) + } + + fun endFunctionDef() { + functionDefs.pop() + } + + fun pushLiteralContext() { + literalContext++ + } + + fun popLiteralContext() { + check(inLiteralContext()) { "Not in literal context" } + literalContext-- + } + + private fun inLiteralContext(): Boolean { + return literalContext > 0 + } + + fun checkLiteralContext() { + check(!inLiteralContext()) { + "Expressions in this context must be able to be evaluated at compile-time." + } + } + + fun pushTypeSpecifierContext() { + typeSpecifierContext++ + } + + fun popTypeSpecifierContext() { + check(inTypeSpecifierContext()) { "Not in type specifier context" } + typeSpecifierContext-- + } + + private fun inTypeSpecifierContext(): Boolean { + return typeSpecifierContext > 0 + } + + companion object { + private fun lookupElementWarning(element: Any?): String { + return when (element) { + is ExpressionDef -> "An expression" + is ParameterDef -> "A parameter" + is ValueSetDef -> "A valueset" + is CodeSystemDef -> "A codesystem" + is CodeDef -> "A code" + is ConceptDef -> "A concept" + is IncludeDef -> "An include" + is AliasedQuerySource -> "An alias" + is LetClause -> "A let" + is OperandDef -> "An operand" + is UsingDef -> "A using" + is Literal -> "A literal" + // default message if no match is made: + else -> "An [unknown structure]" + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryContentType.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryContentType.kt new file mode 100644 index 000000000..0699024c6 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryContentType.kt @@ -0,0 +1,13 @@ +package org.cqframework.cql.cql2elm + +/** This enum lists all the encodings for CQL libraries */ +enum class LibraryContentType(private val mimeType: String) : MimeType { + CQL("text/cql"), + XML("application/elm+xml"), + JSON("application/elm+json"), + COFFEE("application/elm+coffee"); + + override fun mimeType(): String { + return mimeType + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryManager.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryManager.kt new file mode 100644 index 000000000..f88d87e71 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibraryManager.kt @@ -0,0 +1,358 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.io.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.collections.HashSet +import org.cqframework.cql.cql2elm.model.CompiledLibrary +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.cqframework.cql.cql2elm.ucum.UcumService +import org.cqframework.cql.cql2elm.ucum.UcumServiceFactory +import org.cqframework.cql.elm.serializing.ElmLibraryReaderFactory +import org.hl7.cql.model.NamespaceManager +import org.hl7.elm.r1.* + +/** + * Manages a set of CQL libraries. As new library references are encountered during compilation, the + * corresponding source is obtained via librarySourceLoader, compiled and cached for later use. + */ +@Suppress("TooManyFunctions") +class LibraryManager +@JvmOverloads +constructor( + val modelManager: ModelManager, + val cqlCompilerOptions: CqlCompilerOptions = CqlCompilerOptions.defaultOptions(), + libraryCache: MutableMap? = null +) { + enum class CacheMode { + NONE, + READ_ONLY, + READ_WRITE + } + + var namespaceManager = modelManager.namespaceManager + var compiledLibraries: MutableMap = + libraryCache ?: HashMap() + val librarySourceLoader: LibrarySourceLoader = PriorityLibrarySourceLoader() + val ucumService: UcumService by lazy { UcumServiceFactory.load() } + + /* + * A "well-known" library name is one that is allowed to resolve without a + * namespace in a namespace-aware context + */ + fun isWellKnownLibraryName(unqualifiedIdentifier: String?): Boolean { + return when (unqualifiedIdentifier) { + "FHIRHelpers" -> true + else -> false + } + } + + fun resolveLibrary( + libraryIdentifier: VersionedIdentifier, + cacheMode: CacheMode + ): CompiledLibrary { + return this.resolveLibrary(libraryIdentifier, ArrayList(), cacheMode) + } + + fun canResolveLibrary(libraryIdentifier: VersionedIdentifier): Boolean { + // This throws an exception if the library cannot be resolved + this.resolveLibrary(libraryIdentifier) + return true + } + + @JvmOverloads + fun resolveLibrary( + libraryIdentifier: VersionedIdentifier, + errors: MutableList = ArrayList(), + cacheMode: CacheMode = CacheMode.READ_WRITE + ): CompiledLibrary { + require(!libraryIdentifier.id.isNullOrEmpty()) { "libraryIdentifier Id is null." } + var library: CompiledLibrary? + if (cacheMode != CacheMode.NONE) { + library = compiledLibraries[libraryIdentifier] + if (library != null) { + return library + } + } + library = compileLibrary(libraryIdentifier, errors) + if (!CqlCompilerException.hasErrors(errors) && cacheMode == CacheMode.READ_WRITE) { + compiledLibraries[libraryIdentifier] = library + } + return library + } + + @Suppress("LongMethod", "ThrowsCount") + private fun compileLibrary( + libraryIdentifier: VersionedIdentifier, + errors: MutableList? + ): CompiledLibrary { + var result: CompiledLibrary? + if (!cqlCompilerOptions.enableCqlOnly) { + result = tryCompiledLibraryElm(libraryIdentifier, cqlCompilerOptions) + if (result != null) { + sortStatements(result) + return result + } + } + val libraryPath: String = + NamespaceManager.getPath(libraryIdentifier.system, libraryIdentifier.id!!) + try { + val cqlSource: InputStream = + librarySourceLoader.getLibrarySource(libraryIdentifier) + ?: throw CqlIncludeException( + """Could not load source for library ${libraryIdentifier.id}, + version ${libraryIdentifier.version}.""", + libraryIdentifier.system, + libraryIdentifier.id!!, + libraryIdentifier.version + ) + val compiler = + CqlCompiler( + libraryIdentifier.system?.let { namespaceManager.getNamespaceInfoFromUri(it) }, + libraryIdentifier, + this + ) + compiler.run(cqlSource) + errors?.addAll((compiler.exceptions)!!) + result = compiler.compiledLibrary + if ( + (libraryIdentifier.version != null && + libraryIdentifier.version != result!!.identifier!!.version) + ) { + throw CqlIncludeException( + """Library $libraryPath was included as version ${libraryIdentifier.version}, + but version ${result.identifier!!.version} of the library was found.""", + libraryIdentifier.system, + libraryIdentifier.id!!, + libraryIdentifier.version + ) + } + } catch (e: IOException) { + throw CqlIncludeException( + "Errors occurred translating library $libraryPath, version ${libraryIdentifier.version}.", + libraryIdentifier.system!!, + libraryIdentifier.id!!, + libraryIdentifier.version, + e + ) + } + if (result == null) { + throw CqlIncludeException( + "Could not load source for library $libraryPath.", + libraryIdentifier.system, + libraryIdentifier.id!!, + null + ) + } else { + sortStatements(result) + return result + } + } + + private fun sortStatements(compiledLibrary: CompiledLibrary) { + if (compiledLibrary.library!!.statements == null) { + return + } + compiledLibrary.library!!.statements!!.def.sortBy { it.name } + } + + private fun tryCompiledLibraryElm( + libraryIdentifier: VersionedIdentifier, + options: CqlCompilerOptions + ): CompiledLibrary? { + var elm: InputStream? + @Suppress("LoopWithTooManyJumpStatements") + for (type: LibraryContentType in supportedContentTypes) { + if (LibraryContentType.CQL == type) { + continue + } + elm = librarySourceLoader.getLibraryContent(libraryIdentifier, type) + if (elm == null) { + continue + } + return generateCompiledLibraryFromElm(libraryIdentifier, elm, type, options) + } + return null + } + + private fun generateCompiledLibraryFromElm( + @Suppress("UnusedParameter") libraryIdentifier: VersionedIdentifier, + librarySource: InputStream, + type: LibraryContentType, + @Suppress("UnusedParameter") options: CqlCompilerOptions + ): CompiledLibrary? { + var library: Library? = null + var compiledLibrary: CompiledLibrary? = null + try { + library = + ElmLibraryReaderFactory.getReader(type.mimeType()) + .read(InputStreamReader(librarySource)) + } catch (@Suppress("SwallowedException") e: IOException) { + // intentionally ignored + } + if (library != null && checkBinaryCompatibility(library)) { + compiledLibrary = generateCompiledLibrary(library) + } + return compiledLibrary + } + + @Suppress("LongMethod", "CyclomaticComplexMethod", "NestedBlockDepth", "ReturnCount") + private fun generateCompiledLibrary(library: Library?): CompiledLibrary? { + if (library == null) { + return null + } + var compilationSuccess = true + val compiledLibrary = CompiledLibrary() + try { + compiledLibrary.library = library + if (library.identifier != null) { + compiledLibrary.identifier = library.identifier + } + if (library.usings != null) { + for (usingDef in library.usings!!.def) { + compiledLibrary.add(usingDef) + } + } + if (library.includes != null) { + for (includeDef in library.includes!!.def) { + compiledLibrary.add(includeDef) + } + } + if (library.codeSystems != null) { + for (codeSystemDef in library.codeSystems!!.def) { + compiledLibrary.add(codeSystemDef) + } + } + for (valueSetDef in library.valueSets!!.def) { + compiledLibrary.add(valueSetDef) + } + if (library.codes != null) { + for (codeDef in library.codes!!.def) { + compiledLibrary.add(codeDef) + } + } + if (library.concepts != null) { + for (conceptDef in library.concepts!!.def) { + compiledLibrary.add(conceptDef) + } + } + if (library.parameters != null) { + for (parameterDef in library.parameters!!.def) { + compiledLibrary.add(parameterDef) + } + } + if (library.statements != null) { + for (expressionDef in library.statements!!.def) { + + // to do implement an ElmTypeInferencingVisitor; make sure that the resultType + // is set for each node + if (expressionDef.resultType != null) { + compiledLibrary.add(expressionDef) + } else { + compilationSuccess = false + break + } + } + } + } catch (@Suppress("SwallowedException", "TooGenericExceptionCaught") e: Exception) { + compilationSuccess = false + } + if (compilationSuccess) { + return compiledLibrary + } + return null + } + + private fun compilerOptionsMatch(library: Library): Boolean { + val compilerOptions: Set = + CompilerOptions.getCompilerOptions(library) ?: return false + return (compilerOptions == cqlCompilerOptions.options) + } + + private fun checkBinaryCompatibility(library: Library?): Boolean { + if (library == null) { + return false + } + return (isSignatureCompatible(library) && + isVersionCompatible(library) && + compilerOptionsMatch(library)) + } + + private fun isSignatureCompatible(library: Library): Boolean { + return !hasOverloadedFunctions(library) || hasSignature(library) + } + + @Suppress("ReturnCount") + private fun hasOverloadedFunctions(library: Library): Boolean { + if (library.statements == null) { + return false + } + val functionNames: MutableSet = HashSet() + for (ed in library.statements!!.def) { + if (ed is FunctionDef) { + val fd: FunctionDef = ed + val sig = FunctionSig(fd.name!!, fd.operand.size) + if (functionNames.contains(sig)) { + return true + } else { + functionNames.add(sig) + } + } + } + return false + } + + @Suppress("NestedBlockDepth") + private fun hasSignature(library: Library): Boolean { + if (library.statements != null) { + // Just a quick top-level scan for signatures. To fully verify we'd have to + // recurse all + // the way down. At that point, let's just translate. + for (ed in library.statements!!.def) { + if (ed.expression is FunctionRef) { + val fr: FunctionRef = ed.expression as FunctionRef + if (fr.signature.isNotEmpty()) { + return true + } + } + } + } + return false + } + + private fun isVersionCompatible(library: Library): Boolean { + if (cqlCompilerOptions.compatibilityLevel.isNotBlank()) { + val version: String? = CompilerOptions.getCompilerVersion(library) + if (version != null) { + return (version == cqlCompilerOptions.compatibilityLevel) + } + } + return false + } + + internal class FunctionSig(private val name: String, private val numArguments: Int) { + override fun hashCode(): Int { + val prime = 31 + var result = 1 + result = prime * result + name.hashCode() + result = prime * result + numArguments + return result + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null) return false + if (other is FunctionSig) { + return (other.name == name) && other.numArguments == numArguments + } + return false + } + } + + companion object { + private val supportedContentTypes: Array = + arrayOf(LibraryContentType.JSON, LibraryContentType.XML, LibraryContentType.CQL) + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceLoader.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceLoader.kt new file mode 100644 index 000000000..19abe867e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceLoader.kt @@ -0,0 +1,23 @@ +package org.cqframework.cql.cql2elm + +import java.io.InputStream +import org.hl7.elm.r1.VersionedIdentifier + +/** @author mhadley */ +interface LibrarySourceLoader { + fun clearProviders() + + fun getLibrarySource(libraryIdentifier: VersionedIdentifier): InputStream? + + fun registerProvider(provider: LibrarySourceProvider) + + fun getLibraryContent( + libraryIdentifier: VersionedIdentifier, + type: LibraryContentType + ): InputStream? { + if (LibraryContentType.CQL == type) { + return getLibrarySource(libraryIdentifier) + } + return null + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceProvider.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceProvider.kt new file mode 100644 index 000000000..87eaa9cbd --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceProvider.kt @@ -0,0 +1,19 @@ +package org.cqframework.cql.cql2elm + +import java.io.InputStream +import org.hl7.elm.r1.VersionedIdentifier + +interface LibrarySourceProvider { + fun getLibrarySource(libraryIdentifier: VersionedIdentifier): InputStream? + + fun getLibraryContent( + libraryIdentifier: VersionedIdentifier, + type: LibraryContentType + ): InputStream? { + if (LibraryContentType.CQL == type) { + return getLibrarySource(libraryIdentifier) + } + + return null + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceProviderFactory.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceProviderFactory.kt new file mode 100644 index 000000000..6b42ca1c2 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/LibrarySourceProviderFactory.kt @@ -0,0 +1,14 @@ +package org.cqframework.cql.cql2elm + +import java.util.* + +object LibrarySourceProviderFactory { + fun providers(refresh: Boolean): Iterator { + val loader: ServiceLoader = + ServiceLoader.load(LibrarySourceProvider::class.java) + if (refresh) { + loader.reload() + } + return loader.iterator() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/MimeType.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/MimeType.kt new file mode 100644 index 000000000..e832fd5d8 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/MimeType.kt @@ -0,0 +1,5 @@ +package org.cqframework.cql.cql2elm + +interface MimeType { + fun mimeType(): String +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelInfoLoader.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelInfoLoader.kt new file mode 100644 index 000000000..0488ee54e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelInfoLoader.kt @@ -0,0 +1,89 @@ +package org.cqframework.cql.cql2elm + +import java.nio.file.Path +import kotlin.collections.ArrayList +import org.hl7.cql.model.ModelIdentifier +import org.hl7.cql.model.ModelInfoProvider +import org.hl7.cql.model.NamespaceAware +import org.hl7.cql.model.NamespaceManager +import org.hl7.elm_modelinfo.r1.ModelInfo + +class ModelInfoLoader : NamespaceAware, PathAware { + private var path: Path? = null + private var namespaceManager: NamespaceManager? = null + private val providers: MutableList = ArrayList() + private var initialized = false + + private fun getProviders(): List { + if (!initialized) { + initialized = true + val it = ModelInfoProviderFactory.providers(false) + while (it.hasNext()) { + val provider = it.next() + registerModelInfoProvider(provider) + } + } + return providers + } + + fun getModelInfo(modelIdentifier: ModelIdentifier): ModelInfo { + var modelInfo: ModelInfo? = null + for (provider in getProviders()) { + modelInfo = provider.load(modelIdentifier) + if (modelInfo != null) { + break + } + } + requireNotNull(modelInfo) { + "Could not resolve model info provider for model ${ + if (modelIdentifier.system == null) modelIdentifier.id + else NamespaceManager.getPath(modelIdentifier.system, modelIdentifier.id) + }, version ${modelIdentifier.version}." + } + return modelInfo + } + + @JvmOverloads + fun registerModelInfoProvider(provider: ModelInfoProvider, priority: Boolean = false) { + if (namespaceManager != null && provider is NamespaceAware) { + provider.setNamespaceManager(namespaceManager!!) + } + if (path != null && provider is PathAware) { + provider.setPath(path!!) + } + + if (priority) { + providers.add(0, provider) + } else { + providers.add(provider) + } + } + + fun unregisterModelInfoProvider(provider: ModelInfoProvider) { + providers.remove(provider) + } + + fun clearModelInfoProviders() { + providers.clear() + initialized = false + } + + override fun setNamespaceManager(namespaceManager: NamespaceManager) { + this.namespaceManager = namespaceManager + for (provider in getProviders()) { + if (provider is NamespaceAware) { + (provider as NamespaceAware).setNamespaceManager(namespaceManager) + } + } + } + + override fun setPath(path: Path) { + require(path.toFile().isDirectory) { "path '$path' is not a valid directory" } + this.path = path + for (provider in getProviders()) { + if (provider is PathAware) { + provider.setPath(path) + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelInfoProviderFactory.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelInfoProviderFactory.kt new file mode 100644 index 000000000..d31f9107c --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelInfoProviderFactory.kt @@ -0,0 +1,14 @@ +package org.cqframework.cql.cql2elm + +import java.util.* +import org.hl7.cql.model.ModelInfoProvider + +object ModelInfoProviderFactory { + fun providers(refresh: Boolean): Iterator { + val loader = ServiceLoader.load(ModelInfoProvider::class.java) + if (refresh) { + loader.reload() + } + return loader.iterator() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelManager.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelManager.kt new file mode 100644 index 000000000..608baaa16 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelManager.kt @@ -0,0 +1,272 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.nio.file.Path +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import org.cqframework.cql.cql2elm.model.* +import org.hl7.cql.model.* + +/** Created by Bryn on 12/29/2016. */ +@Suppress("TooManyFunctions") +class ModelManager { + val namespaceManager: NamespaceManager + private var path: Path? = null + var modelInfoLoader: ModelInfoLoader? = null + private set + + private val models: MutableMap = HashMap() + private val loadingModels: MutableSet = HashSet() + private val modelsByUri: MutableMap = HashMap() + + /** + * The global cache is by @{org.hl7.cql.model.ModelIdentifier}, while the local cache is by + * name. This is because the translator expects the ModelManager to only permit loading of a + * single version of a given Model in a single translation context, while the global cache is + * for all versions of Models + */ + private val globalCache: MutableMap + private var isDefaultModelInfoLoadingEnabled = true + + constructor() { + namespaceManager = NamespaceManager() + globalCache = ConcurrentHashMap() + initialize() + } + + /** @param globalCache cache for Models by ModelIdentifier. Expected to be thread-safe. */ + constructor(globalCache: MutableMap) { + namespaceManager = NamespaceManager() + this.globalCache = globalCache + initialize() + } + + constructor(path: Path?) { + namespaceManager = NamespaceManager() + globalCache = ConcurrentHashMap() + this.path = path + initialize() + } + + constructor(path: Path?, globalCache: MutableMap) { + namespaceManager = NamespaceManager() + this.globalCache = globalCache + this.path = path + initialize() + } + + constructor(enableDefaultModelInfoLoading: Boolean) { + namespaceManager = NamespaceManager() + globalCache = ConcurrentHashMap() + isDefaultModelInfoLoadingEnabled = enableDefaultModelInfoLoading + initialize() + } + + constructor( + enableDefaultModelInfoLoading: Boolean, + globalCache: MutableMap + ) { + namespaceManager = NamespaceManager() + this.globalCache = globalCache + isDefaultModelInfoLoadingEnabled = enableDefaultModelInfoLoading + initialize() + } + + constructor(enableDefaultModelInfoLoading: Boolean, path: Path?) { + namespaceManager = NamespaceManager() + globalCache = ConcurrentHashMap() + this.path = path + isDefaultModelInfoLoadingEnabled = enableDefaultModelInfoLoading + initialize() + } + + constructor( + enableDefaultModelInfoLoading: Boolean, + path: Path?, + globalCache: MutableMap + ) { + namespaceManager = NamespaceManager() + this.globalCache = globalCache + this.path = path + isDefaultModelInfoLoadingEnabled = enableDefaultModelInfoLoading + initialize() + } + + constructor(namespaceManager: NamespaceManager) { + this.namespaceManager = namespaceManager + globalCache = ConcurrentHashMap() + initialize() + } + + constructor( + namespaceManager: NamespaceManager, + globalCache: MutableMap + ) { + this.namespaceManager = namespaceManager + this.globalCache = globalCache + initialize() + } + + constructor(namespaceManager: NamespaceManager, path: Path?) { + this.namespaceManager = namespaceManager + globalCache = ConcurrentHashMap() + this.path = path + initialize() + } + + constructor( + namespaceManager: NamespaceManager, + path: Path?, + globalCache: MutableMap + ) { + this.namespaceManager = namespaceManager + this.globalCache = globalCache + this.path = path + initialize() + } + + constructor(namespaceManager: NamespaceManager, enableDefaultModelInfoLoading: Boolean) { + this.namespaceManager = namespaceManager + globalCache = ConcurrentHashMap() + isDefaultModelInfoLoadingEnabled = enableDefaultModelInfoLoading + initialize() + } + + constructor( + namespaceManager: NamespaceManager, + enableDefaultModelInfoLoading: Boolean, + globalCache: MutableMap + ) { + this.namespaceManager = namespaceManager + this.globalCache = globalCache + isDefaultModelInfoLoadingEnabled = enableDefaultModelInfoLoading + initialize() + } + + constructor( + namespaceManager: NamespaceManager, + enableDefaultModelInfoLoading: Boolean, + path: Path? + ) { + this.namespaceManager = namespaceManager + globalCache = ConcurrentHashMap() + this.path = path + isDefaultModelInfoLoadingEnabled = enableDefaultModelInfoLoading + initialize() + } + + constructor( + namespaceManager: NamespaceManager, + enableDefaultModelInfoLoading: Boolean, + path: Path?, + globalCache: MutableMap + ) { + this.namespaceManager = namespaceManager + this.globalCache = globalCache + this.path = path + isDefaultModelInfoLoadingEnabled = enableDefaultModelInfoLoading + initialize() + } + + private fun initialize() { + modelInfoLoader = ModelInfoLoader() + modelInfoLoader!!.setNamespaceManager(namespaceManager) + if (path != null) { + modelInfoLoader!!.setPath(path!!) + } + } + + /* + A "well-known" model name is one that is allowed to resolve without a namespace in a namespace-aware context + */ + fun isWellKnownModelName(unqualifiedIdentifier: String?): Boolean { + return if (unqualifiedIdentifier == null) { + false + } else + when (unqualifiedIdentifier) { + "FHIR", + "QDM", + "USCore", + "QICore", + "QUICK" -> true + else -> false + } + } + + private fun buildModel(identifier: ModelIdentifier): Model? { + val model: Model? + require(identifier.id.isNotEmpty()) { "Model identifier Id is required" } + val modelPath = NamespaceManager.getPath(identifier.system, identifier.id) + pushLoading(modelPath) + model = + try { + val modelInfo = modelInfoLoader!!.getModelInfo(identifier) + if (identifier.id == "System") { + SystemModel(modelInfo) + } else { + Model(modelInfo, this) + } + } finally { + popLoading(modelPath) + } + return model + } + + private fun pushLoading(modelId: String) { + require(!loadingModels.contains(modelId)) { "Circular model reference $modelId" } + loadingModels.add(modelId) + } + + private fun popLoading(modelId: String) { + loadingModels.remove(modelId) + } + + @JvmOverloads + fun resolveModel(modelName: String, version: String? = null): Model { + return resolveModel(ModelIdentifier(modelName, version = version)) + } + + /** + * @param modelIdentifier the identifier of the model to resolve + * @return the model + * @throws IllegalArgumentException if an attempt to resolve multiple versions of the same model + * is made or if the model that resolved is not compatible with the requested version + */ + fun resolveModel(modelIdentifier: ModelIdentifier): Model { + val modelPath = NamespaceManager.getPath(modelIdentifier.system, modelIdentifier.id) + var model = models[modelPath] + model?.let { checkModelVersion(modelIdentifier, it) } + if (model == null && globalCache.containsKey(modelIdentifier)) { + model = globalCache[modelIdentifier] + models[modelPath] = model!! + modelsByUri[model.modelInfo.url!!] = model + } + + if (model == null) { + model = buildModel(modelIdentifier) + globalCache[modelIdentifier] = model!! + checkModelVersion(modelIdentifier, model) + models[modelPath] = model + modelsByUri[model.modelInfo.url!!] = model + } + return model + } + + private fun checkModelVersion(modelIdentifier: ModelIdentifier, model: Model?) { + require( + !(modelIdentifier.version != null && + modelIdentifier.version != model!!.modelInfo.version) + ) { + "Could not load model information for model ${modelIdentifier.id}, version ${modelIdentifier.version}" + + " because version ${model!!.modelInfo.version} is already loaded." + } + } + + fun resolveModelByUri(namespaceUri: String): Model { + return modelsByUri[namespaceUri] + ?: throw IllegalArgumentException( + "Could not resolve model with namespace $namespaceUri" + ) + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelResolver.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelResolver.kt new file mode 100644 index 000000000..3d11c26e2 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ModelResolver.kt @@ -0,0 +1,7 @@ +package org.cqframework.cql.cql2elm + +import org.cqframework.cql.cql2elm.model.Model + +interface ModelResolver { + fun getModel(modelName: String): Model +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/PathAware.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/PathAware.kt new file mode 100644 index 000000000..94cb02962 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/PathAware.kt @@ -0,0 +1,7 @@ +package org.cqframework.cql.cql2elm + +import java.nio.file.Path + +interface PathAware { + fun setPath(path: Path) +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/PriorityLibrarySourceLoader.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/PriorityLibrarySourceLoader.kt new file mode 100644 index 000000000..b233536f4 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/PriorityLibrarySourceLoader.kt @@ -0,0 +1,87 @@ +package org.cqframework.cql.cql2elm + +import java.io.InputStream +import java.nio.file.Path +import kotlin.collections.ArrayList +import org.hl7.cql.model.NamespaceAware +import org.hl7.cql.model.NamespaceManager +import org.hl7.elm.r1.VersionedIdentifier + +/** + * Used by LibraryManager to manage a set of library source providers that resolve library includes + * within CQL. Package private since it's not intended to be used outside the context of the + * instantiating LibraryManager instance. + */ +class PriorityLibrarySourceLoader : LibrarySourceLoader, NamespaceAware, PathAware { + private val providers: MutableList = ArrayList() + private var initialized = false + + override fun registerProvider(provider: LibrarySourceProvider) { + if (namespaceManager != null && provider is NamespaceAware) { + provider.setNamespaceManager(namespaceManager!!) + } + + if (path != null && provider is PathAware) { + provider.setPath(path!!) + } + providers.add(provider) + } + + private var path: Path? = null + + override fun setPath(path: Path) { + require(path.toFile().isDirectory) { "path '$path' is not a valid directory" } + this.path = path + for (provider in getProviders()) { + if (provider is PathAware) { + provider.setPath(path) + } + } + } + + override fun clearProviders() { + providers.clear() + initialized = false + } + + private fun getProviders(): List { + if (!initialized) { + initialized = true + val it = LibrarySourceProviderFactory.providers(false) + while (it.hasNext()) { + val provider = it.next() + registerProvider(provider) + } + } + return providers + } + + override fun getLibrarySource(libraryIdentifier: VersionedIdentifier): InputStream? { + return getLibraryContent(libraryIdentifier, LibraryContentType.CQL) + } + + override fun getLibraryContent( + libraryIdentifier: VersionedIdentifier, + type: LibraryContentType + ): InputStream? { + var content: InputStream? + for (provider in getProviders()) { + content = provider.getLibraryContent(libraryIdentifier, type) + if (content != null) { + return content + } + } + return null + } + + private var namespaceManager: NamespaceManager? = null + + override fun setNamespaceManager(namespaceManager: NamespaceManager) { + this.namespaceManager = namespaceManager + for (provider in getProviders()) { + if (provider is NamespaceAware) { + provider.setNamespaceManager(namespaceManager) + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ResultWithPossibleError.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ResultWithPossibleError.kt new file mode 100644 index 000000000..c0b7755d5 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/ResultWithPossibleError.kt @@ -0,0 +1,28 @@ +package org.cqframework.cql.cql2elm + +/** + * Indicate either a populated result or the presence of an error that prevented the result from + * being created. + */ +class ResultWithPossibleError(private val underlyingThingOrNull: T) { + + fun hasError(): Boolean { + return (underlyingThingOrNull == null) + } + + val underlyingResultIfExists: T + get() { + require(!hasError()) { "Should have called hasError() first" } + return underlyingThingOrNull + } + + companion object { + fun withError(): ResultWithPossibleError { + return ResultWithPossibleError(null) + } + + fun withTypeSpecifier(underlyingThingOrNull: T): ResultWithPossibleError { + return ResultWithPossibleError(underlyingThingOrNull) + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/StringEscapeUtils.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/StringEscapeUtils.kt new file mode 100644 index 000000000..94759fd0b --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/StringEscapeUtils.kt @@ -0,0 +1,82 @@ +package org.cqframework.cql.cql2elm + +/** Created by Bryn on 3/22/2017. */ +object StringEscapeUtils { + + // CQL supports the following escape characters in both strings and identifiers: + // \" - double-quote + // \' - single-quote + // \` - backtick + // \\ - backslash + // \/ - slash + // \f - form feed + // \n - newline + // \r - carriage return + // \t - tab + // \\u - unicode hex representation (e.g. \u0020) + private val UNESCAPE_MAP: Map = + mapOf( + "\\\"" to '\"', + "\\'" to '\'', + "\\`" to '`', + "\\\\" to '\\', + "\\/" to '/', + "\\f" to '\u000c', + "\\n" to '\n', + "\\r" to '\r', + "\\t" to '\t' + // unicode escapes are handled separately + ) + + private val ESCAPE_MAP: Map = + UNESCAPE_MAP.entries.associate { it.value to it.key } + + // Longer escape sequences should be matched first to avoid partial matches + private val MULTI_CHAR_UNESCAPE = UNESCAPE_MAP.keys.sortedByDescending { it.length } + private val UNESCAPE_REGEX = + Regex( + MULTI_CHAR_UNESCAPE.joinToString("|") { Regex.escape(it.toString()) } + + // Unicode escape sequence + "|\\\\u[0-9a-fA-F]{4}" + ) + + @JvmStatic + fun escapeCql(input: String): String { + return buildString { + for (char in input) { + append( + // Use the mapped escape sequence or + // default to Unicode for non-printable characters + // '\u0020'..'\u007E' are printable ASCII characters + ESCAPE_MAP[char] + ?: if (char !in '\u0020'..'\u007E') { + @Suppress("MagicNumber") + "\\u${char.code.toString(HEX_RADIX).padStart(4, '0')}" + } else { + char + } + ) + } + } + } + + private const val HEX_RADIX = 16 + + @JvmStatic + fun unescapeCql(input: String): String { + return UNESCAPE_REGEX.replace(input) { matchResult -> + val match = matchResult.value + when { + // Handle standard escape sequences + match in UNESCAPE_MAP -> UNESCAPE_MAP[match].toString() + + // Handle Unicode escapes + match.startsWith("\\u") -> { + val hex = match.substring(2) + hex.toInt(HEX_RADIX).toChar().toString() + } + else -> throw IllegalArgumentException("Invalid escape sequence: $match") + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/StringLibrarySourceProvider.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/StringLibrarySourceProvider.kt new file mode 100644 index 000000000..b59850529 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/StringLibrarySourceProvider.kt @@ -0,0 +1,48 @@ +package org.cqframework.cql.cql2elm + +import java.io.ByteArrayInputStream +import java.io.InputStream +import kotlin.collections.ArrayList +import org.hl7.elm.r1.VersionedIdentifier + +/** + * This class implements the LibrarySourceProvider API, using a set of strings representing CQL + * library content as a source. + */ +class StringLibrarySourceProvider(private val libraries: List) : LibrarySourceProvider { + override fun getLibrarySource(libraryIdentifier: VersionedIdentifier): InputStream? { + val id: String = libraryIdentifier.id!! + val version: String? = libraryIdentifier.version + val maybeQuotedIdPattern = "(\"$id\"|$id)" + var matchText = "(?s).*library\\s+\"?$maybeQuotedIdPattern" + matchText += + if (version != null) { + ("\\s+version\\s+'$version'\\s+(?s).*") + } else { + "\\s+(?s).*" + } + val matches: ArrayList = ArrayList() + for (library: String in libraries) { + if (library.matches(matchText.toRegex())) { + matches.add(library) + } + } + require(matches.size <= 1) { + """"Multiple libraries for id : $libraryIdentifier resolved. + Ensure that there are no duplicates in the input set.""" + .trimMargin() + } + return if (matches.size == 1) ByteArrayInputStream(matches[0].toByteArray()) else null + } + + override fun getLibraryContent( + libraryIdentifier: VersionedIdentifier, + type: LibraryContentType + ): InputStream? { + if (LibraryContentType.CQL == type) { + return getLibrarySource(libraryIdentifier) + } + + return null + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/SystemFunctionResolver.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/SystemFunctionResolver.kt new file mode 100644 index 000000000..842b70f0d --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/SystemFunctionResolver.kt @@ -0,0 +1,734 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import kotlin.collections.ArrayList +import org.cqframework.cql.cql2elm.model.Invocation +import org.cqframework.cql.cql2elm.model.invocation.* +import org.cqframework.cql.cql2elm.model.invocation.DateInvocation.Companion.setDateFieldsFromOperands +import org.cqframework.cql.cql2elm.model.invocation.DateTimeInvocation.Companion.setDateTimeFieldsFromOperands +import org.cqframework.cql.cql2elm.model.invocation.TimeInvocation.Companion.setTimeFieldsFromOperands +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.cqframework.cql.cql2elm.tracking.Trackable.trackbacks +import org.hl7.elm.r1.* + +@Suppress("LargeClass", "TooManyFunctions") +class SystemFunctionResolver(private val builder: LibraryBuilder) { + private val of = builder.objectFactory + + @Suppress("LongMethod", "CyclomaticComplexMethod", "NestedBlockDepth", "ReturnCount") + fun resolveSystemFunction(functionRef: FunctionRef): Invocation? { + if (functionRef.libraryName == null || "System" == functionRef.libraryName) { + when (functionRef.name) { + "AllTrue", + "AnyTrue", + "Avg", + "Count", + "GeometricMean", + "Max", + "Median", + "Min", + "Mode", + "PopulationStdDev", + "PopulationVariance", + "Product", + "StdDev", + "Sum", + "Variance" -> { + return resolveAggregate(functionRef) + } + "Abs", + "Ceiling", + "Exp", + "Floor", + "Ln", + "Negate", + "Precision", + "Predecessor", + "Successor", + "Truncate" -> { + return resolveUnary(functionRef) + } + "HighBoundary", + "Log", + "LowBoundary", + "Modulo", + "Power", + "TruncatedDivide" -> { + return resolveBinary(functionRef) + } + "Round" -> { + return resolveRound(functionRef) + } + "AgeInYears", + "AgeInMonths" -> { + checkNumberOfOperands(functionRef, 0) + return resolveCalculateAge( + builder.enforceCompatible( + patientBirthDateProperty, + builder.resolveTypeName("System", "Date") + ), + resolveAgeRelatedFunctionPrecision(functionRef) + ) + } + "AgeInWeeks", + "AgeInDays", + "AgeInHours", + "AgeInMinutes", + "AgeInSeconds", + "AgeInMilliseconds" -> { + checkNumberOfOperands(functionRef, 0) + return resolveCalculateAge( + builder.ensureCompatible( + patientBirthDateProperty, + builder.resolveTypeName("System", "DateTime") + ), + resolveAgeRelatedFunctionPrecision(functionRef) + ) + } + "AgeInYearsAt", + "AgeInMonthsAt", + "AgeInWeeksAt", + "AgeInDaysAt" -> { + checkNumberOfOperands(functionRef, 1) + val ops: MutableList = ArrayList() + var op = functionRef.operand[0] + // If the op is not a Date or DateTime, attempt to get it to convert it to a + // Date or DateTime + // If the op can be converted to both a Date and a DateTime, throw an ambiguous + // error + if ( + !(op.resultType!!.isSubTypeOf( + builder.resolveTypeName("System", "Date")!! + ) || + op.resultType!!.isSubTypeOf( + builder.resolveTypeName("System", "DateTime")!! + )) + ) { + val dateConversion = + builder.findConversion( + op.resultType!!, + builder.resolveTypeName("System", "Date")!!, + implicit = true, + allowPromotionAndDemotion = false + ) + val dateTimeConversion = + builder.findConversion( + op.resultType!!, + builder.resolveTypeName("System", "DateTime")!!, + implicit = true, + allowPromotionAndDemotion = false + ) + op = + when { + dateConversion != null && dateTimeConversion != null -> { + require(dateConversion.score != dateTimeConversion.score) { + """Ambiguous implicit conversion from + |${op.resultType} to ${dateConversion.toType} + |or ${dateTimeConversion}.""" + .trimMargin() + .replace("\n", "") + } + + if (dateConversion.score < dateTimeConversion.score) { + builder.convertExpression(op, dateConversion) + } else { + builder.convertExpression(op, dateTimeConversion) + } + } + dateConversion != null -> + builder.convertExpression(op, dateConversion) + dateTimeConversion != null -> + builder.convertExpression(op, dateTimeConversion) + else -> { + // ERROR + throw IllegalArgumentException( + """Could not resolve call to operator ${functionRef.name} + with argument of type ${op.resultType.toString()}.""" + .trimIndent() + ) + } + } + } + ops.add(builder.enforceCompatible(patientBirthDateProperty, op.resultType)) + ops.add(op) + return resolveCalculateAgeAt( + ops, + resolveAgeRelatedFunctionPrecision(functionRef) + ) + } + "AgeInHoursAt", + "AgeInMinutesAt", + "AgeInSecondsAt", + "AgeInMillisecondsAt" -> { + val ops: MutableList = ArrayList() + ops.add(patientBirthDateProperty!!) + ops.addAll(functionRef.operand) + return resolveCalculateAgeAt( + ops, + resolveAgeRelatedFunctionPrecision(functionRef) + ) + } + "CalculateAgeInYears", + "CalculateAgeInMonths", + "CalculateAgeInWeeks", + "CalculateAgeInDays", + "CalculateAgeInHours", + "CalculateAgeInMinutes", + "CalculateAgeInSeconds", + "CalculateAgeInMilliseconds" -> { + checkNumberOfOperands(functionRef, 1) + return resolveCalculateAge( + functionRef.operand[0], + resolveAgeRelatedFunctionPrecision(functionRef) + ) + } + "CalculateAgeInYearsAt", + "CalculateAgeInMonthsAt", + "CalculateAgeInWeeksAt", + "CalculateAgeInDaysAt", + "CalculateAgeInHoursAt", + "CalculateAgeInMinutesAt", + "CalculateAgeInSecondsAt", + "CalculateAgeInMillisecondsAt" -> { + return resolveCalculateAgeAt( + functionRef.operand, + resolveAgeRelatedFunctionPrecision(functionRef) + ) + } + "DateTime" -> { + return resolveDateTime(functionRef) + } + "Date" -> { + return resolveDate(functionRef) + } + "Time" -> { + return resolveTime(functionRef) + } + "Now", + "now" -> { + return resolveNow(functionRef) + } + "Today", + "today" -> { + return resolveToday(functionRef) + } + "TimeOfDay", + "timeOfDay" -> { + return resolveTimeOfDay(functionRef) + } + "IndexOf" -> { + return resolveIndexOf(functionRef) + } + "First" -> { + return resolveFirst(functionRef) + } + "Last" -> { + return resolveLast(functionRef) + } + "Skip" -> { + return resolveSkip(functionRef) + } + "Take" -> { + return resolveTake(functionRef) + } + "Tail" -> { + return resolveTail(functionRef) + } + "Contains", + "Expand", + "In", + "Includes", + "IncludedIn", + "ProperIncludes", + "ProperIncludedIn" -> { + return resolveBinary(functionRef) + } + "Distinct", + "Exists", + "Flatten", + "Collapse", + "SingletonFrom", + "ExpandValueSet" -> { + return resolveUnary(functionRef) + } + "Coalesce", + "Intersect", + "Union", + "Except" -> { + return resolveNary(functionRef) + } + "IsNull", + "IsTrue", + "IsFalse" -> { + return resolveUnary(functionRef) + } + "Length", + "Width", + "Size" -> { + return resolveUnary(functionRef) + } + "Indexer", + "StartsWith", + "EndsWith", + "Matches" -> { + return resolveBinary(functionRef) + } + "ReplaceMatches" -> { + return resolveTernary(functionRef) + } + "Concatenate" -> { + return resolveNary(functionRef) + } + "Combine" -> { + return resolveCombine(functionRef) + } + "Split" -> { + return resolveSplit(functionRef) + } + "SplitOnMatches" -> { + return resolveSplitOnMatches(functionRef) + } + "Upper", + "Lower" -> { + return resolveUnary(functionRef) + } + "PositionOf" -> { + return resolvePositionOf(functionRef) + } + "LastPositionOf" -> { + return resolveLastPositionOf(functionRef) + } + "Substring" -> { + return resolveSubstring(functionRef) + } + "Not" -> { + return resolveUnary(functionRef) + } + "And", + "Or", + "Xor", + "Implies" -> { + return resolveBinary(functionRef) + } + "ConvertsToString", + "ConvertsToBoolean", + "ConvertsToInteger", + "ConvertsToLong", + "ConvertsToDecimal", + "ConvertsToDateTime", + "ConvertsToDate", + "ConvertsToTime", + "ConvertsToQuantity", + "ConvertsToRatio", + "ToString", + "ToBoolean", + "ToInteger", + "ToLong", + "ToDecimal", + "ToDateTime", + "ToDate", + "ToTime", + "ToQuantity", + "ToRatio", + "ToConcept", + "ToChars" -> { + return resolveUnary(functionRef) + } + "CanConvertQuantity", + "ConvertQuantity" -> { + return resolveBinary(functionRef) + } + "Equal", + "NotEqual", + "Greater", + "GreaterOrEqual", + "Less", + "LessOrEqual", + "Equivalent" -> { + return resolveBinary(functionRef) + } + "Message" -> return resolveMessage(functionRef) + } + } + return null + } + + // Age-Related Function Support + private fun resolveCalculateAge( + e: Expression?, + p: DateTimePrecision + ): UnaryExpressionInvocation { + val operator = of.createCalculateAge().withPrecision(p).withOperand(e) + val invocation = UnaryExpressionInvocation(operator) + builder.resolveInvocation("System", "CalculateAge", invocation) + return invocation + } + + private fun resolveCalculateAgeAt( + e: List, + p: DateTimePrecision + ): BinaryExpressionInvocation { + val operator = of.createCalculateAgeAt().withPrecision(p).withOperand(e) + val invocation = BinaryExpressionInvocation(operator) + builder.resolveInvocation("System", "CalculateAgeAt", invocation) + return invocation + } + + private val patientBirthDateProperty: Expression? + get() { + val source = builder.resolveIdentifier("Patient", true)!! + val birthDateProperty = builder.defaultModel!!.modelInfo.patientBirthDatePropertyName + // If the property has a qualifier, resolve it as a path (without model mapping) + return if (birthDateProperty!!.indexOf('.') >= 1) { + val property = of.createProperty().withSource(source).withPath(birthDateProperty) + property.resultType = builder.resolvePath(source.resultType, property.path!!) + property + } else { + val resolution = builder.resolveProperty(source.resultType, birthDateProperty) + var result: Expression? = + builder.buildProperty( + source, + resolution!!.name, + resolution.isSearch, + resolution.type + ) + result = builder.applyTargetMap(result, resolution.targetMap) + result + } + } + + // Arithmetic Function Support + private fun resolveRound(functionRef: FunctionRef): RoundInvocation { + require(!(functionRef.operand.isEmpty() || functionRef.operand.size > 2)) { + "Could not resolve call to system operator Round. Expected 1 or 2 arguments." + } + val round = of.createRound().withOperand(functionRef.operand[0]) + if (functionRef.operand.size == 2) { + round.precision = functionRef.operand[1] + } + val invocation = RoundInvocation(round) + builder.resolveInvocation("System", "Round", RoundInvocation(round)) + return invocation + } + + // DateTime Function Support + private fun resolveDateTime(functionRef: FunctionRef): DateTimeInvocation { + val dt = of.createDateTime() + setDateTimeFieldsFromOperands(dt, functionRef.operand) + val invocation = DateTimeInvocation(dt) + builder.resolveInvocation("System", "DateTime", invocation) + return invocation + } + + private fun resolveDate(functionRef: FunctionRef): DateInvocation { + val d = of.createDate() + setDateFieldsFromOperands(d, functionRef.operand) + val invocation = DateInvocation(d) + builder.resolveInvocation("System", "Date", invocation) + return invocation + } + + private fun resolveTime(functionRef: FunctionRef): TimeInvocation { + val t = of.createTime() + setTimeFieldsFromOperands(t, functionRef.operand) + val invocation = TimeInvocation(t) + builder.resolveInvocation("System", "Time", invocation) + return invocation + } + + private fun resolveNow(functionRef: FunctionRef): ZeroOperandExpressionInvocation { + checkNumberOfOperands(functionRef, 0) + val now = of.createNow() + val invocation = ZeroOperandExpressionInvocation(now) + builder.resolveInvocation("System", "Now", invocation) + return invocation + } + + private fun resolveToday(functionRef: FunctionRef): ZeroOperandExpressionInvocation { + checkNumberOfOperands(functionRef, 0) + val today = of.createToday() + val invocation = ZeroOperandExpressionInvocation(today) + builder.resolveInvocation("System", "Today", invocation) + return invocation + } + + private fun resolveTimeOfDay(functionRef: FunctionRef): ZeroOperandExpressionInvocation { + checkNumberOfOperands(functionRef, 0) + val timeOfDay = of.createTimeOfDay() + val invocation = ZeroOperandExpressionInvocation(timeOfDay) + builder.resolveInvocation("System", "TimeOfDay", invocation) + return invocation + } + + // List Function Support + private fun resolveIndexOf(functionRef: FunctionRef): IndexOfInvocation { + checkNumberOfOperands(functionRef, 2) + val indexOf = of.createIndexOf() + indexOf.source = functionRef.operand[0] + indexOf.element = functionRef.operand[1] + val invocation = IndexOfInvocation(indexOf) + builder.resolveInvocation("System", "IndexOf", invocation) + return invocation + } + + private fun resolveFirst(functionRef: FunctionRef): FirstInvocation { + checkNumberOfOperands(functionRef, 1) + val first = of.createFirst() + first.source = functionRef.operand[0] + val invocation = FirstInvocation(first) + builder.resolveInvocation("System", "First", invocation) + return invocation + } + + private fun resolveLast(functionRef: FunctionRef): LastInvocation { + checkNumberOfOperands(functionRef, 1) + val last = of.createLast() + last.source = functionRef.operand[0] + val invocation = LastInvocation(last) + builder.resolveInvocation("System", "Last", invocation) + return invocation + } + + private fun resolveSkip(functionRef: FunctionRef): SkipInvocation { + checkNumberOfOperands(functionRef, 2) + val slice = of.createSlice() + slice.source = functionRef.operand[0] + slice.startIndex = functionRef.operand[1] + slice.endIndex = builder.buildNull(functionRef.operand[1].resultType) + val invocation = SkipInvocation(slice) + builder.resolveInvocation("System", "Skip", invocation) + return invocation + } + + private fun resolveTake(functionRef: FunctionRef): TakeInvocation { + checkNumberOfOperands(functionRef, 2) + val slice = of.createSlice() + slice.source = functionRef.operand[0] + slice.startIndex = builder.createLiteral(0) + val coalesce = + of.createCoalesce().withOperand(functionRef.operand[1], builder.createLiteral(0)) + val naryInvocation = NaryExpressionInvocation(coalesce) + builder.resolveInvocation("System", "Coalesce", naryInvocation) + slice.endIndex = coalesce + val invocation = TakeInvocation(slice) + builder.resolveInvocation("System", "Take", invocation) + return invocation + } + + private fun resolveTail(functionRef: FunctionRef): TailInvocation { + checkNumberOfOperands(functionRef, 1) + val slice = of.createSlice() + slice.source = functionRef.operand[0] + slice.startIndex = builder.createLiteral(1) + slice.endIndex = builder.buildNull(builder.resolveTypeName("System", "Integer")) + val invocation = TailInvocation(slice) + builder.resolveInvocation("System", "Tail", invocation) + return invocation + } + + // String Function Support + private fun resolveCombine(functionRef: FunctionRef): CombineInvocation { + require(!(functionRef.operand.isEmpty() || functionRef.operand.size > 2)) { + "Could not resolve call to system operator Combine. Expected 1 or 2 arguments." + } + val combine = of.createCombine().withSource(functionRef.operand[0]) + if (functionRef.operand.size == 2) { + combine.separator = functionRef.operand[1] + } + val invocation = CombineInvocation(combine) + builder.resolveInvocation("System", "Combine", invocation) + return invocation + } + + private fun resolveSplit(functionRef: FunctionRef): SplitInvocation { + checkNumberOfOperands(functionRef, 2) + val split = + of.createSplit() + .withStringToSplit(functionRef.operand[0]) + .withSeparator(functionRef.operand[1]) + val invocation = SplitInvocation(split) + builder.resolveInvocation("System", "Split", invocation) + return invocation + } + + private fun resolveSplitOnMatches(functionRef: FunctionRef): SplitOnMatchesInvocation { + checkNumberOfOperands(functionRef, 2) + val splitOnMatches = + of.createSplitOnMatches() + .withStringToSplit(functionRef.operand[0]) + .withSeparatorPattern(functionRef.operand[1]) + val invocation = SplitOnMatchesInvocation(splitOnMatches) + builder.resolveInvocation("System", "SplitOnMatches", invocation) + return invocation + } + + private fun resolvePositionOf(functionRef: FunctionRef): PositionOfInvocation { + checkNumberOfOperands(functionRef, 2) + val pos = + of.createPositionOf() + .withPattern(functionRef.operand[0]) + .withString(functionRef.operand[1]) + val invocation = PositionOfInvocation(pos) + builder.resolveInvocation("System", "PositionOf", invocation) + return invocation + } + + private fun resolveLastPositionOf(functionRef: FunctionRef): LastPositionOfInvocation { + checkNumberOfOperands(functionRef, 2) + val pos = + of.createLastPositionOf() + .withPattern(functionRef.operand[0]) + .withString(functionRef.operand[1]) + val invocation = LastPositionOfInvocation(pos) + builder.resolveInvocation("System", "LastPositionOf", invocation) + return invocation + } + + private fun resolveSubstring(functionRef: FunctionRef): SubstringInvocation { + @Suppress("MagicNumber") + require(!(functionRef.operand.size < 2 || functionRef.operand.size > 3)) { + "Could not resolve call to system operator Substring. Expected 2 or 3 arguments." + } + val substring = + of.createSubstring() + .withStringToSub(functionRef.operand[0]) + .withStartIndex(functionRef.operand[1]) + @Suppress("MagicNumber") + if (functionRef.operand.size == 3) { + substring.length = functionRef.operand[2] + } + val invocation = SubstringInvocation(substring) + builder.resolveInvocation("System", "Substring", invocation) + return invocation + } + + // Error Functions + private fun resolveMessage(functionRef: FunctionRef): MessageInvocation { + @Suppress("MagicNumber") + require(functionRef.operand.size == 5) { + "Could not resolve call to system operator Message. Expected 5 arguments." + } + @Suppress("MagicNumber") + val message = + of.createMessage() + .withSource(functionRef.operand[0]) + .withCondition(functionRef.operand[1]) + .withCode(functionRef.operand[2]) + .withSeverity(functionRef.operand[3]) + .withMessage(functionRef.operand[4]) + val invocation = MessageInvocation(message) + builder.resolveInvocation("System", "Message", invocation) + return invocation + } + + // Type Functions + @Suppress("UnusedPrivateMember") + private fun resolveConvert(functionRef: FunctionRef): ConvertInvocation { + checkNumberOfOperands(functionRef, 1) + val convert = of.createConvert().withOperand(functionRef.operand[0]) + val sm = builder.systemModel + when (functionRef.name) { + "ToString" -> convert.toType = builder.dataTypeToQName(sm.string) + "ToBoolean" -> convert.toType = builder.dataTypeToQName(sm.boolean) + "ToInteger" -> convert.toType = builder.dataTypeToQName(sm.integer) + "ToLong" -> convert.toType = builder.dataTypeToQName(sm.long) + "ToDecimal" -> convert.toType = builder.dataTypeToQName(sm.decimal) + "ToQuantity" -> convert.toType = builder.dataTypeToQName(sm.quantity) + "ToRatio" -> convert.toType = builder.dataTypeToQName(sm.ratio) + "ToDate" -> convert.toType = builder.dataTypeToQName(sm.date) + "ToDateTime" -> convert.toType = builder.dataTypeToQName(sm.dateTime) + "ToTime" -> convert.toType = builder.dataTypeToQName(sm.time) + "ToConcept" -> convert.toType = builder.dataTypeToQName(sm.concept) + else -> + throw IllegalArgumentException( + "Could not resolve call to system operator ${functionRef.name}. Unknown conversion type." + ) + } + val invocation = ConvertInvocation(convert) + builder.resolveInvocation("System", functionRef.name!!, invocation) + return invocation + } + + // General Function Support + private inline fun createExpression(functionRef: FunctionRef): T { + return try { + T::class.java.cast(of.javaClass.getMethod("create" + functionRef.name).invoke(of)) + } catch (@Suppress("TooGenericExceptionCaught") e: Exception) { + throw CqlInternalException( + "Could not create instance of Element \"${functionRef.name}\"", + if (functionRef.trackbacks.isNotEmpty()) functionRef.trackbacks[0] else null, + e + ) + } + } + + private fun resolveUnary(functionRef: FunctionRef): UnaryExpressionInvocation<*> { + val operator = createExpression(functionRef) + checkNumberOfOperands(functionRef, 1) + operator.operand = functionRef.operand[0] + val invocation = UnaryExpressionInvocation(operator) + builder.resolveInvocation("System", functionRef.name!!, invocation) + return invocation + } + + private fun resolveBinary(functionRef: FunctionRef): BinaryExpressionInvocation<*> { + val operator = createExpression(functionRef) + checkNumberOfOperands(functionRef, 2) + operator.operand.addAll(functionRef.operand) + val invocation = BinaryExpressionInvocation(operator) + builder.resolveInvocation("System", functionRef.name!!, invocation) + return invocation + } + + private fun resolveTernary(functionRef: FunctionRef): TernaryExpressionInvocation<*> { + val operator = createExpression(functionRef) + @Suppress("MagicNumber") checkNumberOfOperands(functionRef, 3) + operator.operand.addAll(functionRef.operand) + val invocation = TernaryExpressionInvocation(operator) + builder.resolveInvocation("System", functionRef.name!!, invocation) + return invocation + } + + private fun resolveNary(functionRef: FunctionRef): NaryExpressionInvocation { + val operator = createExpression(functionRef) + operator.operand.addAll(functionRef.operand) + val invocation = NaryExpressionInvocation(operator) + builder.resolveInvocation("System", functionRef.name!!, invocation) + return invocation + } + + private fun resolveAggregate(functionRef: FunctionRef): AggregateExpressionInvocation<*> { + val operator = createExpression(functionRef) + checkNumberOfOperands(functionRef, 1) + operator.source = functionRef.operand[0] + val invocation = AggregateExpressionInvocation(operator) + builder.resolveInvocation("System", functionRef.name!!, invocation) + return invocation + } + + private fun checkNumberOfOperands(functionRef: FunctionRef, expectedOperands: Int) { + require(functionRef.operand.size == expectedOperands) { + "Could not resolve call to system operator ${functionRef.name}. Expected $expectedOperands arguments." + } + } + + companion object { + private fun resolveAgeRelatedFunctionPrecision( + functionRef: FunctionRef + ): DateTimePrecision { + val name = functionRef.name + return when { + name!!.contains("Years") -> DateTimePrecision.YEAR + name.contains("Months") -> DateTimePrecision.MONTH + name.contains("Weeks") -> DateTimePrecision.WEEK + name.contains("Days") -> DateTimePrecision.DAY + name.contains("Hours") -> DateTimePrecision.HOUR + name.contains("Minutes") -> DateTimePrecision.MINUTE + name.contains("Second") -> DateTimePrecision.SECOND + name.contains("Milliseconds") -> DateTimePrecision.MILLISECOND + else -> throw IllegalArgumentException("Unknown precision '$name'.") + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/SystemMethodResolver.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/SystemMethodResolver.kt new file mode 100644 index 000000000..54a179c2e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/SystemMethodResolver.kt @@ -0,0 +1,534 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import java.util.* +import org.cqframework.cql.cql2elm.model.QueryContext +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.cqframework.cql.gen.cqlParser +import org.hl7.cql.model.* +import org.hl7.elm.r1.* + +/** Created by Bryn on 12/27/2016. */ +@Suppress("TooManyFunctions") +class SystemMethodResolver( + private val visitor: Cql2ElmVisitor, + private val builder: LibraryBuilder +) { + private val of = builder.objectFactory + + private fun getParams( + target: Expression, + ctx: cqlParser.ParamListContext? + ): MutableList { + val params: MutableList = ArrayList() + params.add(target) + if (ctx?.expression() != null) { + for (param in ctx.expression()) { + params.add(visitor.visit(param) as Expression) + } + } + return params + } + + private fun checkArgumentCount( + ctx: cqlParser.ParamListContext?, + functionName: String, + expectedCount: Int + ) { + var actualCount = 0 + if (ctx?.expression() != null) { + actualCount = ctx.expression().size + } + require(actualCount == expectedCount) { + "Expected ${Integer.valueOf(expectedCount)} argument for method $functionName." + } + } + + private fun enterQueryContext(target: Expression): AliasedQuerySource { + val queryContext = QueryContext() + queryContext.isImplicit = true + // Set to an implicit context to allow for implicit resolution of property names + val sources: MutableList = ArrayList() + val source = of.createAliasedQuerySource().withExpression(target).withAlias("\$this") + source.resultType = target.resultType + sources.add(source) + queryContext.addPrimaryQuerySources(sources) + builder.pushQueryContext(queryContext) + return source + } + + private fun createQuery( + source: AliasedQuerySource, + let: LetClause?, + where: Expression?, + ret: ReturnClause? + ): Query { + val queryContext = builder.peekQueryContext() + val lets: MutableList = arrayListOf() + if (let != null) { + lets.add(let) + } + val query = + of.createQuery() + .withSource(queryContext.querySources) + .withLet(lets) + .withWhere(where) + .withReturn(ret) + if (ret != null) { + query.resultType = ret.resultType + } else { + query.resultType = source.resultType + } + return query + } + + private fun exitQueryContext() { + builder.popQueryContext() + } + + private fun createWhere( + target: Expression, + functionName: String, + ctx: cqlParser.ParamListContext? + ): Query { + val source = enterQueryContext(target) + return try { + checkArgumentCount(ctx, functionName, 1) + var where = visitor.visit(ctx!!.expression(0)!!) as Expression? + if (visitor.dateRangeOptimization) { + where = visitor.optimizeDateRangeInQuery(where, source) + } + createQuery(source, null, where, null) + } finally { + exitQueryContext() + } + } + + // X.ofType(T) === X $this where $this is T + private fun createOfType( + target: Expression, + functionName: String, + ctx: cqlParser.ParamListContext? + ): Expression { + val source = enterQueryContext(target) + return try { + checkArgumentCount(ctx, functionName, 1) + builder.pushTypeSpecifierContext() + val typeArgument: Expression? = + try { + visitor.visit(ctx!!.expression(0)!!) as Expression? + } finally { + builder.popTypeSpecifierContext() + } + require(typeArgument is Literal) { "Expected literal argument" } + require( + DataTypes.equal( + typeArgument.resultType, + builder.resolveTypeName("System", "String") + ) + ) { + "Expected string literal argument" + } + val typeSpecifier = typeArgument.value + val isType = builder.resolveTypeSpecifier(typeSpecifier) + val thisRef = of.createAliasRef().withName(source.alias) + val isSingular = source.resultType !is ListType + val elementType = + if (isSingular) source.resultType else (source.resultType as ListType).elementType + thisRef.resultType = elementType + val isExpression = of.createIs().withOperand(thisRef) + if (isType is NamedType) { + isExpression.isType = builder.dataTypeToQName(isType) + } else { + isExpression.isTypeSpecifier = builder.dataTypeToTypeSpecifier(isType) + } + isExpression.resultType = builder.resolveTypeName("System", "Boolean") + createQuery(source, null, isExpression, null) + } finally { + exitQueryContext() + } + } + + private fun createRepeat( + target: Expression, + functionName: String, + ctx: cqlParser.ParamListContext? + ): Expression { + val source = enterQueryContext(target) + return try { + val isSingular = source.resultType !is ListType + checkArgumentCount(ctx, functionName, 1) + val select = visitor.visit(ctx!!.expression(0)!!) as Expression? + val repeat = of.createRepeat() + repeat.source = target + repeat.element = select + repeat.scope = "\$this" + @Suppress("ForbiddenComment") + // TODO: This isn't quite right, it glosses over the fact that the type of the result + // may include the result + // of invoking the element expression on intermediate results + if (isSingular) { + repeat.resultType = ListType(select!!.resultType!!) + } else { + repeat.resultType = select!!.resultType + } + repeat + } finally { + exitQueryContext() + } + } + + private fun createSelect( + target: Expression, + functionName: String, + ctx: cqlParser.ParamListContext? + ): Expression? { + val isListResult: Boolean + val isSingular: Boolean + val source = enterQueryContext(target) + return try { + isSingular = source.resultType !is ListType + checkArgumentCount(ctx, functionName, 1) + val select = visitor.visit(ctx!!.expression(0)!!) as Expression? + val queryContext = builder.peekQueryContext() + val let = of.createLetClause().withExpression(select).withIdentifier("\$a") + let.resultType = select!!.resultType + queryContext.addLetClause(let) + isListResult = select.resultType is ListType + var letRef = of.createQueryLetRef().withName("\$a") + letRef.resultType = select.resultType + var params: MutableList = ArrayList() + params.add(letRef) + var where = builder.resolveFunction(null, "IsNull", params)!! + params = ArrayList() + params.add(where) + where = builder.resolveFunction(null, "Not", params)!! + val returnClause = of.createReturnClause() + letRef = of.createQueryLetRef().withName("\$a") + letRef.resultType = select.resultType + returnClause.expression = letRef + returnClause.resultType = + if (isSingular) letRef.resultType else ListType(letRef.resultType!!) + val query = createQuery(source, let, where, returnClause) + if (!isSingular && isListResult) { + params = ArrayList() + params.add(query) + builder.resolveFunction(null, "Flatten", params) + } else { + query + } + } finally { + exitQueryContext() + } + } + + private fun gatherChildTypes( + dataType: DataType, + recurse: Boolean, + dataTypes: MutableSet + ) { + when (dataType) { + is ClassType -> { + for (element in dataType.elements) { + val elementType = + if (element.type is ListType) (element.type as ListType).elementType + else element.type + dataTypes.add(elementType) + if (recurse) { + gatherChildTypes(elementType, true, dataTypes) + } + } + } + is TupleType -> { + for (element: TupleTypeElement in dataType.elements) { + val elementType = + if (element.type is ListType) (element.type as ListType).elementType + else element.type + dataTypes.add(elementType) + if (recurse) { + gatherChildTypes(elementType, true, dataTypes) + } + } + } + is ListType -> { + val elementType = dataType.elementType + dataTypes.add(elementType) + if (recurse) { + gatherChildTypes(elementType, true, dataTypes) + } + } + else -> { + dataTypes.add(builder.resolveTypeName("System.Any")!!) + } + } + } + + @Suppress("LongMethod", "CyclomaticComplexMethod") + fun resolveMethod( + target: Expression, + functionName: String, + ctx: cqlParser.ParamListContext?, + mustResolve: Boolean + ): Expression? { + return when (functionName) { + "aggregate" -> builder.resolveFunction(null, "Aggregate", getParams(target, ctx)) + "abs" -> builder.resolveFunction(null, "Abs", getParams(target, ctx)) + "all" -> { + + // .all(criteria) resolves as .where(criteria).select(true).allTrue() + val query = createWhere(target, functionName, ctx) + val returnClause = of.createReturnClause() + returnClause.expression = builder.createLiteral(java.lang.Boolean.valueOf(true)) + if (query.resultType is ListType) { + returnClause.resultType = ListType(returnClause.expression!!.resultType!!) + } else { + returnClause.resultType = returnClause.expression!!.resultType + } + query.`return` = (returnClause) + query.resultType = returnClause.resultType + val params: MutableList = ArrayList() + params.add(query) + builder.resolveFunction(null, "AllTrue", params) + } + "allTrue" -> builder.resolveFunction(null, "AllTrue", getParams(target, ctx)) + "anyTrue" -> builder.resolveFunction(null, "AnyTrue", getParams(target, ctx)) + "allFalse" -> builder.resolveFunction(null, "AllFalse", getParams(target, ctx)) + "anyFalse" -> builder.resolveFunction(null, "AnyFalse", getParams(target, ctx)) + "ceiling" -> builder.resolveFunction(null, "Ceiling", getParams(target, ctx)) + "children" -> { + checkArgumentCount(ctx, functionName, 0) + val children = of.createChildren() + children.source = target + val dataTypes: MutableSet = HashSet() + gatherChildTypes(target.resultType!!, false, dataTypes) + if (dataTypes.size == 1) { + children.resultType = ListType(dataTypes.iterator().next()) + } else { + children.resultType = ListType(ChoiceType(dataTypes)) + } + children + } + "combine" -> { + checkArgumentCount(ctx, functionName, 1) + val elements: MutableList = ArrayList() + val argument = visitor.visit(ctx!!.expression(0)!!) as Expression + elements.add(target) + elements.add(argument) + val elementType = + builder.ensureCompatibleTypes(target.resultType, argument.resultType!!)!! + val list = of.createList() + list.resultType = ListType(elementType) + list.element.add(builder.ensureCompatible(target, elementType)) + list.element.add(builder.ensureCompatible(argument, elementType)) + val params = ArrayList() + params.add(list) + builder.resolveFunction(null, "Flatten", params) + } + "contains" -> { + checkArgumentCount(ctx, functionName, 1) + var params: MutableList = ArrayList() + val argument = visitor.visit(ctx!!.expression(0)!!) as Expression + params.add(argument) + params.add(target) + val result = builder.resolveFunction(null, "PositionOf", params)!! + params = ArrayList() + params.add(result) + params.add(builder.createLiteral(0)) + builder.resolveFunction(null, "GreaterOrEqual", params) + } + "convertsToBoolean" -> + builder.resolveFunction(null, "ConvertsToBoolean", getParams(target, ctx)) + "convertsToDate" -> + builder.resolveFunction(null, "ConvertsToDate", getParams(target, ctx)) + "convertsToDateTime" -> + builder.resolveFunction(null, "ConvertsToDateTime", getParams(target, ctx)) + "convertsToDecimal" -> + builder.resolveFunction(null, "ConvertsToDecimal", getParams(target, ctx)) + "convertsToInteger" -> + builder.resolveFunction(null, "ConvertsToInteger", getParams(target, ctx)) + "convertsToQuantity" -> + builder.resolveFunction(null, "ConvertsToQuantity", getParams(target, ctx)) + "convertsToString" -> + builder.resolveFunction(null, "ConvertsToString", getParams(target, ctx)) + "convertsToTime" -> + builder.resolveFunction(null, "ConvertsToTime", getParams(target, ctx)) + "count" -> builder.resolveFunction(null, "Count", getParams(target, ctx)) + "descendents" -> { + checkArgumentCount(ctx, functionName, 0) + val descendents = of.createDescendents() + descendents.source = target + val dataTypes: MutableSet = HashSet() + gatherChildTypes(target.resultType!!, true, dataTypes) + if (dataTypes.size == 1) { + descendents.resultType = ListType(dataTypes.toTypedArray()[0]) + } else { + descendents.resultType = ListType(ChoiceType(dataTypes)) + } + descendents + } + "distinct" -> builder.resolveFunction(null, "Distinct", getParams(target, ctx)) + "empty" -> { + var params = getParams(target, ctx) + val exists = builder.resolveFunction(null, "Exists", params)!! + params = ArrayList() + params.add(exists) + builder.resolveFunction(null, "Not", params) + } + "endsWith" -> builder.resolveFunction(null, "EndsWith", getParams(target, ctx)) + "exclude" -> builder.resolveFunction(null, "Except", getParams(target, ctx)) + "exists" -> { + if (ctx?.expression() == null || ctx.expression().isEmpty()) { + val params: List = getParams(target, ctx) + builder.resolveFunction(null, "Exists", params) + } else { + // .exists(criteria) resolves as a .where(criteria).exists() + val query = createWhere(target, functionName, ctx) + val params: MutableList = ArrayList() + params.add(query) + builder.resolveFunction(null, "Exists", params) + } + } + "exp" -> builder.resolveFunction(null, "Exp", getParams(target, ctx)) + "first" -> builder.resolveFunction(null, "First", getParams(target, ctx)) + "floor" -> builder.resolveFunction(null, "Floor", getParams(target, ctx)) + "hasValue" -> { + var params = getParams(target, ctx) + val isNull = builder.resolveFunction(null, "IsNull", params)!! + params = ArrayList() + params.add(isNull) + builder.resolveFunction(null, "Not", params) + } + "iif" -> { + var result: Expression? = target + val params: MutableList = ArrayList() + if (result!!.resultType is ListType) { + params.add(result) + result = builder.resolveFunction(null, "SingletonFrom", params) + } + val thenExpression = visitor.visit(ctx!!.expression(0)!!) as Expression? + val elseExpression = + if (ctx.expression().size == 2) + visitor.visit(ctx.expression(1)!!) as Expression? + else of.createNull() + result = + of.createIf() + .withCondition(result) + .withThen(thenExpression) + .withElse(elseExpression) + visitor.resolveIfThenElse(result) + } + "indexOf" -> { + checkArgumentCount(ctx, functionName, 1) + val params: MutableList = ArrayList() + val argument = visitor.visit(ctx!!.expression(0)!!) as Expression + params.add(argument) + params.add(target) + builder.resolveFunction(null, "PositionOf", params) + } + "intersect" -> builder.resolveFunction(null, "Intersect", getParams(target, ctx)) + "is", + "as" -> { + checkArgumentCount(ctx, functionName, 1) + builder.pushTypeSpecifierContext() + val typeArgument: Expression? = + try { + visitor.visit(ctx!!.expression(0)!!) as Expression? + } finally { + builder.popTypeSpecifierContext() + } + require(typeArgument is Literal) { "Expected literal argument" } + require( + DataTypes.equal( + typeArgument.resultType, + builder.resolveTypeName("System", "String") + ) + ) { + "Expected string literal argument" + } + val typeSpecifier = typeArgument.value + val isType = builder.resolveTypeSpecifier(typeSpecifier) + if (functionName == "is") builder.buildIs(target, isType) + else builder.buildAs(target, isType) + } + "last" -> builder.resolveFunction(null, "Last", getParams(target, ctx)) + "lastIndexOf" -> { + checkArgumentCount(ctx, functionName, 1) + val params: MutableList = ArrayList() + val argument = visitor.visit(ctx!!.expression(0)!!) as Expression + params.add(argument) + params.add(target) + builder.resolveFunction(null, "LastPositionOf", params) + } + "length" -> builder.resolveFunction(null, "Length", getParams(target, ctx)) + "ln" -> builder.resolveFunction(null, "Ln", getParams(target, ctx)) + "log" -> builder.resolveFunction(null, "Log", getParams(target, ctx)) + "lower" -> builder.resolveFunction(null, "Lower", getParams(target, ctx)) + "matches" -> builder.resolveFunction(null, "Matches", getParams(target, ctx)) + "memberOf" -> builder.resolveFunction(null, "InValueSet", getParams(target, ctx)) + "not" -> builder.resolveFunction(null, "Not", getParams(target, ctx)) + "ofType" -> createOfType(target, functionName, ctx) + "power" -> builder.resolveFunction(null, "Power", getParams(target, ctx)) + "repeat" -> createRepeat(target, functionName, ctx) + "replace" -> builder.resolveFunction(null, "Replace", getParams(target, ctx)) + "replaceMatches" -> + builder.resolveFunction(null, "ReplaceMatches", getParams(target, ctx)) + "round" -> builder.resolveFunction(null, "Round", getParams(target, ctx)) + "select" -> { + createSelect(target, functionName, ctx) + } + "single" -> builder.resolveFunction(null, "SingletonFrom", getParams(target, ctx)) + "skip" -> builder.resolveFunction(null, "Skip", getParams(target, ctx)) + "sqrt" -> { + checkArgumentCount(ctx, functionName, 0) + val params: MutableList = ArrayList() + params.add(target) + params.add(builder.createLiteral(@Suppress("MagicNumber") 0.5)) + builder.resolveFunction(null, "Power", params) + } + "startsWith" -> builder.resolveFunction(null, "StartsWith", getParams(target, ctx)) + "subsetOf" -> builder.resolveFunction(null, "IncludedIn", getParams(target, ctx)) + "substring" -> builder.resolveFunction(null, "Substring", getParams(target, ctx)) + "subsumes" -> builder.resolveFunction(null, "Subsumes", getParams(target, ctx)) + "subsumedBy" -> builder.resolveFunction(null, "SubsumedBy", getParams(target, ctx)) + "supersetOf" -> builder.resolveFunction(null, "Includes", getParams(target, ctx)) + "tail" -> builder.resolveFunction(null, "Tail", getParams(target, ctx)) + "take" -> builder.resolveFunction(null, "Take", getParams(target, ctx)) + "toBoolean" -> builder.resolveFunction(null, "ToBoolean", getParams(target, ctx)) + "toChars" -> builder.resolveFunction(null, "ToChars", getParams(target, ctx)) + "toDate" -> builder.resolveFunction(null, "ToDate", getParams(target, ctx)) + "toDateTime" -> builder.resolveFunction(null, "ToDateTime", getParams(target, ctx)) + "toDecimal" -> builder.resolveFunction(null, "ToDecimal", getParams(target, ctx)) + "toInteger" -> builder.resolveFunction(null, "ToInteger", getParams(target, ctx)) + "toQuantity" -> builder.resolveFunction(null, "ToQuantity", getParams(target, ctx)) + "toString" -> builder.resolveFunction(null, "ToString", getParams(target, ctx)) + "toTime" -> builder.resolveFunction(null, "ToTime", getParams(target, ctx)) + "trace" -> { + checkArgumentCount(ctx, functionName, 1) + val params: MutableList = ArrayList() + params.add(target) + params.add(builder.createLiteral(true)) + params.add(builder.createLiteral("TRACE")) + params.add(builder.createLiteral("Trace")) + params.add(visitor.visit(ctx!!.expression(0)!!) as Expression) + builder.resolveFunction(null, "Message", params) + } + "truncate" -> builder.resolveFunction(null, "Truncate", getParams(target, ctx)) + "union" -> builder.resolveFunction(null, "Union", getParams(target, ctx)) + "upper" -> builder.resolveFunction(null, "Upper", getParams(target, ctx)) + "where" -> { + createWhere(target, functionName, ctx) + } + else -> { + visitor.resolveFunction( + null, + functionName, + getParams(target, ctx), + mustResolve, + allowPromotionAndDemotion = false, + allowFluent = true + ) + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/TypeBuilder.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/TypeBuilder.kt new file mode 100644 index 000000000..3e97726b9 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/TypeBuilder.kt @@ -0,0 +1,129 @@ +@file:Suppress("WildcardImport") + +package org.cqframework.cql.cql2elm + +import javax.xml.namespace.QName +import kotlin.collections.ArrayList +import org.cqframework.cql.cql2elm.model.Model +import org.cqframework.cql.cql2elm.tracking.Trackable.withResultType +import org.cqframework.cql.elm.IdObjectFactory +import org.hl7.cql.model.* +import org.hl7.elm.r1.ParameterTypeSpecifier +import org.hl7.elm.r1.TupleElementDefinition +import org.hl7.elm.r1.TypeSpecifier +import org.hl7.elm_modelinfo.r1.ModelInfo + +class TypeBuilder(private val of: IdObjectFactory, private val mr: ModelResolver) { + class InternalModelResolver(private val modelManager: ModelManager) : ModelResolver { + override fun getModel(modelName: String): Model { + return modelManager.resolveModel(modelName) + } + } + + constructor( + of: IdObjectFactory, + modelManager: ModelManager + ) : this(of, InternalModelResolver(modelManager)) + + fun dataTypeToQName(type: DataType?): QName { + if (type is NamedType) { + val namedType: NamedType = type + val modelInfo: ModelInfo = mr.getModel(namedType.namespace).modelInfo + return QName( + if (modelInfo.targetUrl != null) modelInfo.targetUrl else modelInfo.url, + if (namedType.target != null) namedType.target else namedType.simpleName + ) + } + + // ERROR: + throw IllegalArgumentException("A named type is required in this context.") + } + + fun dataTypesToTypeSpecifiers(types: List): List { + val result: ArrayList = ArrayList() + for (type: DataType in types) { + result.add(dataTypeToTypeSpecifier(type)) + } + return result + } + + @Suppress("ReturnCount") + fun dataTypeToTypeSpecifier(type: DataType?): TypeSpecifier { + // Convert the given type into an ELM TypeSpecifier representation. + when (type) { + is NamedType -> { + return of.createNamedTypeSpecifier() + .withName(dataTypeToQName(type)) + .withResultType(type) + } + is ListType -> { + return listTypeToTypeSpecifier(type) + } + is IntervalType -> { + return intervalTypeToTypeSpecifier(type) + } + is TupleType -> { + return tupleTypeToTypeSpecifier(type) + } + is ChoiceType -> { + return choiceTypeToTypeSpecifier(type) + } + is TypeParameter -> { + return typeParameterToTypeSpecifier(type) + } + else -> { + throw IllegalArgumentException("Could not convert type $type to a type specifier.") + } + } + } + + private fun listTypeToTypeSpecifier(type: ListType): TypeSpecifier { + return of.createListTypeSpecifier() + .withElementType(dataTypeToTypeSpecifier(type.elementType)) + .withResultType(type) + } + + private fun intervalTypeToTypeSpecifier(type: IntervalType): TypeSpecifier { + return of.createIntervalTypeSpecifier() + .withPointType(dataTypeToTypeSpecifier(type.pointType)) + .withResultType(type) + } + + private fun tupleTypeToTypeSpecifier(type: TupleType): TypeSpecifier { + return of.createTupleTypeSpecifier() + .withElement(tupleTypeElementsToTupleElementDefinitions(type.elements)) + .withResultType(type) + } + + private fun tupleTypeElementsToTupleElementDefinitions( + elements: Iterable + ): List { + val definitions: MutableList = ArrayList() + for (element: TupleTypeElement in elements) { + definitions.add( + of.createTupleElementDefinition() + .withName(element.name) + .withElementType(dataTypeToTypeSpecifier(element.type)) + ) + } + return definitions + } + + private fun choiceTypeToTypeSpecifier(type: ChoiceType): TypeSpecifier { + return of.createChoiceTypeSpecifier() + .withChoice(choiceTypeTypesToTypeSpecifiers(type)) + .withResultType(type) + } + + private fun choiceTypeTypesToTypeSpecifiers(choiceType: ChoiceType): List { + val specifiers: MutableList = ArrayList() + for (type: DataType in choiceType.types) { + specifiers.add(dataTypeToTypeSpecifier(type)) + } + return specifiers + } + + private fun typeParameterToTypeSpecifier(type: TypeParameter): TypeSpecifier { + return ParameterTypeSpecifier().withParameterName(type.identifier) + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/ElmEdit.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/ElmEdit.kt new file mode 100644 index 000000000..35702ab2e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/ElmEdit.kt @@ -0,0 +1,40 @@ +package org.cqframework.cql.cql2elm.elm + +import org.hl7.cql_annotations.r1.Annotation +import org.hl7.cql_annotations.r1.CqlToElmBase +import org.hl7.elm.r1.Element + +enum class ElmEdit : IElmEdit { + REMOVE_LOCATOR { + override fun edit(element: Element) { + element.locator = null + } + }, + REMOVE_ANNOTATION { + override fun edit(element: Element) { + element.localId = null + removeAnnotations(element.annotation) + } + + private fun removeAnnotations(annotations: MutableList) { + for (i in annotations.indices.reversed()) { + val x = annotations[i] + if (x is Annotation) { + x.s = null + // Remove narrative but _not_ tags. + // Tags are necessary for `allowFluent` compiler resolution + // to work correctly + if (x.t.isEmpty()) { + annotations.removeAt(i) + } + } + } + } + }, + REMOVE_RESULT_TYPE { + override fun edit(element: Element) { + element.resultTypeName = null + element.resultTypeSpecifier = null + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/ElmEditor.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/ElmEditor.kt new file mode 100644 index 000000000..4ed3dc388 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/ElmEditor.kt @@ -0,0 +1,31 @@ +package org.cqframework.cql.cql2elm.elm + +import org.cqframework.cql.elm.visiting.FunctionalElmVisitor +import org.hl7.elm.r1.Element +import org.hl7.elm.r1.Library + +class ElmEditor(private val edits: List) { + private val visitor: FunctionalElmVisitor = + FunctionalElmVisitor.from( + { t, _ -> t }, + { current, next -> this.aggregateResults(current, next) } + ) + + fun edit(library: Library) { + visitor.visitLibrary(library, Unit) + + // This is needed because aggregateResults is not called on the library itself. + this.applyEdits(library) + } + + private fun aggregateResults(aggregate: Element?, nextResult: Element?): Element? { + nextResult?.let { applyEdits(it) } + return aggregate + } + + fun applyEdits(trackable: Element) { + for (edit in edits) { + edit.edit(trackable) + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/IElmEdit.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/IElmEdit.kt new file mode 100644 index 000000000..3749b00bb --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/elm/IElmEdit.kt @@ -0,0 +1,7 @@ +package org.cqframework.cql.cql2elm.elm + +import org.hl7.elm.r1.Element + +fun interface IElmEdit { + fun edit(element: Element) +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/CallContext.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/CallContext.kt new file mode 100644 index 000000000..a2edcc0ca --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/CallContext.kt @@ -0,0 +1,35 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.cql.model.DataType + +class CallContext( + val libraryName: String?, + val operatorName: String, + val allowPromotionAndDemotion: Boolean, + val allowFluent: Boolean, + val mustResolve: Boolean, + operandTypes: List +) { + constructor( + libraryName: String?, + operatorName: String, + allowPromotionAndDemotion: Boolean, + allowFluent: Boolean, + mustResolve: Boolean, + vararg operandTypes: DataType + ) : this( + libraryName, + operatorName, + allowPromotionAndDemotion, + allowFluent, + mustResolve, + operandTypes.toList() + ) + + val signature: Signature + + init { + require(operatorName.isNotEmpty()) { "operatorName is empty" } + signature = Signature(operandTypes) + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Chunk.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Chunk.kt new file mode 100644 index 000000000..28d83cd29 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Chunk.kt @@ -0,0 +1,85 @@ +package org.cqframework.cql.cql2elm.model + +import org.antlr.v4.kotlinruntime.misc.Interval +import org.hl7.elm.r1.Element + +/* +If a chunk is a header chunk then the narrative construction can choose to "trim" the content +to avoid the inclusion of whitespace between definitions in the narrative output. +This does have the side effect of needing to reconstitute whitespace when reconstructing the +entire narrative from the source annotations, but that isn't a use case we're optimizing for, +we're focusing on providing minimal required narrative per definition. + */ +class Chunk(var interval: Interval, val isHeaderChunk: Boolean) { + constructor(interval: Interval) : this(interval, false) + + var element: Element? = null + + private val chunks: MutableList = ArrayList() + + private fun ensureChunks() { + if (chunks.isEmpty()) { + chunks.add(Chunk(interval)) + } + } + + fun getChunks(): List { + ensureChunks() + return chunks + } + + fun hasChunks(): Boolean { + return chunks.isNotEmpty() + } + + fun addChunk(chunk: Chunk) { + require(chunk.interval.a >= interval.a && chunk.interval.b <= interval.b) { + "Child chunk cannot be added because it is not contained within the parent chunk." + } + + ensureChunks() + var chunkIndex = -1 + var targetChunk: Chunk? = null + for (i in chunks.indices) { + if ( + chunk.interval.a >= chunks[i].interval.a && chunk.interval.a <= chunks[i].interval.b + ) { + chunkIndex = i + targetChunk = chunks[chunkIndex] + break + } + } + + checkNotNull(targetChunk) { "Unable to find target chunk for insertion." } + + if (chunk.interval.a == targetChunk.interval.a) { + // the chunk being added starts the targetChunk + // insert the chunk at the targetChunk's index + // update the targetChunk's interval start to be the chunk's interval end + 1 + chunks.add(chunkIndex, chunk) + chunkIndex++ + val newA = chunk.interval.b + 1 + while (newA > chunks[chunkIndex].interval.b) { + chunks.removeAt(chunkIndex) + if (chunkIndex >= chunks.size) { + break + } + } + if (chunkIndex < chunks.size) { + chunks[chunkIndex].interval = Interval(newA, chunks[chunkIndex].interval.b) + } + } else { + val newB = chunk.interval.a - 1 + val newA = chunk.interval.b + 1 + val oldA = chunks[chunkIndex].interval.a + val oldB = chunks[chunkIndex].interval.b + chunks[chunkIndex].interval = Interval(oldA, newB) + chunkIndex++ + chunks.add(chunkIndex, chunk) + chunkIndex++ + if (newA <= oldB) { + chunks.add(chunkIndex, Chunk(Interval(newA, oldB))) + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/CompiledLibrary.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/CompiledLibrary.kt new file mode 100644 index 000000000..f94bc790b --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/CompiledLibrary.kt @@ -0,0 +1,263 @@ +package org.cqframework.cql.cql2elm.model + +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import org.hl7.cql.model.DataType +import org.hl7.cql.model.NamespaceManager +import org.hl7.cql_annotations.r1.Annotation +import org.hl7.elm.r1.CodeDef +import org.hl7.elm.r1.CodeSystemDef +import org.hl7.elm.r1.ConceptDef +import org.hl7.elm.r1.Element +import org.hl7.elm.r1.ExpressionDef +import org.hl7.elm.r1.FunctionDef +import org.hl7.elm.r1.IncludeDef +import org.hl7.elm.r1.Library +import org.hl7.elm.r1.ParameterDef +import org.hl7.elm.r1.UsingDef +import org.hl7.elm.r1.ValueSetDef +import org.hl7.elm.r1.VersionedIdentifier + +@Suppress("TooManyFunctions") +class CompiledLibrary { + var identifier: VersionedIdentifier? = null + var library: Library? = null + private val namespace: MutableMap = HashMap() + val operatorMap: OperatorMap = OperatorMap() + private val functionDefs: MutableMap = HashMap() + private val conversions: MutableList = ArrayList() + + private fun checkNamespace(identifier: String) { + val existingResolvedIdentifierContext = resolve(identifier) + existingResolvedIdentifierContext.exactMatchElement?.let { + throw IllegalArgumentException( + "Identifier $identifier is already in use in this library." + ) + } + } + + fun add(using: UsingDef) { + checkNamespace(using.localIdentifier!!) + namespace[using.localIdentifier!!] = using + } + + fun add(include: IncludeDef) { + checkNamespace(include.localIdentifier!!) + namespace[include.localIdentifier!!] = include + } + + fun add(codesystem: CodeSystemDef) { + checkNamespace(codesystem.name!!) + namespace[codesystem.name!!] = codesystem + } + + fun add(valueset: ValueSetDef) { + checkNamespace(valueset.name!!) + namespace[valueset.name!!] = valueset + } + + fun add(code: CodeDef) { + checkNamespace(code.name!!) + namespace[code.name!!] = code + } + + fun add(concept: ConceptDef) { + checkNamespace(concept.name!!) + namespace[concept.name!!] = concept + } + + fun add(parameter: ParameterDef) { + checkNamespace(parameter.name!!) + namespace[parameter.name!!] = parameter + } + + fun add(expression: ExpressionDef) { + if (expression is FunctionDef) { + // Register the operator signature + add(expression, Operator(expression)) + } else { + checkNamespace(expression.name!!) + namespace[expression.name!!] = expression + } + } + + fun remove(expression: ExpressionDef) { + require(expression !is FunctionDef) { "FunctionDef cannot be removed." } + namespace.remove(expression.name) + } + + private fun ensureLibrary(operator: Operator) { + // The operator must be defined in the library in which it is registered + // If the operator is not defined in a library, it is assumed to be defined in this library + // If this library has no identifier, the operator must not have an identifier + operator.libraryName = operator.libraryName ?: identifier?.id + require(operator.libraryName == identifier?.id) { + "Operator ${operator.name} cannot be registered in library ${identifier?.id ?: ""}" + + "because it is defined in library ${operator.libraryName}." + } + } + + @Suppress("UnusedPrivateMember") + private fun ensureResultType(operator: Operator) { + requireNotNull(operator.resultType) { + """Operator ${operator.name} cannot be registered in library ${identifier?.id ?: ""} + because it does not have a result type defined.""" + } + } + + fun add(functionDef: FunctionDef, operator: Operator) { + ensureLibrary(operator) + operatorMap.addOperator(operator) + functionDefs[operator] = functionDef + } + + fun contains(functionDef: FunctionDef?): Boolean { + return contains(Operator(functionDef!!)) + } + + fun contains(operator: Operator?): Boolean { + return operatorMap.containsOperator(operator!!) + } + + fun add(conversion: Conversion) { + require(!conversion.isCast) { + "Casting conversions cannot be registered as part of a library." + } + + conversions.add(conversion) + } + + fun resolve(identifier: String): ResolvedIdentifierContext { + if (namespace.containsKey(identifier)) { + return ResolvedIdentifierContext.exactMatch(identifier, namespace[identifier]) + } + + return namespace.entries + .stream() + .filter { it.key.equals(identifier, ignoreCase = true) } + .map { it.value } + .map { ResolvedIdentifierContext.caseInsensitiveMatch(identifier, it) } + .findFirst() + .orElse(ResolvedIdentifierContext.caseInsensitiveMatch(identifier, null)) + } + + fun resolveUsingRef(identifier: String): UsingDef? { + return resolveIdentifier(identifier, UsingDef::class.java) + } + + fun resolveIncludeRef(identifier: String): IncludeDef? { + return resolveIdentifier(identifier, IncludeDef::class.java) + } + + fun resolveIncludeAlias(identifier: VersionedIdentifier?): String? { + return when { + identifier != null && library?.includes?.def != null -> { + val libraryPath = NamespaceManager.getPath(identifier.system, identifier.id!!) + library!!.includes!!.def.firstOrNull { it.path == libraryPath }?.localIdentifier + } + else -> null + } + } + + fun resolveCodeSystemRef(identifier: String): CodeSystemDef? { + return resolveIdentifier(identifier, CodeSystemDef::class.java) + } + + fun resolveValueSetRef(identifier: String): ValueSetDef? { + return resolveIdentifier(identifier, ValueSetDef::class.java) + } + + fun resolveCodeRef(identifier: String): CodeDef? { + return resolveIdentifier(identifier, CodeDef::class.java) + } + + fun resolveConceptRef(identifier: String): ConceptDef? { + return resolveIdentifier(identifier, ConceptDef::class.java) + } + + fun resolveParameterRef(identifier: String): ParameterDef? { + return resolveIdentifier(identifier, ParameterDef::class.java) + } + + fun resolveExpressionRef(identifier: String): ExpressionDef? { + return resolveIdentifier(identifier, ExpressionDef::class.java) + } + + private fun resolveIdentifier(identifier: String, clazz: Class): T? { + return resolve(identifier).resolveIdentifier(clazz) + } + + fun resolveFunctionRef(identifier: String): Iterable { + val results = ArrayList() + for (ed in library!!.statements!!.def) { + if (ed is FunctionDef && ed.name == identifier) { + results.add(ed) + } + } + + return results + } + + fun resolveFunctionRef( + functionName: String, + signature: List? + ): Iterable { + return when (signature) { + null -> resolveFunctionRef(functionName) + else -> { + val cc = + CallContext( + this.identifier!!.id, + functionName, + allowPromotionAndDemotion = false, + allowFluent = false, + mustResolve = false, + operandTypes = signature + ) + val resolution = resolveCall(cc, ConversionMap()) + val results = ArrayList() + if (resolution != null) { + results.add(resolution.operator.functionDef) + } + results + } + } + } + + fun resolveCall(callContext: CallContext, conversionMap: ConversionMap): OperatorResolution? { + val resolution = operatorMap.resolveOperator(callContext, conversionMap) + + if (resolution != null) { + // For backwards compatibility, a library can indicate that functions it exports are + // allowed to be invoked + // with fluent syntax. This is used in FHIRHelpers to allow fluent resolution, which is + // implicit in 1.4. + if (callContext.allowFluent && !resolution.operator.fluent) { + resolution.allowFluent = getBooleanTag("allowFluent") + } + + // The resolution needs to carry with it the full versioned identifier of the library so + // that it can be + // correctly + // reflected via the alias for the library in the calling context. + resolution.libraryIdentifier = this.identifier + } + + return resolution + } + + fun getConversions(): List { + return conversions + } + + private val annotation: Annotation? + get() = library?.annotation?.firstOrNull { it is Annotation } as Annotation? + + private fun getTag(tagName: String): String? { + return annotation?.t?.firstOrNull { it.name == tagName }?.value + } + + private fun getBooleanTag(tagName: String): Boolean { + return getTag(tagName)?.toBoolean() ?: false + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Conversion.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Conversion.kt new file mode 100644 index 000000000..93e7a9cf1 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Conversion.kt @@ -0,0 +1,175 @@ +package org.cqframework.cql.cql2elm.model + +import org.cqframework.cql.cql2elm.model.ConversionMap.ConversionScore.Cast +import org.cqframework.cql.cql2elm.model.ConversionMap.ConversionScore.ComplexConversion +import org.cqframework.cql.cql2elm.model.ConversionMap.ConversionScore.IntervalDemotion +import org.cqframework.cql.cql2elm.model.ConversionMap.ConversionScore.IntervalPromotion +import org.cqframework.cql.cql2elm.model.ConversionMap.ConversionScore.ListDemotion +import org.cqframework.cql.cql2elm.model.ConversionMap.ConversionScore.ListPromotion +import org.cqframework.cql.cql2elm.model.ConversionMap.ConversionScore.SimpleConversion +import org.hl7.cql.model.ChoiceType +import org.hl7.cql.model.ClassType +import org.hl7.cql.model.DataType +import org.hl7.cql.model.IntervalType +import org.hl7.cql.model.ListType +import org.hl7.cql.model.SimpleType + +class Conversion( + val fromType: DataType, + val toType: DataType, + val isImplicit: Boolean = false, + val conversion: Conversion? = null, + val operator: Operator? = null +) { + constructor( + operator: Operator, + isImplicit: Boolean + ) : this(singletonOperand(operator), ensureResultType(operator), isImplicit, null, operator) + + constructor(fromType: DataType, toType: DataType) : this(fromType, toType, true) { + this.isCast = true + } + + constructor( + fromType: ChoiceType, + toType: DataType, + choiceConversion: Conversion + ) : this(fromType, toType, true, choiceConversion) { + this.isCast = true + } + + constructor( + fromType: DataType, + toType: ChoiceType, + choiceConversion: Conversion + ) : this(fromType, toType, true, choiceConversion) { + this.isCast = true + } + + constructor( + fromType: ListType, + toType: ListType, + elementConversion: Conversion + ) : this(fromType, toType, true, elementConversion) { + this.isListConversion = true + } + + constructor( + fromType: ListType, + toType: DataType, + elementConversion: Conversion? + ) : this(fromType, toType, true, elementConversion) { + this.isListDemotion = true + } + + constructor( + fromType: DataType, + toType: ListType, + elementConversion: Conversion? + ) : this(fromType, toType, true, elementConversion) { + this.isListPromotion = true + } + + constructor( + fromType: IntervalType, + toType: DataType, + elementConversion: Conversion? + ) : this(fromType, toType, true, elementConversion) { + this.isIntervalDemotion = true + } + + constructor( + fromType: DataType, + toType: IntervalType, + elementConversion: Conversion? + ) : this(fromType, toType, true, elementConversion) { + this.isIntervalPromotion = true + } + + constructor( + fromType: IntervalType, + toType: IntervalType, + pointConversion: Conversion + ) : this(fromType, toType, true, pointConversion) { + this.isIntervalConversion = true + } + + private val alternativeConversions: MutableList = mutableListOf() + + fun getAlternativeConversions(): List { + return alternativeConversions + } + + fun hasAlternativeConversions(): Boolean { + return alternativeConversions.isNotEmpty() + } + + fun addAlternativeConversion(alternativeConversion: Conversion) { + require(fromType is ChoiceType) { + "Alternative conversions can only be used with choice types" + } + + // Should also guard against adding an alternative that is not one of the component + // types of the fromType + // This should never happen though with current usage + alternativeConversions.add(alternativeConversion) + } + + val score: Int + get() { + val nestedScore = conversion?.score ?: 0 + return when { + isCast -> Cast.score + nestedScore + isIntervalDemotion -> IntervalDemotion.score + nestedScore + isListDemotion -> ListDemotion.score + nestedScore + isIntervalPromotion -> IntervalPromotion.score + nestedScore + isListPromotion -> ListPromotion.score + nestedScore + isListConversion && toType is ListType && toType.elementType is SimpleType -> + SimpleConversion.score + nestedScore + isListConversion -> ComplexConversion.score + nestedScore + isIntervalConversion && toType is IntervalType && toType.pointType is SimpleType -> + SimpleConversion.score + nestedScore + isIntervalConversion -> ComplexConversion.score + nestedScore + toType is ClassType -> ComplexConversion.score + nestedScore + else -> SimpleConversion.score + nestedScore + } + } + + val isGeneric: Boolean + get() = operator is GenericOperator + + var isCast: Boolean = false + private set + + var isListConversion: Boolean = false + private set + + var isListPromotion: Boolean = false + private set + + var isListDemotion: Boolean = false + private set + + var isIntervalConversion: Boolean = false + private set + + var isIntervalPromotion: Boolean = false + private set + + var isIntervalDemotion: Boolean = false + private set + + companion object { + fun singletonOperand(operator: Operator): DataType { + require(operator.signature.operandTypes.size == 1) { + "Conversion operator must be unary." + } + return operator.signature.operandTypes[0] + } + + fun ensureResultType(operator: Operator): DataType { + requireNotNull(operator.resultType) { "Conversion operator must have a result type." } + return operator.resultType!! + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ConversionMap.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ConversionMap.kt new file mode 100644 index 000000000..c62a6c39b --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ConversionMap.kt @@ -0,0 +1,434 @@ +package org.cqframework.cql.cql2elm.model + +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import org.hl7.cql.model.ChoiceType +import org.hl7.cql.model.DataType +import org.hl7.cql.model.IntervalType +import org.hl7.cql.model.ListType + +@Suppress("TooManyFunctions") +class ConversionMap { + @Suppress("MagicNumber") + enum class TypePrecedenceScore(val score: Int) { + Simple(1), + Tuple(2), + Class(3), + Interval(4), + List(5), + Choice(6), + Other(7) + } + + @Suppress("MagicNumber") + enum class ConversionScore(val score: Int) { + ExactMatch(0), + SubType(1), + Compatible(2), + Cast(3), + SimpleConversion(4), + ComplexConversion(5), + IntervalPromotion(6), + ListDemotion(7), + IntervalDemotion(8), + ListPromotion(9) + } + + private val map: MutableMap> = HashMap() + val genericConversions: MutableList = ArrayList() + var isListDemotionEnabled: Boolean = true + var isListPromotionEnabled: Boolean = true + var isIntervalDemotionEnabled: Boolean = false + var isIntervalPromotionEnabled: Boolean = false + + private fun hasConversion(conversion: Conversion, conversions: List): Boolean { + return conversions.any { it.toType == conversion.toType } + } + + fun getConversionOperator(fromType: DataType, toType: DataType): Operator? { + return this.getConversions(fromType).firstOrNull { it.toType == toType }?.operator + } + + fun add(conversion: Conversion) { + // NOTE: The conversion map supports generic conversions, however, they turned out to be + // quite expensive + // computationally + // so we introduced list promotion and demotion instead (we should add interval promotion + // and demotion too, + // would be quite useful) + // Generic conversions could still be potentially useful, so I left the code, but it's never + // used because the + // generic conversions + // are not added in the SystemLibraryHelper. + if (conversion.isGeneric) { + val conversions = genericConversions + check(!hasConversion(conversion, conversions)) { + "Conversion from ${conversion.fromType} to ${conversion.toType} is already defined." + } + + conversions.add(conversion) + } else { + val conversions = getConversions(conversion.fromType) + check(!hasConversion(conversion, conversions)) { + "Conversion from ${conversion.fromType} to ${conversion.toType} is already defined." + } + + conversions.add(conversion) + } + } + + fun getConversions(fromType: DataType): MutableList { + return map.computeIfAbsent(fromType) { ArrayList() } + } + + /* + Returns conversions for the given type, or any supertype, recursively + */ + private fun getAllConversions(fromType: DataType?): List { + val conversions: MutableList = ArrayList() + var currentType = fromType + while (currentType != null && currentType != DataType.ANY) { + conversions.addAll(getConversions(currentType)) + currentType = currentType.baseType + } + return conversions + } + + private fun findCompatibleConversion(fromType: DataType, toType: DataType): Conversion? { + if (fromType.isCompatibleWith(toType)) { + return Conversion(fromType, toType) + } + + return null + } + + private fun findChoiceConversion( + fromType: ChoiceType, + toType: DataType, + allowPromotionAndDemotion: Boolean, + operatorMap: OperatorMap + ): Conversion? { + var result: Conversion? = null + for (choice in fromType.types) { + val choiceConversion = + findConversion(choice, toType, true, allowPromotionAndDemotion, operatorMap) + if (choiceConversion != null) { + if (result == null) { + result = Conversion(fromType, toType, choiceConversion) + } else { + result.addAlternativeConversion(choiceConversion) + } + } + } + + return result + } + + private fun findTargetChoiceConversion( + fromType: DataType, + toType: ChoiceType, + allowPromotionAndDemotion: Boolean, + operatorMap: OperatorMap + ): Conversion? { + for (choice in toType.types) { + findConversion(fromType, choice, true, allowPromotionAndDemotion, operatorMap)?.let { + return Conversion(fromType, toType, it) + } + } + + return null + } + + private fun findListConversion( + fromType: ListType, + toType: ListType, + operatorMap: OperatorMap + ): Conversion? { + return findConversion( + fromType.elementType, + toType.elementType, + isImplicit = true, + allowPromotionAndDemotion = false, + operatorMap = operatorMap + ) + ?.let { Conversion(fromType, toType, it) } + } + + private fun findIntervalConversion( + fromType: IntervalType, + toType: IntervalType, + operatorMap: OperatorMap + ): Conversion? { + return findConversion( + fromType.pointType, + toType.pointType, + isImplicit = true, + allowPromotionAndDemotion = false, + operatorMap = operatorMap + ) + ?.let { Conversion(fromType, toType, it) } + } + + private fun findListDemotion( + fromType: ListType, + toType: DataType, + operatorMap: OperatorMap + ): Conversion? { + val elementType = fromType.elementType + return if (elementType.isSubTypeOf(toType)) { + Conversion(fromType, toType, null) + } else { + findConversion( + elementType, + toType, + isImplicit = true, + allowPromotionAndDemotion = false, + operatorMap = operatorMap + ) + ?.let { Conversion(fromType, toType, it) } + } + } + + private fun findListPromotion( + fromType: DataType, + toType: ListType, + operatorMap: OperatorMap + ): Conversion? { + return if (fromType.isSubTypeOf(toType.elementType)) { + Conversion(fromType, toType, null) + } else { + findConversion( + fromType, + toType.elementType, + isImplicit = true, + allowPromotionAndDemotion = false, + operatorMap = operatorMap + ) + ?.let { Conversion(fromType, toType, it) } + } + } + + private fun findIntervalDemotion( + fromType: IntervalType, + toType: DataType, + operatorMap: OperatorMap + ): Conversion? { + val pointType = fromType.pointType + return if (pointType.isSubTypeOf(toType)) { + Conversion(fromType, toType, null) + } else { + findConversion( + pointType, + toType, + isImplicit = true, + allowPromotionAndDemotion = false, + operatorMap = operatorMap + ) + ?.let { Conversion(fromType, toType, it) } + } + } + + private fun findIntervalPromotion( + fromType: DataType, + toType: IntervalType, + operatorMap: OperatorMap + ): Conversion? { + return if (fromType.isSubTypeOf(toType.pointType)) { + Conversion(fromType, toType, null) + } else { + findConversion( + fromType, + toType.pointType, + isImplicit = true, + allowPromotionAndDemotion = false, + operatorMap = operatorMap + ) + ?.let { Conversion(fromType, toType, it) } + } + } + + @Suppress("UnusedParameter") + private fun ensureGenericConversionInstantiated( + fromType: DataType, + toType: DataType, + isImplicit: Boolean, + operatorMap: OperatorMap + ): Boolean { + var operatorsInstantiated = false + for (c in genericConversions) { + if (c.operator != null) { + // instantiate the generic... + val instantiationResult = + (c.operator as GenericOperator).instantiate( + Signature(fromType), + operatorMap, + this, + false + ) + val operator = instantiationResult.operator + if (operator != null && !operatorMap.containsOperator(operator)) { + operatorMap.addOperator(operator) + val conversion = Conversion(operator, true) + this.add(conversion) + operatorsInstantiated = true + } + } + } + + return operatorsInstantiated + } + + @Suppress("NestedBlockDepth", "ComplexCondition") + private fun internalFindConversion( + fromType: DataType, + toType: DataType, + isImplicit: Boolean + ): Conversion? { + var result: Conversion? = null + var score = Int.MAX_VALUE + for (conversion in getAllConversions(fromType)) { + if ( + (!isImplicit || conversion.isImplicit) && + (conversion.toType.isSuperTypeOf(toType) || conversion.toType.isGeneric) + ) { + // Lower score is better. If the conversion matches the target type exactly, + // the score is 0. + // If the conversion is generic, the score is 1 (because that will be + // instantiated to an exact + // match) + // If the conversion is a super type, it should only be used if an exact match + // cannot be found. + // If the score is equal to an existing, it indicates a duplicate conversion + val newScore = + ((if (conversion.fromType == fromType) 0 + else (if (conversion.fromType.isGeneric) 1 else 2)) + + (if (conversion.toType == toType) 0 + else (if (conversion.toType.isGeneric) 1 else 2))) + if (newScore < score) { + result = conversion + score = newScore + } else + require(newScore != score) { + // ERROR + "Ambiguous implicit conversion from $fromType to ${result!!.toType} or ${conversion.toType}." + } + } + } + + return result + } + + @Suppress("CyclomaticComplexMethod") + fun findConversion( + fromType: DataType, + toType: DataType, + isImplicit: Boolean, + allowPromotionAndDemotion: Boolean, + operatorMap: OperatorMap + ): Conversion? { + var result = + findCompatibleConversion(fromType, toType) + ?: internalFindConversion(fromType, toType, isImplicit) + + if ( + result == null && + ensureGenericConversionInstantiated(fromType, toType, isImplicit, operatorMap) + ) { + result = internalFindConversion(fromType, toType, isImplicit) + } + + if (result == null) { + // NOTE: FHIRPath Implicit conversion from list to singleton + // If the fromType is a list and the target type is a singleton (potentially with a + // compatible conversion), + // Convert by invoking a singleton + result = + when { + fromType is ListType && + toType !is ListType && + (allowPromotionAndDemotion || isListDemotionEnabled) -> + findListDemotion(fromType, toType, operatorMap) + fromType !is ListType && + toType is ListType && + (allowPromotionAndDemotion || isListPromotionEnabled) -> + findListPromotion(fromType, toType, operatorMap) + fromType is IntervalType && + toType !is IntervalType && + (allowPromotionAndDemotion || isIntervalDemotionEnabled) -> + findIntervalDemotion(fromType, toType, operatorMap) + fromType !is IntervalType && + toType is IntervalType && + (allowPromotionAndDemotion || isIntervalPromotionEnabled) -> + findIntervalPromotion(fromType, toType, operatorMap) + + // If the fromType is a choice, attempt to find a conversion from one of the + // choice + // types + fromType is ChoiceType -> + findChoiceConversion( + fromType, + toType, + allowPromotionAndDemotion, + operatorMap + ) + + // If the target type is a choice, + // attempt to find a conversion to + // one of the choice types + fromType !is ChoiceType && toType is ChoiceType -> + findTargetChoiceConversion( + fromType, + toType, + allowPromotionAndDemotion, + operatorMap + ) + + // If both types are lists, + // attempt to find a conversion between the element + // types + fromType is ListType && toType is ListType -> + findListConversion(fromType, toType, operatorMap) + + // If both types are intervals, attempt to find a conversion between the point + // types + fromType is IntervalType && toType is IntervalType -> + findIntervalConversion(fromType, toType, operatorMap) + else -> null + } + } + + return result + } + + companion object { + @JvmStatic + fun getTypePrecedenceScore(operand: DataType): Int { + return when (operand.javaClass.simpleName) { + "SimpleType" -> TypePrecedenceScore.Simple.score + "TupleType" -> TypePrecedenceScore.Tuple.score + "ClassType" -> TypePrecedenceScore.Class.score + "IntervalType" -> TypePrecedenceScore.Interval.score + "ListType" -> TypePrecedenceScore.List.score + "ChoiceType" -> TypePrecedenceScore.Choice.score + else -> TypePrecedenceScore.Other.score + } + } + + fun getConversionScore( + callOperand: DataType, + operand: DataType, + conversion: Conversion? + ): Int { + return when { + operand == callOperand -> ConversionScore.ExactMatch.score + operand.isSuperTypeOf(callOperand) -> ConversionScore.SubType.score + callOperand.isCompatibleWith(operand) -> ConversionScore.Compatible.score + conversion != null -> conversion.score + else -> + throw IllegalArgumentException( + "Could not determine conversion score for conversion" + ) + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/FunctionHeader.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/FunctionHeader.kt new file mode 100644 index 000000000..6da09b164 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/FunctionHeader.kt @@ -0,0 +1,37 @@ +package org.cqframework.cql.cql2elm.model + +import java.util.* +import org.hl7.elm.r1.FunctionDef +import org.hl7.elm.r1.NamedTypeSpecifier +import org.hl7.elm.r1.TypeSpecifier + +/** POJO for the result of a pre compile operation (AKA: partial compile of function headers) */ +data class FunctionHeader(val functionDef: FunctionDef, val resultType: TypeSpecifier?) { + constructor(functionDef: FunctionDef) : this(functionDef, null) + + var isCompiled: Boolean = false + + val mangledName: String by lazy { + val sb = StringBuilder() + sb.append(functionDef.name) + sb.append("_") + for (od in functionDef.operand) { + sb.append( + when { + od.operandTypeSpecifier is NamedTypeSpecifier -> + (od.operandTypeSpecifier as NamedTypeSpecifier).name + else -> od.operandTypeSpecifier.toString() + } + ) + } + sb.append("_") + sb.toString() + } + + override fun toString(): String { + return StringJoiner(", ", FunctionHeader::class.java.simpleName + "[", "]") + .add("functionDef=$functionDef") + .add("resultType=$resultType") + .toString() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/GenericOperator.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/GenericOperator.kt new file mode 100644 index 000000000..0d97d4db8 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/GenericOperator.kt @@ -0,0 +1,67 @@ +package org.cqframework.cql.cql2elm.model + +import java.util.* +import org.hl7.cql.model.DataType +import org.hl7.cql.model.TypeParameter + +class GenericOperator( + name: String, + signature: Signature, + resultType: DataType?, + private val typeParameters: List +) : Operator(name, signature, resultType) { + constructor( + name: String, + signature: Signature, + resultType: DataType?, + vararg typeParameters: TypeParameter, + ) : this(name, signature, resultType, typeParameters.toList()) + + fun instantiate( + callSignature: Signature, + operatorMap: OperatorMap, + conversionMap: ConversionMap, + allowPromotionAndDemotion: Boolean + ): InstantiationResult { + return instantiate( + callSignature, + null, + operatorMap, + conversionMap, + allowPromotionAndDemotion + ) + } + + @Suppress("UnusedParameter") + private fun instantiate( + callSignature: Signature, + parameters: Map?, + operatorMap: OperatorMap, + conversionMap: ConversionMap, + allowPromotionAndDemotion: Boolean + ): InstantiationResult { + val typeMap: MutableMap = HashMap() + + for (p in typeParameters) { + typeMap[p] = null + } + + if (parameters != null) { + typeMap.putAll(parameters) + } + + val context = + InstantiationContextImpl(typeMap, operatorMap, conversionMap, allowPromotionAndDemotion) + + val instantiable = signature.isInstantiable(callSignature, context) + if (instantiable) { + val result = + Operator(name, signature.instantiate(context), resultType!!.instantiate(context)) + result.accessLevel = accessLevel + result.libraryName = libraryName + return InstantiationResult(this, result, context.conversionScore) + } + + return InstantiationResult(this, null, context.conversionScore) + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/InstantiationContextImpl.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/InstantiationContextImpl.kt new file mode 100644 index 000000000..538b7ff52 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/InstantiationContextImpl.kt @@ -0,0 +1,306 @@ +package org.cqframework.cql.cql2elm.model + +import kotlin.collections.ArrayList +import org.hl7.cql.model.DataType +import org.hl7.cql.model.InstantiationContext +import org.hl7.cql.model.IntervalType +import org.hl7.cql.model.ListType +import org.hl7.cql.model.SimpleType +import org.hl7.cql.model.TypeParameter + +@Suppress( + "ForbiddenComment", + "ReturnCount", + "ComplexCondition", + "NestedBlockDepth", + "CyclomaticComplexMethod" +) +class InstantiationContextImpl( + private val typeMap: MutableMap, + private val operatorMap: OperatorMap, + private val conversionMap: ConversionMap, + private val allowPromotionAndDemotion: Boolean +) : InstantiationContext { + + var conversionScore: Int = 0 + private set + + @Suppress("LongMethod") + override fun isInstantiable(parameter: TypeParameter, callType: DataType): Boolean { + // If the type is not yet bound, bind it to the call type. + val boundType = typeMap[parameter] + if (boundType == null) { + return if (parameter.canBind(callType)) { + typeMap[parameter] = callType + true + } else { + false + } + } else { + // If the type is bound, and is a super type of the call type, return true; + if (boundType.isSuperTypeOf(callType) || callType.isCompatibleWith(boundType)) { + return true + } else if (callType.isSuperTypeOf(boundType) || boundType.isCompatibleWith(callType)) { + // If the call type is a super type of the bound type, switch the bound type for + // this parameter to the + // call type + return if (parameter.canBind(callType)) { + typeMap[parameter] = callType + true + } else { + false + } + } else { + // If there is an implicit conversion path from the call type to the bound type, + // return true + var conversion = + conversionMap.findConversion( + callType, + boundType, + true, + allowPromotionAndDemotion, + operatorMap + ) + if (conversion != null) { + // if the conversion is a list promotion, switch the bound type to the call type + if ( + boundType is ListType && + (boundType.elementType.isSuperTypeOf(callType) || + callType.isCompatibleWith(boundType.elementType)) + ) { + return if (parameter.canBind(callType)) { + typeMap[parameter] = callType + conversionScore -= + ConversionMap.ConversionScore.ListPromotion + .score // This removes the list promotion + true + } else { + false + } + } + return true + } + + // If there is an implicit conversion path from the bound type to the call type + conversion = + conversionMap.findConversion( + boundType, + callType, + true, + allowPromotionAndDemotion, + operatorMap + ) + if (conversion != null) { + // switch the bound type to the call type and return true + return if (parameter.canBind(callType)) { + typeMap[parameter] = callType + conversionScore -= + (if ((conversion.toType is SimpleType)) + ConversionMap.ConversionScore.SimpleConversion.score + else + ConversionMap.ConversionScore.ComplexConversion + .score) // This removes the conversion from the instantiation + true + } else { + false + } + } + + // Find the first supertype that is a supertype of both types + /* + // This code doesn't play well with generic signatures... it ends up treating everything like an Any, + // resulting in all sorts of surprising resolutions + DataType boundCommonSuperType = boundType.getCommonSuperTypeOf(callType); + DataType callCommonSuperType = callType.getCommonSuperTypeOf(boundType); + if (boundCommonSuperType != null && callCommonSuperType != null) { + if (boundCommonSuperType.isSuperTypeOf(callCommonSuperType)) { + if (parameter.canBind(boundCommonSuperType)) { + typeMap.put(parameter, boundCommonSuperType); + return true; + } + else { + return false; + } + } + else { + if (parameter.canBind(callCommonSuperType)) { + typeMap.put(parameter, callCommonSuperType); + return true; + } + else { + return false; + } + } + } + */ + } + } + + return false + } + + override fun instantiate(parameter: TypeParameter): DataType { + val result = + typeMap[parameter] + ?: throw IllegalArgumentException( + "Could not resolve type parameter ${parameter.identifier}." + ) + + return result + } + + override fun getIntervalConversionTargets(callType: DataType): List { + val results = ArrayList() + for (c in conversionMap.getConversions(callType)) { + if (c.toType is IntervalType) { + results.add(c.toType) + conversionScore += ConversionMap.ConversionScore.ComplexConversion.score + } + } + + if (results.isEmpty()) { + for (c in conversionMap.genericConversions) { + if (c.operator != null && c.toType is IntervalType) { + // instantiate the generic... + val instantiationResult = + (c.operator as GenericOperator).instantiate( + Signature(callType), + operatorMap, + conversionMap, + false + ) + val operator = instantiationResult.operator + // Consider impact of conversion score of the generic instantiation on + // this conversion + // score + if (operator != null) { + operatorMap.addOperator(operator) + val conversion = Conversion(operator, true) + conversionMap.add(conversion) + results.add(conversion.toType as IntervalType) + } + } + } + } + + // Add interval promotion if no other conversion is found + if ( + results.isEmpty() && + callType !is IntervalType && + operatorMap.isPointType(callType) && + (allowPromotionAndDemotion || conversionMap.isIntervalPromotionEnabled) + ) { + results.add(IntervalType(callType)) + conversionScore += ConversionMap.ConversionScore.IntervalPromotion.score + } + + return results + } + + override fun getListConversionTargets(callType: DataType): List { + val results = ArrayList() + for (c in conversionMap.getConversions(callType)) { + if (c.toType is ListType) { + results.add(c.toType) + conversionScore += ConversionMap.ConversionScore.ComplexConversion.score + } + } + + if (results.isEmpty()) { + for (c in conversionMap.genericConversions) { + if (c.operator != null && c.toType is ListType) { + // instantiate the generic... + val instantiationResult = + (c.operator as GenericOperator).instantiate( + Signature(callType), + operatorMap, + conversionMap, + false + ) + val operator = instantiationResult.operator + // Consider impact of conversion score of the generic instantiation on + // this conversion + // score + if (operator != null) { + operatorMap.addOperator(operator) + val conversion = Conversion(operator, true) + conversionMap.add(conversion) + results.add(conversion.toType as ListType) + } + } + } + } + + // NOTE: FHIRPath support + // Add list promotion if no other conversion is found + if ( + results.isEmpty() && + callType !is ListType && + (allowPromotionAndDemotion || conversionMap.isListPromotionEnabled) + ) { + results.add(ListType(callType)) + conversionScore += ConversionMap.ConversionScore.ListPromotion.score + } + + return results + } + + override fun getSimpleConversionTargets(callType: DataType): List { + val results = ArrayList() + for (c in conversionMap.getConversions(callType)) { + if (c.toType is SimpleType) { + results.add(c.toType) + conversionScore += ConversionMap.ConversionScore.SimpleConversion.score + } + } + + if (results.isEmpty()) { + for (c in conversionMap.genericConversions) { + if (c.operator != null && c.toType is SimpleType) { + val instantiationResult = + (c.operator as GenericOperator).instantiate( + Signature(callType), + operatorMap, + conversionMap, + false + ) + val operator = instantiationResult.operator + // TODO: Consider impact of conversion score of the generic instantiation on + // this conversion + // score + if (operator != null) { + operatorMap.addOperator(operator) + val conversion = Conversion(operator, true) + conversionMap.add(conversion) + results.add(conversion.toType as SimpleType) + } + } + } + } + + // Add interval demotion if no other conversion is found + if ( + results.isEmpty() && + callType is IntervalType && + callType.pointType is SimpleType && + (allowPromotionAndDemotion || conversionMap.isIntervalDemotionEnabled) + ) { + results.add(callType.pointType as SimpleType) + conversionScore += ConversionMap.ConversionScore.IntervalDemotion.score + } + + // NOTE: FHIRPath Support + // Add list demotion if no other conversion is found + if ( + results.isEmpty() && + callType is ListType && + callType.elementType is SimpleType && + (allowPromotionAndDemotion || conversionMap.isListDemotionEnabled) + ) { + results.add(callType.elementType as SimpleType) + conversionScore += ConversionMap.ConversionScore.ListDemotion.score + } + + return results + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/InstantiationResult.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/InstantiationResult.kt new file mode 100644 index 000000000..1926366d8 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/InstantiationResult.kt @@ -0,0 +1,8 @@ +package org.cqframework.cql.cql2elm.model + +/** Created by Bryn on 12/22/2016. */ +data class InstantiationResult( + val genericOperator: GenericOperator, + val operator: Operator?, + val conversionScore: Int +) diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Invocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Invocation.kt new file mode 100644 index 000000000..6298d8165 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Invocation.kt @@ -0,0 +1,27 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.cql.model.DataType +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.TypeSpecifier + +/** + * The Invocation interface is used to represent an invocation of an operator or function in the ELM + * model. The ELM classes have named properties for their operands, but the Invocation interface + * uses a list of expressions to represent the operands. The implementations of this interface are + * responsible for managing the mapping between the list of expressions and the properties of the + * ELM class. For example, the DateInvocation class maps properties for year, month, and day to the + * first, second, and third expressions in the list of operands. This allows Invocations to be + * handled generically in the CQL-to-ELM translation process. + */ +interface Invocation { + + var signature: List<@JvmSuppressWildcards TypeSpecifier> + + var operands: List<@JvmSuppressWildcards Expression> + + var resultType: DataType? + + val expression: Expression + + var resolution: OperatorResolution? +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/LibraryRef.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/LibraryRef.kt new file mode 100644 index 000000000..93fcf297d --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/LibraryRef.kt @@ -0,0 +1,11 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.elm.r1.Expression + +// Note: This class is only used as a place-holder during resolution in a translator (or +// compiler...) +class LibraryRef(localId: String, val libraryName: String?) : Expression() { + init { + this.localId = localId + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Model.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Model.kt new file mode 100644 index 000000000..fcb5e5e07 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Model.kt @@ -0,0 +1,101 @@ +package org.cqframework.cql.cql2elm.model + +import java.util.* +import org.cqframework.cql.cql2elm.ModelManager +import org.hl7.cql.model.ClassType +import org.hl7.cql.model.DataType +import org.hl7.cql.model.ModelContext +import org.hl7.cql.model.NamedType +import org.hl7.elm_modelinfo.r1.ModelInfo + +open class Model(val modelInfo: ModelInfo, modelManager: ModelManager?) { + private var index: Map = HashMap() + private val classIndex: MutableMap = HashMap() + private val conversions: MutableList = ArrayList() + private val contexts: MutableList = ArrayList() + + private val nameIndex: MutableMap = HashMap() + private val defaultContext: String? + + init { + + val importer = ModelImporter(modelInfo, modelManager) + index = importer.types + for (c in importer.conversions) { + conversions.add(c) + } + + for (c in importer.contexts) { + contexts.add(c) + } + + defaultContext = importer.defaultContextName + + for (t in index.values) { + if (t is ClassType && t.label != null) { + classIndex[casify(t.label!!)] = t + } + + if (t is NamedType) { + nameIndex[casify((t as NamedType).simpleName)] = t + } + } + } + + fun getConversions(): List { + return conversions + } + + fun resolveTypeName(typeName: String): DataType? { + val normalizedTypeName = casify(typeName) + return index[normalizedTypeName] ?: nameIndex[normalizedTypeName] + } + + @Suppress("ReturnCount") + @JvmOverloads + fun resolveContextName(contextName: String, mustResolve: Boolean = true): ModelContext? { + for (context in contexts) { + if (context.name == contextName) { + return context + } + } + + // Resolve to a "default" context definition if the context name matches a type name exactly + val contextType = resolveTypeName(contextName) + if (contextType is ClassType) { + var keyName: String? = null + for (cte in contextType.elements) { + if (cte.name == "id") { + keyName = cte.name + break + } + } + + return ModelContext( + contextName, + contextType, + if (keyName != null) listOf(keyName) else emptyList(), + null + ) + } + + // If we failed to resolve the context name and mustResolve is true + // then throw an error + require(!mustResolve) { + // ERROR: + "Could not resolve context name $contextName in model ${modelInfo.name}." + } + + return null + } + + fun resolveLabel(label: String): ClassType? { + return classIndex[casify(label)] + } + + private fun casify(typeName: String): String { + return if ((modelInfo.isCaseSensitive() != null && modelInfo.isCaseSensitive()!!)) + typeName.lowercase(Locale.getDefault()) + else typeName + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ModelImporter.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ModelImporter.kt new file mode 100644 index 000000000..239010d1b --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ModelImporter.kt @@ -0,0 +1,800 @@ +package org.cqframework.cql.cql2elm.model + +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import org.cqframework.cql.cql2elm.ModelManager +import org.hl7.cql.model.ChoiceType +import org.hl7.cql.model.ClassType +import org.hl7.cql.model.ClassTypeElement +import org.hl7.cql.model.DataType +import org.hl7.cql.model.GenericClassSignatureParser +import org.hl7.cql.model.IntervalType +import org.hl7.cql.model.ListType +import org.hl7.cql.model.ModelContext +import org.hl7.cql.model.ModelIdentifier +import org.hl7.cql.model.NamespaceManager +import org.hl7.cql.model.ProfileType +import org.hl7.cql.model.Relationship +import org.hl7.cql.model.SearchType +import org.hl7.cql.model.SimpleType +import org.hl7.cql.model.TupleType +import org.hl7.cql.model.TupleTypeElement +import org.hl7.cql.model.TypeParameter +import org.hl7.cql.model.TypeParameter.TypeParameterConstraint +import org.hl7.elm_modelinfo.r1.BoundParameterTypeSpecifier +import org.hl7.elm_modelinfo.r1.ChoiceTypeInfo +import org.hl7.elm_modelinfo.r1.ChoiceTypeSpecifier +import org.hl7.elm_modelinfo.r1.ClassInfo +import org.hl7.elm_modelinfo.r1.ClassInfoElement +import org.hl7.elm_modelinfo.r1.IntervalTypeInfo +import org.hl7.elm_modelinfo.r1.IntervalTypeSpecifier +import org.hl7.elm_modelinfo.r1.ListTypeInfo +import org.hl7.elm_modelinfo.r1.ListTypeSpecifier +import org.hl7.elm_modelinfo.r1.ModelInfo +import org.hl7.elm_modelinfo.r1.NamedTypeSpecifier +import org.hl7.elm_modelinfo.r1.ParameterTypeSpecifier +import org.hl7.elm_modelinfo.r1.ProfileInfo +import org.hl7.elm_modelinfo.r1.RelationshipInfo +import org.hl7.elm_modelinfo.r1.SearchInfo +import org.hl7.elm_modelinfo.r1.SimpleTypeInfo +import org.hl7.elm_modelinfo.r1.TupleTypeInfo +import org.hl7.elm_modelinfo.r1.TupleTypeInfoElement +import org.hl7.elm_modelinfo.r1.TupleTypeSpecifier +import org.hl7.elm_modelinfo.r1.TypeInfo +import org.hl7.elm_modelinfo.r1.TypeParameterInfo +import org.hl7.elm_modelinfo.r1.TypeSpecifier + +@Suppress( + "NestedBlockDepth", + "TooManyFunctions", + "TooGenericExceptionThrown", + "ReturnCount", + "UnusedParameter" +) +class ModelImporter(val modelInfo: ModelInfo, val modelManager: ModelManager?) { + private val modelIndex: MutableMap = HashMap() + private val typeInfoIndex: MutableMap = HashMap() + private val resolvedTypes: MutableMap = HashMap() + private val dataTypes: MutableList = ArrayList() + val conversions: MutableList = ArrayList() + val contexts: MutableList = ArrayList() + private var defaultContext: ModelContext? = null + + init { + if (modelManager != null) { + // Import required models + for (requiredModel in modelInfo.requiredModelInfo) { + val model = + modelManager.resolveModel( + ModelIdentifier( + system = NamespaceManager.getUriPart(requiredModel.url), + id = requiredModel.name!!, + version = requiredModel.version, + ) + ) + modelIndex[requiredModel.name!!] = model + } + + // Ensure System model is registered + if (!modelIndex.containsKey("System")) { + val systemModel = modelManager.resolveModel(ModelIdentifier("System")) + modelIndex["System"] = systemModel + } + } + + // Import model types + for (t in this.modelInfo.typeInfo) { + if (t is SimpleTypeInfo) { + typeInfoIndex[ensureUnqualified(t.name!!)] = t + } else if (t is ClassInfo && t.name != null) { + typeInfoIndex[ensureUnqualified(t.name!!)] = t + } + } + + // Import model conversions + for (c in this.modelInfo.conversionInfo) { + val fromType = resolveTypeNameOrSpecifier(c.fromType, c.fromTypeSpecifier) + val toType = resolveTypeNameOrSpecifier(c.toType, c.toTypeSpecifier) + val qualifierIndex = c.functionName!!.indexOf('.') + val libraryName = + if (qualifierIndex >= 0) c.functionName!!.substring(0, qualifierIndex) else null + val functionName = + if (qualifierIndex >= 0) c.functionName!!.substring(qualifierIndex + 1) else null + val operator = Operator(functionName!!, Signature(fromType!!), toType) + operator.libraryName = libraryName + + // All conversions loaded as part of a model are implicit + val conversion = Conversion(operator, true) + conversions.add(conversion) + } + + // Import model contexts + for (c in this.modelInfo.contextInfo) { + val contextType = resolveTypeSpecifier(c.contextType!!) + require(contextType is ClassType) { + // ERROR: + "Model context ${c.name} must be a class type." + } + val modelContext = + ModelContext( + c.name!!, + contextType, + c.keyElement!!.split(";".toRegex()).dropLastWhile { it.isEmpty() }, + c.birthDateElement + ) + + contexts.add(modelContext) + } + + // For backwards compatibility with model info files that don't specify contexts, create a + // default context based + // on the patient class information if it's present + if (contexts.isEmpty() && this.modelInfo.patientClassName != null) { + val contextType = resolveTypeName(this.modelInfo.patientClassName!!) + if (contextType is ClassType) { + val modelContext = + ModelContext( + contextType.simpleName, + contextType, + mutableListOf("id"), + this.modelInfo.patientBirthDatePropertyName + ) + contexts.add(modelContext) + defaultContext = modelContext + } + } + + for (t in this.modelInfo.typeInfo) { + val type = resolveTypeInfo(t) + if (type != null) { + dataTypes.add(type) + } + + if (t is ClassInfo) { + importRelationships(t, type as ClassType) + } + } + } + + val types: Map + get() = resolvedTypes + + val defaultContextName: String? + get() { + if (modelInfo.defaultContext != null) { + return modelInfo.defaultContext + } + + return this.defaultContext?.name + } + + private fun casify( + typeName: String, + caseSensitive: Boolean = modelInfo.isCaseSensitive() ?: false + ): String { + return if (caseSensitive) typeName.lowercase(Locale.getDefault()) else typeName + } + + private fun resolveTypeInfo(t: TypeInfo): DataType? { + return when (t) { + is SimpleTypeInfo -> resolveSimpleType(t) + is ClassInfo -> resolveClassType(t) + is TupleTypeInfo -> resolveTupleType(t) + is IntervalTypeInfo -> resolveIntervalType(t) + is ListTypeInfo -> resolveListType(t) + is ChoiceTypeInfo -> resolveChoiceType(t) + else -> null + } + } + + private fun resolveTypeSpecifier(typeSpecifier: TypeSpecifier): DataType? { + if (typeSpecifier is NamedTypeSpecifier) { + var qualifier = typeSpecifier.namespace + if (qualifier.isNullOrEmpty()) { + qualifier = + typeSpecifier + .modelName // For backwards compatibility, modelName is deprecated in favor + // of + // namespace + } + if (qualifier.isNullOrEmpty()) { + qualifier = modelInfo.name + } + + val qualifiedTypeName = "$qualifier.${typeSpecifier.name}" + return resolveTypeName(qualifiedTypeName) + } + + if (typeSpecifier is IntervalTypeSpecifier) { + val pointType = + resolveTypeNameOrSpecifier( + typeSpecifier.pointType, + typeSpecifier.pointTypeSpecifier + ) + return IntervalType(pointType!!) + } + + if (typeSpecifier is ListTypeSpecifier) { + val elementType = + resolveTypeNameOrSpecifier( + typeSpecifier.elementType, + typeSpecifier.elementTypeSpecifier + ) + if (elementType != null) { + return ListType(elementType) + } + } + + if (typeSpecifier is TupleTypeSpecifier) { + val elements = mutableListOf() + for (specifierElement in typeSpecifier.element) { + val element = + TupleTypeElement( + specifierElement.name!!, + resolveTypeSpecifier(specifierElement.elementType!!)!! + ) + elements.add(element) + } + return TupleType(elements) + } + + if (typeSpecifier is ChoiceTypeSpecifier) { + val choices: MutableList = ArrayList() + for (choice in typeSpecifier.choice) { + val choiceType = resolveTypeSpecifier(choice)!! + choices.add(choiceType) + } + return ChoiceType(choices) + } + + return null + } + + private fun resolveTypeName(typeName: String): DataType? { + // NOTE: Preserving the ability to parse string type specifiers for backwards loading + // compatibility + // typeSpecifier: simpleTypeSpecifier | intervalTypeSpecifier | listTypeSpecifier + // simpleTypeSpecifier: (identifier '.')? identifier + // intervalTypeSpecifier: 'interval' '<' typeSpecifier '>' + // listTypeSpecifier: 'list' '<' typeSpecifier '>' + if (typeName.lowercase(Locale.getDefault()).startsWith("interval<")) { + val pointType = + resolveTypeName( + typeName.substring(typeName.indexOf('<') + 1, typeName.lastIndexOf('>')) + ) + return IntervalType(pointType!!) + } else if (typeName.lowercase(Locale.getDefault()).startsWith("list<")) { + val elementType = + resolveTypeName( + typeName.substring(typeName.indexOf('<') + 1, typeName.lastIndexOf('>')) + ) + return ListType(elementType!!) + } + + var result = lookupType(typeName) + if (result == null) { + val typeInfo = + lookupTypeInfo(ensureUnqualified(typeName)) + ?: throw IllegalArgumentException( + "Could not resolve type info for type name $typeName." + ) + + result = resolveTypeInfo(typeInfo) + } + + return result + } + + private fun resolveTypeNameOrSpecifier( + typeName: String?, + typeSpecifier: TypeSpecifier? + ): DataType? { + return when { + typeName.isNullOrEmpty() && typeSpecifier == null -> null + typeSpecifier != null -> resolveTypeSpecifier(typeSpecifier) + else -> resolveTypeName(typeName!!) + } + } + + private fun lookupType(typeName: String): DataType? { + val resolvedType = resolvedTypes[casify(typeName)] + if (resolvedType != null) { + return resolvedType + } + + val qualifierIndex = typeName.indexOf(".") + if (qualifierIndex < 0) { + return null + } + + val qualifier = typeName.substring(0, qualifierIndex) + if (qualifier == "") { + return null + } + + if (qualifier != modelInfo.name) { + val model = resolveModel(qualifier) ?: return null + + return model.resolveTypeName(typeName) + } + + return null + } + + private fun resolveModel(localIdentifier: String): Model? { + return modelIndex[localIdentifier] + } + + private fun lookupTypeInfo(typeName: String): TypeInfo? { + return typeInfoIndex[typeName] + } + + // This method is used to ensure backwards compatible loading, type names in model info may be + // qualified with the + // model name + private fun ensureQualified(name: String): String { + val qualifier = "${modelInfo.name}." + if (!name.startsWith(qualifier)) { + return "$qualifier$name" + } + + return name + } + + // This method is used to ensure backwards compatible loading, type names in model info may be + // qualified with the + // model name + private fun ensureUnqualified(name: String): String { + if (name.startsWith("${modelInfo.name}.")) { + return name.substring(name.indexOf('.') + 1) + } + + return name + } + + private fun resolveSimpleType(t: SimpleTypeInfo): SimpleType { + val qualifiedTypeName = ensureQualified(t.name!!) + val lookupType = lookupType(qualifiedTypeName) + require(lookupType !is ClassType) { + "Expected instance of SimpleType but found instance of $lookupType instead." + } + + var result = lookupType(qualifiedTypeName) as SimpleType? + if (result == null) { + result = + if (qualifiedTypeName == DataType.ANY.name) { + DataType.ANY + } else { + SimpleType( + qualifiedTypeName, + resolveTypeNameOrSpecifier(t.baseType, t.baseTypeSpecifier), + t.target + ) + } + resolvedTypes[casify(result.name)] = result + } + + return result + } + + private fun resolveTypeNameOrSpecifier(element: TupleTypeInfoElement): DataType? { + var result = resolveTypeNameOrSpecifier(element.elementType, element.elementTypeSpecifier) + if (result == null) { + result = resolveTypeNameOrSpecifier(element.type, element.typeSpecifier) + } + + return result + } + + private fun resolveTupleTypeElements( + infoElements: Collection + ): Collection { + val elements: MutableList = ArrayList() + for (e in infoElements) { + elements.add(TupleTypeElement(e.name!!, resolveTypeNameOrSpecifier(e)!!)) + } + return elements + } + + private fun resolveTupleType(t: TupleTypeInfo): TupleType { + val result = + TupleType( + resolveTupleTypeElements(t.element as Collection) + .toMutableList() + ) + return result + } + + private fun resolveTypeNameOrSpecifier(element: ClassInfoElement): DataType? { + var result = resolveTypeNameOrSpecifier(element.elementType, element.elementTypeSpecifier) + if (result == null) { + result = resolveTypeNameOrSpecifier(element.type, element.typeSpecifier) + } + + return result + } + + /** + * Converts a list of GenericParameterInfo definitions into their corresponding TypeParameter + * representations. + * + * @param parameterInfoList + * @return + */ + private fun resolveGenericParameterDeclarations( + parameterInfoList: List + ): List { + val genericParameters: MutableList = ArrayList() + for (parameterInfo in parameterInfoList) { + val constraint = parameterInfo.constraint + val typeConstraint = + when { + constraint.equals(TypeParameterConstraint.NONE.name, ignoreCase = true) -> { + TypeParameterConstraint.NONE + } + constraint.equals(TypeParameterConstraint.CLASS.name, ignoreCase = true) -> { + TypeParameterConstraint.CLASS + } + constraint.equals(TypeParameterConstraint.TUPLE.name, ignoreCase = true) -> { + TypeParameterConstraint.TUPLE + } + constraint.equals(TypeParameterConstraint.VALUE.name, ignoreCase = true) -> { + TypeParameterConstraint.VALUE + } + constraint.equals(TypeParameterConstraint.CHOICE.name, ignoreCase = true) -> { + TypeParameterConstraint.CHOICE + } + constraint.equals(TypeParameterConstraint.INTERVAL.name, ignoreCase = true) -> { + TypeParameterConstraint.INTERVAL + } + constraint.equals(TypeParameterConstraint.TYPE.name, ignoreCase = true) -> { + TypeParameterConstraint.TYPE + } + else -> TypeParameterConstraint.NONE + } + + genericParameters.add( + TypeParameter( + parameterInfo.name!!, + typeConstraint, + resolveTypeName(parameterInfo.constraintType!!) + ) + ) + } + return genericParameters + } + + /** + * Method resolves the types associated with class elements (i.e., class fields). If the type is + * not resolved, the type System.Any is assigned to this element. + * + * @param classType + * @param infoElements + * @return + */ + private fun resolveClassTypeElements( + classType: ClassType, + infoElements: Collection + ): Collection { + val elements: MutableList = ArrayList() + for (e in infoElements) { + var elementType: DataType? + elementType = + if (isOpenType(e)) { + resolveOpenType(classType, e) + } else if (isBoundParameterType(e)) { + resolveBoundType(classType, e) + } else { + resolveTypeNameOrSpecifier(e) + } + if (elementType == null) { + elementType = resolveTypeName("System.Any") + } + elements.add( + ClassTypeElement( + e.name!!, + elementType!!, + e.isProhibited() ?: false, + e.isOneBased() ?: false, + e.target + ) + ) + } + return elements + } + + /** + * Method returns true if class element is an open element bound to a specific type. For + * instance, if the generic class defines a field: + *
`T field1;`
+ * + * A subclass my bind T to a specific type such as System.Quantity such that the definition + * above becomes: + *
`System.Quantity field1;`
+ * + * @param element + * @return + */ + private fun isBoundParameterType(element: ClassInfoElement): Boolean { + return element.elementTypeSpecifier is BoundParameterTypeSpecifier + } + + /** + * Method resolves the bound type declaration and returns the type if valid. Method throws an + * exception if the type cannot be resolved (does not exist) or if the parameter that this type + * is bound to is not defined in the generic class. Types must be bound to existing generic + * parameters. + * + * @param classType + * @param e + * @return + */ + private fun resolveBoundType(classType: ClassType?, e: ClassInfoElement): DataType? { + val boundType: DataType? + val boundParameterTypeSpecifier = e.elementTypeSpecifier as BoundParameterTypeSpecifier + val parameterName = boundParameterTypeSpecifier.parameterName + val genericParameter = classType!!.getGenericParameterByIdentifier(parameterName!!) + + if (genericParameter == null) { + throw RuntimeException("Unknown symbol $parameterName") + } else { + boundType = resolveTypeName(boundParameterTypeSpecifier.boundType!!) + } + + return boundType + } + + /** + * Returns true if the element's type is a parameterized (non-bound, non-concrete) type such as + *
`T myField;`
+ * + * @param element + * @return + */ + private fun isOpenType(element: ClassInfoElement): Boolean { + return element.elementTypeSpecifier is ParameterTypeSpecifier + } + + /** + * Method to validate open types. An open type must reference a parameter defined in the generic + * class by name and the generic parameter must exist. + * + * Open types are class attribute types that reference one of the generic parameter of the class + * and that have not been bound to a concrete type. + * + * @param classType + * @param e + * @return + */ + private fun resolveOpenType(classType: ClassType, e: ClassInfoElement): DataType { + val elementType: DataType + val parameterTypeSpecifier = e.elementTypeSpecifier as ParameterTypeSpecifier + val parameterName = parameterTypeSpecifier.parameterName + require(!parameterName.isNullOrEmpty()) { "Parameter name must not be null or empty" } + if (classType.getGenericParameterByIdentifier(parameterName) == null) { + throw RuntimeException( + "Open types must reference a valid generic parameter and cannot be null or blank" + ) + } + elementType = + TypeParameter( + parameterTypeSpecifier.parameterName!!, + TypeParameterConstraint.TYPE, + null + ) + return elementType + } + + private fun resolveClassTypeSearch(t: ClassType?, s: SearchInfo): SearchType { + return SearchType(s.name!!, s.path!!, resolveTypeNameOrSpecifier(s.type, s.typeSpecifier)!!) + } + + private fun resolveClassType(t: ClassInfo): ClassType { + requireNotNull(t.name) { "Class definition must have a name." } + val qualifiedName = ensureQualified(t.name!!) + var result = lookupType(qualifiedName) as ClassType? + + if (result == null) { + result = + if (t is ProfileInfo) { + ProfileType( + qualifiedName, + resolveTypeNameOrSpecifier(t.baseType, t.baseTypeSpecifier) ?: DataType.ANY + ) + } else { + // Added to support generic notation in ModelInfo file for class type names + // (e.g., MyGeneric) and + // base classes (e.g., Map). + if (t.name!!.contains("<")) { + handleGenericType(t.name!!, t.baseType!!) + } else { + if (t.baseType != null && t.baseType!!.contains("<")) { + handleGenericType(t.name!!, t.baseType!!) + } else { + ClassType( + qualifiedName, + resolveTypeNameOrSpecifier(t.baseType, t.baseTypeSpecifier) + ?: DataType.ANY + ) + } + } + } + + resolvedTypes[casify(result.name)] = result + + result.addGenericParameter( + resolveGenericParameterDeclarations(t.parameter as List) + ) + + result.addElements( + resolveClassTypeElements(result, t.element as Collection) + ) + + for (si in t.search) { + result.addSearch(resolveClassTypeSearch(result, si)) + } + + // Here we handle the case when a type is not a generic but its base type is a generic + // type whose parameters + // have all been bound to concrete types (no remaining degrees of freedom) and is not + // expressed in generic + // notation in the model-info file. + if (isParentGeneric(result) && !t.baseType!!.contains("<")) { + validateFreeAndBoundParameters(result, t) + } + + result.apply { + identifier = t.identifier + label = t.label + target = t.target + isRetrievable = t.isRetrievable()!! + primaryCodePath = t.primaryCodePath + } + } + + return result + } + + private fun resolveContext(contextName: String): ModelContext { + for (context in this.contexts) { + if (context.name == contextName) { + return context + } + } + + throw IllegalArgumentException("Could not resolve context name $contextName.") + } + + private fun resolveRelationship(relationshipInfo: RelationshipInfo): Relationship { + val modelContext = resolveContext(relationshipInfo.context!!) + val relationship = + Relationship( + modelContext, + relationshipInfo.relatedKeyElement!!.split(";").dropLastWhile { it.isEmpty() } + ) + return relationship + } + + private fun importRelationships(c: ClassInfo, t: ClassType) { + for (r in c.contextRelationship) { + t.addRelationship(resolveRelationship(r)) + } + + for (r in c.targetContextRelationship) { + t.addTargetRelationship(resolveRelationship(r)) + } + } + + private fun resolveIntervalType(t: IntervalTypeInfo): IntervalType { + val result = IntervalType(resolveTypeNameOrSpecifier(t.pointType, t.pointTypeSpecifier)!!) + return result + } + + private fun resolveListType(t: ListTypeInfo): ListType { + val result = ListType(resolveTypeNameOrSpecifier(t.elementType, t.elementTypeSpecifier)!!) + return result + } + + private fun resolveChoiceType(t: ChoiceTypeInfo): ChoiceType { + val types = ArrayList() + if (t.choice.isNotEmpty()) { + for (typeSpecifier in t.choice) { + types.add(resolveTypeSpecifier(typeSpecifier)!!) + } + } else { + for (typeSpecifier in t.type) { + types.add(resolveTypeSpecifier(typeSpecifier)!!) + } + } + return ChoiceType(types) + } + + /** + * Method checks to see if a class' parameters covers its parent parameters. These represent + * remaining degrees of freedom in the child class. For instance, + *
`MyGeneric extends SomeOtherGeneric`
+ * + * All parameters in the parent class, not covered by the child class must be bound to a + * concrete type. In the above example, the parameter S is bound to the type String. + * + * If a parameter in the parent type is not covered by a child parameter nor bound to a concrete + * type, an exception is thrown indicating that the symbol is not known. In the example below, T + * is neither covered nor bound and thus is an unknown symbol. `
MyGeneric extends
+     * SomeOtherGeneric<String,T>
` + * + * @param type + * @param definition + */ + fun validateFreeAndBoundParameters(type: ClassType?, definition: ClassInfo) { + val coveredParameters: MutableList = ArrayList() + val boundParameters: MutableList = ArrayList() + + (type!!.baseType as ClassType).genericParameters.forEach { + val parameterName: String = it.identifier + if (type.getGenericParameterByIdentifier(parameterName, true) != null) { + coveredParameters.add(parameterName) + } else { + boundParameters.add(parameterName) + } + } + + if (boundParameters.isNotEmpty()) { + definition.element.forEach { + if (it.elementTypeSpecifier is BoundParameterTypeSpecifier) { + val name: String = + (it.elementTypeSpecifier as BoundParameterTypeSpecifier).parameterName!! + val paramIndex: Int = boundParameters.indexOf(name) + if (paramIndex >= 0) { + boundParameters.removeAt(paramIndex) + } + } + } + if (boundParameters.isNotEmpty()) { + throw RuntimeException("Unknown symbols $boundParameters") + } + } + } + + /** + * Method returns true if the class' base type is a generic type. + * + * @param type + * @return True if the parent of class 'type' is a generic class. + */ + fun isParentGeneric(type: ClassType): Boolean { + return type.baseType is ClassType && type.baseType.isGeneric + } + + /** + * Converts a generic type declaration represented as a string into the corresponding generic + * ClassType (i.e., a class type that specifies generic parameters). + * + * @param baseType The base type for the generic class type. + * @param genericSignature The signature of the generic type such as Map<K,V>. + * @return + */ + private fun handleGenericType(genericSignature: String, baseType: String): ClassType { + val parser = GenericClassSignatureParser(genericSignature, baseType, resolvedTypes) + val genericClassType = parser.parseGenericSignature() + + return genericClassType + } + + /** + * Checks whether descendant is a valid subtype of ancestor. + * + * @param descendant + * @param ancestor + * @return + */ + private fun conformsTo(descendant: DataType?, ancestor: DataType?): Boolean { + val conforms = + if ((descendant != null && ancestor != null) && descendant == ancestor) { + true + } else { + conformsTo(descendant!!.baseType, ancestor) + } + return conforms + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Operator.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Operator.kt new file mode 100644 index 000000000..43faa1bf7 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Operator.kt @@ -0,0 +1,41 @@ +package org.cqframework.cql.cql2elm.model + +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.hl7.cql.model.DataType +import org.hl7.elm.r1.AccessModifier +import org.hl7.elm.r1.FunctionDef + +@Suppress("LongParameterList") +open class Operator( + val name: String, + var signature: Signature, + var resultType: DataType?, + var functionDef: FunctionDef?, + var accessLevel: AccessModifier = AccessModifier.PUBLIC, + var fluent: Boolean = false, + var external: Boolean = false +) { + constructor( + name: String, + signature: Signature, + resultType: DataType? + ) : this(name, signature, resultType, null) + + constructor( + functionDef: FunctionDef + ) : this( + functionDef.name!!, + Signature(functionDef.operand.map { it.resultType!! }), + functionDef.resultType, + functionDef, + functionDef.accessLevel!!, + functionDef.isFluent() ?: false, + functionDef.isExternal() ?: false + ) + + init { + require(name.isNotEmpty()) { "name is null or empty" } + } + + var libraryName: String? = null +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorEntry.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorEntry.kt new file mode 100644 index 000000000..a381fd782 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorEntry.kt @@ -0,0 +1,393 @@ +package org.cqframework.cql.cql2elm.model + +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import org.hl7.cql.model.ChoiceType +import org.hl7.cql.model.DataType + +class OperatorEntry(val name: String) { + + private val signatures = SignatureNodes() + private val genericOperators: MutableMap = HashMap() + + init { + require(name.isNotEmpty()) { "name is empty" } + } + + private class SignatureNode(val operator: Operator) { + val signature: Signature + get() = operator.signature + + /* + The invocation signature is the call signature with arguments of type Any set to the operand types + */ + private fun getInvocationSignature( + callSignature: Signature, + operatorSignature: Signature + ): Signature { + if (callSignature.size == operatorSignature.size) { + val invocationTypes = mutableListOf() + val callTypes = callSignature.operandTypes + val operatorTypes = operatorSignature.operandTypes + var isResolved = false + for (i in callTypes.indices) { + val callType = callTypes[i] + val operatorType = operatorTypes[i] + if (callType == DataType.ANY && operatorType != DataType.ANY) { + isResolved = true + invocationTypes.add(operatorType) + } else { + invocationTypes.add(callType) + } + } + if (isResolved) { + return Signature(invocationTypes) + } + } + return callSignature + } + + @Suppress("LongParameterList", "UnusedParameter") + private fun getOperatorResolution( + operator: Operator, + callSignature: Signature, + invocationSignature: Signature, + conversionMap: ConversionMap, + operatorMap: OperatorMap, + allowPromotionAndDemotion: Boolean, + requireConversions: Boolean + ): OperatorResolution? { + val conversions = + getConversions( + callSignature, + operator.signature, + conversionMap, + operatorMap, + allowPromotionAndDemotion + ) + if (requireConversions && conversions == null) { + return null + } + + val result = OperatorResolution(operator, conversions) + return result + } + + @Suppress("ReturnCount") + fun resolve( + callContext: CallContext, + conversionMap: ConversionMap, + operatorMap: OperatorMap + ): List { + var results: MutableList = ArrayList() + val invocationSignature = + getInvocationSignature(callContext.signature, operator.signature) + + // Attempt exact match against this signature + if (operator.signature == invocationSignature) { + val result = + getOperatorResolution( + operator, + callContext.signature, + invocationSignature, + conversionMap, + operatorMap, + callContext.allowPromotionAndDemotion, + false + ) + if (result != null) { + results.add(result) + return results + } + } + + // Attempt to resolve against sub signatures + results = subSignatures.resolve(callContext, conversionMap, operatorMap) + + // If no subsignatures match, attempt subType match against this signature + if (results.isEmpty() && operator.signature.isSuperTypeOf(invocationSignature)) { + val result = + getOperatorResolution( + operator, + callContext.signature, + invocationSignature, + conversionMap, + operatorMap, + callContext.allowPromotionAndDemotion, + false + ) + if (result != null) { + results.add(result) + return results + } + } + + if (results.isEmpty()) { + // Attempt to find a conversion path from the call signature to the target signature + val result = + getOperatorResolution( + operator, + callContext.signature, + invocationSignature, + conversionMap, + operatorMap, + callContext.allowPromotionAndDemotion, + true + ) + if (result != null) { + results.add(result) + } + } + + return results + } + + @Suppress("ReturnCount") + private fun getConversions( + callSignature: Signature?, + operatorSignature: Signature?, + conversionMap: ConversionMap, + operatorMap: OperatorMap, + allowPromotionAndDemotion: Boolean + ): Array? { + if ( + callSignature == null || + operatorSignature == null || + callSignature.size != operatorSignature.size + ) { + return null + } + + val conversions = arrayOfNulls(callSignature.size) + val isConvertible = + callSignature.isConvertibleTo( + operatorSignature, + conversionMap, + operatorMap, + allowPromotionAndDemotion, + conversions + ) + + if (isConvertible) { + return conversions + } + + return null + } + + val subSignatures: SignatureNodes = SignatureNodes() + + fun hasSubSignatures(): Boolean { + return subSignatures.hasSignatures() + } + + override fun hashCode(): Int { + return operator.signature.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other is SignatureNode) { + return operator.name == other.operator.name && (this.signature == other.signature) + } + + return false + } + + override fun toString(): String { + return operator.toString() + } + } + + private class SignatureNodes { + private val signatures: MutableMap = HashMap() + + fun hasSignatures(): Boolean { + return signatures.isNotEmpty() + } + + fun contains(operator: Operator?): Boolean { + var result = signatures.containsKey(operator!!.signature) + if (!result) { + for (n in signatures.values) { + result = n.subSignatures.contains(operator) + if (result) { + break + } + } + } + + return result + } + + fun add(node: SignatureNode?) { + requireNotNull(node) { "node is null." } + + require(!signatures.containsKey(node.signature)) { + "Operator ${node.operator.name} already has a registration for signature: ${node.signature}." + } + + var added = false + for (n in signatures.values) { + if (n.signature.isSuperTypeOf(node.signature)) { + n.subSignatures.add(node) + added = true + break + } + } + + if (!added) { + for (n in signatures.values.toTypedArray()) { + if (node.signature.isSuperTypeOf(n.signature)) { + signatures.remove(n.signature) + node.subSignatures.add(n) + } + } + + signatures[node.signature] = node + } + } + + fun resolve( + callContext: CallContext, + conversionMap: ConversionMap, + operatorMap: OperatorMap + ): MutableList { + val results = arrayListOf() + + var signatureCount = 0 + for (n in signatures.values) { + if (n.signature.size == callContext.signature.size) { + signatureCount++ + + // Any subSignature will count as an overload + if (n.hasSubSignatures()) { + signatureCount++ + } + } + + val nodeResults = n.resolve(callContext, conversionMap, operatorMap) + results.addAll(nodeResults) + } + + if (signatureCount > 1) { + for (result in results) { + result.operatorHasOverloads = true + } + } + + return results + } + } + + fun containsOperator(operator: Operator?): Boolean { + return if (operator is GenericOperator) { + containsGenericOperator(operator) + } else { + signatures.contains(operator) + } + } + + fun addOperator(operator: Operator) { + if (operator is GenericOperator) { + addGenericOperator(operator) + } else { + signatures.add(SignatureNode(operator)) + } + } + + private fun containsGenericOperator(operator: GenericOperator): Boolean { + return genericOperators.containsKey(operator.signature) + } + + private fun addGenericOperator(operator: GenericOperator) { + require(!genericOperators.containsKey(operator.signature)) { + "Operator $name already has a generic registration for signature: ${operator.signature}." + } + + genericOperators[operator.signature] = operator + } + + @Suppress("NestedBlockDepth") + private fun expandChoices(callSignature: Signature): List { + val signatures = ArrayList() + if (callSignature.containsChoices) { + val operandList = ArrayList>() + for (operand in callSignature.operandTypes) { + val list = ArrayList() + if (operand is ChoiceType) { + for (type in operand.types) { + list.add(type) + } + } else { + list.add(operand) + } + operandList.add(list) + } + + val result = arrayOfNulls(callSignature.size) + collectSignatures(operandList, result, 0, signatures) + } else { + signatures.add(callSignature) + } + return signatures + } + + private fun collectSignatures( + operandList: ArrayList>, + result: Array, + k: Int, + signatures: MutableList + ) { + if (k == operandList.size) { + val noNulls = result.toList().requireNoNulls() + signatures.add(Signature(noNulls)) + } else { + for (j in operandList[k].indices) { + result[k] = operandList[k][j] + collectSignatures(operandList, result, k + 1, signatures) + } + } + } + + fun resolve( + callContext: CallContext, + operatorMap: OperatorMap, + conversionMap: ConversionMap + ): List { + // Attempt to instantiate any generic signatures + // If the callContext signature contains choices, attempt instantiation with all possible + // combinations of + // the call signature (ouch, this could really hurt...) + val callSignatures = expandChoices(callContext.signature) + for (callSignature in callSignatures) { + val instantiations = + instantiate( + callSignature, + operatorMap, + conversionMap, + callContext.allowPromotionAndDemotion + ) + for (instantiation in instantiations) { + // If the generic signature was instantiated, store it as an actual signature. + if (!signatures.contains(instantiation)) { + signatures.add(SignatureNode(instantiation)) + } + } + } + + return signatures.resolve(callContext, conversionMap, operatorMap) + } + + private fun instantiate( + signature: Signature, + operatorMap: OperatorMap, + conversionMap: ConversionMap, + allowPromotionAndDemotion: Boolean + ): List { + return genericOperators.values + .map { + it.instantiate(signature, operatorMap, conversionMap, allowPromotionAndDemotion) + } + .mapNotNull { it.operator } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorMap.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorMap.kt new file mode 100644 index 000000000..75fb184f3 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorMap.kt @@ -0,0 +1,128 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.cql.model.DataType + +class OperatorMap { + private val operators: MutableMap = HashMap() + + fun containsOperator(operator: Operator): Boolean { + val entry = getEntry(operator.name) + return entry.containsOperator(operator) + } + + fun addOperator(operator: Operator) { + val entry = getEntry(operator.name) + entry.addOperator(operator) + } + + private fun getEntry(operatorName: String): OperatorEntry { + return operators.computeIfAbsent(operatorName) { OperatorEntry(operatorName) } + } + + private fun supportsOperator( + libraryName: String?, + operatorName: String, + vararg signature: DataType + ): Boolean { + val call = + CallContext( + libraryName, + operatorName, + allowPromotionAndDemotion = false, + allowFluent = false, + mustResolve = false, + operandTypes = signature + ) + // Intentionally empty ConversionMap since we're only checking for support + return resolveOperator(call, ConversionMap()) != null + } + + // Returns true if the given type supports the operations necessary to be the point type of an + // interval + // (i.e. comparison, successor, and predecessor) + fun isPointType(type: DataType): Boolean { + return supportsOperator("System", "LessOrEqual", type, type) && + supportsOperator("System", "Successor", type) + } + + @Suppress("LongMethod", "CyclomaticComplexMethod", "NestedBlockDepth") + fun resolveOperator( + callContext: CallContext, + conversionMap: ConversionMap + ): OperatorResolution? { + val entry = getEntry(callContext.operatorName) + val results = entry.resolve(callContext, this, conversionMap) + + // Score each resolution and return the lowest score + // Duplicate scores indicate ambiguous match + var result: OperatorResolution? = null + if (results.isNotEmpty()) { + var lowestScore = Int.MAX_VALUE + var lowestScoringResults: MutableList = ArrayList() + for (resolution in results) { + val operands = resolution.operator.signature.operandTypes + val callOperands = callContext.signature.operandTypes + val conversions = if (resolution.hasConversions()) resolution.conversions else null + var score = ConversionMap.ConversionScore.ExactMatch.score + for (i in operands.indices) { + val operand = operands[i] + val callOperand = callOperands[i] + val conversion = conversions?.get(i) + score += ConversionMap.getConversionScore(callOperand, operand, conversion) + } + + resolution.score = score + + if (score < lowestScore) { + lowestScore = score + lowestScoringResults.clear() + lowestScoringResults.add(resolution) + } else if (score == lowestScore) { + lowestScoringResults.add(resolution) + } + } + + if (lowestScoringResults.size > 1) { + var lowestTypeScore = Int.MAX_VALUE + val lowestTypeScoringResults: MutableList = ArrayList() + for (resolution in lowestScoringResults) { + var typeScore = ConversionMap.ConversionScore.ExactMatch.score + for (operand in resolution.operator.signature.operandTypes) { + typeScore += ConversionMap.getTypePrecedenceScore(operand) + } + + if (typeScore < lowestTypeScore) { + lowestTypeScore = typeScore + lowestTypeScoringResults.clear() + lowestTypeScoringResults.add(resolution) + } else if (typeScore == lowestTypeScore) { + lowestTypeScoringResults.add(resolution) + } + } + + lowestScoringResults = lowestTypeScoringResults + } + + if (lowestScoringResults.size > 1) { + if (callContext.mustResolve) { + // ERROR: + val message = + StringBuilder("Call to operator ") + .append(callContext.operatorName) + .append(callContext.signature) + .append(" is ambiguous with: ") + for ((operator) in lowestScoringResults) { + message.append("\n - ").append(operator.name).append(operator.signature) + } + throw IllegalArgumentException(message.toString()) + } else { + return null + } + } else { + result = lowestScoringResults[0] + } + } + + return result + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorResolution.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorResolution.kt new file mode 100644 index 000000000..ae23c9017 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/OperatorResolution.kt @@ -0,0 +1,32 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.elm.r1.VersionedIdentifier + +data class OperatorResolution(var operator: Operator, val conversions: List) { + constructor( + operator: Operator, + conversions: Array? + ) : this(operator, conversions?.toList() ?: emptyList()) + + var score: Int = 0 + var allowFluent: Boolean = false + + fun hasConversions(): Boolean = conversions.filterNotNull().isNotEmpty() + + /* + The versioned identifier + (fully qualified, versioned, library identifier of the library + in which the resolved operator is defined.) + This is set by the library resolution to allow the calling context + to understand the defined location + of the resolved operator. + */ + var libraryIdentifier: VersionedIdentifier? = null + + /* + The local alias for the resolved library. This is set by the libraryBuilder to allow the invocation + to set the library alias if necessary. + */ + var libraryName: String? = null + var operatorHasOverloads: Boolean = false +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/PropertyResolution.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/PropertyResolution.kt new file mode 100644 index 000000000..2037b9d4a --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/PropertyResolution.kt @@ -0,0 +1,49 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.cql.model.ClassTypeElement +import org.hl7.cql.model.DataType +import org.hl7.cql.model.SearchType +import org.hl7.cql.model.TupleTypeElement + +/** Created by Bryn on 4/19/2019. */ +data class PropertyResolution( + val type: DataType, + val name: String, + val targetMap: String? = null, + val isSearch: Boolean = false +) { + constructor(e: ClassTypeElement) : this(e.type, e.name, e.target) + + constructor(e: TupleTypeElement) : this(e.type, e.name) + + constructor(s: SearchType) : this(s.type, s.name, isSearch = true) + + @JvmOverloads + constructor( + type: DataType, + name: String, + targetMaps: Map? = null + ) : this(type, name, targetMaps.toTargetMapString()) + + companion object { + private fun Map?.toTargetMapString(): String? { + return when { + this.isNullOrEmpty() -> null + else -> { + val builder = StringBuilder() + for ((key, value) in this) { + if (builder.isNotEmpty()) { + builder.append(";") + } + if (this.size > 1) { + builder.append(key.toString()) + builder.append(":") + } + builder.append(value) + } + builder.toString() + } + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/QueryContext.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/QueryContext.kt new file mode 100644 index 000000000..2869710ac --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/QueryContext.kt @@ -0,0 +1,103 @@ +package org.cqframework.cql.cql2elm.model + +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.hl7.cql.model.DataType +import org.hl7.cql.model.ListType +import org.hl7.elm.r1.AliasedQuerySource +import org.hl7.elm.r1.LetClause + +@Suppress("TooManyFunctions") +class QueryContext { + private val sources = HashMap() + private val lets = HashMap() + + private fun internalAddQuerySource(source: AliasedQuerySource) { + sources[source.alias!!] = source + } + + // Adds a related (i.e. with or without) source, which does not change cardinality of the query + fun addRelatedQuerySource(source: AliasedQuerySource) { + internalAddQuerySource(source) + } + + // Adds primary sources, which affect cardinality (any primary plural source results in a plural + // query) + fun addPrimaryQuerySources(sources: Collection) { + for (source in sources) { + internalAddQuerySource(source) + if (source.resultType is ListType) { + isSingular = false + } + } + } + + val querySources: Collection + get() = sources.values + + fun removeQuerySource(source: AliasedQuerySource) { + sources.remove(source.alias) + } + + fun removeQuerySources(sources: Collection) { + for (source in sources) { + removeQuerySource(source) + } + } + + fun addLetClause(let: LetClause) { + lets[let.identifier!!] = let + } + + private fun removeLetClause(let: LetClause) { + lets.remove(let.identifier) + } + + fun removeLetClauses(lets: Collection) { + for (let in lets) { + removeLetClause(let) + } + } + + fun resolveAlias(identifier: String): AliasedQuerySource? { + return sources[identifier] + } + + fun resolveLet(identifier: String): LetClause? { + return lets[identifier] + } + + var isSingular: Boolean = true + private set + + private var inSourceClauseValue = false + + fun enterSourceClause() { + inSourceClauseValue = true + } + + fun exitSourceClause() { + inSourceClauseValue = false + } + + fun inSourceClause(): Boolean { + return inSourceClauseValue + } + + private var inSortClauseValue = false + + fun enterSortClause() { + inSortClauseValue = true + } + + fun exitSortClause() { + inSortClauseValue = false + } + + fun inSortClause(): Boolean { + return inSortClauseValue + } + + var isImplicit: Boolean = false + var resultElementType: DataType? = null + var referencesSpecificContextValue = false +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ResolvedIdentifierContext.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ResolvedIdentifierContext.kt new file mode 100644 index 000000000..b46652d21 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/ResolvedIdentifierContext.kt @@ -0,0 +1,94 @@ +package org.cqframework.cql.cql2elm.model + +import java.util.* +import org.hl7.elm.r1.CodeDef +import org.hl7.elm.r1.CodeSystemDef +import org.hl7.elm.r1.ConceptDef +import org.hl7.elm.r1.ContextDef +import org.hl7.elm.r1.Element +import org.hl7.elm.r1.ExpressionDef +import org.hl7.elm.r1.OperandDef +import org.hl7.elm.r1.ParameterDef +import org.hl7.elm.r1.TupleElementDefinition +import org.hl7.elm.r1.ValueSetDef + +/** + * Context for resolved identifiers containing the identifier, the resolved element (if non-null) as + * well as the type of matching done to retrieve the element, whether case-sensitive or + * case-insensitive. + */ +@ExposedCopyVisibility +data class ResolvedIdentifierContext +private constructor( + private val identifier: String, + private val element: Element?, + private val matchType: ResolvedIdentifierMatchType +) { + private enum class ResolvedIdentifierMatchType { + EXACT, + CASE_INSENSITIVE + } + + val exactMatchElement: Element? + get() { + return if (isExactMatch) element else null + } + + private val isExactMatch: Boolean + get() = ResolvedIdentifierMatchType.EXACT == matchType + + fun warnCaseInsensitiveIfApplicable(): String? { + if (element != null && !isExactMatch) { + return getName(element)?.let { + "Could not find identifier: [$identifier]. Did you mean [$it]?" + } + } + + return null + } + + fun resolveIdentifier(clazz: Class): T? { + return if (exactMatchElement != null && clazz.isInstance(element)) { + clazz.cast(element) + } else { + null + } + } + + fun getElementOfType(clazz: Class): Optional { + if (clazz.isInstance(element)) { + return Optional.of(clazz.cast(element)) + } + + return Optional.empty() + } + + companion object { + fun exactMatch(identifier: String, element: Element?): ResolvedIdentifierContext { + return ResolvedIdentifierContext(identifier, element, ResolvedIdentifierMatchType.EXACT) + } + + fun caseInsensitiveMatch(identifier: String, element: Element?): ResolvedIdentifierContext { + return ResolvedIdentifierContext( + identifier, + element, + ResolvedIdentifierMatchType.CASE_INSENSITIVE + ) + } + + private fun getName(element: Element): String? { + return when (element) { + is ExpressionDef -> element.name + is ValueSetDef -> element.name + is OperandDef -> element.name + is TupleElementDefinition -> element.name + is CodeDef -> element.name + is ConceptDef -> element.name + is ParameterDef -> element.name + is CodeSystemDef -> element.name + is ContextDef -> element.name + else -> null + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Signature.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Signature.kt new file mode 100644 index 000000000..70c6d3770 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Signature.kt @@ -0,0 +1,75 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.cql.model.ChoiceType +import org.hl7.cql.model.DataType +import org.hl7.cql.model.InstantiationContext + +data class Signature(val operandTypes: List) { + constructor(vararg types: DataType) : this(types.toList()) + + val containsChoices by lazy { operandTypes.any { it is ChoiceType } } + val size: Int + get() = operandTypes.size + + fun isSuperTypeOf(other: Signature): Boolean { + return size == other.size && + operandTypes.zip(other.operandTypes).all { it.first.isSuperTypeOf(it.second) } + } + + fun isSubTypeOf(other: Signature): Boolean { + return size == other.size && + operandTypes.zip(other.operandTypes).all { it.first.isSubTypeOf(it.second) } + } + + fun isInstantiable(callSignature: Signature, context: InstantiationContext): Boolean { + return size == callSignature.size && + operandTypes.zip(callSignature.operandTypes).all { + it.first.isInstantiable(it.second, context) + } + } + + fun instantiate(context: InstantiationContext): Signature { + return Signature(operandTypes.map { it.instantiate(context) }) + } + + fun isConvertibleTo( + other: Signature, + conversionMap: ConversionMap?, + operatorMap: OperatorMap, + allowPromotionAndDemotion: Boolean, + conversions: Array + ): Boolean { + return size == other.size && + run { + // Each operand must be a subtype or convertible + // Store the conversions for each operand + // If a conversion is needed and not found, return false (not convertible) + for (i in operandTypes.indices) { + val first = operandTypes[i] + val second = other.operandTypes[i] + if (first.isSubTypeOf(second)) { + continue + } + + conversions[i] = + conversionMap?.findConversion( + first, + second, + true, + allowPromotionAndDemotion, + operatorMap + ) + + if (conversions[i] == null) { + return@run false + } + } + + return@run true + } + } + + override fun toString(): String { + return "(" + operandTypes.joinToString { it.toString() } + ")" + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/SystemLibraryHelper.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/SystemLibraryHelper.kt new file mode 100644 index 000000000..e3d245e72 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/SystemLibraryHelper.kt @@ -0,0 +1,2967 @@ +package org.cqframework.cql.cql2elm.model + +import org.cqframework.cql.cql2elm.TypeBuilder +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.hl7.cql.model.IntervalType +import org.hl7.cql.model.ListType +import org.hl7.cql.model.NamedType +import org.hl7.cql.model.TypeParameter +import org.hl7.elm.r1.FunctionDef +import org.hl7.elm.r1.OperandDef +import org.hl7.elm.r1.VersionedIdentifier + +@Suppress("LargeClass", "LongMethod") +object SystemLibraryHelper { + @JvmStatic + fun load(systemModel: SystemModel, tb: TypeBuilder): CompiledLibrary { + val system = CompiledLibrary() + system.identifier = VersionedIdentifier().withId("System").withVersion("1.0") + + // Logical Operators + add( + system, + tb, + Operator( + "And", + Signature(systemModel.boolean, systemModel.boolean), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Or", Signature(systemModel.boolean, systemModel.boolean), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "Xor", + Signature(systemModel.boolean, systemModel.boolean), + systemModel.boolean + ) + ) + add(system, tb, Operator("Not", Signature(systemModel.boolean), systemModel.boolean)) + add( + system, + tb, + Operator( + "Implies", + Signature(systemModel.boolean, systemModel.boolean), + systemModel.boolean + ) + ) + + // Nullological Operators + add(system, tb, Operator("IsNull", Signature(systemModel.any), systemModel.boolean)) + add(system, tb, Operator("IsTrue", Signature(systemModel.boolean), systemModel.boolean)) + add(system, tb, Operator("IsFalse", Signature(systemModel.boolean), systemModel.boolean)) + // Coalesce(list) + // Coalesce(T, T) + // Coalesce(T, T, T) + // Coalesce(T, T, T, T) + // Coalesce(T, T, T, T, T) + add( + system, + tb, + GenericOperator( + "Coalesce", + Signature(ListType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + ) + add( + system, + tb, + GenericOperator( + "Coalesce", + Signature(TypeParameter("T"), TypeParameter("T")), + TypeParameter("T"), + TypeParameter("T") + ) + ) + add( + system, + tb, + GenericOperator( + "Coalesce", + Signature(TypeParameter("T"), TypeParameter("T"), TypeParameter("T")), + TypeParameter("T"), + TypeParameter("T") + ) + ) + add( + system, + tb, + GenericOperator( + "Coalesce", + Signature( + TypeParameter("T"), + TypeParameter("T"), + TypeParameter("T"), + TypeParameter("T") + ), + TypeParameter("T"), + TypeParameter("T") + ) + ) + add( + system, + tb, + GenericOperator( + "Coalesce", + Signature( + TypeParameter("T"), + TypeParameter("T"), + TypeParameter("T"), + TypeParameter("T"), + TypeParameter("T") + ), + TypeParameter("T"), + TypeParameter("T") + ) + ) + + // Conversion Operators + // ToString(Boolean) : String + // ToString(Integer) : String + // ToString(Long) : String + // ToString(Decimal) : String + // ToString(DateTime) : String + // ToString(Date) : String + // ToString(Time) : String + // ToString(Quantity) : String + // ToString(Ratio) : String + // ToString(String) : String + val booleanToString = + Operator("ToString", Signature(systemModel.boolean), systemModel.string) + add(system, tb, booleanToString) + add(system, tb, Conversion(booleanToString, false)) + val integerToString = + Operator("ToString", Signature(systemModel.integer), systemModel.string) + add(system, tb, integerToString) + add(system, tb, Conversion(integerToString, false)) + val longToString = Operator("ToString", Signature(systemModel.long), systemModel.string) + add(system, tb, longToString) + add(system, tb, Conversion(longToString, false)) + val decimalToString = + Operator("ToString", Signature(systemModel.decimal), systemModel.string) + add(system, tb, decimalToString) + add(system, tb, Conversion(decimalToString, false)) + val dateTimeToString = + Operator("ToString", Signature(systemModel.dateTime), systemModel.string) + add(system, tb, dateTimeToString) + add(system, tb, Conversion(dateTimeToString, false)) + val dateToString = Operator("ToString", Signature(systemModel.date), systemModel.string) + add(system, tb, dateToString) + add(system, tb, Conversion(dateToString, false)) + val timeToString = Operator("ToString", Signature(systemModel.time), systemModel.string) + add(system, tb, timeToString) + add(system, tb, Conversion(timeToString, false)) + val quantityToString = + Operator("ToString", Signature(systemModel.quantity), systemModel.string) + add(system, tb, quantityToString) + add(system, tb, Conversion(quantityToString, false)) + val ratioToString = Operator("ToString", Signature(systemModel.ratio), systemModel.string) + add(system, tb, ratioToString) + add(system, tb, Conversion(ratioToString, false)) + + // Operator stringToString = new Operator("ToString", new + // Signature(systemModel.getString()), + // systemModel.getString()); + // add(system, tb, stringToString); + // add(system, tb, new Conversion(stringToString, false)); + + // ToBoolean(Boolean) : Boolean + // ToBoolean(Integer) : Boolean + // ToBoolean(Decimal) : Boolean + // ToBoolean(Long) : Boolean + // ToBoolean(String) : Boolean + val stringToBoolean = + Operator("ToBoolean", Signature(systemModel.string), systemModel.boolean) + add(system, tb, stringToBoolean) + add(system, tb, Conversion(stringToBoolean, false)) + val integerToBoolean = + Operator("ToBoolean", Signature(systemModel.integer), systemModel.boolean) + add(system, tb, integerToBoolean) + add(system, tb, Conversion(integerToBoolean, false)) + val decimalToBoolean = + Operator("ToBoolean", Signature(systemModel.decimal), systemModel.boolean) + add(system, tb, decimalToBoolean) + add(system, tb, Conversion(decimalToBoolean, false)) + val longToBoolean = Operator("ToBoolean", Signature(systemModel.long), systemModel.boolean) + add(system, tb, longToBoolean) + add(system, tb, Conversion(longToBoolean, false)) + + // Operator booleanToBoolean = new Operator("ToBoolean", new + // Signature(systemModel.getBoolean()), + // systemModel.getBoolean()); + // add(system, tb, booleanToBoolean); + // add(system, tb, new Conversion(booleanToBoolean, false)); + + // ToChars(String) : List(String) + val toChars = + Operator("ToChars", Signature(systemModel.string), ListType(systemModel.string)) + add(system, tb, toChars) + add(system, tb, Conversion(toChars, false)) + + // ToInteger(String) : Integer + // ToInteger(Boolean) : Integer + // ToInteger(Long) : Integer + // ToInteger(Integer) : Integer + val stringToInteger = + Operator("ToInteger", Signature(systemModel.string), systemModel.integer) + add(system, tb, stringToInteger) + add(system, tb, Conversion(stringToInteger, false)) + val longToInteger = Operator("ToInteger", Signature(systemModel.long), systemModel.integer) + add(system, tb, longToInteger) + add(system, tb, Conversion(longToInteger, false)) + val booleanToInteger = + Operator("ToInteger", Signature(systemModel.boolean), systemModel.integer) + add(system, tb, booleanToInteger) + add(system, tb, Conversion(booleanToInteger, false)) + + // Operator integerToInteger = new Operator("ToInteger", new + // Signature(systemModel.getInteger()), + // systemModel.getInteger()); + // add(system, tb, integerToInteger); + // add(system, tb, new Conversion(integerToInteger, false)); + + // ToLong(Boolean) : Long + // ToLong(String) : Long + // ToLong(Integer) : Long + // ToLong(Long) : Long + val stringToLong = Operator("ToLong", Signature(systemModel.string), systemModel.long) + add(system, tb, stringToLong) + add(system, tb, Conversion(stringToLong, false)) + val integerToLong = Operator("ToLong", Signature(systemModel.integer), systemModel.long) + add(system, tb, integerToLong) + add(system, tb, Conversion(integerToLong, true)) + // Operator longToLong = new Operator("ToLong", new Signature(systemModel.getLong()), + // systemModel.getLong()); + // add(system, tb, longToLong); + // add(system, tb, new Conversion(longToLong, false)); + val booleanToLong = Operator("ToLong", Signature(systemModel.boolean), systemModel.long) + add(system, tb, booleanToLong) + add(system, tb, Conversion(booleanToLong, false)) + + // ToDecimal(Boolean) : Decimal + // ToDecimal(String) : Decimal + // ToDecimal(Integer) : Decimal + // ToDecimal(Long) : Decimal + // ToDecimal(Decimal) : Decimal + val stringToDecimal = + Operator("ToDecimal", Signature(systemModel.string), systemModel.decimal) + add(system, tb, stringToDecimal) + add(system, tb, Conversion(stringToDecimal, false)) + val integerToDecimal = + Operator("ToDecimal", Signature(systemModel.integer), systemModel.decimal) + add(system, tb, integerToDecimal) + add(system, tb, Conversion(integerToDecimal, true)) + val longToDecimal = Operator("ToDecimal", Signature(systemModel.long), systemModel.decimal) + add(system, tb, longToDecimal) + add(system, tb, Conversion(longToDecimal, true)) + // Operator decimalToDecimal = new Operator("ToDecimal", new + // Signature(systemModel.getDecimal()), + // systemModel.getDecimal()); + // add(system, tb, decimalToDecimal); + // add(system, tb, new Conversion(decimalToDecimal, false)); + val booleanToDecimal = + Operator("ToDecimal", Signature(systemModel.boolean), systemModel.decimal) + add(system, tb, booleanToDecimal) + add(system, tb, Conversion(booleanToDecimal, false)) + + // ToDateTime(String) : DateTime + // ToDateTime(Date) : DateTime + // ToDateTime(DateTime) : DateTime + val stringToDateTime = + Operator("ToDateTime", Signature(systemModel.string), systemModel.dateTime) + add(system, tb, stringToDateTime) + add(system, tb, Conversion(stringToDateTime, false)) + val dateToDateTime = + Operator("ToDateTime", Signature(systemModel.date), systemModel.dateTime) + add(system, tb, dateToDateTime) + add(system, tb, Conversion(dateToDateTime, true)) + + // Operator dateTimeToDateTime = new Operator("ToDateTime", new + // Signature(systemModel.getDateTime()), + // systemModel.getDateTime()); + // add(system, tb, dateTimeToDateTime); + // add(system, tb, new Conversion(dateTimeToDateTime, false)); + + // ToDate(DateTime) : Date + // ToDate(String) : Date + // ToDate(Date) : Date + val stringToDate = Operator("ToDate", Signature(systemModel.string), systemModel.date) + add(system, tb, stringToDate) + add(system, tb, Conversion(stringToDate, false)) + val dateTimeToDate = Operator("ToDate", Signature(systemModel.dateTime), systemModel.date) + add(system, tb, dateTimeToDate) + add(system, tb, Conversion(dateTimeToDate, false)) + + // Operator dateToDate = new Operator("ToDate", new Signature(systemModel.getDate()), + // systemModel.getDate()); + // add(system, tb, dateToDate); + // add(system, tb, new Conversion(dateToDate, false)); + + // ToTime(String) : Time + // ToTime(Time) : Time + val stringToTime = Operator("ToTime", Signature(systemModel.string), systemModel.time) + add(system, tb, stringToTime) + add(system, tb, Conversion(stringToTime, false)) + + // Operator timeToTime = new Operator("ToTime", new Signature(systemModel.getTime()), + // systemModel.getTime()); + // add(system, tb, timeToTime); + // add(system, tb, new Conversion(timeToTime, false)); + + // ToQuantity(String) : Quantity + // ToQuantity(Integer) : Quantity + // ToQuantity(Ratio) : Quantity + // ToQuantity(Decimal) : Quantity + // ToQuantity(Quantity) : Quantity + val stringToQuantity = + Operator("ToQuantity", Signature(systemModel.string), systemModel.quantity) + add(system, tb, stringToQuantity) + add(system, tb, Conversion(stringToQuantity, false)) + val ratioToQuantity = + Operator("ToQuantity", Signature(systemModel.ratio), systemModel.quantity) + add(system, tb, ratioToQuantity) + add(system, tb, Conversion(ratioToQuantity, false)) + val integerToQuantity = + Operator("ToQuantity", Signature(systemModel.integer), systemModel.quantity) + add(system, tb, integerToQuantity) + add(system, tb, Conversion(integerToQuantity, true)) + val decimalToQuantity = + Operator("ToQuantity", Signature(systemModel.decimal), systemModel.quantity) + add(system, tb, decimalToQuantity) + add(system, tb, Conversion(decimalToQuantity, true)) + + // Operator quantityToQuantity = new Operator("ToQuantity", new + // Signature(systemModel.getQuantity()), + // systemModel.getQuantity()); + // add(system, tb, quantityToQuantity); + // add(system, tb, new Conversion(quantityToQuantity, false)); + + // ToRatio(String) : Ratio + // ToRatio(Ratio) : Ratio + val stringToRatio = Operator("ToRatio", Signature(systemModel.string), systemModel.ratio) + add(system, tb, stringToRatio) + add(system, tb, Conversion(stringToRatio, false)) + + // Operator ratioToRatio = new Operator("ToRatio", new Signature(systemModel.getRatio()), + // systemModel.getRatio()); + // add(system, tb, ratioToRatio); + // add(system, tb, new Conversion(ratioToRatio, false)); + + // ConvertsToBoolean(Any): Boolean + var convertsTo = + Operator("ConvertsToBoolean", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToInteger(Any): Boolean + convertsTo = Operator("ConvertsToInteger", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToLong(Any): Boolean + convertsTo = Operator("ConvertsToLong", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToDecimal + convertsTo = Operator("ConvertsToDecimal", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToDateTime + convertsTo = Operator("ConvertsToDateTime", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToDate + convertsTo = Operator("ConvertsToDate", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToTime + convertsTo = Operator("ConvertsToTime", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToString + convertsTo = Operator("ConvertsToString", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToQuantity + convertsTo = Operator("ConvertsToQuantity", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + // ConvertsToRatio + convertsTo = Operator("ConvertsToRatio", Signature(systemModel.any), systemModel.boolean) + add(system, tb, convertsTo) + + // CanConvertQuantity + val canConvertToQuantity = + Operator( + "CanConvertQuantity", + Signature(systemModel.quantity, systemModel.string), + systemModel.boolean + ) + add(system, tb, canConvertToQuantity) + + // ConvertQuantity + val convertToQuantity = + Operator( + "ConvertQuantity", + Signature(systemModel.quantity, systemModel.string), + systemModel.quantity + ) + add(system, tb, convertToQuantity) + + // Comparison Operators + // Equal(T, T) : Boolean + // TypeParameter T = new TypeParameter("T", TypeParameter.TypeParameterConstraint.VALUE, + // null); + // add(system, tb, new GenericOperator("Equal", new Signature(T, T), + // systemModel.getBoolean(), T)); + // Equal(C, C) : Boolean + var C = TypeParameter("C", TypeParameter.TypeParameterConstraint.CLASS, null) + add(system, tb, GenericOperator("Equal", Signature(C, C), systemModel.boolean, C)) + // Equal(R, R) : Boolean + var R = TypeParameter("R", TypeParameter.TypeParameterConstraint.TUPLE, null) + add(system, tb, GenericOperator("Equal", Signature(R, R), systemModel.boolean, R)) + // Equal(H, H) : Boolean + var H = TypeParameter("H", TypeParameter.TypeParameterConstraint.CHOICE, null) + add(system, tb, GenericOperator("Equal", Signature(H, H), systemModel.boolean, H)) + // Equal(Any, Any) : Boolean + // add(system, tb, new Operator("Equal", new Signature(systemModel.getAny(), + // systemModel.getAny()), + // systemModel.getBoolean())); + // Equivalent(T, T) : Boolean + // T = new TypeParameter("T", TypeParameter.TypeParameterConstraint.VALUE, null); + // add(system, tb, new GenericOperator("Equivalent", new Signature(T, T), + // systemModel.getBoolean(), T)); + // Equivalent(C, C) : Boolean + C = TypeParameter("C", TypeParameter.TypeParameterConstraint.CLASS, null) + add(system, tb, GenericOperator("Equivalent", Signature(C, C), systemModel.boolean, C)) + // Equivalent(R, R) : Boolean + R = TypeParameter("R", TypeParameter.TypeParameterConstraint.TUPLE, null) + add(system, tb, GenericOperator("Equivalent", Signature(R, R), systemModel.boolean, R)) + // Equivalent(H, H) : Boolean + H = TypeParameter("H", TypeParameter.TypeParameterConstraint.CHOICE, null) + add(system, tb, GenericOperator("Equivalent", Signature(H, H), systemModel.boolean, H)) + + // Equivalent(Any, Any) : Boolean + // add(system, tb, new Operator("Equivalent", new Signature(systemModel.getAny(), + // systemModel.getAny()), + // systemModel.getBoolean())); + add( + system, + tb, + Operator( + "Equal", + Signature(systemModel.boolean, systemModel.boolean), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.boolean, systemModel.boolean), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equal", + Signature(systemModel.integer, systemModel.integer), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.integer, systemModel.integer), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Less", + Signature(systemModel.integer, systemModel.integer), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "LessOrEqual", + Signature(systemModel.integer, systemModel.integer), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Greater", + Signature(systemModel.integer, systemModel.integer), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "GreaterOrEqual", + Signature(systemModel.integer, systemModel.integer), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Equal", Signature(systemModel.long, systemModel.long), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.long, systemModel.long), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Less", Signature(systemModel.long, systemModel.long), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "LessOrEqual", + Signature(systemModel.long, systemModel.long), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Greater", Signature(systemModel.long, systemModel.long), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "GreaterOrEqual", + Signature(systemModel.long, systemModel.long), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equal", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Less", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "LessOrEqual", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Greater", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "GreaterOrEqual", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equal", + Signature(systemModel.string, systemModel.string), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.string, systemModel.string), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Less", Signature(systemModel.string, systemModel.string), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "LessOrEqual", + Signature(systemModel.string, systemModel.string), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Greater", + Signature(systemModel.string, systemModel.string), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "GreaterOrEqual", + Signature(systemModel.string, systemModel.string), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equal", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Less", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "LessOrEqual", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Greater", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "GreaterOrEqual", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Equal", Signature(systemModel.date, systemModel.date), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.date, systemModel.date), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Less", Signature(systemModel.date, systemModel.date), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "LessOrEqual", + Signature(systemModel.date, systemModel.date), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Greater", Signature(systemModel.date, systemModel.date), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "GreaterOrEqual", + Signature(systemModel.date, systemModel.date), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Equal", Signature(systemModel.time, systemModel.time), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.time, systemModel.time), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Less", Signature(systemModel.time, systemModel.time), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "LessOrEqual", + Signature(systemModel.time, systemModel.time), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Greater", Signature(systemModel.time, systemModel.time), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "GreaterOrEqual", + Signature(systemModel.time, systemModel.time), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equal", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Less", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "LessOrEqual", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Greater", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "GreaterOrEqual", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Equal", Signature(systemModel.ratio, systemModel.ratio), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.ratio, systemModel.ratio), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Equal", Signature(systemModel.code, systemModel.code), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.code, systemModel.code), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equal", + Signature(systemModel.concept, systemModel.concept), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Equivalent", + Signature(systemModel.concept, systemModel.concept), + systemModel.boolean + ) + ) + + // Arithmetic Operators + add(system, tb, Operator("Abs", Signature(systemModel.integer), systemModel.integer)) + add(system, tb, Operator("Abs", Signature(systemModel.long), systemModel.long)) + add(system, tb, Operator("Abs", Signature(systemModel.decimal), systemModel.decimal)) + add(system, tb, Operator("Abs", Signature(systemModel.quantity), systemModel.quantity)) + + add( + system, + tb, + Operator( + "Add", + Signature(systemModel.integer, systemModel.integer), + systemModel.integer + ) + ) + add( + system, + tb, + Operator("Add", Signature(systemModel.long, systemModel.long), systemModel.long) + ) + add( + system, + tb, + Operator( + "Add", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "Add", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.quantity + ) + ) + + add(system, tb, Operator("Ceiling", Signature(systemModel.decimal), systemModel.integer)) + + add( + system, + tb, + Operator( + "Divide", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.decimal + ) + ) + // add(system, tb, new Operator("Divide", new Signature(systemModel.getQuantity(), + // systemModel.getDecimal()), + // systemModel.getQuantity())); + add( + system, + tb, + Operator( + "Divide", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.quantity + ) + ) + + add(system, tb, Operator("Exp", Signature(systemModel.decimal), systemModel.decimal)) + + add(system, tb, Operator("Floor", Signature(systemModel.decimal), systemModel.integer)) + + add( + system, + tb, + Operator( + "HighBoundary", + Signature(systemModel.decimal, systemModel.integer), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "HighBoundary", + Signature(systemModel.date, systemModel.integer), + systemModel.date + ) + ) + add( + system, + tb, + Operator( + "HighBoundary", + Signature(systemModel.dateTime, systemModel.integer), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "HighBoundary", + Signature(systemModel.time, systemModel.integer), + systemModel.time + ) + ) + + add( + system, + tb, + Operator( + "Log", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.decimal + ) + ) + + add( + system, + tb, + Operator( + "LowBoundary", + Signature(systemModel.decimal, systemModel.integer), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "LowBoundary", + Signature(systemModel.date, systemModel.integer), + systemModel.date + ) + ) + add( + system, + tb, + Operator( + "LowBoundary", + Signature(systemModel.dateTime, systemModel.integer), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "LowBoundary", + Signature(systemModel.time, systemModel.integer), + systemModel.time + ) + ) + + add(system, tb, Operator("Ln", Signature(systemModel.decimal), systemModel.decimal)) + + // MaxValue() : T + // MinValue() : T + add( + system, + tb, + Operator( + "Modulo", + Signature(systemModel.integer, systemModel.integer), + systemModel.integer + ) + ) + add( + system, + tb, + Operator("Modulo", Signature(systemModel.long, systemModel.long), systemModel.long) + ) + add( + system, + tb, + Operator( + "Modulo", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "Modulo", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.quantity + ) + ) + + add( + system, + tb, + Operator( + "Multiply", + Signature(systemModel.integer, systemModel.integer), + systemModel.integer + ) + ) + add( + system, + tb, + Operator("Multiply", Signature(systemModel.long, systemModel.long), systemModel.long) + ) + add( + system, + tb, + Operator( + "Multiply", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "Multiply", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.quantity + ) + ) + + add(system, tb, Operator("Negate", Signature(systemModel.integer), systemModel.integer)) + add(system, tb, Operator("Negate", Signature(systemModel.long), systemModel.long)) + add(system, tb, Operator("Negate", Signature(systemModel.decimal), systemModel.decimal)) + add(system, tb, Operator("Negate", Signature(systemModel.quantity), systemModel.quantity)) + + add(system, tb, Operator("Precision", Signature(systemModel.decimal), systemModel.integer)) + add(system, tb, Operator("Precision", Signature(systemModel.date), systemModel.integer)) + add(system, tb, Operator("Precision", Signature(systemModel.dateTime), systemModel.integer)) + add(system, tb, Operator("Precision", Signature(systemModel.time), systemModel.integer)) + + add( + system, + tb, + Operator("Predecessor", Signature(systemModel.integer), systemModel.integer) + ) + add(system, tb, Operator("Predecessor", Signature(systemModel.long), systemModel.long)) + add( + system, + tb, + Operator("Predecessor", Signature(systemModel.decimal), systemModel.decimal) + ) + add(system, tb, Operator("Predecessor", Signature(systemModel.date), systemModel.date)) + add( + system, + tb, + Operator("Predecessor", Signature(systemModel.dateTime), systemModel.dateTime) + ) + add(system, tb, Operator("Predecessor", Signature(systemModel.time), systemModel.time)) + add( + system, + tb, + Operator("Predecessor", Signature(systemModel.quantity), systemModel.quantity) + ) + + add( + system, + tb, + Operator( + "Power", + Signature(systemModel.integer, systemModel.integer), + systemModel.integer + ) + ) + add( + system, + tb, + Operator("Power", Signature(systemModel.long, systemModel.long), systemModel.long) + ) + add( + system, + tb, + Operator( + "Power", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.decimal + ) + ) + + add(system, tb, Operator("Round", Signature(systemModel.decimal), systemModel.decimal)) + add( + system, + tb, + Operator( + "Round", + Signature(systemModel.decimal, systemModel.integer), + systemModel.decimal + ) + ) + + add( + system, + tb, + Operator( + "Subtract", + Signature(systemModel.integer, systemModel.integer), + systemModel.integer + ) + ) + add( + system, + tb, + Operator("Subtract", Signature(systemModel.long, systemModel.long), systemModel.long) + ) + add( + system, + tb, + Operator( + "Subtract", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "Subtract", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.quantity + ) + ) + + add(system, tb, Operator("Successor", Signature(systemModel.integer), systemModel.integer)) + add(system, tb, Operator("Successor", Signature(systemModel.long), systemModel.long)) + add(system, tb, Operator("Successor", Signature(systemModel.decimal), systemModel.decimal)) + add(system, tb, Operator("Successor", Signature(systemModel.date), systemModel.date)) + add( + system, + tb, + Operator("Successor", Signature(systemModel.dateTime), systemModel.dateTime) + ) + add(system, tb, Operator("Successor", Signature(systemModel.time), systemModel.time)) + add( + system, + tb, + Operator("Successor", Signature(systemModel.quantity), systemModel.quantity) + ) + + add(system, tb, Operator("Truncate", Signature(systemModel.decimal), systemModel.integer)) + + add( + system, + tb, + Operator( + "TruncatedDivide", + Signature(systemModel.integer, systemModel.integer), + systemModel.integer + ) + ) + add( + system, + tb, + Operator( + "TruncatedDivide", + Signature(systemModel.long, systemModel.long), + systemModel.long + ) + ) + add( + system, + tb, + Operator( + "TruncatedDivide", + Signature(systemModel.decimal, systemModel.decimal), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "TruncatedDivide", + Signature(systemModel.quantity, systemModel.quantity), + systemModel.quantity + ) + ) + + // String operators + add( + system, + tb, + Operator("Add", Signature(systemModel.string, systemModel.string), systemModel.string) + ) + add( + system, + tb, + Operator("Combine", Signature(ListType(systemModel.string)), systemModel.string) + ) + add( + system, + tb, + Operator( + "Combine", + Signature(ListType(systemModel.string), systemModel.string), + systemModel.string + ) + ) + add( + system, + tb, + Operator( + "Concatenate", + Signature(systemModel.string, systemModel.string), + systemModel.string + ) + ) + add( + system, + tb, + Operator( + "EndsWith", + Signature(systemModel.string, systemModel.string), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Indexer", + Signature(systemModel.string, systemModel.integer), + systemModel.string + ) + ) + add( + system, + tb, + Operator( + "LastPositionOf", + Signature(systemModel.string, systemModel.string), + systemModel.integer + ) + ) + add(system, tb, Operator("Length", Signature(systemModel.string), systemModel.integer)) + add(system, tb, Operator("Lower", Signature(systemModel.string), systemModel.string)) + add( + system, + tb, + Operator( + "Matches", + Signature(systemModel.string, systemModel.string), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "PositionOf", + Signature(systemModel.string, systemModel.string), + systemModel.integer + ) + ) + add( + system, + tb, + Operator( + "ReplaceMatches", + Signature(systemModel.string, systemModel.string, systemModel.string), + systemModel.string + ) + ) + add( + system, + tb, + Operator( + "Split", + Signature(systemModel.string, systemModel.string), + ListType(systemModel.string) + ) + ) + add( + system, + tb, + Operator( + "SplitOnMatches", + Signature(systemModel.string, systemModel.string), + ListType(systemModel.string) + ) + ) + add( + system, + tb, + Operator( + "StartsWith", + Signature(systemModel.string, systemModel.string), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Substring", + Signature(systemModel.string, systemModel.integer), + systemModel.string + ) + ) + add( + system, + tb, + Operator( + "Substring", + Signature(systemModel.string, systemModel.integer, systemModel.integer), + systemModel.string + ) + ) + add(system, tb, Operator("Upper", Signature(systemModel.string), systemModel.string)) + + // Date/Time Operators + add( + system, + tb, + Operator( + "Add", + Signature(systemModel.dateTime, systemModel.quantity), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator("Add", Signature(systemModel.date, systemModel.quantity), systemModel.date) + ) + add( + system, + tb, + Operator("Add", Signature(systemModel.time, systemModel.quantity), systemModel.time) + ) + add( + system, + tb, + Operator( + "After", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("After", Signature(systemModel.date, systemModel.date), systemModel.boolean) + ) + add( + system, + tb, + Operator("After", Signature(systemModel.time, systemModel.time), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "Before", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("Before", Signature(systemModel.date, systemModel.date), systemModel.boolean) + ) + add( + system, + tb, + Operator("Before", Signature(systemModel.time, systemModel.time), systemModel.boolean) + ) + add(system, tb, Operator("DateTime", Signature(systemModel.integer), systemModel.dateTime)) + add( + system, + tb, + Operator( + "DateTime", + Signature(systemModel.integer, systemModel.integer), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "DateTime", + Signature(systemModel.integer, systemModel.integer, systemModel.integer), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "DateTime", + Signature( + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer + ), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "DateTime", + Signature( + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer + ), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "DateTime", + Signature( + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer + ), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "DateTime", + Signature( + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer + ), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "DateTime", + Signature( + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.decimal + ), + systemModel.dateTime + ) + ) + add(system, tb, Operator("Date", Signature(systemModel.integer), systemModel.date)) + add( + system, + tb, + Operator("Date", Signature(systemModel.integer, systemModel.integer), systemModel.date) + ) + add( + system, + tb, + Operator( + "Date", + Signature(systemModel.integer, systemModel.integer, systemModel.integer), + systemModel.date + ) + ) + add(system, tb, Operator("DateFrom", Signature(systemModel.dateTime), systemModel.date)) + add(system, tb, Operator("TimeFrom", Signature(systemModel.dateTime), systemModel.time)) + add( + system, + tb, + Operator("TimezoneFrom", Signature(systemModel.dateTime), systemModel.decimal) + ) + add( + system, + tb, + Operator("TimezoneOffsetFrom", Signature(systemModel.dateTime), systemModel.decimal) + ) + add( + system, + tb, + Operator("DateTimeComponentFrom", Signature(systemModel.dateTime), systemModel.integer) + ) + add( + system, + tb, + Operator("DateTimeComponentFrom", Signature(systemModel.date), systemModel.integer) + ) + add( + system, + tb, + Operator("DateTimeComponentFrom", Signature(systemModel.time), systemModel.integer) + ) + add( + system, + tb, + Operator( + "DifferenceBetween", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.integer + ) + ) + add( + system, + tb, + Operator( + "DifferenceBetween", + Signature(systemModel.date, systemModel.date), + systemModel.integer + ) + ) + add( + system, + tb, + Operator( + "DifferenceBetween", + Signature(systemModel.time, systemModel.time), + systemModel.integer + ) + ) + add( + system, + tb, + Operator( + "DurationBetween", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.integer + ) + ) + add( + system, + tb, + Operator( + "DurationBetween", + Signature(systemModel.date, systemModel.date), + systemModel.integer + ) + ) + add( + system, + tb, + Operator( + "DurationBetween", + Signature(systemModel.time, systemModel.time), + systemModel.integer + ) + ) + add(system, tb, Operator("Now", Signature(), systemModel.dateTime)) + add(system, tb, Operator("now", Signature(), systemModel.dateTime)) + add( + system, + tb, + Operator( + "SameAs", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("SameAs", Signature(systemModel.date, systemModel.date), systemModel.boolean) + ) + add( + system, + tb, + Operator("SameAs", Signature(systemModel.time, systemModel.time), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "SameOrAfter", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "SameOrAfter", + Signature(systemModel.date, systemModel.date), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "SameOrAfter", + Signature(systemModel.time, systemModel.time), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "SameOrBefore", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "SameOrBefore", + Signature(systemModel.date, systemModel.date), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "SameOrBefore", + Signature(systemModel.time, systemModel.time), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "Subtract", + Signature(systemModel.dateTime, systemModel.quantity), + systemModel.dateTime + ) + ) + add( + system, + tb, + Operator( + "Subtract", + Signature(systemModel.date, systemModel.quantity), + systemModel.date + ) + ) + add( + system, + tb, + Operator( + "Subtract", + Signature(systemModel.time, systemModel.quantity), + systemModel.time + ) + ) + add(system, tb, Operator("Today", Signature(), systemModel.date)) + add(system, tb, Operator("today", Signature(), systemModel.date)) + add(system, tb, Operator("Time", Signature(systemModel.integer), systemModel.time)) + add( + system, + tb, + Operator("Time", Signature(systemModel.integer, systemModel.integer), systemModel.time) + ) + add( + system, + tb, + Operator( + "Time", + Signature(systemModel.integer, systemModel.integer, systemModel.integer), + systemModel.time + ) + ) + add( + system, + tb, + Operator( + "Time", + Signature( + systemModel.integer, + systemModel.integer, + systemModel.integer, + systemModel.integer + ), + systemModel.time + ) + ) + add(system, tb, Operator("TimeOfDay", Signature(), systemModel.time)) + add(system, tb, Operator("timeOfDay", Signature(), systemModel.time)) + + // Interval Operators + // After(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "After", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Before(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "Before", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Collapse(list>) : list> + // Collapse(list>, Quantity) : list> + add( + system, + tb, + GenericOperator( + "Collapse", + Signature(ListType(IntervalType(TypeParameter("T"))), systemModel.quantity), + ListType(IntervalType(TypeParameter("T"))), + TypeParameter("T") + ) + ) + // Contains(interval, T) : Boolean + add( + system, + tb, + GenericOperator( + "Contains", + Signature(IntervalType(TypeParameter("T")), TypeParameter("T")), + systemModel.boolean, + TypeParameter("T") + ) + ) + // End(interval) : T + add( + system, + tb, + GenericOperator( + "End", + Signature(IntervalType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + ) + // Ends(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "Ends", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Equal(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "Equal", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Equivalent(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "Equivalent", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Except(interval, interval) : interval + add( + system, + tb, + GenericOperator( + "Except", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + IntervalType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Expand(list>) : list> + // Expand(list>, Quantity) : list> + // Expand(interval) : List + // Expand(interval, Quantity) : list + add( + system, + tb, + GenericOperator( + "Expand", + Signature(ListType(IntervalType(TypeParameter("T"))), systemModel.quantity), + ListType(IntervalType(TypeParameter("T"))), + TypeParameter("T") + ) + ) + add( + system, + tb, + GenericOperator( + "Expand", + Signature(IntervalType(TypeParameter("T")), systemModel.quantity), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // In(T, interval) : Boolean + add( + system, + tb, + GenericOperator( + "In", + Signature(TypeParameter("T"), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Includes(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "Includes", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // IncludedIn(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "IncludedIn", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Intersect(interval, interval) : interval + add( + system, + tb, + GenericOperator( + "Intersect", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + IntervalType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Meets(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "Meets", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // MeetsBefore(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "MeetsBefore", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // MeetsAfter(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "MeetsAfter", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Overlaps(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "Overlaps", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // OverlapsBefore(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "OverlapsBefore", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // OverlapsAfter(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "OverlapsAfter", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // PointFrom(interval) : T + val pointFrom = + GenericOperator( + "PointFrom", + Signature(IntervalType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + add(system, tb, pointFrom) + // ProperContains(interval, T) : Boolean + add( + system, + tb, + GenericOperator( + "ProperContains", + Signature(IntervalType(TypeParameter("T")), TypeParameter("T")), + systemModel.boolean, + TypeParameter("T") + ) + ) + // ProperIn(T, interval) : Boolean + add( + system, + tb, + GenericOperator( + "ProperIn", + Signature(TypeParameter("T"), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // ProperIncludes(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "ProperIncludes", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // ProperIncludedIn(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "ProperIncludedIn", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // SameAs(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "SameAs", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // SameOrAfter(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "SameOrAfter", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // SameOrBefore(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "SameOrBefore", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Size(interval) : T + add( + system, + tb, + GenericOperator( + "Size", + Signature(IntervalType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + ) + // Start(interval) : T + add( + system, + tb, + GenericOperator( + "Start", + Signature(IntervalType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + ) + // Starts(interval, interval) : Boolean + add( + system, + tb, + GenericOperator( + "Starts", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Union(interval, interval) : interval + add( + system, + tb, + GenericOperator( + "Union", + Signature(IntervalType(TypeParameter("T")), IntervalType(TypeParameter("T"))), + IntervalType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Width(interval) : T + add( + system, + tb, + GenericOperator( + "Width", + Signature(IntervalType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + ) + + // List Operators + // Contains(list, T) : Boolean + add( + system, + tb, + GenericOperator( + "Contains", + Signature(ListType(TypeParameter("T")), TypeParameter("T")), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Distinct(list) : list + add( + system, + tb, + GenericOperator( + "Distinct", + Signature(ListType(TypeParameter("T"))), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Equal(list, list) : Boolean + add( + system, + tb, + GenericOperator( + "Equal", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Equivalent(list, list) : Boolean + add( + system, + tb, + GenericOperator( + "Equivalent", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Except(list, list) : list + add( + system, + tb, + GenericOperator( + "Except", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Exists(list) : Boolean + add( + system, + tb, + GenericOperator( + "Exists", + Signature(ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Flatten(list>) : list + add( + system, + tb, + GenericOperator( + "Flatten", + Signature(ListType(ListType(TypeParameter("T")))), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // First(list) : T + add( + system, + tb, + GenericOperator( + "First", + Signature(ListType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + ) + // In(T, list) : Boolean + add( + system, + tb, + GenericOperator( + "In", + Signature(TypeParameter("T"), ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Includes(list, list) : Boolean + add( + system, + tb, + GenericOperator( + "Includes", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // IncludedIn(list, list) : Boolean + add( + system, + tb, + GenericOperator( + "IncludedIn", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // Indexer(list, integer) : T + add( + system, + tb, + GenericOperator( + "Indexer", + Signature(ListType(TypeParameter("T")), systemModel.integer), + TypeParameter("T"), + TypeParameter("T") + ) + ) + // IndexOf(list, T) : Integer + add( + system, + tb, + GenericOperator( + "IndexOf", + Signature(ListType(TypeParameter("T")), TypeParameter("T")), + systemModel.integer, + TypeParameter("T") + ) + ) + // Intersect(list, list) : list + add( + system, + tb, + GenericOperator( + "Intersect", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Last(list) : T + add( + system, + tb, + GenericOperator( + "Last", + Signature(ListType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + ) + // Length(list) : Integer + add( + system, + tb, + GenericOperator( + "Length", + Signature(ListType(TypeParameter("T"))), + systemModel.integer, + TypeParameter("T") + ) + ) + // ProperContains(list, T) : Boolean + add( + system, + tb, + GenericOperator( + "ProperContains", + Signature(ListType(TypeParameter("T")), TypeParameter("T")), + systemModel.boolean, + TypeParameter("T") + ) + ) + // ProperIn(T, list) : Boolean + add( + system, + tb, + GenericOperator( + "ProperIn", + Signature(TypeParameter("T"), ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // ProperIncludes(list, list) : Boolean + add( + system, + tb, + GenericOperator( + "ProperIncludes", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // ProperIncludedIn(list, list) : Boolean + add( + system, + tb, + GenericOperator( + "ProperIncludedIn", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + systemModel.boolean, + TypeParameter("T") + ) + ) + // SingletonFrom(list) : T + val singletonFrom = + GenericOperator( + "SingletonFrom", + Signature(ListType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + add(system, tb, singletonFrom) + //// NOTE: FHIRPath Implicit List Demotion + // Generic conversions turned out to be computationally expensive, so we added explicit list + // promotion/demotion + // in the conversion map directly instead. + // add(system, tb, new Conversion(singletonFrom, true)); + // Skip(list, Integer): list + add( + system, + tb, + GenericOperator( + "Skip", + Signature(ListType(TypeParameter("T")), systemModel.integer), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Tail(list): list + add( + system, + tb, + GenericOperator( + "Tail", + Signature(ListType(TypeParameter("T"))), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Take(list, Integer): list + add( + system, + tb, + GenericOperator( + "Take", + Signature(ListType(TypeParameter("T")), systemModel.integer), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + // Union(list, list) : list + add( + system, + tb, + GenericOperator( + "Union", + Signature(ListType(TypeParameter("T")), ListType(TypeParameter("T"))), + ListType(TypeParameter("T")), + TypeParameter("T") + ) + ) + + // NOTE: FHIRPath Implicit List Promotion operator + // GenericOperator toList = new GenericOperator("List", new Signature(new + // TypeParameter("T")), new ListType(new + // TypeParameter("T")), new TypeParameter("T")); + // add(system, tb, toList); + // add(system, tb, new Conversion(toList, true)); + + // Aggregate Operators + add( + system, + tb, + Operator("AllTrue", Signature(ListType(systemModel.boolean)), systemModel.boolean) + ) + add( + system, + tb, + Operator("AnyTrue", Signature(ListType(systemModel.boolean)), systemModel.boolean) + ) + add( + system, + tb, + Operator("Avg", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("Avg", Signature(ListType(systemModel.quantity)), systemModel.quantity) + ) + // Count(list) : Integer + add( + system, + tb, + GenericOperator( + "Count", + Signature(ListType(TypeParameter("T"))), + systemModel.integer, + TypeParameter("T") + ) + ) + //// Count(list) : Integer + // add(system, tb, new Operator("Count", new Signature(new ListType(systemModel.getAny())), + // systemModel.getInteger())); + add( + system, + tb, + Operator("GeometricMean", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("Max", Signature(ListType(systemModel.integer)), systemModel.integer) + ) + add(system, tb, Operator("Max", Signature(ListType(systemModel.long)), systemModel.long)) + add( + system, + tb, + Operator("Max", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("Max", Signature(ListType(systemModel.quantity)), systemModel.quantity) + ) + add( + system, + tb, + Operator("Max", Signature(ListType(systemModel.dateTime)), systemModel.dateTime) + ) + add(system, tb, Operator("Max", Signature(ListType(systemModel.date)), systemModel.date)) + add(system, tb, Operator("Max", Signature(ListType(systemModel.time)), systemModel.time)) + add( + system, + tb, + Operator("Max", Signature(ListType(systemModel.string)), systemModel.string) + ) + add( + system, + tb, + Operator("Min", Signature(ListType(systemModel.integer)), systemModel.integer) + ) + add(system, tb, Operator("Min", Signature(ListType(systemModel.long)), systemModel.long)) + add( + system, + tb, + Operator("Min", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("Min", Signature(ListType(systemModel.quantity)), systemModel.quantity) + ) + add( + system, + tb, + Operator("Min", Signature(ListType(systemModel.dateTime)), systemModel.dateTime) + ) + add(system, tb, Operator("Min", Signature(ListType(systemModel.date)), systemModel.date)) + add(system, tb, Operator("Min", Signature(ListType(systemModel.time)), systemModel.time)) + add( + system, + tb, + Operator("Min", Signature(ListType(systemModel.string)), systemModel.string) + ) + add( + system, + tb, + Operator("Median", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("Median", Signature(ListType(systemModel.quantity)), systemModel.quantity) + ) + // Mode(list) : T + add( + system, + tb, + GenericOperator( + "Mode", + Signature(ListType(TypeParameter("T"))), + TypeParameter("T"), + TypeParameter("T") + ) + ) + add( + system, + tb, + Operator( + "PopulationStdDev", + Signature(ListType(systemModel.decimal)), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "PopulationStdDev", + Signature(ListType(systemModel.quantity)), + systemModel.quantity + ) + ) + add( + system, + tb, + Operator( + "PopulationVariance", + Signature(ListType(systemModel.decimal)), + systemModel.decimal + ) + ) + add( + system, + tb, + Operator( + "PopulationVariance", + Signature(ListType(systemModel.quantity)), + systemModel.quantity + ) + ) + add( + system, + tb, + Operator("Product", Signature(ListType(systemModel.integer)), systemModel.integer) + ) + add( + system, + tb, + Operator("Product", Signature(ListType(systemModel.long)), systemModel.long) + ) + add( + system, + tb, + Operator("Product", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("Product", Signature(ListType(systemModel.quantity)), systemModel.quantity) + ) + add( + system, + tb, + Operator("StdDev", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("StdDev", Signature(ListType(systemModel.quantity)), systemModel.quantity) + ) + add( + system, + tb, + Operator("Sum", Signature(ListType(systemModel.integer)), systemModel.integer) + ) + add(system, tb, Operator("Sum", Signature(ListType(systemModel.long)), systemModel.long)) + add( + system, + tb, + Operator("Sum", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("Sum", Signature(ListType(systemModel.quantity)), systemModel.quantity) + ) + add( + system, + tb, + Operator("Variance", Signature(ListType(systemModel.decimal)), systemModel.decimal) + ) + add( + system, + tb, + Operator("Variance", Signature(ListType(systemModel.quantity)), systemModel.quantity) + ) + + // Clinical + // ToConcept(Code) + val codeToConcept = Operator("ToConcept", Signature(systemModel.code), systemModel.concept) + add(system, tb, codeToConcept) + add(system, tb, Conversion(codeToConcept, true)) + // ToConcept(list) + val codesToConcept = + Operator("ToConcept", Signature(ListType(systemModel.code)), systemModel.concept) + add(system, tb, codesToConcept) + add(system, tb, Conversion(codesToConcept, false)) + + add( + system, + tb, + Operator("CalculateAge", Signature(systemModel.dateTime), systemModel.integer) + ) + add(system, tb, Operator("CalculateAge", Signature(systemModel.date), systemModel.integer)) + add( + system, + tb, + Operator( + "CalculateAgeAt", + Signature(systemModel.dateTime, systemModel.dateTime), + systemModel.integer + ) + ) + add( + system, + tb, + Operator( + "CalculateAgeAt", + Signature(systemModel.date, systemModel.date), + systemModel.integer + ) + ) + + add(system, tb, Operator("InValueSet", Signature(systemModel.string), systemModel.boolean)) + add(system, tb, Operator("InValueSet", Signature(systemModel.code), systemModel.boolean)) + add(system, tb, Operator("InValueSet", Signature(systemModel.concept), systemModel.boolean)) + + add( + system, + tb, + Operator( + "InValueSet", + Signature(systemModel.string, systemModel.valueSet), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "InValueSet", + Signature(systemModel.code, systemModel.valueSet), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "InValueSet", + Signature(systemModel.concept, systemModel.valueSet), + systemModel.boolean + ) + ) + + add( + system, + tb, + Operator("AnyInValueSet", Signature(ListType(systemModel.string)), systemModel.boolean) + ) + add( + system, + tb, + Operator("AnyInValueSet", Signature(ListType(systemModel.code)), systemModel.boolean) + ) + add( + system, + tb, + Operator("AnyInValueSet", Signature(ListType(systemModel.concept)), systemModel.boolean) + ) + + add( + system, + tb, + Operator( + "AnyInValueSet", + Signature(ListType(systemModel.string), systemModel.valueSet), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "AnyInValueSet", + Signature(ListType(systemModel.code), systemModel.valueSet), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "AnyInValueSet", + Signature(ListType(systemModel.concept), systemModel.valueSet), + systemModel.boolean + ) + ) + + add( + system, + tb, + Operator("InCodeSystem", Signature(systemModel.string), systemModel.boolean) + ) + add(system, tb, Operator("InCodeSystem", Signature(systemModel.code), systemModel.boolean)) + add( + system, + tb, + Operator("InCodeSystem", Signature(systemModel.concept), systemModel.boolean) + ) + + add( + system, + tb, + Operator( + "InCodeSystem", + Signature(systemModel.string, systemModel.codeSystem), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "InCodeSystem", + Signature(systemModel.code, systemModel.codeSystem), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "InCodeSystem", + Signature(systemModel.concept, systemModel.codeSystem), + systemModel.boolean + ) + ) + + add( + system, + tb, + Operator( + "AnyInCodeSystem", + Signature(ListType(systemModel.string)), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator("AnyInCodeSystem", Signature(ListType(systemModel.code)), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "AnyInCodeSystem", + Signature(ListType(systemModel.concept)), + systemModel.boolean + ) + ) + + add( + system, + tb, + Operator( + "AnyInCodeSystem", + Signature(ListType(systemModel.string), systemModel.codeSystem), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "AnyInCodeSystem", + Signature(ListType(systemModel.code), systemModel.codeSystem), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "AnyInCodeSystem", + Signature(ListType(systemModel.concept), systemModel.codeSystem), + systemModel.boolean + ) + ) + + val expandValueSet = + Operator("ExpandValueSet", Signature(systemModel.valueSet), ListType(systemModel.code)) + add(system, tb, expandValueSet) + add(system, tb, Conversion(expandValueSet, true)) + + add( + system, + tb, + Operator("Subsumes", Signature(systemModel.code, systemModel.code), systemModel.boolean) + ) + add( + system, + tb, + Operator( + "Subsumes", + Signature(systemModel.concept, systemModel.concept), + systemModel.boolean + ) + ) + + add( + system, + tb, + Operator( + "SubsumedBy", + Signature(systemModel.code, systemModel.code), + systemModel.boolean + ) + ) + add( + system, + tb, + Operator( + "SubsumedBy", + Signature(systemModel.concept, systemModel.concept), + systemModel.boolean + ) + ) + + // Errors + // Message(source T, condition Boolean, code String, severity String, message String) T + add( + system, + tb, + GenericOperator( + "Message", + Signature( + TypeParameter("T"), + systemModel.boolean, + systemModel.string, + systemModel.string, + systemModel.string + ), + TypeParameter("T"), + TypeParameter("T") + ) + ) + + return system + } + + private fun add(systemLibrary: CompiledLibrary, tb: TypeBuilder, operator: Operator) { + // In the case that an operator is added directly, manufacture a FunctionDef so it can be + // referred to in ELM + // Analysis + val fd = FunctionDef() + fd.name = operator.name + var n = 0 + for (dataType in operator.signature.operandTypes) { + n++ + val od = OperandDef().withName("param${n}") + if (dataType is NamedType) { + od.operandType = tb.dataTypeToQName(dataType) + } else { + od.operandTypeSpecifier = tb.dataTypeToTypeSpecifier(dataType) + } + od.resultType = dataType + fd.operand.add(od) + } + operator.functionDef = fd + + systemLibrary.add(fd, operator) + } + + @Suppress("UnusedParameter") + private fun add(systemLibrary: CompiledLibrary, tb: TypeBuilder, conversion: Conversion) { + systemLibrary.add(conversion) + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/SystemModel.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/SystemModel.kt new file mode 100644 index 000000000..5e2f1ba14 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/SystemModel.kt @@ -0,0 +1,58 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.cql.model.DataType +import org.hl7.cql.model.SimpleType +import org.hl7.elm_modelinfo.r1.ModelInfo + +class SystemModel(modelInfo: ModelInfo) : Model(modelInfo, null) { + val any: DataType + get() = this.resolveTypeName("Any")!! + + val boolean: DataType + get() = this.resolveTypeName("Boolean")!! + + val integer: DataType + get() = this.resolveTypeName("Integer")!! + + val long: DataType + get() = this.resolveTypeName("Long")!! + + val decimal: DataType + get() = this.resolveTypeName("Decimal")!! + + val string: DataType + get() = this.resolveTypeName("String")!! + + val dateTime: DataType + get() = this.resolveTypeName("DateTime")!! + + val date: DataType + get() = this.resolveTypeName("Date")!! + + val time: DataType + get() = this.resolveTypeName("Time")!! + + val quantity: DataType + get() = this.resolveTypeName("Quantity")!! + + val ratio: DataType + get() = this.resolveTypeName("Ratio")!! + + val code: DataType + get() = this.resolveTypeName("Code")!! + + val concept: DataType + get() = this.resolveTypeName("Concept")!! + + val vocabulary: DataType + get() = this.resolveTypeName("Vocabulary")!! + + val codeSystem: DataType + get() = this.resolveTypeName("CodeSystem")!! + + val valueSet: DataType + get() = this.resolveTypeName("ValueSet")!! + + val void: DataType + get() = SimpleType("Void") +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/TimingOperatorContext.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/TimingOperatorContext.kt new file mode 100644 index 000000000..659cc1252 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/TimingOperatorContext.kt @@ -0,0 +1,5 @@ +package org.cqframework.cql.cql2elm.model + +import org.hl7.elm.r1.Expression + +class TimingOperatorContext(var left: Expression, var right: Expression) diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Version.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Version.kt new file mode 100644 index 000000000..8c7d16a87 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/Version.kt @@ -0,0 +1,138 @@ +package org.cqframework.cql.cql2elm.model + +import java.util.regex.Pattern +import kotlin.math.max + +/** Created by Bryn on 3/2/2017. */ +/** + * Implements a comparable version for use in comparing CQL artifact versions. Supports versions + * specified in filename strings according to the following pattern: + * [v]{{major}}(.|-){{minor}}(.|-){{patch}}(.|-){{build}} where major, minor, and patch are all + * required to be unsigned integers, and build is any string + * + * Examples: 1.0.0 -> major: 1, minor: 0, patch: 0 v1-0-0 -> major: 1, minor: 0, patch: 0 + * v1-0-0-SNAPSHOT -> major: 1, minor: 0, patch: 0, build: snapshot + * + * NOTE: Deliberately not using Apache ComparableVersion to a) avoid dependencies on Maven and b) + * allow for more flexible version strings used by MAT file naming conventions. + */ +@Suppress("MagicNumber") +data class Version(private val version: String) : Comparable { + init { + initVersion() + } + + var majorVersion: Int? = null + private set + + var minorVersion: Int? = null + private set + + var patchVersion: Int? = null + private set + + var buildVersion: String? = null + private set + + private fun initVersion() { + val parts = versionPartPattern.split(version).dropLastWhile { it.isEmpty() } + for (i in 0 until max(parts.size, 4)) { + var part = if (i < parts.size) parts[i] else "" + if (part.startsWith("v")) { + part = part.substring(1) + } + + when (i) { + 0 -> majorVersion = part.toUnsignedIntOrNull() + 1 -> minorVersion = part.toUnsignedIntOrNull() + 2 -> patchVersion = part.toUnsignedIntOrNull() + 3 -> buildVersion = part + else -> buildVersion += "-$part" + } + } + } + + private fun compareTo(that: Version, level: Int): Int { + require(this.isComparable && that.isComparable) { "The versions are not comparable" } + for (i in 0 until max(level, 4)) { + + val comparison = + when (i) { + 0 -> majorVersion.compareToNullable(that.majorVersion) + 1 -> minorVersion.compareToNullable(that.minorVersion) + 2 -> patchVersion.compareToNullable(that.patchVersion) + 3 -> buildVersion.compareToNullable(that.buildVersion) + else -> 0 + } + + if (comparison != 0) return comparison + } + return 0 + } + + private fun String?.compareToNullable(that: String?): Int { + return when { + this == null && that == null -> 0 + this == null -> -1 + that == null -> 1 + else -> this.compareTo(that, ignoreCase = true) + } + } + + private fun Int?.compareToNullable(that: Int?): Int { + return when { + this == null && that == null -> 0 + this == null -> -1 + that == null -> 1 + else -> this.compareTo(that) + } + } + + override fun compareTo(other: Version?): Int { + return if (other == null) 1 else compareTo(other, 4) + } + + // Two versions are compatible if the major version matches and the minor version of the + // current version is greater than or equal to the minor version of the other version + fun compatibleWith(that: Version?): Boolean { + return when { + that == null -> false + this.isComparable && that.isComparable -> compareTo(that, 2) >= 0 + else -> matchStrictly(that) + } + } + + fun matchStrictly(that: Version): Boolean { + return this.version == that.version + } + + private fun String.toUnsignedIntOrNull(): Int? { + return if (isUnsignedInteger.matcher(this).matches()) this.toInt() else null + } + + private fun isComparable(level: Int): Boolean { + return when (level) { + 0 -> majorVersion != null + 1 -> majorVersion != null && minorVersion != null + 2 -> majorVersion != null && minorVersion != null && patchVersion != null + 3 -> + majorVersion != null && + minorVersion != null && + patchVersion != null && + buildVersion != null + else -> false + } + } + + val isComparable: Boolean + get() = this.isComparable(2) + + override fun toString(): String { + return version + } + + companion object { + private val isUnsignedInteger: Pattern = Pattern.compile("[0-9]+") + private val versionPartPattern = Pattern.compile("[.\\-]") + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AbstractExpressionInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AbstractExpressionInvocation.kt new file mode 100644 index 000000000..100bbffd2 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AbstractExpressionInvocation.kt @@ -0,0 +1,23 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.cqframework.cql.cql2elm.model.Invocation +import org.cqframework.cql.cql2elm.model.OperatorResolution +import org.cqframework.cql.cql2elm.tracking.Trackable.resultType +import org.hl7.cql.model.DataType +import org.hl7.elm.r1.Expression + +/** + * The AbstractExpressionInvocation can be used to more simply make invocations for classes that + * only extend Expression. + */ +abstract class AbstractExpressionInvocation(override val expression: E) : + Invocation { + + override var resultType: DataType? + get() = expression.resultType + set(resultType) { + expression.resultType = resultType + } + + override var resolution: OperatorResolution? = null +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AggregateExpressionInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AggregateExpressionInvocation.kt new file mode 100644 index 000000000..72b46040e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AggregateExpressionInvocation.kt @@ -0,0 +1,22 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.AggregateExpression +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.TypeSpecifier + +class AggregateExpressionInvocation(expression: A) : + AbstractExpressionInvocation(expression) { + + override var operands: List + get() = listOf(expression.source!!) + set(operands) { + require(operands.size == 1) { "Unary operator expected." } + expression.source = operands[0] + } + + override var signature: List + get() = expression.signature + set(signature) { + expression.signature = signature.toMutableList() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AnyInCodeSystemInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AnyInCodeSystemInvocation.kt new file mode 100644 index 000000000..0a013bba5 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AnyInCodeSystemInvocation.kt @@ -0,0 +1,20 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.AnyInCodeSystem +import org.hl7.elm.r1.Expression + +/** Created by Bryn on 9/12/2018. */ +class AnyInCodeSystemInvocation(expression: AnyInCodeSystem) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.codes, expression.codesystemExpression) + set(operands) { + require(operands.size in 1..2) { + "AnyInCodeSystem operator requires one or two operands." + } + expression.codes = operands[0] + if (operands.size > 1) { + expression.codesystemExpression = operands[1] + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AnyInValueSetInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AnyInValueSetInvocation.kt new file mode 100644 index 000000000..df9e5d374 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/AnyInValueSetInvocation.kt @@ -0,0 +1,20 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.AnyInValueSet +import org.hl7.elm.r1.Expression + +/** Created by Bryn on 9/12/2018. */ +class AnyInValueSetInvocation(expression: AnyInValueSet) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.codes, expression.valuesetExpression) + set(operands) { + require(operands.size in 1..2) { + "AnyInValueSet operator requires one or two operands." + } + expression.codes = operands[0] + if (operands.size > 1) { + expression.valuesetExpression = operands[1] + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/BinaryExpressionInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/BinaryExpressionInvocation.kt new file mode 100644 index 000000000..fc7800d96 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/BinaryExpressionInvocation.kt @@ -0,0 +1,14 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.BinaryExpression +import org.hl7.elm.r1.Expression + +class BinaryExpressionInvocation(expression: B) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = expression.operand + set(operands) { + require(operands.size == 2) { "BinaryExpression requires two operands." } + expression.operand = operands.toMutableList() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/CombineInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/CombineInvocation.kt new file mode 100644 index 000000000..9ccf08c00 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/CombineInvocation.kt @@ -0,0 +1,17 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Combine +import org.hl7.elm.r1.Expression + +class CombineInvocation(expression: Combine) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.source, expression.separator) + set(operands) { + require(operands.size in 1..2) { "Combine operator requires one or two operands." } + + expression.source = operands[0] + if (operands.size > 1) { + expression.separator = operands[1] + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/ConvertInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/ConvertInvocation.kt new file mode 100644 index 000000000..74fc3f3e1 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/ConvertInvocation.kt @@ -0,0 +1,13 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Convert +import org.hl7.elm.r1.Expression + +class ConvertInvocation(expression: Convert) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.operand!!) + set(operands) { + require(operands.size == 1) { "Unary operator expected." } + expression.operand = operands[0] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/DateInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/DateInvocation.kt new file mode 100644 index 000000000..ef17e40ad --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/DateInvocation.kt @@ -0,0 +1,27 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Date +import org.hl7.elm.r1.Expression + +class DateInvocation(expression: Date) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.year, expression.month, expression.day) + set(operands) { + setDateFieldsFromOperands(expression, operands) + } + + @Suppress("MagicNumber") + companion object { + @JvmStatic + fun setDateFieldsFromOperands(dt: Date, operands: List) { + require(operands.size in 1..3) { "Date operator requires one to three operands." } + dt.year = operands[0] + if (operands.size > 1) { + dt.month = operands[1] + } + if (operands.size > 2) { + dt.day = operands[2] + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/DateTimeInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/DateTimeInvocation.kt new file mode 100644 index 000000000..1808460f1 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/DateTimeInvocation.kt @@ -0,0 +1,54 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.DateTime +import org.hl7.elm.r1.Expression + +class DateTimeInvocation(expression: DateTime) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = + listOfNotNull( + expression.year, + expression.month, + expression.day, + expression.hour, + expression.minute, + expression.second, + expression.millisecond, + expression.timezoneOffset + ) + set(operands) { + setDateTimeFieldsFromOperands(expression, operands) + } + + @Suppress("MagicNumber") + companion object { + @JvmStatic + fun setDateTimeFieldsFromOperands(dt: DateTime, operands: List) { + require(operands.size in 1..8) { "DateTime operator requires one to eight operands." } + + dt.year = operands[0] + if (operands.size > 1) { + dt.month = operands[1] + } + if (operands.size > 2) { + dt.day = operands[2] + } + if (operands.size > 3) { + dt.hour = operands[3] + } + if (operands.size > 4) { + dt.minute = operands[4] + } + if (operands.size > 5) { + dt.second = operands[5] + } + if (operands.size > 6) { + dt.millisecond = operands[6] + } + if (operands.size > 7) { + dt.timezoneOffset = operands[7] + } + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/FirstInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/FirstInvocation.kt new file mode 100644 index 000000000..2ba939476 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/FirstInvocation.kt @@ -0,0 +1,13 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.First + +class FirstInvocation(expression: First) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.source!!) + set(operands) { + require(operands.size == 1) { "Unary operator expected." } + expression.source = operands[0] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/FunctionRefInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/FunctionRefInvocation.kt new file mode 100644 index 000000000..e445517b4 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/FunctionRefInvocation.kt @@ -0,0 +1,30 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.cqframework.cql.cql2elm.model.OperatorResolution +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.FunctionRef +import org.hl7.elm.r1.TypeSpecifier + +class FunctionRefInvocation(expression: FunctionRef) : + AbstractExpressionInvocation(expression) { + override var operands: List + get() = expression.operand + set(operands) { + expression.operand = operands.toMutableList() + } + + override var signature: List + get() = expression.signature + set(signature) { + expression.signature = signature.toMutableList() + } + + override var resolution: OperatorResolution? + get() = super.resolution + set(resolution) { + super.resolution = resolution + if (resolution?.libraryName != expression.libraryName) { + expression.libraryName = resolution?.libraryName + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/InCodeSystemInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/InCodeSystemInvocation.kt new file mode 100644 index 000000000..c10a814c9 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/InCodeSystemInvocation.kt @@ -0,0 +1,18 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.InCodeSystem + +class InCodeSystemInvocation(expression: InCodeSystem) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.code, expression.codesystemExpression) + set(operands) { + require(operands.size in 1..2) { "InCodeSystem operator requires one or two operands." } + + expression.code = operands[0] + if (operands.size > 1) { + expression.codesystemExpression = operands[1] + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/InValueSetInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/InValueSetInvocation.kt new file mode 100644 index 000000000..e7e2e105e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/InValueSetInvocation.kt @@ -0,0 +1,17 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.InValueSet + +class InValueSetInvocation(expression: InValueSet) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.code, expression.valuesetExpression) + set(operands) { + require(operands.size in 1..2) { "InValueSet operator requires one or two operands." } + expression.code = operands[0] + if (operands.size > 1) { + expression.valuesetExpression = operands[1] + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/IndexOfInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/IndexOfInvocation.kt new file mode 100644 index 000000000..9d8f39f0a --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/IndexOfInvocation.kt @@ -0,0 +1,14 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.IndexOf + +class IndexOfInvocation(expression: IndexOf) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.source, expression.element) + set(operands) { + require(operands.size == 2) { "IndexOf operator requires two operands." } + expression.source = operands[0] + expression.element = operands[1] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/LastInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/LastInvocation.kt new file mode 100644 index 000000000..e4e6385cd --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/LastInvocation.kt @@ -0,0 +1,13 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Last + +class LastInvocation(expression: Last) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.source!!) + set(operands) { + require(operands.size == 1) { "Unary operator expected." } + expression.source = operands[0] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/LastPositionOfInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/LastPositionOfInvocation.kt new file mode 100644 index 000000000..bd4cbeb9e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/LastPositionOfInvocation.kt @@ -0,0 +1,15 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.LastPositionOf + +class LastPositionOfInvocation(expression: LastPositionOf) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.pattern!!, expression.string!!) + set(operands) { + require(operands.size == 2) { "LastPositionOf operator requires two operands." } + expression.pattern = operands[0] + expression.string = operands[1] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/MessageInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/MessageInvocation.kt new file mode 100644 index 000000000..61fac77ea --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/MessageInvocation.kt @@ -0,0 +1,25 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Message + +class MessageInvocation(expression: Message) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = + listOf( + expression.source!!, + expression.condition!!, + expression.code!!, + expression.severity!!, + expression.message!! + ) + @Suppress("MagicNumber") + set(operands) { + require(operands.size == 5) { "Message operator requires five operands." } + expression.source = operands[0] + expression.condition = operands[1] + expression.code = operands[2] + expression.severity = operands[3] + expression.message = operands[4] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/NaryExpressionInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/NaryExpressionInvocation.kt new file mode 100644 index 000000000..a79977fbb --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/NaryExpressionInvocation.kt @@ -0,0 +1,13 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.NaryExpression + +class NaryExpressionInvocation(expression: NaryExpression) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = expression.operand + set(operands) { + expression.operand = operands.toMutableList() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/OperatorExpressionInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/OperatorExpressionInvocation.kt new file mode 100644 index 000000000..3c2426789 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/OperatorExpressionInvocation.kt @@ -0,0 +1,14 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.OperatorExpression +import org.hl7.elm.r1.TypeSpecifier + +/** Created by Bryn on 4/12/2018. */ +abstract class OperatorExpressionInvocation(expression: O) : + AbstractExpressionInvocation(expression) { + override var signature: List<@JvmSuppressWildcards TypeSpecifier> + get() = expression.signature + set(signature) { + expression.signature = signature.toMutableList() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/PositionOfInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/PositionOfInvocation.kt new file mode 100644 index 000000000..7ea8cf165 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/PositionOfInvocation.kt @@ -0,0 +1,15 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.PositionOf + +class PositionOfInvocation(expression: PositionOf) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.pattern!!, expression.string!!) + set(operands) { + require(operands.size == 2) { "PositionOf operator requires two operands." } + expression.pattern = operands[0] + expression.string = operands[1] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/RoundInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/RoundInvocation.kt new file mode 100644 index 000000000..ce9d69e1f --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/RoundInvocation.kt @@ -0,0 +1,16 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Round + +class RoundInvocation(expression: Round) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.operand, expression.precision) + set(operands) { + require(operands.size in 1..2) { "Round operator requires one or two operands." } + expression.operand = operands[0] + if (operands.size > 1) { + expression.precision = operands[1] + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SkipInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SkipInvocation.kt new file mode 100644 index 000000000..cb8049649 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SkipInvocation.kt @@ -0,0 +1,16 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Slice + +/** Created by Bryn on 5/17/2017. */ +class SkipInvocation(expression: Slice) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.source!!, expression.startIndex!!) + set(operands) { + require(operands.size == 2) { "Skip operator requires two operands." } + + expression.source = operands[0] + expression.startIndex = operands[1] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SplitInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SplitInvocation.kt new file mode 100644 index 000000000..9d3ec1d19 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SplitInvocation.kt @@ -0,0 +1,14 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Split + +class SplitInvocation(expression: Split) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.stringToSplit!!, expression.separator!!) + set(operands) { + require(operands.size == 2) { "Split operator requires two operands." } + expression.stringToSplit = operands[0] + expression.separator = operands[1] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SplitOnMatchesInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SplitOnMatchesInvocation.kt new file mode 100644 index 000000000..b7d707560 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SplitOnMatchesInvocation.kt @@ -0,0 +1,15 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.SplitOnMatches + +class SplitOnMatchesInvocation(expression: SplitOnMatches) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.stringToSplit!!, expression.separatorPattern!!) + set(operands) { + require(operands.size == 2) { "SplitOnMatches operator requires two operands." } + expression.stringToSplit = operands[0] + expression.separatorPattern = operands[1] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SubstringInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SubstringInvocation.kt new file mode 100644 index 000000000..87c34cc7e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/SubstringInvocation.kt @@ -0,0 +1,19 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Substring + +class SubstringInvocation(expression: Substring) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOfNotNull(expression.stringToSub, expression.startIndex, expression.length) + @Suppress("MagicNumber") + set(operands) { + require(operands.size in 2..3) { "Substring operator requires two or three operands." } + expression.stringToSub = operands[0] + expression.startIndex = operands[1] + if (operands.size > 2) { + expression.length = operands[2] + } + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TailInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TailInvocation.kt new file mode 100644 index 000000000..63c7c79d1 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TailInvocation.kt @@ -0,0 +1,14 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Slice + +/** Created by Bryn on 5/17/2017. */ +class TailInvocation(expression: Slice) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.source!!) + set(operands) { + require(operands.size == 1) { "Unary operator expected." } + expression.source = operands[0] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TakeInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TakeInvocation.kt new file mode 100644 index 000000000..288805f6e --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TakeInvocation.kt @@ -0,0 +1,15 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Slice + +/** Created by Bryn on 5/17/2017. */ +class TakeInvocation(expression: Slice) : OperatorExpressionInvocation(expression) { + override var operands: List + get() = listOf(expression.source!!, expression.endIndex!!) + set(operands) { + require(operands.size == 2) { "Take operator requires two operands." } + expression.source = operands[0] + expression.endIndex = operands[1] + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TernaryExpressionInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TernaryExpressionInvocation.kt new file mode 100644 index 000000000..d190b1fd7 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TernaryExpressionInvocation.kt @@ -0,0 +1,13 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.TernaryExpression + +class TernaryExpressionInvocation(expression: T) : + OperatorExpressionInvocation(expression) { + override var operands: List + get() = expression.operand + set(operands) { + expression.operand = operands.toMutableList() + } +} diff --git a/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TimeInvocation.kt b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TimeInvocation.kt new file mode 100644 index 000000000..2708ea3e8 --- /dev/null +++ b/Src/java/cql-to-elm/src/main/kotlin/org/cqframework/cql/cql2elm/model/invocation/TimeInvocation.kt @@ -0,0 +1,36 @@ +package org.cqframework.cql.cql2elm.model.invocation + +import org.hl7.elm.r1.Expression +import org.hl7.elm.r1.Time + +class TimeInvocation(expression: Time) : OperatorExpressionInvocation