au.csiro.pathling.terminology.Relation Maven / Gradle / Ivy
Show all versions of terminology Show documentation
/*
* Copyright 2022 Commonwealth Scientific and Industrial Research
* Organisation (CSIRO) ABN 41 687 119 230.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package au.csiro.pathling.terminology;
import au.csiro.pathling.fhirpath.encoding.SimpleCoding;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
/**
* Represents relation between codings with implicit coding equality.
*
* Implicitly two codings that are equal are related. It may be a transitive or non-transitive
* relation depending on the construction, i.e: the creator is responsible for explicitly defining
* all related pairs for transitive closure (except of the equality).
*
* @author Piotr Szul
*/
@ToString
@EqualsAndHashCode
public class Relation implements Serializable {
private static final long serialVersionUID = -8124924480216379884L;
/**
* An entry representing the existence of relation between {@code form} and {@code to}.
*/
@Data
@AllArgsConstructor(staticName = "of")
public static class Entry implements Serializable {
private static final long serialVersionUID = 1L;
@NonNull
private final SimpleCoding from;
@NonNull
private final SimpleCoding to;
}
/**
* Set of codings with contains() following the coding's equivalence semantics.
*
* @author Piotr Szul
*/
static class CodingSet {
@Nonnull
private final Set allCodings;
@Nonnull
private final Set unversionedCodings;
CodingSet(@Nonnull final Set allCodings) {
this.allCodings = allCodings;
this.unversionedCodings =
allCodings.stream().map(SimpleCoding::toNonVersioned).collect(Collectors.toSet());
}
/**
* Belongs to set operation with the coding's equivalence semantics, i.e. if the set includes an
* unversioned coding, it contains any versioned coding with the same system code, and; if a set
* contains a versioned coding, it contains its corresponding unversioned coding as well.
*
* @param c coding
*/
boolean contains(@Nonnull final SimpleCoding c) {
return allCodings.contains(c) || (c.isVersioned()
? allCodings.contains(c.toNonVersioned())
: unversionedCodings.contains(c));
}
}
@Nonnull
private final Map> mappings;
/**
* Private constructor. Use {@link #equality()} or {@link #fromMappings} to create instances.
*/
private Relation(@Nonnull final Map> mappings) {
this.mappings = mappings;
}
/**
* Expands given set of Codings using with the closure, that is produces a set of Codings that are
* in the relation with the given set.
*/
@Nonnull
private Set expand(@Nonnull final Set codings) {
final Relation.CodingSet baseSet = new Relation.CodingSet(codings);
return Stream
.concat(codings.stream(), mappings.entrySet().stream()
.filter(kv -> baseSet.contains(kv.getKey())).flatMap(kv -> kv.getValue().stream()))
.collect(Collectors.toSet());
}
/**
* Checks if any of the Codings in the right set is in the relation with any of the Codings in the
* left set.
*
* @param left a collections of codings.
* @param right a collection of codings.
* @return true if any left coding is related to any right coding.
*/
public boolean anyRelates(@Nonnull final Collection left,
@Nonnull final Collection right) {
// filter out null SystemAndCodes
final Set leftSet =
left.stream().filter(SimpleCoding::isDefined).collect(Collectors.toSet());
final Relation.CodingSet expansion = new Relation.CodingSet(expand(leftSet));
return right.stream().anyMatch(expansion::contains);
}
/**
* Constructs relation from given list of related pairs of codings.
*
* The relation is assumed to include coding equality so {@code (A,A)} is assumed. All other the
* related pairs need to be explicitly listed. If the relation is meant to represent a transitive
* closure with implicit equality such that: {@code A -> B -> C} then the entry list must include
* the all pairs of: {@code [(A,B), (B,C), (A, C)]}.
*
* @param entries the list of pair of codings that are related.
* @return the relation instance.
*/
@Nonnull
public static Relation fromMappings(@Nonnull final Collection entries) {
final Map> groupedMappings =
entries.stream().collect(Collectors.groupingBy(Entry::getFrom));
final Map> groupedCodings =
groupedMappings.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
.map(Entry::getTo)
.collect(Collectors.toList())));
return new Relation(groupedCodings);
}
/**
* Constructs a relation that only includes coding equality.
*
* @return the equality relation instance.
*/
@Nonnull
public static Relation equality() {
return new Relation(Collections.emptyMap());
}
}