Skip to content

Commit

Permalink
Remove choice type specifier type if empty.
Browse files Browse the repository at this point in the history
  • Loading branch information
antvaset committed Aug 16, 2024
1 parent dfafdb9 commit 83b5f1e
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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.ElmEditEnum;
import org.cqframework.cql.cql2elm.elm.ElmEditor;
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
import org.cqframework.cql.cql2elm.preprocessor.CqlPreprocessor;
Expand Down Expand Up @@ -232,9 +233,10 @@ public Library run(CharStream is) {
// 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);
!options.contains(EnableAnnotations) ? ElmEditEnum.REMOVE_ANNOTATION : null,
!options.contains(EnableResultTypes) ? ElmEditEnum.REMOVE_RESULT_TYPE : null,
!options.contains(EnableLocators) ? ElmEditEnum.REMOVE_LOCATOR : null,
ElmEditEnum.REMOVE_CHOICE_TYPE_SPECIFIER_TYPE_IF_EMPTY);

new ElmEditor(edits).edit(library);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,7 @@
package org.cqframework.cql.cql2elm.elm;

import org.hl7.cql_annotations.r1.Annotation;
import org.hl7.elm.r1.Element;

public enum ElmEdit {
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);
}
};

public abstract void edit(Element element);
public interface ElmEdit {
void edit(Element element);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.cqframework.cql.cql2elm.elm;

import org.hl7.cql_annotations.r1.Annotation;
import org.hl7.elm.r1.ChoiceTypeSpecifier;
import org.hl7.elm.r1.Element;

public enum ElmEditEnum implements ElmEdit {
REMOVE_LOCATOR {
public void edit(Element element) {
element.setLocator(null);
}
},
REMOVE_ANNOTATION {
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 {
public void edit(Element element) {
element.setResultTypeName(null);
element.setResultTypeSpecifier(null);
}
},
REMOVE_CHOICE_TYPE_SPECIFIER_TYPE_IF_EMPTY {
// The ChoiceTypeSpecifier ELM node has a deprecated `type` element which, if not null, clashes with the
// `"type" : "ChoiceTypeSpecifier"` field in the JSON serialization of the node. This does not happen in the XML
// serialization which nests <type> tags inside <ChoiceTypeSpecifier>.
// Because the `type` element is deprecated, it is not normally populated by the compiler anymore and
// stays null in the ChoiceTypeSpecifier instance. It is however set to an empty list if you just call
// ChoiceTypeSpecifier.getType() (which we do during the ELM optimization stage in the compiler), so
// this edit is needed to "protect" the downstream JSON serialization if it can be done without data loss.

public void edit(Element element) {
if (element instanceof ChoiceTypeSpecifier) {
var choice = (ChoiceTypeSpecifier) element;
if (choice.getType().isEmpty()) {
choice.setType(null);
}
}
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,24 @@
public class ElmEditor {

private final List<ElmEdit> edits;
private final FunctionalElmVisitor<Void, List<ElmEdit>> visitor;
private final FunctionalElmVisitor<Trackable, Void> visitor;

public ElmEditor(List<ElmEdit> edits) {
this.edits = Objects.requireNonNull(edits);
this.visitor = Visitors.from(ElmEditor::applyEdits);
this.visitor = Visitors.from((elm, context) -> elm, this::applyEdits);
}

public void edit(Library library) {
this.visitor.visitLibrary(library, edits);
this.visitor.visitLibrary(library, null);
}

protected static Void applyEdits(Trackable trackable, List<ElmEdit> edits) {
if (!(trackable instanceof Element)) {
return null;
protected Trackable applyEdits(Trackable aggregate, Trackable nextResult) {
if (nextResult instanceof Element) {
for (ElmEdit edit : edits) {
edit.edit((Element) nextResult);
}
}

for (ElmEdit edit : edits) {
edit.edit((Element) trackable);
}

return null;
return aggregate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.cqframework.cql.cql2elm.elm;

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;
import org.hl7.elm.r1.ChoiceTypeSpecifier;
import org.hl7.elm.r1.NamedTypeSpecifier;
import org.hl7.elm.r1.TypeSpecifier;
import org.junit.jupiter.api.Test;

class ElmEditTest {

@Test
void removeChoiceTypeSpecifierTypeIfEmpty() {
var extChoiceTypeSpecifier = new ExtChoiceTypeSpecifier();

extChoiceTypeSpecifier.setType(List.of());
ElmEditEnum.REMOVE_CHOICE_TYPE_SPECIFIER_TYPE_IF_EMPTY.edit(extChoiceTypeSpecifier);
assertNull(extChoiceTypeSpecifier.getType());

var typeSpecifiers = List.of((TypeSpecifier) new NamedTypeSpecifier());
extChoiceTypeSpecifier.setType(typeSpecifiers);
ElmEditEnum.REMOVE_CHOICE_TYPE_SPECIFIER_TYPE_IF_EMPTY.edit(extChoiceTypeSpecifier);
assertSame(typeSpecifiers, extChoiceTypeSpecifier.getType());
}

private static class ExtChoiceTypeSpecifier extends ChoiceTypeSpecifier {
public List<TypeSpecifier> getType() {
return type;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.cqframework.cql.cql2elm.elm;

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;
import org.hl7.elm.r1.ChoiceTypeSpecifier;
import org.hl7.elm.r1.Element;
import org.junit.jupiter.api.Test;

class ElmEditorTest {
boolean editCalled = false;

@Test
void applyEdits() {
editCalled = false;
var editCounter = new ElmEdit() {
public void edit(Element element) {
editCalled = true;
}
};
var edits = List.of((ElmEdit) editCounter);
ElmEditor editor = new ElmEditor(edits);

editCalled = false;
editor.applyEdits(null, null);
assertFalse(editCalled);

editCalled = false;
editor.applyEdits(null, new ChoiceTypeSpecifier());
assertTrue(editCalled);
}
}

0 comments on commit 83b5f1e

Please sign in to comment.