From b40b490614356c9a4d6d31afe667fe0bcc3dd3cd Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Tue, 28 Nov 2017 14:58:02 +0100 Subject: [PATCH 1/4] Split flattener in separate parts --- .../archie/flattener/CAttributeFlattener.java | 318 ++++++++++ .../com/nedap/archie/flattener/Flattener.java | 553 +----------------- .../nedap/archie/flattener/FlattenerUtil.java | 37 ++ .../flattener/OperationalTemplateCreator.java | 215 +++++++ .../flattener/TerminologyFlattener.java | 2 +- .../archie/flattener/TupleFlattener.java | 45 ++ 6 files changed, 646 insertions(+), 524 deletions(-) create mode 100644 tools/src/main/java/com/nedap/archie/flattener/CAttributeFlattener.java create mode 100644 tools/src/main/java/com/nedap/archie/flattener/FlattenerUtil.java create mode 100644 tools/src/main/java/com/nedap/archie/flattener/OperationalTemplateCreator.java create mode 100644 tools/src/main/java/com/nedap/archie/flattener/TupleFlattener.java diff --git a/tools/src/main/java/com/nedap/archie/flattener/CAttributeFlattener.java b/tools/src/main/java/com/nedap/archie/flattener/CAttributeFlattener.java new file mode 100644 index 000000000..c3b4c181f --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/flattener/CAttributeFlattener.java @@ -0,0 +1,318 @@ +package com.nedap.archie.flattener; + +import com.nedap.archie.aom.ArchetypeModelObject; +import com.nedap.archie.aom.CAttribute; +import com.nedap.archie.aom.CAttributeTuple; +import com.nedap.archie.aom.CComplexObject; +import com.nedap.archie.aom.CComplexObjectProxy; +import com.nedap.archie.aom.CObject; +import com.nedap.archie.aom.CPrimitiveObject; +import com.nedap.archie.aom.CPrimitiveTuple; +import com.nedap.archie.aom.SiblingOrder; +import com.nedap.archie.aom.utils.AOMUtils; +import com.nedap.archie.base.MultiplicityInterval; +import com.nedap.archie.paths.PathSegment; +import com.nedap.archie.paths.PathUtil; +import com.nedap.archie.query.AOMPathQuery; +import com.nedap.archie.query.APathQuery; +import com.nedap.archie.rminfo.RMAttributeInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * Flattens attributes, taking sibling order into account. + */ +class CAttributeFlattener { + + private final Flattener flattener; + + public CAttributeFlattener(Flattener flattener) { + this.flattener = flattener; + } + + protected void flattenSingleAttribute(CComplexObject newObject, CAttribute attribute) { + if(attribute.getDifferentialPath() != null) { + //this overrides a specific path + ArchetypeModelObject object = newObject.itemAtPath(attribute.getDifferentialPath()); + if(object == null) { + //it is possible that the object points to a reference, in which case we need to clone the referenced node, then try again + //AOM spec paragraph 7.2: 'proxy reference targets are expanded inline if the child archetype overrides them.' + //also examples in ADL2 spec about internal references + //so find the internal references here! + //TODO: AOMUtils.pathAtSpecializationLevel(pathSegments.subList(0, pathSegments.size()-1), flatParent.specializationDepth()); + CComplexObjectProxy internalReference = new AOMPathQuery(attribute.getDifferentialPath()).findAnyInternalReference(newObject); + if(internalReference != null) { + //in theory this can be a use node within a use node. + ComplexObjectProxyReplacement complexObjectProxyReplacement = + ComplexObjectProxyReplacement.getComplexObjectProxyReplacement(internalReference); + if(complexObjectProxyReplacement != null) { + complexObjectProxyReplacement.replace(); + //and again! + flattenSingleAttribute(newObject, attribute); + } else { + throw new RuntimeException("cannot find target in CComplexObjectProxy"); + } + } else { + //lookup the parent and try to add the last attribute if it does not exist + List pathSegments = new APathQuery(attribute.getDifferentialPath()).getPathSegments(); + String pathMinusLastNode = PathUtil.getPath(pathSegments.subList(0, pathSegments.size()-1)); + CObject parentObject = newObject.itemAtPath(pathMinusLastNode); + if(parentObject != null && parentObject instanceof CComplexObject) { + //attribute does not exist, but does exist in RM (or it would not have passed the ArchetypeValidator, or the person using + //this flattener does not care + CAttribute realAttribute = new CAttribute(pathSegments.get(pathSegments.size()-1).getNodeName()); + ((CComplexObject) parentObject).addAttribute(realAttribute); + flattenAttribute(newObject, realAttribute, attribute); + } + + } + } + else if(object instanceof CAttribute) { + CAttribute realAttribute = (CAttribute) object; + flattenAttribute(newObject, realAttribute, attribute); + } else if (object instanceof CObject) { + //TODO: what does this mean? + } + + } else { + //this overrides the same path + flattenAttribute(newObject, newObject.getAttribute(attribute.getRmAttributeName()), attribute); + } + } + + protected void flattenAttribute(CComplexObject root, CAttribute attributeInParent, CAttribute attributeInSpecialization) { + if(attributeInParent == null) { + CAttribute childCloned = attributeInSpecialization.clone(); + root.addAttribute(childCloned); + } else { + + attributeInParent.setExistence(FlattenerUtil.getPossiblyOverridenValue(attributeInParent.getExistence(), attributeInSpecialization.getExistence())); + attributeInParent.setCardinality(FlattenerUtil.getPossiblyOverridenValue(attributeInParent.getCardinality(), attributeInSpecialization.getCardinality())); + + if (attributeInSpecialization.getChildren().size() > 0 && attributeInSpecialization.getChildren().get(0) instanceof CPrimitiveObject) { + //in case of a primitive object, just replace all nodes + attributeInParent.setChildren(attributeInSpecialization.getChildren()); + } else { + + //ordering the children correctly is tricky. + // First reorder parentCObjects if necessary, in the case that a sibling order refers to a redefined node + // in the attributeInSpecialization archetype + reorderSiblingOrdersReferringToSameLevel(attributeInSpecialization); + + //Now maintain an insertion anchor + //for when a sibling node has been set somewhere. + // insert everything after/before the anchor if it is set, + // at the defined position from the spec if it is null + SiblingOrder anchor = null; + + List parentCObjects = attributeInParent.getChildren(); + + for (CObject specializedChildCObject : attributeInSpecialization.getChildren()) { + + //find matching attributeInParent and create the child node with it + CObject matchingParentObject = findMatchingParentCObject(specializedChildCObject, parentCObjects); + + + if(specializedChildCObject.getSiblingOrder() != null) { + //new sibling order, update the anchor + anchor = specializedChildCObject.getSiblingOrder(); + } + + if (anchor != null) { + mergeObjectIntoAttribute(attributeInParent, specializedChildCObject, matchingParentObject, attributeInSpecialization.getChildren(), anchor); + anchor = nextAnchor(anchor, specializedChildCObject); + } else { //no sibling order + CObject specializedObject = flattener.createSpecializeCObject(attributeInParent, matchingParentObject, specializedChildCObject); + if(matchingParentObject == null) { + //extension nodes should be added to the last position + attributeInParent.addChild(specializedObject); + } else { + attributeInParent.addChild(specializedObject, SiblingOrder.createAfter(findLastSpecializedChildDirectlyAfter(attributeInParent, matchingParentObject))); + if(shouldRemoveParent(specializedChildCObject, matchingParentObject, attributeInSpecialization.getChildren())) { + //we should remove the attributeInParent + attributeInParent.removeChild(matchingParentObject.getNodeId()); + } + } + } + } + } + } + } + + /** + * Given the last used siblingorder anchor and the last specialized object added, return the SiblingOrder where the + * next specialized object should be added - if that next object does not have a new sibling order + * @param lastAnchor + * @param lastSpecializedObject + * @return + */ + private SiblingOrder nextAnchor(SiblingOrder lastAnchor, CObject lastSpecializedObject) { + if(lastAnchor.isBefore()) { + return lastAnchor; + } else { + return SiblingOrder.createAfter(lastSpecializedObject.getNodeId()); + } + } + + /** + * Add the specializedChildObject to the parentAttribute at the given siblingOrder. This method automatically checks if the matchingParentObject should be removed, and removes it if necessary. + * + * @param parentAttribute the attribute to add the new object to + * @param specializedChildCObject the specialized object that should be merged into the parent object + * @param matchingParentObject the matching parent CObject for the given specializedChildObject + * @param allSpecializedChildren all the specialized children in the same container as specialedChildCObject + * @param siblingOrder the sibling order where to add the specializedChild to. Directly adds, no preprocessing or anchor support in this method, you must do that before. + */ + private void mergeObjectIntoAttribute(CAttribute parentAttribute, CObject specializedChildCObject, CObject matchingParentObject, List allSpecializedChildren, SiblingOrder siblingOrder) { + CObject specializedObject = flattener.createSpecializeCObject(parentAttribute, matchingParentObject, specializedChildCObject); + if (shouldRemoveParent(specializedChildCObject, matchingParentObject, allSpecializedChildren)) { + parentAttribute.removeChild(matchingParentObject.getNodeId()); + } + parentAttribute.addChild(specializedObject, siblingOrder); + } + + + /** + * If the following occurs: + * + * after[id3] + * ELEMENT[id2] + * ELEMENT[id3.1] + * + * Reorder it and remove sibling orders: + * + * ELEMENT[id3.1] + * ELEMENT[id2] + * + * If sibling order do not refer to specialized nodes at this level, leaves them alone + * @param parent the attribute to reorder the child nodes for + */ + private void reorderSiblingOrdersReferringToSameLevel(CAttribute parent) { + for(CObject cObject:new ArrayList<>(parent.getChildren())) { + if(cObject.getSiblingOrder() != null) { + String matchingNodeId = findCObjectMatchingSiblingOrder(cObject.getSiblingOrder(), parent.getChildren()); + if(matchingNodeId != null) { + parent.removeChild(cObject.getNodeId()); + SiblingOrder siblingOrder = new SiblingOrder(); + siblingOrder.setSiblingNodeId(matchingNodeId); + siblingOrder.setBefore(cObject.getSiblingOrder().isBefore()); + parent.addChild(cObject, siblingOrder); + cObject.setSiblingOrder(null);//unset sibling order, it has been processed already + } + } + } + } + + /** + * Find the CObject in the given list of cObjects that matches with the given sibling order + * @param siblingOrder + * @param cObjectList + * @return + */ + private String findCObjectMatchingSiblingOrder(SiblingOrder siblingOrder, List cObjectList) { + for(CObject object:cObjectList) { + if(FlattenerUtil.isOverriddenIdCode(object.getNodeId(), siblingOrder.getSiblingNodeId())) { + return object.getNodeId(); + } + } + return null; + } + + /** + * Give an attribute and a CObject that is a child of that attribute, find the node id of the last object that is + * in the list of nodes directly after the child attribute that specialize the matching parent. If the parent + * has not yet been specialized, returns the parent node id + * + * @param parent + * @param matchingParentObject + * @return + */ + private String findLastSpecializedChildDirectlyAfter(CAttribute parent, CObject matchingParentObject) { + int matchingIndex = parent.getIndexOfChildWithNodeId(matchingParentObject.getNodeId()); + String result = matchingParentObject.getNodeId(); + for(int i = matchingIndex+1; i < parent.getChildren().size(); i++) { + if(FlattenerUtil.isOverriddenIdCode(parent.getChildren().get(i).getNodeId(), matchingParentObject.getNodeId())) { + result = parent.getChildren().get(i).getNodeId(); + } + } + return result; + } + + /** + * For the given specialized CObject that is to be added to the archetype, specializing matchingParentObject, and given + * the list of all specialized children of the same container as the given specialized cobject, return if the parent + * should be removed from the resulting list of flattened CObjects after inserting the specialized check or not. + * + * + * @param specializedChildCObject the specialized child object that is to be added + * @param matchingParentObject the CObject that matches with the specializedChildObject. Can be null. + * @param allSpecializedChildren all specialized children under the specializing child container + * @return + */ + private boolean shouldRemoveParent(CObject specializedChildCObject, CObject matchingParentObject, List allSpecializedChildren) { + if(matchingParentObject == null) { + return false; + } + List allMatchingChildren = new ArrayList<>(); + for (CObject specializedChild : allSpecializedChildren) { + if (FlattenerUtil.isOverridenCObject(specializedChild, matchingParentObject)) { + allMatchingChildren.add(specializedChild); + } + + } + //the last matching child should possibly replace the parent, the rest should just add + //if there is just one child, that's fine, it should still work + if(allMatchingChildren.get(allMatchingChildren.size()-1).getNodeId().equalsIgnoreCase(specializedChildCObject.getNodeId())) { + return shouldReplaceParent(matchingParentObject, allMatchingChildren); + } + return false; + } + + private boolean shouldReplaceParent(CObject parent, List differentialNodes) { + for(CObject differentialNode: differentialNodes) { + if(differentialNode.getNodeId().equals(parent.getNodeId())) { + //same node id, so no specialization + return true; + } + } + MultiplicityInterval occurrences = parent.effectiveOccurrences(flattener.getLookup()::referenceModelPropMultiplicity); + //isSingle/isMultiple is tricky and not doable just in the parser. Don't use those + if(isSingle(parent.getParent())) { + return true; + } else if(occurrences != null && occurrences.upperIsOne()) { + //REFINE the parent node case 1, the parent has occurrences upper == 1 + return true; + } else if (differentialNodes.size() == 1 + && differentialNodes.get(0).effectiveOccurrences(flattener.getLookup()::referenceModelPropMultiplicity).upperIsOne()) { + //REFINE the parent node case 2, only one child with occurrences upper == 1 + return true; + } + return false; + } + + private boolean isSingle(CAttribute attribute) { + if(attribute != null && attribute.getParent() != null && attribute.getDifferentialPath() == null) { + RMAttributeInfo attributeInfo = flattener.getLookup().getAttributeInfo(attribute.getParent().getRmTypeName(), attribute.getRmAttributeName()); + return attributeInfo != null && !attributeInfo.isMultipleValued(); + } + return false; + } + + /** + * Find the matching parent CObject given a specialized child. REturns null if not found. + * @param specializedChildCObject + * @param parentCObjects + * @return + */ + private CObject findMatchingParentCObject(CObject specializedChildCObject, List parentCObjects) { + for (CObject parentCObject : parentCObjects) { + if (FlattenerUtil.isOverridenCObject(specializedChildCObject, parentCObject)) { + return parentCObject; + } + } + return null; + } + + +} diff --git a/tools/src/main/java/com/nedap/archie/flattener/Flattener.java b/tools/src/main/java/com/nedap/archie/flattener/Flattener.java index be5f50b15..3a2e0ff27 100644 --- a/tools/src/main/java/com/nedap/archie/flattener/Flattener.java +++ b/tools/src/main/java/com/nedap/archie/flattener/Flattener.java @@ -1,25 +1,14 @@ package com.nedap.archie.flattener; -import com.google.common.collect.Lists; import com.nedap.archie.aom.*; -import com.nedap.archie.aom.terminology.ArchetypeTerm; -import com.nedap.archie.aom.terminology.ArchetypeTerminology; -import com.nedap.archie.aom.utils.AOMUtils; import com.nedap.archie.aom.utils.ArchetypeParsePostProcesser; -import com.nedap.archie.base.MultiplicityInterval; -import com.nedap.archie.paths.PathSegment; -import com.nedap.archie.paths.PathUtil; -import com.nedap.archie.query.AOMPathQuery; -import com.nedap.archie.query.APathQuery; import com.nedap.archie.rminfo.ModelInfoLookup; -import com.nedap.archie.rminfo.RMAttributeInfo; import com.nedap.archie.rminfo.ReferenceModels; -import com.nedap.archie.rules.Assertion; + +import static com.nedap.archie.flattener.FlattenerUtil.*; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Stack; /** @@ -50,6 +39,11 @@ public class Flattener { private RulesFlattener rulesFlattener = new RulesFlattener(); + CAttributeFlattener cAttributeFlattener = new CAttributeFlattener(this); + private TupleFlattener tupleFlattener = new TupleFlattener(); + + private OperationalTemplateCreator optCreator = new OperationalTemplateCreator(this); + public Flattener(ArchetypeRepository repository, ReferenceModels models) { this.repository = new OverridingArchetypeRepository(repository); @@ -88,10 +82,10 @@ public Archetype flatten(Archetype toFlatten) { String parentId = toFlatten.getParentArchetypeId(); if(parentId == null) { if(createOperationalTemplate) { - OperationalTemplate template = createOperationalTemplate(toFlatten); + OperationalTemplate template = optCreator.createOperationalTemplate(toFlatten); result = template; //make an operational template by just filling complex object proxies and archetype slots - fillSlots(template); + optCreator.fillSlots(template); TerminologyFlattener.filterLanguages(template, removeLanguagesFromMetaData, languagesToKeep); result = template; } else { @@ -125,8 +119,8 @@ public Archetype flatten(Archetype toFlatten) { this.result = null; if(createOperationalTemplate) { - result = createOperationalTemplate(parent); - overrideArchetypeId(result, child); + result = optCreator.createOperationalTemplate(parent); + optCreator.overrideArchetypeId(result, child); } else { result = parent.clone(); } @@ -135,14 +129,14 @@ public Archetype flatten(Archetype toFlatten) { //2. fill archetype slots if we are creating an operational template flattenDefinition(result, child); if(createOperationalTemplate) { - removeZeroOccurrencesConstraints(result); + optCreator.removeZeroOccurrencesConstraints(result); } else { prohibitZeroOccurrencesConstraints(result); } rulesFlattener.combineRules(child, result, "prefix", "", "", true /* override statements with same tag */);//TODO: actually set a unique prefix if(createOperationalTemplate) { - fillSlots(result); + optCreator.fillSlots((OperationalTemplate) result); } TerminologyFlattener.flattenTerminology(result, child); @@ -166,39 +160,6 @@ public Archetype flatten(Archetype toFlatten) { return result; } - - public void fillSlots(Archetype archetype) { //should this be OperationalTemplate? - closeArchetypeSlots(archetype); - fillArchetypeRoots(archetype); - fillComplexObjectProxies(archetype); - } - - /** Zero occurrences and existence constraint processing when creating OPT templates. Removes attributes */ - private void removeZeroOccurrencesConstraints(Archetype archetype) { - Stack workList = new Stack<>(); - workList.push(archetype.getDefinition()); - while(!workList.isEmpty()) { - CObject object = workList.pop(); - List attributesToRemove = new ArrayList<>(); - for(CAttribute attribute:object.getAttributes()) { - if(attribute.getExistence() != null && attribute.getExistence().getUpper() == 0 && !attribute.getExistence().isUpperUnbounded()) { - attributesToRemove.add(attribute); - } else { - List objectsToRemove = new ArrayList<>(); - for (CObject child : attribute.getChildren()) { - if (!child.isAllowed()) { - objectsToRemove.add(child); - } - workList.push(child); - } - attribute.getChildren().removeAll(objectsToRemove); - } - - } - object.getAttributes().removeAll(attributesToRemove); - } - } - /** Zero occurrences and existence constraint processing when flattening. Does not remove attributes*/ private void prohibitZeroOccurrencesConstraints(Archetype archetype) { Stack workList = new Stack<>(); @@ -225,183 +186,14 @@ private void prohibitZeroOccurrencesConstraints(Archetype archetype) { } } - private void closeArchetypeSlots(Archetype archetype) { - Stack workList = new Stack<>(); - workList.push(archetype.getDefinition()); - while(!workList.isEmpty()) { - CObject object = workList.pop(); - for(CAttribute attribute:object.getAttributes()) { - List toRemove = new ArrayList<>(); - for(CObject child:attribute.getChildren()) { - if(child instanceof ArchetypeSlot) { //use_archetype - if(((ArchetypeSlot) child).isClosed()) { - toRemove.add(child); - } - } - workList.push(child); - } - attribute.getChildren().removeAll(toRemove); - } - } - } - - private void fillArchetypeRoots(Archetype result) { - - Stack workList = new Stack<>(); - workList.push(result.getDefinition()); - while(!workList.isEmpty()) { - CObject object = workList.pop(); - for(CAttribute attribute:object.getAttributes()) { - for(CObject child:attribute.getChildren()) { - if(child instanceof CArchetypeRoot) { //use_archetype - fillArchetypeRoot((CArchetypeRoot) child); - } - workList.push(child); - } - } - } - } - - private void fillComplexObjectProxies(Archetype result) { - - Stack workList = new Stack<>(); - workList.push(result.getDefinition()); - List replacements = new ArrayList<>(); - while(!workList.isEmpty()) { - CObject object = workList.pop(); - for(CAttribute attribute:object.getAttributes()) { - for(CObject child:attribute.getChildren()) { - if(child instanceof CComplexObjectProxy) { //use_node - ComplexObjectProxyReplacement possibleReplacement = - ComplexObjectProxyReplacement.getComplexObjectProxyReplacement((CComplexObjectProxy) child); - if(possibleReplacement != null) { - replacements.add(possibleReplacement); - } else { - throw new RuntimeException("cannot find target in CComplexObjectProxy"); - } - - } - workList.push(child); - } - } - } - for(ComplexObjectProxyReplacement replacement:replacements) { - replacement.replace(); - } - } - - private void fillArchetypeRoot(CArchetypeRoot root) { - if(createOperationalTemplate) { - String archetypeRef = root.getArchetypeRef(); - String newArchetypeRef = archetypeRef; - Archetype archetype = this.repository.getArchetype(archetypeRef); - if(archetype instanceof TemplateOverlay){ - //we want to be able to check which archetype this is in the UI. If it's an overlay, that means retrieving the non-operational template - //which is a hassle. - //That's a problem. Is this the way to fix is? - newArchetypeRef = archetype.getParentArchetypeId(); - } - if (archetype == null) { - throw new IllegalArgumentException("Archetype with reference :" + archetypeRef + " not found."); - } - - archetype = getNewFlattener().flatten(archetype); - - // - CComplexObject rootToFill = root; - if(useComplexObjectForArchetypeSlotReplacement) { - rootToFill = archetype.getDefinition(); - root.getParent().replaceChild(root.getNodeId(), rootToFill); - } else { - rootToFill.setAttributes(archetype.getDefinition().getAttributes()); - rootToFill.setAttributeTuples(archetype.getDefinition().getAttributeTuples()); - rootToFill.setDefaultValue(archetype.getDefinition().getDefaultValue()); - } - String newNodeId = archetype.getArchetypeId().getFullId(); - - ArchetypeTerminology terminology = archetype.getTerminology(); - - //The node id will be replaced from "id1" to something like "openEHR-EHR-COMPOSITION.template_overlay.v1.0.0 - //so store it in the terminology as well - Map> termDefinitions = terminology.getTermDefinitions(); - - for(String language: termDefinitions.keySet()) { - Map translations = termDefinitions.get(language); - translations.put(newNodeId, TerminologyFlattener.getTerm(terminology.getTermDefinitions(), language, archetype.getDefinition().getNodeId())); - } - - //rootToFill.setNodeId(newNodeId); - if(!useComplexObjectForArchetypeSlotReplacement) { - root.setArchetypeRef(newNodeId); - } - OperationalTemplate templateResult = (OperationalTemplate) result; - - //todo: should we filter this? - if(archetype instanceof OperationalTemplate) { - OperationalTemplate template = (OperationalTemplate) archetype; - //add all the component terminologies, otherwise we lose translation - for(String subarchetypeId:template.getComponentTerminologies().keySet()) { - templateResult.addComponentTerminology(subarchetypeId, template.getComponentTerminologies().get(subarchetypeId)); - } - } - - templateResult.addComponentTerminology(newNodeId, terminology); - - String prefix = archetype.getArchetypeId().getConceptId() + "_"; - rulesFlattener.combineRules(archetype, root.getArchetype(), prefix, prefix, rootToFill.getPath(), false); //TODO: add prefixes to make them unique, or some other way of making unique - //todo: do we have to put something in the terminology extracts? - //templateResult.addTerminologyExtract(child.getNodeId(), archetype.getTerminology().); - } - - } - - private static OperationalTemplate createOperationalTemplate(Archetype archetype) { - Archetype toClone = archetype.clone(); //clone so we do not overwrite the parent archetype. never - OperationalTemplate result = new OperationalTemplate(); - result.setArchetypeId((ArchetypeHRID) archetype.getArchetypeId().clone()); - result.setDefinition(toClone.getDefinition()); - result.setDifferential(false); - - result.setRmRelease(toClone.getRmRelease()); - result.setAdlVersion(toClone.getAdlVersion()); - result.setTerminology(toClone.getTerminology()); - result.setGenerated(true); - result.setOtherMetaData(toClone.getOtherMetaData()); - result.setRules(toClone.getRules()); - result.setBuildUid(toClone.getBuildUid()); - result.setDescription(toClone.getDescription()); - result.setOriginalLanguage(toClone.getOriginalLanguage()); - result.setTranslations(toClone.getTranslations()); - - return result; - } - - private static void overrideArchetypeId(Archetype result, Archetype override) { - result.setArchetypeId(override.getArchetypeId()); - result.setParentArchetypeId(override.getParentArchetypeId()); - } - private void flattenDefinition(Archetype parent, Archetype specialized) { parent.setArchetypeId(specialized.getArchetypeId()); //TODO: override all metadata? - flattenCObject(null, parent.getDefinition(), Lists.newArrayList(specialized.getDefinition())); + createSpecializeCObject(null, parent.getDefinition(), specialized.getDefinition()); } - private void flattenCObject(CAttribute attribute, CObject parent, List specializedList) { - List newNodes = new ArrayList<>(); - for(CObject specialized:specializedList) { - CObject newObject = createSpecializeCObject(attribute, parent, specialized); - newNodes.add(newObject); - } - - if(attribute != null && !newNodes.isEmpty()) { - boolean shouldReplaceParent = shouldReplaceParent(parent, newNodes); - attribute.replaceChildren(parent.getNodeId(), newNodes, !shouldReplaceParent /* remove original */); - } - } - - private CObject createSpecializeCObject(CAttribute attribute, CObject parent, CObject specialized) { + protected CObject createSpecializeCObject(CAttribute attribute, CObject parent, CObject specialized) { if(parent == null) { return specialized;//TODO: clone? } @@ -461,35 +253,9 @@ private CObject cloneSpecializedObject(CAttribute attribute, CObject parent, COb return newObject; } - private boolean shouldReplaceParent(CObject parent, List differentialNodes) { - for(CObject differentialNode: differentialNodes) { - if(differentialNode.getNodeId().equals(parent.getNodeId())) { - //same node id, so no specialization - return true; - } - } - MultiplicityInterval occurrences = parent.effectiveOccurrences(lookup::referenceModelPropMultiplicity); - //isSingle/isMultiple is tricky and not doable just in the parser. Don't use those - if(isSingle(parent.getParent())) { - return true; - } else if(occurrences != null && occurrences.upperIsOne()) { - //REFINE the parent node case 1, the parent has occurrences upper == 1 - return true; - } else if (differentialNodes.size() == 1 - && differentialNodes.get(0).effectiveOccurrences(lookup::referenceModelPropMultiplicity).upperIsOne()) { - //REFINE the parent node case 2, only one child with occurrences upper == 1 - return true; - } - return false; - } - private boolean isSingle(CAttribute attribute) { - if(attribute != null && attribute.getParent() != null && attribute.getDifferentialPath() == null) { - RMAttributeInfo attributeInfo = lookup.getAttributeInfo(attribute.getParent().getRmTypeName(), attribute.getRmAttributeName()); - return attributeInfo != null && !attributeInfo.isMultipleValued(); - } - return false; - } + + private void flattenArchetypeSlot(ArchetypeSlot parent, ArchetypeSlot specialized) { if(specialized.isClosed()) { @@ -524,94 +290,14 @@ private void flattenCComplexObject(CComplexObject newObject, CComplexObject spec } for(CAttribute attribute:specialized.getAttributes()) { - flattenSingleAttribute(newObject, attribute); + cAttributeFlattener.flattenSingleAttribute(newObject, attribute); } for(CAttributeTuple tuple:specialized.getAttributeTuples()) { - flattenTuple(newObject, tuple); - } - } - - private void flattenTuple(CComplexObject newObject, CAttributeTuple tuple) { - CAttributeTuple matchingTuple = AOMUtils.findMatchingTuple(newObject.getAttributeTuples(), tuple); - - - CAttributeTuple tupleClone = (CAttributeTuple) tuple.clone(); - if(matchingTuple == null) { - //add - newObject.addAttributeTuple(tupleClone); - } else { - //replace - newObject.getAttributeTuples().remove(matchingTuple); - newObject.addAttributeTuple(tupleClone); - } - for(CAttribute attribute:tupleClone.getMembers()){ - //replace the entire attribute with the attribute from the new tuple - //this should be all there is to do. - newObject.replaceAttribute(attribute); - } - //update all parent references - for(CPrimitiveTuple primitiveTuple:tupleClone.getTuples()) { - int i = 0; - for(CPrimitiveObject object:primitiveTuple.getMembers()) { - object.setSocParent(primitiveTuple); - object.setParent((tupleClone.getMembers().get(i))); - i++; - } - } - } - - - private void flattenSingleAttribute(CComplexObject newObject, CAttribute attribute) { - if(attribute.getDifferentialPath() != null) { - //this overrides a specific path - ArchetypeModelObject object = newObject.itemAtPath(attribute.getDifferentialPath()); - if(object == null) { - //it is possible that the object points to a reference, in which case we need to clone the referenced node, then try again - //AOM spec paragraph 7.2: 'proxy reference targets are expanded inline if the child archetype overrides them.' - //also examples in ADL2 spec about internal references - //so find the internal references here! - //TODO: AOMUtils.pathAtSpecializationLevel(pathSegments.subList(0, pathSegments.size()-1), flatParent.specializationDepth()); - CComplexObjectProxy internalReference = new AOMPathQuery(attribute.getDifferentialPath()).findAnyInternalReference(newObject); - if(internalReference != null) { - //in theory this can be a use node within a use node. - ComplexObjectProxyReplacement complexObjectProxyReplacement = - ComplexObjectProxyReplacement.getComplexObjectProxyReplacement(internalReference); - if(complexObjectProxyReplacement != null) { - complexObjectProxyReplacement.replace(); - //and again! - flattenSingleAttribute(newObject, attribute); - } else { - throw new RuntimeException("cannot find target in CComplexObjectProxy"); - } - } else { - //lookup the parent and try to add the last attribute if it does not exist - List pathSegments = new APathQuery(attribute.getDifferentialPath()).getPathSegments(); - String pathMinusLastNode = PathUtil.getPath(pathSegments.subList(0, pathSegments.size()-1)); - CObject parentObject = newObject.itemAtPath(pathMinusLastNode); - if(parentObject != null && parentObject instanceof CComplexObject) { - //attribute does not exist, but does exist in RM (or it would not have passed the ArchetypeValidator, or the person using - //this flattener does not care - CAttribute realAttribute = new CAttribute(pathSegments.get(pathSegments.size()-1).getNodeName()); - ((CComplexObject) parentObject).addAttribute(realAttribute); - flattenAttribute(newObject, realAttribute, attribute); - } - - } - } - else if(object instanceof CAttribute) { - CAttribute realAttribute = (CAttribute) object; - flattenAttribute(newObject, realAttribute, attribute); - } else if (object instanceof CObject) { - //TODO: what does this mean? - } - - } else { - //this overrides the same path - flattenAttribute(newObject, newObject.getAttribute(attribute.getRmAttributeName()), attribute); + tupleFlattener.flattenTuple(newObject, tuple); } } - private Flattener getNewFlattener() { + protected Flattener getNewFlattener() { return new Flattener(repository, referenceModels) .createOperationalTemplate(false) //do not create operational template except at the end. .useComplexObjectForArchetypeSlotReplacement(useComplexObjectForArchetypeSlotReplacement); @@ -622,203 +308,24 @@ private Flattener useComplexObjectForArchetypeSlotReplacement(boolean useComplex return this; } - - private void flattenAttribute(CComplexObject root, CAttribute parent, CAttribute specialized) { - if(parent == null) { - CAttribute childCloned = specialized.clone(); - root.addAttribute(childCloned); - } else { - - parent.setExistence(getPossiblyOverridenValue(parent.getExistence(), specialized.getExistence())); - parent.setCardinality(getPossiblyOverridenValue(parent.getCardinality(), specialized.getCardinality())); - - if (specialized.getChildren().size() > 0 && specialized.getChildren().get(0) instanceof CPrimitiveObject) { - //in case of a primitive object, just replace all nodes - parent.setChildren(specialized.getChildren()); - } else { - - //ordering the children correctly is tricky. - // First reorder parentCObjects if necessary, in the case that a sibling order refers to a redefined node - // in the specialized archetype - reorderSiblingOrdersReferringToSameLevel(specialized); - - //Now maintain an insertion anchor - //for when a sibling node has been set somewhere. - // insert everything after/before the anchor if it is set, - // at the defined position from the spec if it is null - SiblingOrder anchor = null; - - List parentCObjects = parent.getChildren(); - - for (CObject specializedChildCObject : specialized.getChildren()) { - - //find matching parent and create the child node with it - CObject matchingParentObject = findMatchingParentCObject(specializedChildCObject, parentCObjects); - CObject specializedObject = createSpecializeCObject(parent, matchingParentObject, specializedChildCObject); - - if(specializedChildCObject.getSiblingOrder() != null) { - //this node has a sibling order, insert in correct place - if(!shouldAddNewNode(specializedChildCObject, matchingParentObject, specialized.getChildren())) { - parent.removeChild(matchingParentObject.getNodeId()); - } - parent.addChild(specializedObject, specializedChildCObject.getSiblingOrder()); - if(specializedChildCObject.getSiblingOrder().isBefore()) { - anchor = specializedChildCObject.getSiblingOrder(); - } else { - anchor = SiblingOrder.createAfter(specializedChildCObject.getNodeId()); - } - } else if (anchor != null) { - - if(!shouldAddNewNode(specializedChildCObject, matchingParentObject, specialized.getChildren())) { - parent.removeChild(matchingParentObject.getNodeId()); - - } - parent.addChild(specializedObject, anchor); - - if(!anchor.isBefore()) { - anchor.setSiblingNodeId(specializedChildCObject.getNodeId()); - } - } else { //no sibling order - - if(matchingParentObject == null) { - //extension nodes should be added to the last position - parent.addChild(specializedObject); - } else { - parent.addChild(specializedObject, SiblingOrder.createAfter(findLastSpecializedChildDirectlyAfter(parent, matchingParentObject))); - if(!shouldAddNewNode(specializedChildCObject, matchingParentObject, specialized.getChildren())) { - //we should remove the parent - parent.removeChild(matchingParentObject.getNodeId()); - } - } - } - } - } - } - } - - /** - * If the following occurs: - * - * after[id3] - * ELEMENT[id2] - * ELEMENT[id3.1] - * - * Reorder it and remove sibling orders: - * - * ELEMENT[id3.1] - * ELEMENT[id2] - * - * If sibling order do not refer to specialized nodes at this level, leaves them alone - * @param parent the attribute to reorder the child nodes for - */ - private void reorderSiblingOrdersReferringToSameLevel(CAttribute parent) { - for(CObject cObject:new ArrayList<>(parent.getChildren())) { - if(cObject.getSiblingOrder() != null) { - String matchingNodeId = findCObjectMatchingSiblingOrder(cObject.getSiblingOrder(), parent.getChildren()); - if(matchingNodeId != null) { - parent.removeChild(cObject.getNodeId()); - SiblingOrder siblingOrder = new SiblingOrder(); - siblingOrder.setSiblingNodeId(matchingNodeId); - siblingOrder.setBefore(cObject.getSiblingOrder().isBefore()); - parent.addChild(cObject, siblingOrder); - cObject.setSiblingOrder(null);//unset sibling order, it has been processed already - } - } - } - } - - private String findCObjectMatchingSiblingOrder(SiblingOrder siblingOrder, List cObjectList) { - for(CObject object:cObjectList) { - if(isOverriddenIdCode(object.getNodeId(), siblingOrder.getSiblingNodeId())) { - return object.getNodeId(); - } - } - return null; - } - - /** - * Give an attribute and a CObject that is a child of that attribute, find the node id of the last object that is - * in the list of nodes directly after the child attribute that specialize the matching parent. If the parent - * has not yet been specialized, returns the parent node id - * - * @param parent - * @param matchingParentObject - * @return - */ - private String findLastSpecializedChildDirectlyAfter(CAttribute parent, CObject matchingParentObject) { - int matchingIndex = parent.getIndexOfChildWithNodeId(matchingParentObject.getNodeId()); - String result = matchingParentObject.getNodeId(); - for(int i = matchingIndex+1; i < parent.getChildren().size(); i++) { - if(isOverriddenIdCode(parent.getChildren().get(i).getNodeId(), matchingParentObject.getNodeId())) { - result = parent.getChildren().get(i).getNodeId(); - } - } - return result; - } - - private boolean shouldAddNewNode(CObject specializedChildCObject, CObject matchingParentObject, List specializedChildren) { - if(matchingParentObject == null) { - return true; - } - List allMatchingChildren = new ArrayList<>(); - for (CObject specializedChild : specializedChildren) { - if (isOverridenCObject(specializedChild, matchingParentObject)) { - allMatchingChildren.add(specializedChild); - } - - } - //the last matching child should possibly replace the parent, the rest should just add - //if there is just one child, that's fine, it should still work - if(allMatchingChildren.get(allMatchingChildren.size()-1).getNodeId().equalsIgnoreCase(specializedChildCObject.getNodeId())) { - return !shouldReplaceParent(matchingParentObject, allMatchingChildren); - } - return true; + public boolean isUseComplexObjectForArchetypeSlotReplacement() { + return useComplexObjectForArchetypeSlotReplacement; } - /** - * Find the matching parent CObject given a specialized child. REturns null if not found. - * @param specializedChildCObject - * @param parentCObjects - * @return - */ - private CObject findMatchingParentCObject(CObject specializedChildCObject, List parentCObjects) { - for (CObject parentCObject : parentCObjects) { - if (isOverridenCObject(specializedChildCObject, parentCObject)) { - return parentCObject; - } - } - return null; + protected ModelInfoLookup getLookup() { + return lookup; } - private boolean isOverridenCObject(CObject specialized, CObject parent) { - String specializedNodeId = specialized.getNodeId(); - String parentNodeId = parent.getNodeId(); - return isOverriddenIdCode(specializedNodeId, parentNodeId); - } - public static boolean isOverriddenIdCode(String specializedNodeId, String parentNodeId) { - if(specializedNodeId.equalsIgnoreCase(parentNodeId)) { - return true; - } - if(specializedNodeId.lastIndexOf('.') > 0) { - specializedNodeId = specializedNodeId.substring(0, specializedNodeId.lastIndexOf('.'));//-1? - } - return specializedNodeId.equals(parentNodeId) || specializedNodeId.startsWith(parentNodeId + "."); + public boolean getCreateOperationalTemplate() { + return createOperationalTemplate; } - private List getPossiblyOverridenListValue(List parent, List child) { - if(child != null && !child.isEmpty()) { - return child; - } - return parent; + protected RulesFlattener getRulesFlattener() { + return rulesFlattener; } - public T getPossiblyOverridenValue(T parent, T specialized) { - if(specialized != null) { - return specialized; - } - return parent; + public OverridingArchetypeRepository getRepository() { + return repository; } - - } diff --git a/tools/src/main/java/com/nedap/archie/flattener/FlattenerUtil.java b/tools/src/main/java/com/nedap/archie/flattener/FlattenerUtil.java new file mode 100644 index 000000000..94cac2f48 --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/flattener/FlattenerUtil.java @@ -0,0 +1,37 @@ +package com.nedap.archie.flattener; + +import com.nedap.archie.aom.CObject; +import com.nedap.archie.rules.Assertion; + +import java.util.List; + +public class FlattenerUtil { + + public static boolean isOverridenCObject(CObject specialized, CObject parent) { + return isOverriddenIdCode(specialized.getNodeId(), parent.getNodeId()); + } + + public static boolean isOverriddenIdCode(String specializedNodeId, String parentNodeId) { + if(specializedNodeId.equalsIgnoreCase(parentNodeId)) { + return true; + } + if(specializedNodeId.lastIndexOf('.') > 0) { + specializedNodeId = specializedNodeId.substring(0, specializedNodeId.lastIndexOf('.'));//-1? + } + return specializedNodeId.equals(parentNodeId) || specializedNodeId.startsWith(parentNodeId + "."); + } + + public static List getPossiblyOverridenListValue(List parent, List child) { + if(child != null && !child.isEmpty()) { + return child; + } + return parent; + } + + public static T getPossiblyOverridenValue(T parent, T specialized) { + if(specialized != null) { + return specialized; + } + return parent; + } +} diff --git a/tools/src/main/java/com/nedap/archie/flattener/OperationalTemplateCreator.java b/tools/src/main/java/com/nedap/archie/flattener/OperationalTemplateCreator.java new file mode 100644 index 000000000..20b8c5e87 --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/flattener/OperationalTemplateCreator.java @@ -0,0 +1,215 @@ +package com.nedap.archie.flattener; + +import com.nedap.archie.aom.*; +import com.nedap.archie.aom.terminology.ArchetypeTerm; +import com.nedap.archie.aom.terminology.ArchetypeTerminology; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +/** + * Creates operational templates. Not to be used externally, use the Flattener with the right parameters to + * create operational templates + */ +class OperationalTemplateCreator { + + private final Flattener flattener; + + OperationalTemplateCreator(Flattener flattener) { + this.flattener = flattener; + } + + public static OperationalTemplate createOperationalTemplate(Archetype archetype) { + Archetype toClone = archetype.clone(); //clone so we do not overwrite the parent archetype. never + OperationalTemplate result = new OperationalTemplate(); + result.setArchetypeId((ArchetypeHRID) archetype.getArchetypeId().clone()); + result.setDefinition(toClone.getDefinition()); + result.setDifferential(false); + + result.setRmRelease(toClone.getRmRelease()); + result.setAdlVersion(toClone.getAdlVersion()); + result.setTerminology(toClone.getTerminology()); + result.setGenerated(true); + result.setOtherMetaData(toClone.getOtherMetaData()); + result.setRules(toClone.getRules()); + result.setBuildUid(toClone.getBuildUid()); + result.setDescription(toClone.getDescription()); + result.setOriginalLanguage(toClone.getOriginalLanguage()); + result.setTranslations(toClone.getTranslations()); + + return result; + } + + public static void overrideArchetypeId(Archetype result, Archetype override) { + result.setArchetypeId(override.getArchetypeId()); + result.setParentArchetypeId(override.getParentArchetypeId()); + } + + public void fillSlots(OperationalTemplate archetype) { //should this be OperationalTemplate? + closeArchetypeSlots(archetype); + fillArchetypeRoots(archetype); + fillComplexObjectProxies(archetype); + } + + /** Zero occurrences and existence constraint processing when creating OPT templates. Removes attributes */ + public void removeZeroOccurrencesConstraints(Archetype archetype) { + Stack workList = new Stack<>(); + workList.push(archetype.getDefinition()); + while(!workList.isEmpty()) { + CObject object = workList.pop(); + List attributesToRemove = new ArrayList<>(); + for(CAttribute attribute:object.getAttributes()) { + if(attribute.getExistence() != null && attribute.getExistence().getUpper() == 0 && !attribute.getExistence().isUpperUnbounded()) { + attributesToRemove.add(attribute); + } else { + List objectsToRemove = new ArrayList<>(); + for (CObject child : attribute.getChildren()) { + if (!child.isAllowed()) { + objectsToRemove.add(child); + } + workList.push(child); + } + attribute.getChildren().removeAll(objectsToRemove); + } + + } + object.getAttributes().removeAll(attributesToRemove); + } + } + + + private void closeArchetypeSlots(OperationalTemplate archetype) { + Stack workList = new Stack<>(); + workList.push(archetype.getDefinition()); + while(!workList.isEmpty()) { + CObject object = workList.pop(); + for(CAttribute attribute:object.getAttributes()) { + List toRemove = new ArrayList<>(); + for(CObject child:attribute.getChildren()) { + if(child instanceof ArchetypeSlot) { //use_archetype + if(((ArchetypeSlot) child).isClosed()) { + toRemove.add(child); + } + } + workList.push(child); + } + attribute.getChildren().removeAll(toRemove); + } + } + } + + private void fillArchetypeRoots(OperationalTemplate result) { + + Stack workList = new Stack<>(); + workList.push(result.getDefinition()); + while(!workList.isEmpty()) { + CObject object = workList.pop(); + for(CAttribute attribute:object.getAttributes()) { + for(CObject child:attribute.getChildren()) { + if(child instanceof CArchetypeRoot) { //use_archetype + fillArchetypeRoot((CArchetypeRoot) child, result); + } + workList.push(child); + } + } + } + } + + private void fillComplexObjectProxies(OperationalTemplate result) { + + Stack workList = new Stack<>(); + workList.push(result.getDefinition()); + List replacements = new ArrayList<>(); + while(!workList.isEmpty()) { + CObject object = workList.pop(); + for(CAttribute attribute:object.getAttributes()) { + for(CObject child:attribute.getChildren()) { + if(child instanceof CComplexObjectProxy) { //use_node + ComplexObjectProxyReplacement possibleReplacement = + ComplexObjectProxyReplacement.getComplexObjectProxyReplacement((CComplexObjectProxy) child); + if(possibleReplacement != null) { + replacements.add(possibleReplacement); + } else { + throw new RuntimeException("cannot find target in CComplexObjectProxy"); + } + + } + workList.push(child); + } + } + } + for(ComplexObjectProxyReplacement replacement:replacements) { + replacement.replace(); + } + } + + private void fillArchetypeRoot(CArchetypeRoot root, OperationalTemplate result) { + if(flattener.getCreateOperationalTemplate()) { + String archetypeRef = root.getArchetypeRef(); + String newArchetypeRef = archetypeRef; + Archetype archetype = flattener.getRepository().getArchetype(archetypeRef); + if(archetype instanceof TemplateOverlay){ + //we want to be able to check which archetype this is in the UI. If it's an overlay, that means retrieving the non-operational template + //which is a hassle. + //That's a problem. Is this the way to fix is? + newArchetypeRef = archetype.getParentArchetypeId(); + } + if (archetype == null) { + throw new IllegalArgumentException("Archetype with reference :" + archetypeRef + " not found."); + } + + archetype = flattener.getNewFlattener().flatten(archetype); + + // + CComplexObject rootToFill = root; + if(flattener.isUseComplexObjectForArchetypeSlotReplacement()) { + rootToFill = archetype.getDefinition(); + root.getParent().replaceChild(root.getNodeId(), rootToFill); + } else { + rootToFill.setAttributes(archetype.getDefinition().getAttributes()); + rootToFill.setAttributeTuples(archetype.getDefinition().getAttributeTuples()); + rootToFill.setDefaultValue(archetype.getDefinition().getDefaultValue()); + } + String newNodeId = archetype.getArchetypeId().getFullId(); + + ArchetypeTerminology terminology = archetype.getTerminology(); + + //The node id will be replaced from "id1" to something like "openEHR-EHR-COMPOSITION.template_overlay.v1.0.0 + //so store it in the terminology as well + Map> termDefinitions = terminology.getTermDefinitions(); + + for(String language: termDefinitions.keySet()) { + Map translations = termDefinitions.get(language); + translations.put(newNodeId, TerminologyFlattener.getTerm(terminology.getTermDefinitions(), language, archetype.getDefinition().getNodeId())); + } + + //rootToFill.setNodeId(newNodeId); + if(!flattener.isUseComplexObjectForArchetypeSlotReplacement()) { + root.setArchetypeRef(newNodeId); + } + + //todo: should we filter this? + if(archetype instanceof OperationalTemplate) { + OperationalTemplate template = (OperationalTemplate) archetype; + //add all the component terminologies, otherwise we lose translation + for(String subarchetypeId:template.getComponentTerminologies().keySet()) { + result.addComponentTerminology(subarchetypeId, template.getComponentTerminologies().get(subarchetypeId)); + } + } + + result.addComponentTerminology(newNodeId, terminology); + + String prefix = archetype.getArchetypeId().getConceptId() + "_"; + flattener.getRulesFlattener().combineRules(archetype, root.getArchetype(), prefix, prefix, rootToFill.getPath(), false); + //todo: do we have to put something in the terminology extracts? + //templateResult.addTerminologyExtract(child.getNodeId(), archetype.getTerminology().); + } + + } + + + + +} diff --git a/tools/src/main/java/com/nedap/archie/flattener/TerminologyFlattener.java b/tools/src/main/java/com/nedap/archie/flattener/TerminologyFlattener.java index 3c348098a..9f188b6f0 100644 --- a/tools/src/main/java/com/nedap/archie/flattener/TerminologyFlattener.java +++ b/tools/src/main/java/com/nedap/archie/flattener/TerminologyFlattener.java @@ -85,7 +85,7 @@ private static void flattenValueSets(Map childValueSets, Map resultValueSets, String specializedId) { return resultValueSets.values().stream() - .filter((valueSet) -> Flattener.isOverriddenIdCode(specializedId, valueSet.getId())) + .filter((valueSet) -> FlattenerUtil.isOverriddenIdCode(specializedId, valueSet.getId())) .findAny().orElse(null); } diff --git a/tools/src/main/java/com/nedap/archie/flattener/TupleFlattener.java b/tools/src/main/java/com/nedap/archie/flattener/TupleFlattener.java new file mode 100644 index 000000000..69b6e02a2 --- /dev/null +++ b/tools/src/main/java/com/nedap/archie/flattener/TupleFlattener.java @@ -0,0 +1,45 @@ +package com.nedap.archie.flattener; + +import com.nedap.archie.aom.CAttribute; +import com.nedap.archie.aom.CAttributeTuple; +import com.nedap.archie.aom.CComplexObject; +import com.nedap.archie.aom.CPrimitiveObject; +import com.nedap.archie.aom.CPrimitiveTuple; +import com.nedap.archie.aom.utils.AOMUtils; + +class TupleFlattener { + + + public TupleFlattener() { + + } + + protected void flattenTuple(CComplexObject newObject, CAttributeTuple tuple) { + CAttributeTuple matchingTuple = AOMUtils.findMatchingTuple(newObject.getAttributeTuples(), tuple); + + + CAttributeTuple tupleClone = (CAttributeTuple) tuple.clone(); + if(matchingTuple == null) { + //add + newObject.addAttributeTuple(tupleClone); + } else { + //replace + newObject.getAttributeTuples().remove(matchingTuple); + newObject.addAttributeTuple(tupleClone); + } + for(CAttribute attribute:tupleClone.getMembers()){ + //replace the entire attribute with the attribute from the new tuple + //this should be all there is to do. + newObject.replaceAttribute(attribute); + } + //update all parent references + for(CPrimitiveTuple primitiveTuple:tupleClone.getTuples()) { + int i = 0; + for(CPrimitiveObject object:primitiveTuple.getMembers()) { + object.setSocParent(primitiveTuple); + object.setParent((tupleClone.getMembers().get(i))); + i++; + } + } + } +} From 009216208c4a1ba1c6c4bfaffda75ac4e3aa3077 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Wed, 29 Nov 2017 15:37:29 +0100 Subject: [PATCH 2/4] Fix referenceModelPropertyMultiplicity to work on a differential path instead of just an attribute name --- .../main/java/com/nedap/archie/aom/CObject.java | 2 +- .../nedap/archie/rminfo/ModelInfoLookup.java | 17 +++++++++++++++-- .../nedap/archie/rminfo/RMAttributeInfo.java | 7 ++++++- .../rminfo/ReflectionModelInfoLookup.java | 2 ++ .../archetypevalidator/ArchetypeValidator.java | 1 + 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/aom/src/main/java/com/nedap/archie/aom/CObject.java b/aom/src/main/java/com/nedap/archie/aom/CObject.java index fdcc42694..b6c0d2aba 100644 --- a/aom/src/main/java/com/nedap/archie/aom/CObject.java +++ b/aom/src/main/java/com/nedap/archie/aom/CObject.java @@ -323,7 +323,7 @@ public MultiplicityInterval effectiveOccurrences(BiFunction Date: Wed, 29 Nov 2017 15:38:13 +0100 Subject: [PATCH 3/4] Fix serialization of differential paths --- .../serializer/adl/constraints/CComplexObjectSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aom/src/main/java/com/nedap/archie/serializer/adl/constraints/CComplexObjectSerializer.java b/aom/src/main/java/com/nedap/archie/serializer/adl/constraints/CComplexObjectSerializer.java index 2a797d13c..82e790160 100644 --- a/aom/src/main/java/com/nedap/archie/serializer/adl/constraints/CComplexObjectSerializer.java +++ b/aom/src/main/java/com/nedap/archie/serializer/adl/constraints/CComplexObjectSerializer.java @@ -85,7 +85,7 @@ private Set getTupleAttributeNames(T cobj) { private void buildAttribute(CAttribute cattr) { builder.tryNewLine(); - if (cattr.getRmAttributeName() != null) { + if (cattr.getDifferentialPath() == null) { builder.append(cattr.getRmAttributeName()); } else { builder.append(cattr.getDifferentialPath()); From b0c9c9c42f87708c0654c52cc7641d61ed38cf0b Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Thu, 30 Nov 2017 11:57:14 +0100 Subject: [PATCH 4/4] Add test for effectiveOccurrences --- .../com/nedap/archie/aom/CObjectTest.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/tools/src/test/java/com/nedap/archie/aom/CObjectTest.java b/tools/src/test/java/com/nedap/archie/aom/CObjectTest.java index d6d74609e..792b05aca 100644 --- a/tools/src/test/java/com/nedap/archie/aom/CObjectTest.java +++ b/tools/src/test/java/com/nedap/archie/aom/CObjectTest.java @@ -1,10 +1,14 @@ package com.nedap.archie.aom; import com.nedap.archie.ArchieLanguageConfiguration; +import com.nedap.archie.base.Cardinality; +import com.nedap.archie.base.MultiplicityInterval; import com.nedap.archie.testutil.TestUtil; import org.junit.Before; import org.junit.Test; +import java.util.function.BiFunction; + import static org.junit.Assert.assertEquals; /** @@ -53,6 +57,132 @@ public void testReplaceChild() { } + @Test + public void effectiveOccurrencesReturnsOccurrencesWhenPresent() { + CComplexObject parentCObject = new CComplexObject(); + parentCObject.setRmTypeName("COMPOSITION"); + CAttribute parentAttribute = new CAttribute(); + parentAttribute.setRmAttributeName("content"); + parentCObject.addAttribute(parentAttribute); + CComplexObject object = new CComplexObject(); + object.setRmTypeName("OBSERVATION"); + object.setOccurrences(MultiplicityInterval.createBounded(3,5)); + parentAttribute.addChild(object); + + assertEquals(MultiplicityInterval.createBounded(3, 5), object.effectiveOccurrences(new BiFunction() { + @Override + public MultiplicityInterval apply(String s, String s2) { + return new MultiplicityInterval(1,2); + } + })); + } + + @Test + public void effectiveOccurrencesReturnsDefaultOccurrences() { + CComplexObject parentCObject = new CComplexObject(); + parentCObject.setRmTypeName("COMPOSITION"); + CAttribute parentAttribute = new CAttribute(); + parentAttribute.setRmAttributeName("content"); + parentCObject.addAttribute(parentAttribute); + CComplexObject object = new CComplexObject(); + object.setRmTypeName("OBSERVATION"); + parentAttribute.addChild(object); + + assertEquals(MultiplicityInterval.createBounded(1, 2), object.effectiveOccurrences(new BiFunction() { + @Override + public MultiplicityInterval apply(String s, String s2) { + return new MultiplicityInterval(1,2); + } + })); + } + + @Test + public void effectiveOccurrencesCardinalityUnboundedDefinesUnboundedUpper() { + CComplexObject parentCObject = new CComplexObject(); + parentCObject.setRmTypeName("COMPOSITION"); + CAttribute parentAttribute = new CAttribute(); + parentAttribute.setCardinality(Cardinality.mandatoryAndUnbounded()); + parentAttribute.setRmAttributeName("content"); + parentCObject.addAttribute(parentAttribute); + CComplexObject object = new CComplexObject(); + object.setRmTypeName("OBSERVATION"); + parentAttribute.addChild(object); + + //even though it's mandatory, doesn't have to be _this_ element, so 0. + assertEquals(MultiplicityInterval.createUpperUnbounded(0), object.effectiveOccurrences(new BiFunction() { + @Override + public MultiplicityInterval apply(String s, String s2) { + return new MultiplicityInterval(1,2); + } + })); + } + + @Test + public void effectiveOccurrencesCardinalityBoundedDefinesUpper() { + CComplexObject parentCObject = new CComplexObject(); + parentCObject.setRmTypeName("COMPOSITION"); + CAttribute parentAttribute = new CAttribute(); + parentAttribute.setRmAttributeName("content"); + Cardinality cardinality = new Cardinality(); + cardinality.setInterval(MultiplicityInterval.createBounded(3, 4)); + parentAttribute.setCardinality(cardinality); + parentCObject.addAttribute(parentAttribute); + CComplexObject object = new CComplexObject(); + object.setRmTypeName("OBSERVATION"); + parentAttribute.addChild(object); + + //even though it's mandatory, doesn't have to be _this_ element, so 0. + assertEquals(MultiplicityInterval.createBounded(0, 4), object.effectiveOccurrences(new BiFunction() { + @Override + public MultiplicityInterval apply(String s, String s2) { + return new MultiplicityInterval(1,2); + } + })); + } + + @Test + public void effectiveOccurrencesExistenceDefinesLower() { + CComplexObject parentCObject = new CComplexObject(); + parentCObject.setRmTypeName("ELEMENT"); + CAttribute parentAttribute = new CAttribute(); + parentAttribute.setRmAttributeName("value"); + Cardinality cardinality = new Cardinality(); + cardinality.setInterval(MultiplicityInterval.createBounded(3, 4)); + parentAttribute.setCardinality(cardinality); + parentCObject.addAttribute(parentAttribute); + parentAttribute.setExistence(MultiplicityInterval.createMandatory()); + CComplexObject object = new CComplexObject(); + object.setRmTypeName("DV_CODED_TEXT"); + parentAttribute.addChild(object); + + assertEquals(MultiplicityInterval.createBounded(1, 4), object.effectiveOccurrences(new BiFunction() { + @Override + public MultiplicityInterval apply(String s, String s2) { + return new MultiplicityInterval(0,5); + } + })); + } + + @Test + public void effectiveOccurrencesExistenceIgnoredWhenDefaultOccurrencesUsed() { + CComplexObject parentCObject = new CComplexObject(); + parentCObject.setRmTypeName("ELEMENT"); + CAttribute parentAttribute = new CAttribute(); + parentAttribute.setRmAttributeName("value"); + + parentCObject.addAttribute(parentAttribute); + parentAttribute.setExistence(MultiplicityInterval.createMandatory()); + CComplexObject object = new CComplexObject(); + object.setRmTypeName("DV_CODED_TEXT"); + parentAttribute.addChild(object); + + assertEquals(MultiplicityInterval.createBounded(0, 5), object.effectiveOccurrences(new BiFunction() { + @Override + public MultiplicityInterval apply(String s, String s2) { + return new MultiplicityInterval(0,5); + } + })); + }