
001/*- 002 * #%L 003 * HAPI FHIR - Master Data Management 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.mdm.rules.json; 021 022import ca.uhn.fhir.context.ConfigurationException; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.mdm.api.MdmMatchResultEnum; 025import jakarta.annotation.Nonnull; 026 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.Map; 030import java.util.Set; 031import java.util.stream.Collectors; 032 033public class VectorMatchResultMap { 034 private final MdmRulesJson myMdmRulesJson; 035 private Map<Long, MdmMatchResultEnum> myVectorToMatchResultMap = new HashMap<>(); 036 private Set<Long> myMatchVectors = new HashSet<>(); 037 private Set<Long> myPossibleMatchVectors = new HashSet<>(); 038 private Map<Long, String> myVectorToFieldMatchNamesMap = new HashMap<>(); 039 040 VectorMatchResultMap(MdmRulesJson theMdmRulesJson) { 041 myMdmRulesJson = theMdmRulesJson; 042 // no reason to hold the entire mdmRulesJson here 043 initMap(); 044 } 045 046 private void initMap() { 047 for (Map.Entry<String, MdmMatchResultEnum> entry : 048 myMdmRulesJson.getMatchResultMap().entrySet()) { 049 put(entry.getKey(), entry.getValue()); 050 } 051 } 052 053 @Nonnull 054 public MdmMatchResultEnum get(Long theMatchVector) { 055 return myVectorToMatchResultMap.computeIfAbsent(theMatchVector, this::computeMatchResult); 056 } 057 058 private MdmMatchResultEnum computeMatchResult(Long theVector) { 059 if (myMatchVectors.stream().anyMatch(v -> (v & theVector) == v)) { 060 return MdmMatchResultEnum.MATCH; 061 } 062 if (myPossibleMatchVectors.stream().anyMatch(v -> (v & theVector) == v)) { 063 return MdmMatchResultEnum.POSSIBLE_MATCH; 064 } 065 return MdmMatchResultEnum.NO_MATCH; 066 } 067 068 public Set<String> getMatchedRules(Long theVector) { 069 if (theVector == null) { 070 return new HashSet<>(); 071 } 072 return myVectorToFieldMatchNamesMap.entrySet().stream() 073 .filter(e -> ((e.getKey() & theVector) == e.getKey())) 074 .map(Map.Entry::getValue) 075 .collect(Collectors.toSet()); 076 } 077 078 private void put(String theFieldMatchNames, MdmMatchResultEnum theMatchResult) { 079 long vector = getVector(theFieldMatchNames); 080 myVectorToFieldMatchNamesMap.put(vector, theFieldMatchNames); 081 myVectorToMatchResultMap.put(vector, theMatchResult); 082 if (theMatchResult == MdmMatchResultEnum.MATCH) { 083 myMatchVectors.add(vector); 084 } else if (theMatchResult == MdmMatchResultEnum.POSSIBLE_MATCH) { 085 myPossibleMatchVectors.add(vector); 086 } 087 } 088 089 /** 090 * Calculates the vector for the match rule. 091 * 092 * This calculation uses the index of the match field and the binary 093 * shift operator (<<) to calculate the field. 094 * 095 * See {@link ca.uhn.fhir.mdm.rules.matcher.util.MatchRuleUtil#MAX_RULE_COUNT} 096 * 097 * @param theFieldMatchNames the match rule (eg, "match_field1,match_field2") 098 * @return the vector expressed as a long 099 */ 100 public long getVector(String theFieldMatchNames) { 101 long retval = 0; 102 for (String fieldMatchName : splitFieldMatchNames(theFieldMatchNames)) { 103 int index = getFieldMatchIndex(fieldMatchName); 104 if (index == -1) { 105 throw new ConfigurationException(Msg.code(1523) + "There is no matchField with name " + fieldMatchName); 106 } 107 retval |= (1L << index); 108 } 109 return retval; 110 } 111 112 @Nonnull 113 static String[] splitFieldMatchNames(String theFieldMatchNames) { 114 return theFieldMatchNames.split(",\\s*"); 115 } 116 117 private int getFieldMatchIndex(final String theFieldMatchName) { 118 for (int i = 0; i < myMdmRulesJson.size(); ++i) { 119 if (myMdmRulesJson.get(i).getName().equals(theFieldMatchName)) { 120 return i; 121 } 122 } 123 return -1; 124 } 125 126 public String getFieldMatchNames(long theVector) { 127 return myVectorToFieldMatchNamesMap.get(theVector); 128 } 129 130 public Set<String> getAllFieldMatchNames() { 131 return myVectorToFieldMatchNamesMap.keySet().stream() 132 .map(key -> myVectorToFieldMatchNamesMap.get(key)) 133 .collect(Collectors.toSet()); 134 } 135}