diff --git a/pom.xml b/pom.xml
index c988612c..d8940f8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
org.pankratzlab
donor-check
- 0.0.14
+ 0.1.15
jar
A stand-alone tool for validating DonorNet typing entries.
@@ -73,6 +73,11 @@
+
+ org.pankratzlab
+ BackgroundDataProcessor-java8
+ 1.0-SNAPSHOT
+
org.apache.maven
maven-model
diff --git a/src/main/java/org/pankratzlab/unet/deprecated/hla/Antigen.java b/src/main/java/org/pankratzlab/unet/deprecated/hla/Antigen.java
index c3fe0719..d876d2aa 100644
--- a/src/main/java/org/pankratzlab/unet/deprecated/hla/Antigen.java
+++ b/src/main/java/org/pankratzlab/unet/deprecated/hla/Antigen.java
@@ -2,7 +2,7 @@
* #%L
* DonorCheck
* %%
- * Copyright (C) 2018 - 2019 Computational Pathology - University of Minnesota
+ * Copyright (C) 2018 - 2024 Computational Pathology - University of Minnesota
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
@@ -39,7 +39,6 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
@@ -232,16 +231,22 @@ private List parse(String[] p) {
int[] iVal = new int[1];
Matcher matcher = SPEC_PATTERN.matcher(spec);
matcher.find();
- // Extracts value from parents if they were present
- String val = matcher.group(2) != null ? matcher.group(2) : matcher.group(1);
+
+ // Extracts value outside of parentheses if they were present (changed from returning value
+ // inside parens, 5/24 - @rcoleb)
+ String val = matcher.group(1);
if (val.contains(SPEC_DELIM)) {
// Was passed XX:YY
return parse(val.split(SPEC_DELIM));
}
// "0105" should be "01:05"
- if (val.length() > 2 && val.charAt(0) == '0') {
- return parse(new String[] {spec.substring(0, 2), spec.substring(2)});
+ if (val.length() > 2) {
+ if (val.charAt(0) == '0') {
+ return parse(new String[] {spec.substring(0, 2), spec.substring(2)});
+ } else {
+ // System.out.println();
+ }
}
iVal[0] = Integer.parseInt(val);
return parse(iVal);
diff --git a/src/main/java/org/pankratzlab/unet/deprecated/hla/AntigenDictionary.java b/src/main/java/org/pankratzlab/unet/deprecated/hla/AntigenDictionary.java
index b5b7d227..ccf24d3a 100644
--- a/src/main/java/org/pankratzlab/unet/deprecated/hla/AntigenDictionary.java
+++ b/src/main/java/org/pankratzlab/unet/deprecated/hla/AntigenDictionary.java
@@ -31,7 +31,6 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
@@ -45,11 +44,7 @@
/** Persistent map of {@link HLAType}s to equivalent {@link SeroType}s. Use when converting. */
public final class AntigenDictionary implements Serializable {
- private static final long serialVersionUID = 10L;
-
- public static final int LATEST_REVISION =
- 1 + HLAType.LATEST_REVISION + SeroType.LATEST_REVISION + Antigen.LATEST_REVISION;
- private int revision = LATEST_REVISION;
+ private static final long serialVersionUID = 12L;
public static final String REL_DNA_SER_PROP = "rel.dna.ser.file";
@@ -178,6 +173,7 @@ private static void parseDictionaries(Callable readerSupplier) {
}
ImmutableSetMultimap.Builder hlaBuilder = ImmutableSetMultimap.builder();
ImmutableSetMultimap.Builder seroBuilder = ImmutableSetMultimap.builder();
+
// NB: what's considered a valid HLA type diverges from the HLA map keyset and thus must be
// tracked separately
Builder validHLATypes = ImmutableSet.builder();
@@ -208,16 +204,20 @@ private static void parseDictionaries(Callable readerSupplier) {
// Parse out the serological specificities for this mapping
// Since the columns are ordered by specificity, we use the first column with valid entries
- Set seroSpecs = new HashSet<>();
- for (int i = 2; i <= 5 && i < columns.length; i++) {
- String types = columns[i];
- // Each HLA type may map to multiple serotypes
- for (String t : types.split(TYPE_DELIM)) {
- if (!t.isEmpty()) {
- seroSpecs.add(t);
- }
- }
+ // Set seroSpecs = new LinkedHashSet<>();
+ List seroSpecs = new ArrayList<>();
+ if (!columns[2].isEmpty()) {
+ seroSpecs.add(columns[2]);
}
+ // for (int i = 2; i <= 5 && i < columns.length; i++) {
+ // String types = columns[i];
+ // // Each HLA type may map to multiple serotypes
+ // for (String t : types.split(TYPE_DELIM)) {
+ // if (!t.isEmpty() && !seroSpecs.contains(t)) {
+ // seroSpecs.add(t);
+ // }
+ // }
+ // }
// Skip null types
if (seroSpecs.stream().anyMatch(NULL_TYPE::equals)) {
@@ -239,25 +239,26 @@ private static void parseDictionaries(Callable readerSupplier) {
for (int i = 0; i < specValues.length; i++) {
specValues[i] = specValues[i].trim().replaceAll("[^0-9]", "");
spec.add(Integer.parseInt(specValues[i]));
+ }
+
+ HLAType hlaType = new HLAType(l, spec);
+
+ SeroLocus sl = l.sero();
+ for (String t : seroSpecs) {
+ // Convert unknown types to the first spec value
+ if (UNKNOWN_TYPE.equals(t)) {
+ t = specValues[0];
+ }
+ SeroType seroType = new SeroType(sl, t);
+ hlaBuilder.put(hlaType, seroType);
- HLAType hlaType = new HLAType(l, spec);
-
- SeroLocus sl = l.sero();
- for (String t : seroSpecs) {
- // Convert unknown types to the first spec value
- if (UNKNOWN_TYPE.equals(t)) {
- t = specValues[0];
- }
- SeroType seroType = new SeroType(sl, t);
- hlaBuilder.put(hlaType, seroType);
-
- // Only map from sero > hla if we have 2 or more specificities
- if (spec.size() > 1) {
- validHLATypes.add(hlaType);
- seroBuilder.put(seroType, hlaType);
- }
+ // Only map from sero > hla if we have 2 or more specificities
+ if (spec.size() > 1) {
+ validHLATypes.add(hlaType);
+ seroBuilder.put(seroType, hlaType);
}
}
+ // }
}
// Build the singleton map and write it to disk
diff --git a/src/main/java/org/pankratzlab/unet/deprecated/hla/HLALocus.java b/src/main/java/org/pankratzlab/unet/deprecated/hla/HLALocus.java
index ebc158ef..e30da2b3 100644
--- a/src/main/java/org/pankratzlab/unet/deprecated/hla/HLALocus.java
+++ b/src/main/java/org/pankratzlab/unet/deprecated/hla/HLALocus.java
@@ -25,10 +25,10 @@
/** {@link Locus} implementation for HLA types */
public enum HLALocus implements Locus {
- A(SeroLocus.A, TIER_1), B(SeroLocus.B, TIER_1), C(SeroLocus.C, TIER_1), DRB1(SeroLocus.DRB,
- TIER_2), DRB3(SeroLocus.DRB, TIER_2), DRB4(SeroLocus.DRB, TIER_2), DRB5(SeroLocus.DRB,
- TIER_2), DQA1(SeroLocus.DQA, TIER_2), DQB1(SeroLocus.DQB, TIER_2), DPA1(SeroLocus.DPA,
- TIER_2), DPB1(SeroLocus.DPB, TIER_2), MICA(SeroLocus.MICA, TIER_2);
+ A(SeroLocus.A, MHC_CLASS_1), B(SeroLocus.B, MHC_CLASS_1), C(SeroLocus.C, MHC_CLASS_1), DRB1(SeroLocus.DRB,
+ MHC_CLASS_2), DRB3(SeroLocus.DRB, MHC_CLASS_2), DRB4(SeroLocus.DRB, MHC_CLASS_2), DRB5(SeroLocus.DRB,
+ MHC_CLASS_2), DQA1(SeroLocus.DQA, MHC_CLASS_2), DQB1(SeroLocus.DQB, MHC_CLASS_2), DPA1(SeroLocus.DPA,
+ MHC_CLASS_2), DPB1(SeroLocus.DPB, MHC_CLASS_2), MICA(SeroLocus.MICA, MHC_CLASS_2);
private final SeroLocus sero;
private final int tier;
diff --git a/src/main/java/org/pankratzlab/unet/deprecated/hla/HLAType.java b/src/main/java/org/pankratzlab/unet/deprecated/hla/HLAType.java
index 343ba6e1..cb77694b 100644
--- a/src/main/java/org/pankratzlab/unet/deprecated/hla/HLAType.java
+++ b/src/main/java/org/pankratzlab/unet/deprecated/hla/HLAType.java
@@ -21,7 +21,9 @@
*/
package org.pankratzlab.unet.deprecated.hla;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -29,7 +31,6 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-
import com.google.common.primitives.Ints;
/** {@link Antigen} implementation for HLA antigens */
@@ -85,27 +86,54 @@ public HLAType(HLALocus l, List p) {
* Note: this method is similar to {@link AntigenDictionary#lookup(HLAType)}, with two exceptions:
*
*
- * In the case of multiple {@link SeroType} mappings, only the first will be returned
+ * In the case of multiple {@link SeroType} mappings, only the first will be returned
* If there is no explicit mapping for this type, a {@code SeroType} will be created using the
* equivalent {@link SeroLocus} and the first value in this type's {@link #spec()}
*
*
- * @return {@link SeroType} equivalent of this antigen
- * @throws IllegalStateException If this type has ambiguous serotype equivalencies
+ * @return {@link SeroType} equivalent of this antigen
+ *
+ *
*/
public SeroType equiv() {
+ Set lookup = new HashSet<>();
try {
- Set lookup = AntigenDictionary.lookup(this);
- if (lookup.size() == 1) {
- return lookup.iterator().next();
- } else if (lookup.size() > 1) {
- throw new IllegalStateException(
- "HLA type: " + this + " has multiple serotype equivalencies");
- }
+ lookup = AntigenDictionary.lookup(this);
} catch (IllegalArgumentException e) {
+ if (this.resolution() < 3) {
+ // grow spec with 01s until a mapping (if any) is found)
+ HLAType t = this;
+ while (t != null && lookup.isEmpty()) {
+ t = growSpec(t);
+ try {
+ lookup = AntigenDictionary.lookup(t);
+ } catch (IllegalArgumentException e2) {
+ // might not need this
+ lookup = new HashSet<>();
+ }
+ }
+ } else if (this.resolution() > 2) {
+ // reduce spec by removing any 01s
+ HLAType t = this;
+ while (t != null && lookup.isEmpty()) {
+ t = reduceSpec(t);
+ try {
+ lookup = AntigenDictionary.lookup(t);
+ } catch (IllegalArgumentException e2) {
+ // might not need this
+ lookup = new HashSet<>();
+ }
+ }
+ }
// No-op
}
+ if (lookup.size() == 1) {
+ return lookup.iterator().next();
+ } else if (lookup.size() > 1) {
+ return lookup.iterator().next();
+ }
+
// If we don't have an explicit mapping of this HLAType, just use the first spec
return lowResEquiv();
}
@@ -148,6 +176,49 @@ protected List parse(int[] p) {
return values;
}
+ /**
+ * @param equivType Input type to reduce
+ * @return The input {@link HLAType} with its tailing "01" field removed, or null if the allele
+ * can not be reduced
+ */
+ public static HLAType reduceSpec(HLAType equivType) {
+ List spec = equivType.spec();
+
+ if (spec.size() < 2 || (spec.size() < 4 && spec.get(spec.size() - 1) != 1)) {
+ // We can only remove a trailing "01 "specificity, and only if we have 3- or more fields
+ return null;
+ }
+
+ spec = spec.subList(0, spec.size() - 1);
+ return HLAType.modifiedSpec(equivType, spec);
+ }
+
+ /**
+ * @param equivType Input type to expand
+ * @return The input {@link HLAType} with an additional "01" field, or null if the allele can not
+ * be further expanded
+ */
+ public static HLAType growSpec(HLAType equivType) {
+ List spec = new ArrayList<>(equivType.spec());
+
+ if (spec.size() >= 4) {
+ // We can only expand 2- and 3-field specificities
+ return null;
+ }
+
+ spec.add(1);
+
+ return HLAType.modifiedSpec(equivType, spec);
+ }
+
+ /** Helper method to create an updated HLAType */
+ private static HLAType modifiedSpec(HLAType equivType, List spec) {
+ if (equivType instanceof NullType) {
+ return new NullType(equivType.locus(), spec);
+ }
+ return new HLAType(equivType.locus(), spec);
+ }
+
/** @see Antigen#is(String, Pattern) */
public static boolean is(String text) {
return Antigen.is(text, TYPE_PATTERN);
diff --git a/src/main/java/org/pankratzlab/unet/deprecated/hla/Locus.java b/src/main/java/org/pankratzlab/unet/deprecated/hla/Locus.java
index 262bb845..b8674eb2 100644
--- a/src/main/java/org/pankratzlab/unet/deprecated/hla/Locus.java
+++ b/src/main/java/org/pankratzlab/unet/deprecated/hla/Locus.java
@@ -23,8 +23,8 @@
/** Marker interface for antigen loci */
public interface Locus extends Comparable {
- public static int TIER_1 = 1;
- public static int TIER_2 = 2;
+ public static int MHC_CLASS_1 = 1;
+ public static int MHC_CLASS_2 = 2;
/** @return Name of this locus */
String name();
diff --git a/src/main/java/org/pankratzlab/unet/deprecated/hla/SeroLocus.java b/src/main/java/org/pankratzlab/unet/deprecated/hla/SeroLocus.java
index c3eabec6..92c6b1a6 100644
--- a/src/main/java/org/pankratzlab/unet/deprecated/hla/SeroLocus.java
+++ b/src/main/java/org/pankratzlab/unet/deprecated/hla/SeroLocus.java
@@ -29,8 +29,8 @@
/** {@link Locus} implementation for serological types */
public enum SeroLocus implements Locus {
- A(2, TIER_1), B(2, TIER_1), C(3, TIER_1), DRB(1, TIER_2, "DR"), DQB(1, TIER_2, "DQ"), DQA(1,
- TIER_2), DPB(1, TIER_2, "DP"), DPA(1, TIER_2), MICA(1, -1);
+ A(2, MHC_CLASS_1), B(2, MHC_CLASS_1), C(3, MHC_CLASS_1), DRB(1, MHC_CLASS_2, "DR"), DQB(1, MHC_CLASS_2, "DQ"), DQA(1,
+ MHC_CLASS_2), DPB(1, MHC_CLASS_2, "DP"), DPA(1, MHC_CLASS_2), MICA(1, -1);
private final int severity;
private final int tier;
diff --git a/src/main/java/org/pankratzlab/unet/deprecated/hla/SourceType.java b/src/main/java/org/pankratzlab/unet/deprecated/hla/SourceType.java
new file mode 100644
index 00000000..d448f573
--- /dev/null
+++ b/src/main/java/org/pankratzlab/unet/deprecated/hla/SourceType.java
@@ -0,0 +1,5 @@
+package org.pankratzlab.unet.deprecated.hla;
+
+public enum SourceType {
+ Score6, SureTyper, DonorNet;
+}
diff --git a/src/main/java/org/pankratzlab/unet/hapstats/CommonWellDocumented.java b/src/main/java/org/pankratzlab/unet/hapstats/CommonWellDocumented.java
index 1646b753..26004308 100644
--- a/src/main/java/org/pankratzlab/unet/hapstats/CommonWellDocumented.java
+++ b/src/main/java/org/pankratzlab/unet/hapstats/CommonWellDocumented.java
@@ -24,7 +24,6 @@
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -36,7 +35,6 @@
import org.jsoup.select.Elements;
import org.pankratzlab.unet.deprecated.hla.HLAProperties;
import org.pankratzlab.unet.deprecated.hla.HLAType;
-import org.pankratzlab.unet.deprecated.hla.NullType;
import org.pankratzlab.unet.parser.XmlDonorParser;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@@ -71,7 +69,7 @@ public final class CommonWellDocumented {
CacheBuilder.newBuilder().build(CacheLoader.from(CommonWellDocumented::doGetStatus));
public static enum Status {
- COMMON(1.0), INTERMEDIATE(0.5), WELL_DOCUMENTED(0.5), UNKNOWN(0.0);
+ COMMON(1.0), INTERMEDIATE(0.5), WELL_DOCUMENTED(0.25), UNKNOWN(0.0);
private final double weight;
@@ -144,32 +142,37 @@ public static void init() {
cd.setContentText("You may change this selection from the DonorCheck menu bar.");
Optional result = cd.showAndWait();
result.ifPresent(r -> {
- try {
- switch (r) {
- case CWD_200:
- loadCWD200();
- break;
- case CIWD_300:
- loadCIWD300();
- break;
- default:
- break;
- }
-
- // save the selected value
- HLAProperties.get().setProperty(CWD_PROP, r.name());
-
- } catch (Exception e) {
- e.printStackTrace();
- }
+ loadCIWDVersion(r);
});
if (!result.isPresent()) {
- throw new IllegalStateException("CWD DB must be selected");
+ // user cancelled, load previous/default version
+ loadCIWDVersion(def);
}
}
+ private static void loadCIWDVersion(SOURCE r) {
+ try {
+ switch (r) {
+ case CWD_200:
+ loadCWD200();
+ break;
+ case CIWD_300:
+ loadCIWD300();
+ break;
+ default:
+ break;
+ }
+
+ // save the selected value
+ HLAProperties.get().setProperty(CWD_PROP, r.name());
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
public static void loadCWD200() {
// -- Read allele frequency map --
try (InputStream htmlStream = XmlDonorParser.class.getResourceAsStream(ALLELE_FREQ_PATH_200)) {
@@ -278,7 +281,7 @@ public static HLAType getCWDType(HLAType type) {
// adding or removing trailing :01's does not change the allele specificity
// Try adding :01's to the specificity
HLAType specModified = type;
- while (Objects.nonNull((specModified = growSpec(specModified)))) {
+ while (Objects.nonNull((specModified = HLAType.growSpec(specModified)))) {
if (ALLELE_FREQS.containsKey(specModified)) {
return specModified;
}
@@ -286,7 +289,7 @@ public static HLAType getCWDType(HLAType type) {
// Try removing fourth field, or tailing :01's, to the specificity
specModified = type;
- while (Objects.nonNull((specModified = reduceSpec(specModified)))) {
+ while (Objects.nonNull((specModified = HLAType.reduceSpec(specModified)))) {
if (ALLELE_FREQS.containsKey(specModified)) {
return specModified;
}
@@ -302,7 +305,7 @@ private static Status doGetStatus(HLAType type) {
// adding or removing trailing :01's does not change the allele specificity
// Try adding :01's to the specificity
HLAType specModified = type;
- while (Objects.nonNull((specModified = growSpec(specModified)))) {
+ while (Objects.nonNull((specModified = HLAType.growSpec(specModified)))) {
if (ALLELE_FREQS.containsKey(specModified)) {
return ALLELE_FREQS.get(specModified);
}
@@ -310,7 +313,7 @@ private static Status doGetStatus(HLAType type) {
// Try removing fourth field, or tailing :01's, to the specificity
specModified = type;
- while (Objects.nonNull((specModified = reduceSpec(specModified)))) {
+ while (Objects.nonNull((specModified = HLAType.reduceSpec(specModified)))) {
if (ALLELE_FREQS.containsKey(specModified)) {
return ALLELE_FREQS.get(specModified);
}
@@ -318,49 +321,6 @@ private static Status doGetStatus(HLAType type) {
return Status.UNKNOWN;
}
- /**
- * @param equivType Input type to reduce
- * @return The input {@link HLAType} with its tailing "01" field removed, or null if the allele
- * can not be reduced
- */
- private static HLAType reduceSpec(HLAType equivType) {
- List spec = equivType.spec();
-
- if (spec.size() < 2 || (spec.size() < 4 && spec.get(spec.size() - 1) != 1)) {
- // We can only remove a trailing "01 "specificity, and only if we have 3- or more fields
- return null;
- }
-
- spec = spec.subList(0, spec.size() - 1);
- return modifiedSpec(equivType, spec);
- }
-
- /**
- * @param equivType Input type to expand
- * @return The input {@link HLAType} with an additional "01" field, or null if the allele can not
- * be further expanded
- */
- private static HLAType growSpec(HLAType equivType) {
- List spec = new ArrayList<>(equivType.spec());
-
- if (spec.size() >= 4) {
- // We can only expand 2- and 3-field specificities
- return null;
- }
-
- spec.add(1);
-
- return modifiedSpec(equivType, spec);
- }
-
- /** Helper method to create an updated HLAType */
- private static HLAType modifiedSpec(HLAType equivType, List spec) {
- if (equivType instanceof NullType) {
- return new NullType(equivType.locus(), spec);
- }
- return new HLAType(equivType.locus(), spec);
- }
-
/**
* @param alleles Set of alleles
* @return A combined weighting of the input allele set, based on their CWD frequencies
diff --git a/src/main/java/org/pankratzlab/unet/jfx/StyleableChoiceDialog.java b/src/main/java/org/pankratzlab/unet/jfx/StyleableChoiceDialog.java
deleted file mode 100644
index c3228e92..00000000
--- a/src/main/java/org/pankratzlab/unet/jfx/StyleableChoiceDialog.java
+++ /dev/null
@@ -1,253 +0,0 @@
-package org.pankratzlab.unet.jfx;
-
-import java.util.Collection;
-import java.util.Map;
-import com.google.common.collect.Multimap;
-import com.sun.javafx.scene.control.skin.resources.ControlResources;
-import javafx.application.Platform;
-import javafx.beans.property.ReadOnlyObjectProperty;
-import javafx.collections.ObservableList;
-import javafx.geometry.Pos;
-import javafx.scene.Node;
-import javafx.scene.control.ButtonBar.ButtonData;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.ComboBox;
-import javafx.scene.control.Dialog;
-import javafx.scene.control.DialogPane;
-import javafx.scene.control.Label;
-import javafx.scene.control.ListCell;
-import javafx.scene.control.ListView;
-import javafx.scene.layout.GridPane;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.Region;
-import javafx.util.Callback;
-import javafx.util.StringConverter;
-
-/**
- * A dialog that shows a list of choices to the user, from which they can pick one item at most.
- *
- * @see Dialog
- * @param The type of the items to show to the user, and the type that is returned via
- * {@link #getResult()} when the dialog is dismissed.
- * @since JavaFX 8u40
- */
-@SuppressWarnings("restriction")
-public class StyleableChoiceDialog extends Dialog {
-
- /**************************************************************************
- *
- * Fields
- *
- **************************************************************************/
-
- private final GridPane grid;
- private final Label label;
- private final ComboBox comboBox1;
- private final ComboBox comboBox2;
- private final T defaultChoice;
-
- /**************************************************************************
- *
- * Constructor
- *
- **************************************************************************/
-
- /**
- * Creates a new ChoiceDialog instance with the first argument specifying the default choice that
- * should be shown to the user, and the second argument specifying a collection of all available
- * choices for the user. It is expected that the defaultChoice be one of the elements in the
- * choices collection. If this is not true, then defaultChoice will be set to null and the dialog
- * will show with the initial choice set to the first item in the list of choices.
- *
- * @param defaultChoice The item to display as the pre-selected choice in the dialog. This item
- * must be contained within the choices varargs array.
- * @param choices All possible choices to present to the user.
- */
- public StyleableChoiceDialog(T defaultChoice, Collection choices,
- Multimap secondChoiceMap, Map valueMap) {
- final DialogPane dialogPane = getDialogPane();
-
- // -- grid
- this.grid = new GridPane();
- this.grid.setHgap(10);
- this.grid.setVgap(10);
- this.grid.setMaxWidth(Double.MAX_VALUE);
- this.grid.setAlignment(Pos.CENTER_LEFT);
-
- // -- label
- label = createContentLabel(dialogPane.getContentText());
- label.setPrefWidth(Region.USE_COMPUTED_SIZE);
- label.textProperty().bind(dialogPane.contentTextProperty());
-
- setTitle(ControlResources.getString("Dialog.confirm.title"));
- dialogPane.setHeaderText(ControlResources.getString("Dialog.confirm.header"));
- dialogPane.getStyleClass().add("choice-dialog");
- dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
-
- final double MIN_WIDTH = 150;
-
- comboBox2 = new ComboBox();
- comboBox2.setMinWidth(MIN_WIDTH);
- if (choices != null) {
- comboBox2.getItems().addAll(choices);
- }
- comboBox2.setMaxWidth(Double.MAX_VALUE);
- GridPane.setHgrow(comboBox2, Priority.ALWAYS);
- GridPane.setFillWidth(comboBox2, true);
- comboBox2.setMaxHeight(10);
- comboBox2.getSelectionModel().selectFirst();
-
- comboBox1 = new ComboBox();
- // comboBox1.setEditable(true);
- comboBox1.setMinWidth(MIN_WIDTH);
- if (choices != null) {
- comboBox1.getItems().addAll(choices);
- }
- comboBox1.setMaxWidth(Double.MAX_VALUE);
- GridPane.setHgrow(comboBox1, Priority.ALWAYS);
- GridPane.setFillWidth(comboBox1, true);
- comboBox1.setMaxHeight(10);
-
- // comboBox1.setOnKeyTyped((kv) -> {
- // try {
- // comboBox1.getEditor().getText();
- // comboBox1.getItems().clear();
- //
- // comboBox1.getItems().addAll(choices);
- // comboBox1.show();
- // } catch (Throwable t) {
- // t.printStackTrace();
- // }
- // // TODO
- // });
-
- comboBox1.valueProperty().addListener((obs, oldV, newV) -> {
- try {
- final Collection c = secondChoiceMap.get(newV);
-
- comboBox2.getSelectionModel().clearSelection();
- comboBox2.getItems().setAll(c);
- comboBox2.setDisable(c.isEmpty());
- comboBox2.setPromptText(c.isEmpty() ? "No valid pairings found." : null);
- } catch (Throwable t) {
- t.printStackTrace();
- }
- });
-
- this.defaultChoice = comboBox1.getItems().contains(defaultChoice) ? defaultChoice : null;
-
- if (defaultChoice == null) {
- comboBox1.getSelectionModel().selectFirst();
- } else {
- comboBox1.getSelectionModel().select(defaultChoice);
- }
-
- dialogPane.contentTextProperty().addListener(o -> updateGrid(comboBox1));
-
- updateGrid(comboBox1);
-
- setResultConverter((dialogButton) -> {
- ButtonData data = dialogButton == null ? null : dialogButton.getButtonData();
- return data == ButtonData.OK_DONE ? getSelectedItem() : null;
- });
- }
-
- /**************************************************************************
- *
- * Public API
- *
- **************************************************************************/
-
- /**
- * Returns the currently selected item in the dialog.
- */
- public final T getSelectedItem() {
- return comboBox1.getSelectionModel().getSelectedItem();
- }
-
- public final T getSelectedSecondItem() {
- return comboBox2.getSelectionModel().getSelectedItem();
- }
-
- public void setCombo1CellFactory(Callback, ListCell> value) {
- comboBox1.setCellFactory(value);
- }
-
- public void setCombo2CellFactory(Callback, ListCell> value) {
- comboBox2.setCellFactory(value);
- }
-
- public void setCombo1ButtonCell(ListCell value) {
- comboBox1.setButtonCell(value);
- }
-
- public void setCombo2ButtonCell(ListCell value) {
- comboBox2.setButtonCell(value);
- }
-
- public void setConverter1(StringConverter c) {
- comboBox1.setConverter(c);
- }
-
- public void setConverter2(StringConverter c) {
- comboBox2.setConverter(c);
- }
-
- /**
- * Returns the property representing the currently selected item in the dialog.
- */
- public final ReadOnlyObjectProperty selectedItemProperty() {
- return comboBox1.getSelectionModel().selectedItemProperty();
- }
-
- /**
- * Sets the currently selected item in the dialog.
- *
- * @param item The item to select in the dialog.
- */
- public final void setSelectedItem(T item) {
- comboBox1.getSelectionModel().select(item);
- }
-
- /**
- * Returns the list of all items that will be displayed to users. This list can be modified by the
- * developer to add, remove, or reorder the items to present to the user.
- */
- public final ObservableList getItems() {
- return comboBox1.getItems();
- }
-
- /**
- * Returns the default choice that was specified in the constructor.
- */
- public final T getDefaultChoice() {
- return defaultChoice;
- }
-
- /**************************************************************************
- *
- * Private Implementation
- *
- **************************************************************************/
-
- private static Label createContentLabel(String text) {
- Label label = new Label(text);
- label.setMaxWidth(Double.MAX_VALUE);
- label.setMaxHeight(Double.MAX_VALUE);
- label.getStyleClass().add("content");
- label.setWrapText(true);
- label.setPrefWidth(360);
- return label;
- }
-
- private void updateGrid(Node focusReq) {
- grid.getChildren().clear();
-
- grid.add(label, 0, 0);
- grid.add(comboBox1, 0, 1);
- grid.add(comboBox2, 0, 2);
- getDialogPane().setContent(grid);
-
- Platform.runLater(() -> focusReq.requestFocus());
- }
-}
diff --git a/src/main/java/org/pankratzlab/unet/jfx/StyleableContingentChoiceDialog.java b/src/main/java/org/pankratzlab/unet/jfx/StyleableContingentChoiceDialog.java
new file mode 100644
index 00000000..00611807
--- /dev/null
+++ b/src/main/java/org/pankratzlab/unet/jfx/StyleableContingentChoiceDialog.java
@@ -0,0 +1,378 @@
+package org.pankratzlab.unet.jfx;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import com.google.common.collect.Multimap;
+import com.sun.javafx.scene.control.skin.resources.ControlResources;
+import javafx.application.Platform;
+import javafx.beans.binding.Bindings;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.ButtonBar.ButtonData;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.DialogPane;
+import javafx.scene.control.Label;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.control.RadioButton;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import javafx.util.Callback;
+import javafx.util.StringConverter;
+
+/**
+ * A dialog that shows a list of choices to the user, from which they can pick one item at most.
+ *
+ * @see Dialog
+ * @param The type of the items to show to the user, and the type that is returned via
+ * {@link #getResult()} when the dialog is dismissed.
+ * @since JavaFX 8u40
+ */
+@SuppressWarnings("restriction")
+public class StyleableContingentChoiceDialog extends Dialog {
+
+ /**************************************************************************
+ *
+ * Fields
+ *
+ **************************************************************************/
+
+ private final GridPane grid;
+ private final Label label;
+ private final RadioButton opt1Choice;
+ private final RadioButton opt2Choice;
+ private final RadioButton manualChoice;
+ private final ComboBox comboBox1;
+ private final ComboBox comboBox2;
+ private final CheckBox checkboxFilter1;
+ private final CheckBox checkboxFilter2;
+ private final Predicate filterPredicate;
+ private final Option opt1;
+ private final Option opt2;
+
+
+ /**************************************************************************
+ *
+ * Constructor
+ *
+ **************************************************************************/
+
+ /**
+ * Creates a new ChoiceDialog instance with the first argument specifying the default choice that
+ * should be shown to the user, and the second argument specifying a collection of all available
+ * choices for the user. It is expected that the defaultChoice be one of the elements in the
+ * choices collection. If this is not true, then defaultChoice will be set to null and the dialog
+ * will show with the initial choice set to the first item in the list of choices.
+ *
+ * @param defaultChoice The item to display as the pre-selected choice in the dialog. This item
+ * must be contained within the choices varargs array.
+ * @param choices All possible choices to present to the user.
+ * @param InvalidCoices Choices which, if selected, will disable the "Ok" button
+ *
+ * TODO Encapsulate filterName / filterPredicate so they are optional
+ */
+ public StyleableContingentChoiceDialog(T defaultChoice, Collection choices,
+ Collection invalidChoices, Multimap secondChoiceMap,
+ Map valueMap, String filterName,
+ Predicate filter/*
+ * , BiPredicate textEntryMatcher
+ */, Option opt1, Option opt2, Node manualChoiceGraphic) {
+
+ // this.textEntryPredicate = textEntryMatcher;
+ this.opt1 = opt1;
+ this.opt2 = opt2;
+
+ final DialogPane dialogPane = getDialogPane();
+
+ // -- grid
+ this.grid = new GridPane();
+ this.grid.setHgap(10);
+ this.grid.setVgap(10);
+ this.grid.setMaxWidth(Double.MAX_VALUE);
+ this.grid.setAlignment(Pos.CENTER_LEFT);
+
+ // -- label
+ label = createContentLabel(dialogPane.getContentText());
+ label.textProperty().bind(dialogPane.contentTextProperty());
+
+ setTitle(ControlResources.getString("Dialog.confirm.title"));
+ dialogPane.setHeaderText(ControlResources.getString("Dialog.confirm.header"));
+ dialogPane.getStyleClass().add("choice-dialog");
+ dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
+
+ final double MIN_WIDTH = 50;
+
+ comboBox2 = new ComboBox();
+ comboBox2.setMinWidth(MIN_WIDTH);
+ comboBox2.setMaxWidth(Double.MAX_VALUE);
+ GridPane.setHgrow(comboBox2, Priority.SOMETIMES);
+ GridPane.setFillWidth(comboBox2, true);
+ comboBox2.setMaxHeight(10);
+ comboBox2.setDisable(true);
+
+ comboBox1 = new ComboBox();
+ comboBox1.setMinWidth(MIN_WIDTH);
+ if (choices != null) {
+ comboBox1.getItems().addAll(choices);
+ }
+ comboBox1.setMaxWidth(Double.MAX_VALUE);
+ GridPane.setHgrow(comboBox1, Priority.SOMETIMES);
+ GridPane.setFillWidth(comboBox1, true);
+ comboBox1.setMaxHeight(10);
+
+ this.filterPredicate = filter;
+
+ checkboxFilter1 = new CheckBox(filterName);
+ checkboxFilter2 = new CheckBox(filterName);
+
+ checkboxFilter1.selectedProperty()
+ .addListener((obs, oldV, newV) -> updateFirstComboChoices(newV, choices));
+ checkboxFilter2.selectedProperty()
+ .addListener((obs, oldV, newV) -> updateSecondComboState(secondChoiceMap,
+ comboBox1.getSelectionModel().getSelectedItem(), newV));
+
+ opt1Choice = new RadioButton();
+ opt2Choice = new RadioButton();
+
+ manualChoice = new RadioButton();
+ manualChoice.setGraphic(manualChoiceGraphic);
+
+ ToggleGroup group = new ToggleGroup();
+ opt1Choice.setToggleGroup(group);
+ opt2Choice.setToggleGroup(group);
+ manualChoice.setToggleGroup(group);
+
+ if (defaultChoice != null) {
+ comboBox1.getSelectionModel().select(defaultChoice);
+ }
+
+ // bind mouse behavior so the radio buttons fire events when the graphics are interacted with
+ opt1.graphic.setOnMouseClicked(event -> opt1Choice.fireEvent(event));
+ opt1.graphic.setOnMouseEntered(event -> opt1Choice.fireEvent(event));
+ opt1.graphic.setOnMouseExited(event -> opt1Choice.fireEvent(event));
+ opt1.graphic.setOnMousePressed(event -> opt1Choice.fireEvent(event));
+ opt1.graphic.setOnMouseReleased(event -> opt1Choice.fireEvent(event));
+
+ opt2.graphic.setOnMouseClicked(event -> opt2Choice.fireEvent(event));
+ opt2.graphic.setOnMouseEntered(event -> opt2Choice.fireEvent(event));
+ opt2.graphic.setOnMouseExited(event -> opt2Choice.fireEvent(event));
+ opt2.graphic.setOnMousePressed(event -> opt2Choice.fireEvent(event));
+ opt2.graphic.setOnMouseReleased(event -> opt2Choice.fireEvent(event));
+
+ // these are basic bindings
+ checkboxFilter1.disableProperty().bind(manualChoice.selectedProperty().not());
+ checkboxFilter2.disableProperty().bind(manualChoice.selectedProperty().not());
+ comboBox1.disableProperty().bind(manualChoice.selectedProperty().not());
+
+ // this is a more complex binding:
+ // we want to disable the combobox if no options are available
+ comboBox2.disableProperty().bind(Bindings.createBooleanBinding(() -> {
+ boolean enable = manualChoice.selectedProperty().get()
+ && !comboBox1.getSelectionModel().isEmpty() && !comboBox2.getItems().isEmpty();
+ return !enable;
+ }, manualChoice.selectedProperty(), comboBox1.selectionModelProperty(),
+ comboBox1.getSelectionModel().selectedItemProperty(), comboBox2.itemsProperty()));
+
+ // update the second combobox items when the first combobox selection changes
+ comboBox1.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
+ // comboBox1.valueProperty().addListener((obs, oldV, newV) -> {
+ try {
+ updateSecondComboState(secondChoiceMap, newV, checkboxFilter2.isSelected());
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ });
+
+ Node okButton = dialogPane.lookupButton(ButtonType.OK);
+
+ okButton.disableProperty().bind(Bindings.createBooleanBinding(() -> {
+ return (comboBox2.disableProperty().get()
+ || invalidChoices.contains(comboBox1.getSelectionModel().getSelectedItem())
+ || invalidChoices.contains(comboBox2.getSelectionModel().getSelectedItem())
+ || comboBox2.getSelectionModel().isEmpty())
+ && !(opt1Choice.selectedProperty().get() || opt2Choice.selectedProperty().get());
+ // for this .isEmpty() call to work,
+ // the binding must include the
+ // selectedItemProperty as a dependency
+ }, comboBox2.disableProperty(), comboBox2.getSelectionModel().selectedItemProperty(),
+ opt1Choice.selectedProperty(), opt2Choice.selectedProperty()));
+
+ dialogPane.contentTextProperty()
+ .addListener(o -> updateGrid(this.getDialogPane().lookupButton(ButtonType.CANCEL)));
+
+ updateGrid(this.getDialogPane().lookupButton(ButtonType.CANCEL));
+
+ setResultConverter((dialogButton) -> {
+ ButtonData data = dialogButton == null ? null : dialogButton.getButtonData();
+ return data == ButtonData.OK_DONE ? getSelectedItem() : null;
+ });
+
+ }
+
+ private ObservableList getFirstComboChoices(boolean filterActive, Collection choices) {
+ ObservableList filteredChoices =
+ choices.stream().filter(item -> !filterActive || filterPredicate.test(item))
+ .collect(Collectors.toCollection(FXCollections::observableArrayList));
+ return filteredChoices;
+ }
+
+ private ObservableList getSecondComboChoices(Multimap secondChoiceMap,
+ T selectedFirstItem, boolean filterActive) {
+ ObservableList secondChoices;
+ secondChoices = FXCollections.observableArrayList(secondChoiceMap.get(selectedFirstItem));
+ if (filterActive) {
+ secondChoices = secondChoices.stream().filter(filterPredicate)
+ .collect(Collectors.toCollection(FXCollections::observableArrayList));
+ }
+ return secondChoices;
+ }
+
+ private void updateFirstComboChoices(boolean filterActive, Collection choices) {
+ ObservableList filteredChoices = getFirstComboChoices(filterActive, choices);
+ comboBox1.setItems(filteredChoices);
+ if (!filteredChoices.contains(comboBox1.getSelectionModel().getSelectedItem())) {
+ comboBox1.getSelectionModel().clearSelection();
+ }
+ }
+
+ private void updateSecondComboState(Multimap secondChoiceMap, T selectedFirstItem,
+ boolean filterActive) {
+ ObservableList secondChoices;
+ secondChoices = getSecondComboChoices(secondChoiceMap, selectedFirstItem, filterActive);
+ comboBox2.setItems(secondChoices);
+ if (!secondChoices.contains(comboBox2.getSelectionModel().getSelectedItem())) {
+ comboBox2.getSelectionModel().clearSelection();
+ }
+ }
+
+
+
+ /**************************************************************************
+ *
+ * Public API
+ *
+ **************************************************************************/
+
+ /**
+ * Returns the currently selected item in the dialog.
+ */
+ public final T getSelectedItem() {
+ if (manualChoice.isSelected()) {
+ return comboBox1.getSelectionModel().getSelectedItem();
+ } else if (opt1Choice.isSelected()) {
+ return opt1.opt1;
+ } else if (opt2Choice.isSelected()) {
+ return opt2.opt1;
+ }
+ return null;
+ }
+
+ public final T getSelectedSecondItem() {
+ if (manualChoice.isSelected()) {
+ return comboBox2.getSelectionModel().getSelectedItem();
+ } else if (opt1Choice.isSelected()) {
+ return opt1.opt2;
+ } else if (opt2Choice.isSelected()) {
+ return opt2.opt2;
+ }
+ return null;
+ }
+
+ public void setCombo1CellFactory(Callback, ListCell> value) {
+ comboBox1.setCellFactory(value);
+ }
+
+ public void setCombo2CellFactory(Callback, ListCell> value) {
+ comboBox2.setCellFactory(value);
+ }
+
+ public void setCombo1ButtonCell(ListCell value) {
+ comboBox1.setButtonCell(value);
+ }
+
+ public void setCombo2ButtonCell(ListCell value) {
+ comboBox2.setButtonCell(value);
+ }
+
+ public void setConverter1(StringConverter c) {
+ comboBox1.setConverter(c);
+ }
+
+ public void setConverter2(StringConverter c) {
+ comboBox2.setConverter(c);
+ }
+
+ /**************************************************************************
+ *
+ * Private Implementation
+ *
+ **************************************************************************/
+
+ private static Label createContentLabel(String text) {
+ Label label = new Label(text);
+ label.setMaxWidth(Double.MAX_VALUE);
+ label.setMaxHeight(Double.MAX_VALUE);
+ label.getStyleClass().add("content");
+ label.setWrapText(true);
+ label.setPrefWidth(360);
+ return label;
+ }
+
+ private void updateGrid(Node focusReq) {
+ grid.getChildren().clear();
+
+ grid.add(label, 0, 0);
+ HBox hBox1 = new HBox();
+ hBox1.getChildren().add(opt1Choice);
+ hBox1.getChildren().add(opt1.graphic);
+ HBox.setHgrow(opt1.graphic, Priority.ALWAYS);
+
+
+ grid.add(hBox1, 0, 1);
+ HBox hBox2 = new HBox();
+ hBox2.getChildren().add(opt2Choice);
+ hBox2.getChildren().add(opt2.graphic);
+ HBox.setHgrow(opt2.graphic, Priority.ALWAYS);
+ grid.add(hBox2, 0, 2);
+ grid.add(manualChoice, 0, 3);
+ VBox subGrid = new VBox();
+ subGrid.setSpacing(10);
+ subGrid.setPadding(new Insets(0, 0, 0, 20));
+ subGrid.getChildren().add(checkboxFilter1);
+ subGrid.getChildren().add(comboBox1);
+ subGrid.getChildren().add(checkboxFilter2);
+ subGrid.getChildren().add(comboBox2);
+ grid.add(subGrid, 0, 4);
+
+ grid.setPadding(new Insets(10, 25, 10, 25));
+
+ getDialogPane().setContent(grid);
+
+ Platform.runLater(() -> focusReq.requestFocus());
+ }
+
+ public static class Option {
+ final Node graphic;
+ final T opt1;
+ final T opt2;
+
+ public Option(Node graphic, T opt1, T opt2) {
+ this.graphic = graphic;
+ this.opt1 = opt1;
+ this.opt2 = opt2;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/pankratzlab/unet/jfx/wizard/FileInputController.java b/src/main/java/org/pankratzlab/unet/jfx/wizard/FileInputController.java
index 4a9d4514..f5ab711f 100644
--- a/src/main/java/org/pankratzlab/unet/jfx/wizard/FileInputController.java
+++ b/src/main/java/org/pankratzlab/unet/jfx/wizard/FileInputController.java
@@ -25,13 +25,18 @@
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
+import org.pankratzlab.BackgroundDataProcessor;
import org.pankratzlab.unet.deprecated.hla.CurrentDirectoryProvider;
+import org.pankratzlab.unet.deprecated.hla.HLALocus;
import org.pankratzlab.unet.deprecated.jfx.JFXPropertyHelper;
import org.pankratzlab.unet.deprecated.jfx.JFXUtilHelper;
import org.pankratzlab.unet.jfx.DonorNetUtils;
import org.pankratzlab.unet.model.ValidationModel;
import org.pankratzlab.unet.model.ValidationModelBuilder;
+import org.pankratzlab.unet.model.ValidationModelBuilder.ValidationResult;
import org.pankratzlab.unet.model.ValidationTable;
+import org.pankratzlab.unet.model.remap.GUIRemapProcessor;
+import org.pankratzlab.unet.model.remap.GUIRemapProcessor.PresentableAlleleChoices;
import org.pankratzlab.unet.parser.DonorFileParser;
import org.pankratzlab.unet.parser.HtmlDonorParser;
import org.pankratzlab.unet.parser.PdfDonorParser;
@@ -155,35 +160,38 @@ private void selectDonorFile(ActionEvent event, DonorFileParser donorParser,
ValidationModelBuilder builder = new ValidationModelBuilder();
try {
+ // parse the model
donorParser.parseModel(builder, selectedFile);
- // building the model may involve user input, so we need to run this on the JavaFX
- // Application thread
- Platform.runLater(() -> {
+ // check that the model is valid
+ ValidationResult validationResult = builder.validate();
- // check that the model is valid
- org.pankratzlab.unet.model.ValidationModelBuilder.ValidationResult validationResult =
- builder.validate();
+ if (!validationResult.valid) {
+ // if a value is present, show error message
+ alertInvalid(donorParser, selectedFile, validationResult);
+ }
- if (!validationResult.valid) {
+ // check for corrections
+ if (builder.hasCorrections()) {
- // if a value is present, show error message
- if (validationResult.validationMessage.isPresent()) {
- Alert alert = new Alert(AlertType.ERROR);
- alert.setHeaderText(donorParser.getErrorText()
- + "\nPlease notify the developers as this may indicate the data has changed."
- + "\nOffending file: " + selectedFile.getName());
- alert.showAndWait();
- }
+ BackgroundDataProcessor choiceSupplier =
+ new BackgroundDataProcessor<>(builder.getNonCWDLoci(),
+ (locus) -> PresentableAlleleChoices.create(locus,
+ builder.getAllelePairs(locus)),
+ (t) -> {
+ t.printStackTrace();
+ return null;
+ });
+ GUIRemapProcessor processor = new GUIRemapProcessor(choiceSupplier);
- // either way, invalid model so fail
- return;
- }
- Task buildModelText = JFXUtilHelper.createProgressTask(() -> {
- // valid model, build and set
+ Platform.runLater(() -> {
try {
- setter.accept(getTable(), builder.build());
- linkedFile.set(selectedFile);
+ ValidationResult validationResult1 = builder.processCorrections(processor);
+ if (!validationResult1.valid) {
+ alertInvalid(donorParser, selectedFile, validationResult);
+ } else {
+ finish(donorParser, setter, linkedFile, selectedFile, builder);
+ }
} catch (Throwable e) {
Platform.runLater(() -> {
Alert alert = new Alert(AlertType.ERROR);
@@ -194,10 +202,14 @@ private void selectDonorFile(ActionEvent event, DonorFileParser donorParser,
e.printStackTrace();
});
}
+ });
+ } else {
+ Platform.runLater(() -> {
+ finish(donorParser, setter, linkedFile, selectedFile, builder);
});
- new Thread(buildModelText).start();
- });
+ }
+
} catch (Throwable e) {
Platform.runLater(() -> {
Alert alert = new Alert(AlertType.ERROR);
@@ -216,6 +228,42 @@ private void selectDonorFile(ActionEvent event, DonorFileParser donorParser,
}
}
+ private void alertInvalid(DonorFileParser donorParser, File selectedFile,
+ ValidationResult validationResult) {
+ if (validationResult.validationMessage.isPresent()) {
+ Platform.runLater(() -> {
+ Alert alert = new Alert(AlertType.ERROR);
+ alert.setHeaderText(donorParser.getErrorText()
+ + "\nPlease notify the developers as this may indicate the data has changed."
+ + "\nOffending file: " + selectedFile.getName());
+ alert.showAndWait();
+ });
+ }
+ }
+
+ private void finish(DonorFileParser donorParser,
+ BiConsumer setter, ReadOnlyObjectWrapper linkedFile,
+ File selectedFile, ValidationModelBuilder builder) {
+ Task buildModelText = JFXUtilHelper.createProgressTask(() -> {
+ // valid model, build and set
+ try {
+ setter.accept(getTable(), builder.build());
+ linkedFile.set(selectedFile);
+ } catch (Throwable e) {
+ Platform.runLater(() -> {
+ Alert alert = new Alert(AlertType.ERROR);
+ alert.setHeaderText(donorParser.getErrorText()
+ + "\nPlease notify the developers as this may indicate the data has changed."
+ + "\nOffending file: " + selectedFile.getName());
+ alert.showAndWait();
+ e.printStackTrace();
+ });
+ }
+
+ });
+ new Thread(buildModelText).start();
+ }
+
/**
* The {@link FileInputController#FILE_DISPLAY_CLASS} value links to the
* {@link resources/fileInput.css} style-sheet.
diff --git a/src/main/java/org/pankratzlab/unet/jfx/wizard/ValidationResultsController.java b/src/main/java/org/pankratzlab/unet/jfx/wizard/ValidationResultsController.java
index 4de60ea2..c21d2448 100644
--- a/src/main/java/org/pankratzlab/unet/jfx/wizard/ValidationResultsController.java
+++ b/src/main/java/org/pankratzlab/unet/jfx/wizard/ValidationResultsController.java
@@ -285,7 +285,6 @@ void initialize() {
@Override
protected void refreshTable(ValidationTable table) {
// Can only finish the wizard if the validation is successful
-
rootPane.setInvalidBinding(table.isValidProperty().not());
resultsTable.setItems(table.getValidationRows());
table.isValidProperty().addListener(e -> updateDisplay(table.isValidProperty().get()));
@@ -296,7 +295,6 @@ protected void refreshTable(ValidationTable table) {
bcHaplotypeTable.setItems(table.getBCHaplotypeRows());
drdqHaplotypeTable.setItems(table.getDRDQHaplotypeRows());
-
}
/** Perform required actions when the page is being displayed */
diff --git a/src/main/java/org/pankratzlab/unet/model/DRDQHaplotypeRow.java b/src/main/java/org/pankratzlab/unet/model/DRDQHaplotypeRow.java
index 5aab3cca..c06d0d53 100644
--- a/src/main/java/org/pankratzlab/unet/model/DRDQHaplotypeRow.java
+++ b/src/main/java/org/pankratzlab/unet/model/DRDQHaplotypeRow.java
@@ -22,11 +22,9 @@
package org.pankratzlab.unet.model;
import java.util.Objects;
-
import org.pankratzlab.unet.deprecated.hla.HLAType;
import org.pankratzlab.unet.hapstats.Haplotype;
import org.pankratzlab.unet.hapstats.RaceGroup;
-
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
@@ -63,9 +61,17 @@ public DRDQHaplotypeRow(RaceGroup ethnicity, Haplotype haplotype) {
break;
}
}
- if (Objects.isNull(dqb1) || Objects.isNull(drb1) || Objects.isNull(drb345)) {
+ if (Objects.isNull(dqb1)) {
+ throw new IllegalArgumentException(
+ "Invalid DRB345-DRB1-DQB1 haplotype, missing DQB1: " + haplotype.toShortString());
+ }
+ if (Objects.isNull(drb1)) {
+ throw new IllegalArgumentException(
+ "Invalid DRB345-DRB1-DQB1 haplotype, missing DRB1: " + haplotype.toShortString());
+ }
+ if (Objects.isNull(drb345)) {
throw new IllegalArgumentException(
- "Invalid DRB345-DRB1-DQB1 haplotype: " + haplotype.toShortString());
+ "Invalid DRB345-DRB1-DQB1 haplotype, missing DRB345: " + haplotype.toShortString());
}
alleleDRB1 = getAlleleWrapper(drb1);
diff --git a/src/main/java/org/pankratzlab/unet/model/ValidationModel.java b/src/main/java/org/pankratzlab/unet/model/ValidationModel.java
index 2be44ff8..a297db34 100644
--- a/src/main/java/org/pankratzlab/unet/model/ValidationModel.java
+++ b/src/main/java/org/pankratzlab/unet/model/ValidationModel.java
@@ -32,6 +32,7 @@
import org.pankratzlab.unet.deprecated.hla.HLALocus;
import org.pankratzlab.unet.deprecated.hla.HLAType;
import org.pankratzlab.unet.deprecated.hla.SeroType;
+import org.pankratzlab.unet.deprecated.hla.SourceType;
import org.pankratzlab.unet.hapstats.Haplotype;
import org.pankratzlab.unet.hapstats.RaceGroup;
import org.pankratzlab.unet.model.ValidationModelBuilder.TypePair;
@@ -50,7 +51,7 @@ public class ValidationModel {
private final String donorId;
private final String source;
- private final String sourceType;
+ private final SourceType sourceType;
private final ImmutableSortedSet aLocus;
private final ImmutableSortedSet bLocus;
private final ImmutableSortedSet cLocus;
@@ -68,11 +69,12 @@ public class ValidationModel {
private final ImmutableMultimap drdqHaplotypes;
private final ImmutableMap, Set>> remapping;
- public ValidationModel(String donorId, String source, String sourceType, Collection a,
- Collection b, Collection c, Collection drb,
- Collection dqb, Collection dqa, Collection dpa,
- Collection dpb, boolean bw4, boolean bw6, List dr51, List dr52,
- List dr53, Multimap bcCwdHaplotypes,
+ public ValidationModel(String donorId, String source, SourceType sourceType,
+ Collection a, Collection b, Collection c,
+ Collection drb, Collection dqb, Collection dqa,
+ Collection dpa, Collection dpb, boolean bw4, boolean bw6,
+ List dr51, List dr52, List dr53,
+ Multimap bcCwdHaplotypes,
Multimap drdqCwdHaplotypes,
Map, Set>> remapping) {
this.donorId = donorId;
@@ -111,7 +113,7 @@ public String getSource() {
return source;
}
- public String getSourceType() {
+ public SourceType getSourceType() {
return sourceType;
}
@@ -357,7 +359,7 @@ public String toString() {
StringJoiner sj = new StringJoiner("\n");
sj.add(getDonorId());
sj.add(getSource());
- sj.add(getSourceType());
+ sj.add(getSourceType().name());
addPair(sj, getA1(), getA2());
addPair(sj, getB1(), getB2());
addPair(sj, getC1(), getC2());
diff --git a/src/main/java/org/pankratzlab/unet/model/ValidationModelBuilder.java b/src/main/java/org/pankratzlab/unet/model/ValidationModelBuilder.java
index 30fa3c36..a84d4d49 100644
--- a/src/main/java/org/pankratzlab/unet/model/ValidationModelBuilder.java
+++ b/src/main/java/org/pankratzlab/unet/model/ValidationModelBuilder.java
@@ -21,8 +21,6 @@
*/
package org.pankratzlab.unet.model;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
@@ -41,8 +39,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
-import java.util.function.Function;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
@@ -52,41 +48,32 @@
import org.pankratzlab.unet.deprecated.hla.NullType;
import org.pankratzlab.unet.deprecated.hla.SeroLocus;
import org.pankratzlab.unet.deprecated.hla.SeroType;
+import org.pankratzlab.unet.deprecated.hla.SourceType;
import org.pankratzlab.unet.hapstats.AlleleGroups;
import org.pankratzlab.unet.hapstats.CommonWellDocumented;
import org.pankratzlab.unet.hapstats.CommonWellDocumented.Status;
import org.pankratzlab.unet.hapstats.Haplotype;
import org.pankratzlab.unet.hapstats.HaplotypeFrequencies;
import org.pankratzlab.unet.hapstats.RaceGroup;
-import org.pankratzlab.unet.jfx.StyleableChoiceDialog;
+import org.pankratzlab.unet.model.remap.CancellationException;
+import org.pankratzlab.unet.model.remap.RemapProcessor;
import org.pankratzlab.unet.parser.util.BwSerotypes;
import org.pankratzlab.unet.parser.util.BwSerotypes.BwGroup;
import org.pankratzlab.unet.parser.util.DRAssociations;
import org.pankratzlab.unet.parser.util.RabinKarp;
-import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.BiMap;
import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.MultimapBuilder.SortedSetMultimapBuilder;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
-import javafx.scene.control.ListCell;
-import javafx.scene.control.Tooltip;
-import javafx.scene.text.Text;
-import javafx.scene.text.TextFlow;
-import javafx.util.Duration;
-import javafx.util.StringConverter;
/**
* Mutable builder class for creating a {@link ValidationModel}.
@@ -114,7 +101,7 @@ public class ValidationModelBuilder {
private String donorId;
private String source;
- private String sourceType;
+ private SourceType sourceType;
// TODO replace per-locus data structures with maps from [Sero|HLA]Locus to data structure
// (or tables as appropriate)
@@ -122,13 +109,17 @@ public class ValidationModelBuilder {
private Set aLocusCWD;
private Set bLocusCWD;
private Set cLocusCWD;
- private Set aLocusCWDTypes;
- private Set bLocusCWDTypes;
- private Set cLocusCWDTypes;
private Set aLocusFirst = new LinkedHashSet<>();
private Set bLocusFirst = new LinkedHashSet<>();
private Set cLocusFirst = new LinkedHashSet<>();
+ private Set aLocusCWDTypes;
+ private Set bLocusCWDTypes;
+ private Set cLocusCWDTypes;
+ private Set aLocusFirstTypes;
+ private Set bLocusFirstTypes;
+ private Set cLocusFirstTypes;
+
private Map possibleAllelePairings = new HashMap<>();
private Map donorAllelePairings = new HashMap<>();
@@ -136,20 +127,26 @@ public class ValidationModelBuilder {
private Map, Set>> remapping = new HashMap<>();
private Set drbLocus;
- private Set dqaLocus;
- private Set dqbLocus;
- private Set dpaLocus;
-
- // private Set drbLocusTypes;
- private Set dqaLocusTypes;
- private Set dqbLocusTypes;
- private Set dpaLocusTypes;
- private Set dpbLocusTypes;
-
private Set drbLocusNonCWD;
+
+ private Set dqaLocus;
private Set dqaLocusNonCWD;
+ private Set dqaLocusAlleles;
+ private Set dqaLocusAllelesNonCWD;
+
+ private Set dqbLocus;
private Set dqbLocusNonCWD;
+ private Set dqbLocusAlleles;
+ private Set dqbLocusAllelesNonCWD;
+
+ private Set dpaLocus;
private Set dpaLocusNonCWD;
+ private Set dpaLocusAlleles;
+ private Set dpaLocusAllelesNonCWD;
+
+ private Set dpbLocus;
+ private Set dpbLocusAlleles;
+ private Set dpbLocusAllelesNonCWD;
private Set dpbLocusNonCWD;
private Boolean bw4;
@@ -177,11 +174,16 @@ public ValidationModelBuilder source(String source) {
}
/** @param source Name for the source of this model */
- public ValidationModelBuilder sourceType(String sourceType) {
+ public ValidationModelBuilder sourceType(SourceType sourceType) {
this.sourceType = sourceType;
return this;
}
+ /** @param source Name for the source of this model */
+ public SourceType getSourceType() {
+ return sourceType;
+ }
+
public ValidationModelBuilder a(String aSeroType) {
if (test1(aSeroType)) {
return null;
@@ -197,6 +199,12 @@ public ValidationModelBuilder aType(HLAType aType) {
return this;
}
+ public ValidationModelBuilder aTypeNonCWD(HLAType aType) {
+ aLocusFirstTypes = makeIfNull(aLocusFirstTypes);
+ addToLocus(aLocusFirstTypes, HLALocus.A, aType);
+ return this;
+ }
+
public ValidationModelBuilder aNonCWD(String aType) {
if (test1(aType)) {
return null;
@@ -221,6 +229,11 @@ public ValidationModelBuilder bType(HLAType bType) {
return this;
}
+ public ValidationModelBuilder bTypeNonCWD(HLAType bType) {
+ bLocusFirstTypes = makeIfNull(bLocusFirstTypes);
+ addToLocus(bLocusFirstTypes, HLALocus.B, bType);
+ return this;
+ }
public ValidationModelBuilder bNonCWD(String bType) {
if (test1(bType)) {
@@ -246,6 +259,11 @@ public ValidationModelBuilder cType(HLAType cType) {
return this;
}
+ public ValidationModelBuilder cTypeNonCWD(HLAType cType) {
+ cLocusFirstTypes = makeIfNull(cLocusFirstTypes);
+ addToLocus(cLocusFirstTypes, HLALocus.C, cType);
+ return this;
+ }
public ValidationModelBuilder cNonCWD(String cType) {
if (test1(cType)) {
@@ -300,6 +318,13 @@ public ValidationModelBuilder drbNonCWD(String drbType) {
return this;
}
+ /**
+ * DR51 is a HLA-DR serotype that recognizes the antigens encoded by the minor DR locus HLA-DRB5
+ * (cc wikipedia)
+ *
+ * @param dr51
+ * @return
+ */
public ValidationModelBuilder dr51(String dr51) {
if (isPositive(dr51)) {
dr51Locus.add(new HLAType(HLALocus.DRB5, dr51));
@@ -307,6 +332,12 @@ public ValidationModelBuilder dr51(String dr51) {
return this;
}
+ /**
+ * DR52 is an HLA-DR serotype that recognizes gene products of HLA-DRB3 locus (cc wikipedia)
+ *
+ * @param dr52
+ * @return
+ */
public ValidationModelBuilder dr52(String dr52) {
if (isPositive(dr52)) {
dr52Locus.add(new HLAType(HLALocus.DRB3, dr52));
@@ -314,6 +345,12 @@ public ValidationModelBuilder dr52(String dr52) {
return this;
}
+ /**
+ * DR53 is an HLA-DR serotype that recognizes gene products of HLA-DRB4 (cc wikipedia)
+ *
+ * @param dr53
+ * @return
+ */
public ValidationModelBuilder dr53(String dr53) {
if (isPositive(dr53)) {
dr53Locus.add(new HLAType(HLALocus.DRB4, dr53));
@@ -350,20 +387,21 @@ public ValidationModelBuilder dpaNonCWD(String dpaType) {
public ValidationModelBuilder dpbNonCWD(String dpbType) {
dpbLocusNonCWD = makeIfNull(dpbLocusNonCWD);
- // Shorten the allele designation to allele group and specific HLA protein. Further fields can
- // not be entered into UNOS
- if (!Strings.isNullOrEmpty(dpbType) && dpbType.matches(".*\\d.*")) {
- HLAType tmpDPB1 = new HLAType(HLALocus.DPB1, dpbType);
- if (tmpDPB1.spec().size() > 2) {
- tmpDPB1 =
- new HLAType(HLALocus.DPB1, new int[] {tmpDPB1.spec().get(0), tmpDPB1.spec().get(1)});
- }
- dpbLocusNonCWD.add(tmpDPB1);
- }
+ // // Shorten the allele designation to allele group and specific HLA protein. Further fields
+ // can
+ // // not be entered into UNOS
+ // if (!Strings.isNullOrEmpty(dpbType) && dpbType.matches(".*\\d.*")) {
+ HLAType tmpDPB1 = new HLAType(HLALocus.DPB1, dpbType);
+ // if (tmpDPB1.spec().size() > 2) {
+ // tmpDPB1 =
+ // new HLAType(HLALocus.DPB1, new int[] {tmpDPB1.spec().get(0), tmpDPB1.spec().get(1)});
+ // }
+ dpbLocusNonCWD.add(tmpDPB1);
+ // }
return this;
}
- public ValidationModelBuilder dqb(String dqbType) {
+ public ValidationModelBuilder dqbSerotype(String dqbType) {
if (test2(dqbType)) {
return null;
}
@@ -372,7 +410,7 @@ public ValidationModelBuilder dqb(String dqbType) {
return this;
}
- public ValidationModelBuilder dqa(String dqaType) {
+ public ValidationModelBuilder dqaSerotype(String dqaType) {
if (test2(dqaType)) {
return null;
}
@@ -381,7 +419,7 @@ public ValidationModelBuilder dqa(String dqaType) {
return this;
}
- public ValidationModelBuilder dpa(String dpaType) {
+ public ValidationModelBuilder dpaSerotype(String dpaType) {
if (test2(dpaType)) {
return null;
}
@@ -390,18 +428,23 @@ public ValidationModelBuilder dpa(String dpaType) {
return this;
}
- public ValidationModelBuilder dpb(String dpbType) {
- dpbLocusTypes = makeIfNull(dpbLocusTypes);
- // Shorten the allele designation to allele group and specific HLA protein. Further fields can
- // not be entered into UNOS
- if (!Strings.isNullOrEmpty(dpbType) && dpbType.matches(".*\\d.*")) {
- HLAType tmpDPB1 = new HLAType(HLALocus.DPB1, dpbType);
- if (tmpDPB1.spec().size() > 2) {
- tmpDPB1 =
- new HLAType(HLALocus.DPB1, new int[] {tmpDPB1.spec().get(0), tmpDPB1.spec().get(1)});
- }
- dpbLocusTypes.add(tmpDPB1);
- }
+ public ValidationModelBuilder dpb(HLAType dpbType) {
+ dpbLocusAlleles = makeIfNull(dpbLocusAlleles);
+ dpbLocusAlleles.add(dpbType);
+ return this;
+ }
+
+ public ValidationModelBuilder dpbSerotype(String dpbType) {
+ dpbLocus = makeIfNull(dpbLocus);
+ HLAType tmpDPB1 = new HLAType(HLALocus.DPB1, dpbType);
+ dpbLocus.add(trim(tmpDPB1));
+
+ return this;
+ }
+
+ public ValidationModelBuilder dpbNonCIWD(HLAType dpbType) {
+ dpbLocusAllelesNonCWD = makeIfNull(dpbLocusAllelesNonCWD);
+ dpbLocusAllelesNonCWD.add(dpbType);
return this;
}
@@ -409,8 +452,17 @@ public ValidationModelBuilder dqbType(HLAType dqbType) {
if (test2(dqbType)) {
return null;
}
- dqbLocusTypes = makeIfNull(dqbLocusTypes);
- addToLocus(dqbLocusTypes, HLALocus.DQB1, dqbType);
+ dqbLocusAlleles = makeIfNull(dqbLocusAlleles);
+ addToLocus(dqbLocusAlleles, HLALocus.DQB1, dqbType);
+ return this;
+ }
+
+ public ValidationModelBuilder dqbTypeNonCWD(HLAType dqbType) {
+ if (test2(dqbType)) {
+ return null;
+ }
+ dqbLocusAllelesNonCWD = makeIfNull(dqbLocusAllelesNonCWD);
+ addToLocus(dqbLocusAllelesNonCWD, HLALocus.DQB1, dqbType);
return this;
}
@@ -418,8 +470,17 @@ public ValidationModelBuilder dqaType(HLAType dqaType) {
if (test2(dqaType)) {
return null;
}
- dqaLocusTypes = makeIfNull(dqaLocusTypes);
- addToLocus(dqaLocusTypes, HLALocus.DQA1, dqaType);
+ dqaLocusAlleles = makeIfNull(dqaLocusAlleles);
+ addToLocus(dqaLocusAlleles, HLALocus.DQA1, dqaType);
+ return this;
+ }
+
+ public ValidationModelBuilder dqaTypeNonCWD(HLAType dqaType) {
+ if (test2(dqaType)) {
+ return null;
+ }
+ dqaLocusAllelesNonCWD = makeIfNull(dqaLocusAllelesNonCWD);
+ addToLocus(dqaLocusAllelesNonCWD, HLALocus.DQA1, dqaType);
return this;
}
@@ -427,8 +488,17 @@ public ValidationModelBuilder dpaType(HLAType dpaType) {
if (test2(dpaType)) {
return null;
}
- dpaLocusTypes = makeIfNull(dpaLocusTypes);
- addToLocus(dpaLocusTypes, HLALocus.DPA1, dpaType);
+ dpaLocusAlleles = makeIfNull(dpaLocusAlleles);
+ addToLocus(dpaLocusAlleles, HLALocus.DPA1, dpaType);
+ return this;
+ }
+
+ public ValidationModelBuilder dpaTypeNonCWD(HLAType dpaType) {
+ if (test2(dpaType)) {
+ return null;
+ }
+ dpaLocusAllelesNonCWD = makeIfNull(dpaLocusAllelesNonCWD);
+ addToLocus(dpaLocusAllelesNonCWD, HLALocus.DPA1, dpaType);
return this;
}
@@ -504,25 +574,6 @@ public void locusIsNonCIWD(HLAType locus) {
nonCWDLoci.add(locus.locus());
}
- private final class AlleleStringConverter extends StringConverter> {
- private final PresentableAlleleChoices choices;
-
- private AlleleStringConverter(PresentableAlleleChoices choices) {
- this.choices = choices;
- }
-
- @Override
- public String toString(Supplier object) {
- final String selectedData = choices.getSelectedData(object);
- return selectedData == null ? "" : selectedData;
- }
-
- @Override
- public Supplier fromString(String string) {
- return null;
- }
- }
-
public class ValidationResult {
public final boolean valid;
@@ -539,6 +590,56 @@ public ValidationResult validate() {
return ensureValidity();
}
+ private boolean test(Set set) {
+ return set == null || set.isEmpty();
+ }
+
+ private boolean test1(Set set) {
+ return set == null || set.isEmpty();
+ }
+
+ private Set getFinalTypes(HLALocus locus) {
+ Set returnTypes;
+ switch (locus) {
+ case A:
+ returnTypes = test(aLocusFirst) ? aLocusCWD : aLocusFirst;
+ break;
+ case B:
+ returnTypes = test(bLocusFirst) ? bLocusCWD : bLocusFirst;
+ break;
+ case C:
+ returnTypes = test(cLocusFirst) ? cLocusCWD : cLocusFirst;
+ break;
+ case DRB1:
+ case DRB3:
+ case DRB4:
+ case DRB5:
+ returnTypes = test(drbLocusNonCWD) ? drbLocus : drbLocusNonCWD;
+ break;
+ case DQA1:
+ returnTypes = test(dqaLocusNonCWD) ? dqaLocus : dqaLocusNonCWD;
+ break;
+ case DQB1:
+ returnTypes = test(dqbLocusNonCWD) ? dqbLocus : dqbLocusNonCWD;
+ break;
+ case DPA1:
+ returnTypes = test(dpaLocusNonCWD) ? dpaLocus : dpaLocusNonCWD;
+ break;
+ case DPB1:
+ case MICA:
+ default:
+ return null;
+ }
+ returnTypes = new LinkedHashSet<>(returnTypes);
+ if (remapping.containsKey(locus)) {
+ List types = remapping.get(locus).getRight().stream().map(TypePair::getSeroType)
+ .collect(Collectors.toList());
+ returnTypes.clear();
+ returnTypes.addAll(types);
+ }
+ return returnTypes;
+ }
+
/** @return The immutable {@link ValidationModel} based on the current builder state. */
public ValidationModel build() {
Multimap bcCwdHaplotypes = buildBCHaplotypes(bHaplotypes, cHaplotypes);
@@ -548,12 +649,51 @@ public ValidationModel build() {
frequencyTable.clear();
- ValidationModel validationModel = new ValidationModel(donorId, source, sourceType, aLocusCWD,
- bLocusCWD, cLocusCWD, drbLocus, dqbLocus, dqaLocus, dpaLocus, dpbLocusTypes, bw4, bw6,
- dr51Locus, dr52Locus, dr53Locus, bcCwdHaplotypes, drDqDR345Haplotypes, remapping);
+ ValidationModel validationModel = new ValidationModel(donorId, source, sourceType,
+ getFinalTypes(HLALocus.A), getFinalTypes(HLALocus.B), getFinalTypes(HLALocus.C),
+ getFinalTypes(HLALocus.DRB1), getFinalTypes(HLALocus.DQB1), getFinalTypes(HLALocus.DQA1),
+ getFinalTypes(HLALocus.DPA1), getFinalDPBTypes(), bw4, bw6, dr51Locus, dr52Locus, dr53Locus,
+ bcCwdHaplotypes, drDqDR345Haplotypes, remapping);
return validationModel;
}
+ // Shorten the allele designation to allele group and specific HLA protein. Further fields can
+ // not be entered into UNOS
+ private Set getFinalDPBTypes() {
+ Set dpbTypes = new HashSet<>();
+ Set dpbSource = new LinkedHashSet<>();
+
+ Set returnTypes = new LinkedHashSet<>();
+ if (remapping.containsKey(HLALocus.DPB1)) {
+ List types = remapping.get(HLALocus.DPB1).getRight().stream()
+ .map(TypePair::getHlaType).collect(Collectors.toList());
+ returnTypes.clear();
+ returnTypes.addAll(types);
+ }
+ if (!returnTypes.isEmpty()) {
+ dpbSource.addAll(returnTypes);
+ } else {
+ dpbSource = test1(dpbLocusAllelesNonCWD) ? dpbLocusAlleles : dpbLocusAllelesNonCWD;
+ Set dpbBackup = test1(dpbLocusNonCWD) ? dpbLocus : trim(dpbLocusNonCWD);
+ if (dpbSource == null || dpbSource.isEmpty()) {
+ dpbSource = dpbBackup.stream().map(s -> new HLAType(HLALocus.DPB1, s.spec()))
+ .collect(ImmutableSet.toImmutableSet());
+ }
+ }
+ for (HLAType dpbType1 : dpbSource) {
+ String dpbType = dpbType1.specString();
+ if (!Strings.isNullOrEmpty(dpbType) && dpbType.matches(".*\\d.*")) {
+ HLAType tmpDPB1 = new HLAType(HLALocus.DPB1, dpbType);
+ if (tmpDPB1.spec().size() > 2) {
+ tmpDPB1 =
+ new HLAType(HLALocus.DPB1, new int[] {tmpDPB1.spec().get(0), tmpDPB1.spec().get(1)});
+ }
+ dpbTypes.add(tmpDPB1);
+ }
+ }
+ return dpbTypes;
+ }
+
/**
* Helper method to build the B/C haplotypes. Extra filtering is needed based on the Bw groups.
*/
@@ -720,8 +860,11 @@ private void generateStrandOneHaplotypes(
// sorts in descending order, notice h2's weight is found first
Comparator c = ((h1, h2) -> {
- return Double.compare(CommonWellDocumented.getEquivStatus(h2).getWeight(),
+ int d = Double.compare(CommonWellDocumented.getEquivStatus(h2).getWeight(),
CommonWellDocumented.getEquivStatus(h1).getWeight());
+ if (d != 0)
+ return d;
+ return h2.compareTo(h1);
});
firstStrandTypes.sort(c);
secondStrandTypes.sort(c);
@@ -888,14 +1031,14 @@ private void pruneUnknown(Multimap typesForStrand) {
private ValidationResult ensureValidity() {
// Ensure all fields have been set
for (Object o : Lists.newArrayList(donorId, source, aLocusCWD, bLocusCWD, cLocusCWD, drbLocus,
- dqbLocus, dqaLocus, dpbLocusTypes, bw4, bw6)) {
+ dqbLocus, dqaLocus, getFinalDPBTypes(), bw4, bw6)) {
if (Objects.isNull(o)) {
return new ValidationResult(false, Optional.of("ValidationModel incomplete"));
}
}
// Ensure all sets have a reasonable number of entries
for (Set> set : ImmutableList.of(aLocusCWD, bLocusCWD, cLocusCWD, drbLocus, dqbLocus,
- dqaLocus, dpbLocusTypes)) {
+ dqaLocus, getFinalDPBTypes())) {
if (set.isEmpty() || set.size() > 2) {
return new ValidationResult(false,
Optional.of("ValidationModel contains invalid allele count: " + set));
@@ -915,541 +1058,176 @@ private ValidationResult ensureValidity() {
Collections.sort(dr52Locus);
Collections.sort(dr53Locus);
- if (!nonCWDLoci.isEmpty()) {
- for (HLALocus locus : nonCWDLoci) {
-
- Set locusSet;
- Set typesSet;
- AllelePairings allelePairs = possibleAllelePairings.get(locus);
- AllelePairings donorPairs = donorAllelePairings.get(locus);
-
- switch (locus) {
- case A:
- locusSet = aLocusCWD;
- typesSet = aLocusCWDTypes;
- break;
- case B:
- locusSet = bLocusCWD;
- typesSet = bLocusCWDTypes;
- break;
- case C:
- locusSet = cLocusCWD;
- typesSet = cLocusCWDTypes;
- break;
- case DPA1:
- locusSet = dpaLocus;
- typesSet = dpaLocusTypes;
- break;
- case DQA1:
- locusSet = dqaLocus;
- typesSet = dqaLocusTypes;
- break;
- case DPB1:
- locusSet = null;
- typesSet = dpbLocusTypes;
- case DQB1:
- locusSet = dqbLocus;
- typesSet = dqbLocusTypes;
- default:
- allelePairs = null;
- donorPairs = null;
- locusSet = null;
- typesSet = null;
- break;
- }
- if (allelePairs == null || donorPairs == null) {
- // TODO
- continue;
- }
-
- Iterator iter = locusSet == null ? null : locusSet.iterator();
- String header = "Assigned allele pair (" + typesSet.stream().map(ht -> {
- return (iter == null ? "" : (iter.next()) + " / ") + ht.specString() + " - "
- + CommonWellDocumented.getEquivStatus(ht);
- }).collect(Collectors.joining(", ")) + ") for HLA-" + locus.name()
- + " locus is not Common / Well-Documented.";
-
- String text =
- "Please select desired allele pair for this locus. Selecting the first allele will populate valid pairings for the second allele.";
-
- PresentableAlleleChoices choices = PresentableAlleleChoices.create(allelePairs);
-
- final List> allChoices = choices.getAllChoices();
-
- Function, String> tooltipProvider = (tf) -> {
- return Objects.toString(choices.dataMap.get(tf), null);
- };
-
- StyleableChoiceDialog> cd = new StyleableChoiceDialog<>(
- allChoices.get(0), allChoices, choices.getAllSecondChoices(), choices.dataMap);
- cd.setTitle("Select HLA-" + locus.name() + " Alleles");
- cd.setHeaderText(header);
- cd.setContentText(text);
- cd.setCombo1CellFactory(listView -> new SimpleTableObjectListCell(tooltipProvider));
- cd.setCombo1ButtonCell(new SimpleTableObjectListCell(tooltipProvider));
- cd.setCombo2CellFactory(listView -> new SimpleTableObjectListCell(tooltipProvider));
- cd.setCombo2ButtonCell(new SimpleTableObjectListCell(tooltipProvider));
- cd.setConverter1(new AlleleStringConverter(choices));
- cd.setConverter2(new AlleleStringConverter(choices));
-
- Optional> result = cd.showAndWait();
-
- if (!result.isPresent()) {
- return new ValidationResult(false, Optional.empty());
- }
-
- Supplier selAllele1 = result.get();
- Supplier selAllele2 = cd.getSelectedSecondItem();
-
- String allele1 = choices.getSelectedData(selAllele1);
- String allele2 = choices.getSelectedData(selAllele2);
-
- HLAType hlaType1 = HLAType.valueOf(allele1);
- HLAType hlaType2 = HLAType.valueOf(allele2);
- SeroType seroType1 = hlaType1.equivSafe();
- SeroType seroType2 = hlaType2.equivSafe();
-
- final Iterator typeIter = typesSet.iterator();
- HLAType hlaType1Old = typeIter.next();
- HLAType hlaType2Old = typeIter.hasNext() ? typeIter.next() : hlaType1Old;
-
- final Iterator seroIter = locusSet.iterator();
- SeroType seroType1Old = seroIter.next();
- SeroType seroType2Old = seroIter.hasNext() ? seroIter.next() : seroType1Old;
-
- locusSet.clear();
- locusSet.add(seroType1);
- locusSet.add(seroType2);
- typesSet.clear();
- typesSet.add(hlaType1);
- typesSet.add(hlaType2);
-
- boolean a1Match =
- hlaType1.compareTo(hlaType1Old) == 0 || hlaType1.compareTo(hlaType2Old) == 0;
- boolean a2Match =
- hlaType2.compareTo(hlaType1Old) == 0 || hlaType2.compareTo(hlaType2Old) == 0;
-
- if (!a1Match || !a2Match) {
- ImmutableSortedSet prevSet = ImmutableSortedSet
- .of(new TypePair(hlaType1Old, seroType1Old), new TypePair(hlaType2Old, seroType2Old));
-
- ImmutableSortedSet newSet = ImmutableSortedSet
- .of(new TypePair(hlaType1, seroType1), new TypePair(hlaType2, seroType2));
-
-
- remapping.put(locus, Pair.of(prevSet, newSet));
- }
- }
-
- }
-
return new ValidationResult(true, Optional.empty());
}
- public abstract static class PresentableDataChoices {
- List choices;
- Multimap secondChoices;
- BiMap dataMap;
-
- public PresentableDataChoices(List choices, Multimap secondChoices,
- BiMap presentationToDataMap) {
- this.choices = choices;
- this.secondChoices = secondChoices;
- this.dataMap = presentationToDataMap;
- }
-
- public List getAllChoices() {
- return choices;
- }
-
- public Multimap getAllSecondChoices() {
- return secondChoices;
- }
-
- public Collection getSecondChoices(T firstChoice) {
- return secondChoices.get(firstChoice);
- }
-
- public abstract List getMatchingChoices(String userInput);
-
- public R getSelectedData(T selected) {
- return dataMap.get(selected);
- }
-
+ public boolean hasCorrections() {
+ return !nonCWDLoci.isEmpty();
}
- public static class PresentableAlleleChoices
- extends PresentableDataChoices, String> {
-
- public static PresentableAlleleChoices create(AllelePairings allelePairings) {
- List> userChoices = new ArrayList<>();
- ListMultimap, Supplier> secondChoices =
- SortedSetMultimapBuilder.hashKeys().arrayListValues().build();
- BiMap, String> presentationToDataMap = HashBiMap.create();
-
- userChoices.add(() -> new TextFlow());
-
- List alleleKeys = new ArrayList<>(allelePairings.map.keySet());
- sortAlleleStrings(alleleKeys);
-
- List choiceList = condenseIntoGroups(allelePairings, alleleKeys, true);
-
- for (String allele : choiceList) {
- Supplier supp = () -> getText(allele);
-
- userChoices.add(supp);
- presentationToDataMap.put(supp, allele);
- }
-
- for (Supplier choice : userChoices) {
- String data = presentationToDataMap.get(choice);
- if (data == null) {
- continue;
- }
- String pairingAllele = data.contains("-") ? data.split("-")[0] : data;
-
- List pairings = new ArrayList<>(allelePairings.getValidPairings(pairingAllele));
- sortAlleleStrings(pairings);
-
- List secondChoicePairings = condenseIntoGroups(allelePairings, pairings, false);
-
- for (String pairing : secondChoicePairings) {
- Supplier presentationView = presentationToDataMap.inverse().get(pairing);
- if (presentationView == null) {
- presentationView = () -> getText(pairing);
- presentationToDataMap.put(presentationView, pairing);
- }
- secondChoices.put(choice, presentationView);
- }
- }
-
- return new PresentableAlleleChoices(userChoices, secondChoices, presentationToDataMap,
- allelePairings);
- }
-
- private static List condenseIntoGroups(AllelePairings allelePairings,
- List alleleKeys, boolean checkPairings) {
- List> subsets = new ArrayList<>();
- List subset = new ArrayList<>();
-
- HLAType prev3FieldType = null;
-
- // all allele strings should be in the correct order now
- // now let's condense into subsets of the same allele types, with
- // separate subsets for N/n and LSCAQ/lscaq alleles
- for (String allele : alleleKeys) {
- if (allele.matches(NOT_EXPRESSED) || allele.matches(NOT_ON_CELL_SURFACE)) {
- // make sure to add the currently-being-built subset first!
- if (subset.size() > 0) {
- subsets.add(subset);
- subset = new ArrayList<>();
- }
- // add null or lscaq alleles as single entries
- subsets.add(Lists.newArrayList(allele));
- // clear out known three-field type
- prev3FieldType = null;
- continue;
- }
-
- HLAType hType = HLAType.valueOf(allele);
- if (hType.resolution() <= 2) {
- // make sure to add the currently-being-built subset first!
- if (subset.size() > 0) {
- subsets.add(subset);
- subset = new ArrayList<>();
- }
- // add two-field alleles as single entries
- subsets.add(Lists.newArrayList(allele));
- // clear out known three-field type
- prev3FieldType = null;
- continue;
- }
+ public ValidationResult processCorrections(RemapProcessor remapProcessor) {
- // we know resolution is >= 3
+ boolean cancelled = false;
- HLAType curr3FieldType = (hType.resolution() == 3) ? hType
- : new HLAType(hType.locus(), hType.spec().get(0), hType.spec().get(1),
- hType.spec().get(2));
- if (prev3FieldType != null && prev3FieldType.compareTo(curr3FieldType) != 0) {
- // new three field type
- subsets.add(subset);
- subset = new ArrayList<>();
- }
- // same or new three-field type, either way:
- // update known three-field type, and
- // add four-field type to subset
- prev3FieldType = curr3FieldType;
- subset.add(allele);
+ for (HLALocus locus : nonCWDLoci) {
+ Pair, Set> remapPair = null;
+ try {
+ remapPair = remapProcessor.processRemapping(locus, this);
+ } catch (CancellationException e) {
+ cancelled = true;
+ break;
}
- if (subset.size() > 0) {
- subsets.add(subset);
- }
-
- List choiceList = new ArrayList<>();
-
- // now we need to process the subset lists
- // single element subsets can be added directly
- // -- this preserves null/not-expressed subsets
- // multiple element subsets need to be further
- // separated into subsets: all alleles in a subset must
- // map to the same serotype and the same second choice alleles
-
- for (List sub : subsets) {
- if (sub.size() == 1) {
- choiceList.add(sub.get(0));
- continue;
- }
-
- List> subSubsets = new ArrayList<>();
- List subSubset = new ArrayList<>();
-
- SeroType prevSero = null;
- Set prevPairings = null;
-
- for (String subAllele : sub) {
- HLAType type = HLAType.valueOf(subAllele);
- SeroType newSero = type.equivSafe();
- HashSet newPairings =
- checkPairings ? Sets.newHashSet(allelePairings.getValidPairings(subAllele))
- : new HashSet<>();
- if (prevSero == null) {
- prevSero = newSero;
- prevPairings = newPairings;
- } else if (prevSero.compareTo(newSero) != 0) {
- if (subSubset.size() > 0) {
- subSubsets.add(subSubset);
- subSubset = new ArrayList<>();
- }
- prevSero = newSero;
- prevPairings = newPairings;
- } else if (!prevPairings.containsAll(newPairings)
- || !newPairings.containsAll(prevPairings)) {
- if (subSubset.size() > 0) {
- subSubsets.add(subSubset);
- subSubset = new ArrayList<>();
- }
- prevSero = newSero;
- prevPairings = newPairings;
- }
- subSubset.add(subAllele);
- }
- if (subSubset.size() > 0) {
- subSubsets.add(subSubset);
- subSubset = new ArrayList<>();
- }
-
- for (List subS : subSubsets) {
- if (subS.size() == 1) {
- choiceList.add(subS.get(0));
- } else {
- choiceList.add(subS.get(0) + "-" + subS.get(subS.size() - 1));
- }
- }
+ if (remapPair != null) {
+ remapping.put(locus, remapPair);
}
- return choiceList;
- }
-
- private static void sortAlleleStrings(List alleleKeys) {
- alleleKeys.sort((s1, s2) -> {
- boolean check1N = s1.matches(NOT_EXPRESSED);
- boolean check1C = s1.matches(NOT_ON_CELL_SURFACE);
- boolean check2N = s2.matches(NOT_EXPRESSED);
- boolean check2C = s2.matches(NOT_ON_CELL_SURFACE);
- boolean check1 = check1N || check1C;
- boolean check2 = check2N || check2C;
- char s1C = s1.charAt(s1.length() - 1);
- char s2C = s2.charAt(s2.length() - 1);
- String s1Hs = check1 ? s1.substring(0, s1.length() - 1) : s1;
- String s2Hs = check2 ? s2.substring(0, s2.length() - 1) : s2;
-
- HLAType h1 = HLAType.valueOf(s1Hs);
- HLAType h2 = HLAType.valueOf(s2Hs);
-
- int comp;
- if ((comp = h1.compareTo(h2)) != 0)
- return comp;
-
- if (check1 && !check2) {
- // first element ends with special character, meaning second element should come first
- return 1;
- } else if (check2 && !check1) {
- // second element ends with special character, meaning first element should come first
- return -1;
- }
-
- // both end with a special character
- if (check1N && check2N) {
- // both null and same HLAType - these are the same allele
- return 0;
- } else if (check1N && !check2N) {
- // first element is null, second is lscaq, second comes first
- return 1;
- } else if (!check1N && check2N) {
- // first element is lscaq, second is null, first comes first
- return -1;
- }
-
-
- // this block could probably be condensed based on knowing null status from previous checks,
- // but it's easier to just duplicate the logic for now...
-
- // both end with a special character
- if (check1C && check2C) {
- // both null and same HLAType - these are the same allele
- return 0;
- } else if (check1C && !check2C) {
- // first element is null, second is lscaq, second comes first
- return 1;
- } else if (!check1C && check2C) {
- // first element is lscaq, second is null, first comes first
- return -1;
- }
-
- // TODO dunno what's going on here... probably should do something special?
- return 0;
- });
}
- private final AllelePairings allelePairs;
-
- private PresentableAlleleChoices(List> choices,
- Multimap, Supplier> secondChoices,
- BiMap, String> presentationToDataMap, AllelePairings allelePairs) {
- super(choices, secondChoices, presentationToDataMap);
- this.allelePairs = allelePairs;
- }
-
- @Override
- public List> getMatchingChoices(String userInput) {
- return allelePairs.getMatchingAlleles(userInput).stream().map(s -> dataMap.inverse().get(s))
- .filter(Predicates.notNull()).collect(Collectors.toList());
+ if (cancelled) {
+ return new ValidationResult(false, Optional.empty());
}
+ return new ValidationResult(true, Optional.empty());
}
- private static TextFlow getText(String allele) {
- List textNodes = new ArrayList<>();
- if (allele.contains("-")) {
- String[] a = allele.split("-");
- addTextNodes(textNodes, a[0]);
- textNodes.add(new Text("-"));
- addTextNodes(textNodes, a[1]);
- } else {
- addTextNodes(textNodes, allele);
+ public Set getCWDSeroTypesForLocus(HLALocus locus) {
+
+ switch (locus) {
+ case A:
+ return aLocusCWD;
+ case B:
+ return bLocusCWD;
+ case C:
+ return cLocusCWD;
+ case DPA1:
+ return dpaLocus;
+ case DQA1:
+ return dqaLocus;
+ case DPB1:
+ return dpbLocus;
+ case DQB1:
+ return dqbLocus;
+ case DRB1:
+ case DRB3:
+ case DRB4:
+ case DRB5:
+ return drbLocus;
+ default:
+ return null;
+ }
+
+ }
+
+ public Set getAllSeroTypesForLocus(HLALocus locus) {
+
+ switch (locus) {
+ case A:
+ return aLocusFirst;
+ case B:
+ return bLocusFirst;
+ case C:
+ return cLocusFirst;
+ case DPA1:
+ return dpaLocusNonCWD;
+ case DQA1:
+ return dqaLocusNonCWD;
+ case DPB1:
+ return trim(dpbLocusNonCWD);
+ case DQB1:
+ return dqbLocusNonCWD;
+ case DRB1:
+ case DRB3:
+ case DRB4:
+ case DRB5:
+ return drbLocusNonCWD;
+ default:
+ return null;
+ }
+
+ }
+
+ private Set trim(Set set) {
+ return set.stream().map(this::trim).collect(Collectors.toCollection(HashSet::new));
+ }
+
+ private SeroType trim(HLAType hlaType) {
+ List seroStr = new ArrayList<>();
+ seroStr.add(String.valueOf(hlaType.spec().get(0)));
+
+ if (Objects.equals(HLALocus.DPA1, hlaType.locus())
+ || Objects.equals(HLALocus.DPB1, hlaType.locus())) {
+ // DPA and DPB report two fields
+ seroStr.add(String.valueOf(hlaType.spec().get(1)));
+ }
+
+ String[] array = seroStr.toArray(new String[seroStr.size()]);
+ return new SeroType(hlaType.locus().sero(), array);
+ }
+
+ public Set getCWDTypesForLocus(HLALocus locus) {
+
+ switch (locus) {
+ case A:
+ return aLocusCWDTypes;
+ case B:
+ return bLocusCWDTypes;
+ case C:
+ return cLocusCWDTypes;
+ case DPA1:
+ return dpaLocusAlleles;
+ case DQA1:
+ return dqaLocusAlleles;
+ case DPB1:
+ return dpbLocusAlleles;
+ case DQB1:
+ return dqbLocusAlleles;
+ case DRB1:
+ case DRB3:
+ case DRB4:
+ case DRB5:
+ default:
+ return null;
+ }
+
+ }
+
+ public Set getAllTypesForLocus(HLALocus locus) {
+ switch (locus) {
+ case A:
+ return aLocusFirstTypes;
+ case B:
+ return bLocusFirstTypes;
+ case C:
+ return cLocusFirstTypes;
+ case DPA1:
+ return dpaLocusAllelesNonCWD;
+ case DQA1:
+ return dqaLocusAllelesNonCWD;
+ case DPB1:
+ return dpbLocusAllelesNonCWD;
+ case DQB1:
+ return dqbLocusAllelesNonCWD;
+ case DRB1:
+ case DRB3:
+ case DRB4:
+ case DRB5:
+ case MICA:
+ default:
+ return null;
}
-
- Text[] nodes = textNodes.toArray(new Text[textNodes.size()]);
- TextFlow tf = new TextFlow(nodes);
- return tf;
}
- private static void addTextNodes(List textNodes, final String allele) {
- HLAType alleleType = HLAType.valueOf(allele);
- HLAType cwdType1 = CommonWellDocumented.getCWDType(alleleType);
- Status status1 = CommonWellDocumented.getStatus(alleleType);
-
- textNodes.add(new Text(alleleType.locus().name() + "*"));
-
- String specString = alleleType.specString();
- boolean match = allele.matches(NOT_EXPRESSED) || allele.matches(NOT_ON_CELL_SURFACE);
-
- if (status1 != Status.UNKNOWN) {
-
- if (cwdType1.specString().length() < specString.length()) {
- Text t1 = new Text(cwdType1.specString());
- t1.setStyle("-fx-font-weight:bold;");
- textNodes.add(t1);
- textNodes.add(new Text(specString.substring(cwdType1.specString().length())));
- } else {
- Text t1 = new Text(specString);
- t1.setStyle("-fx-font-weight:bold;");
- textNodes.add(t1);
- }
-
- if (match) {
- textNodes.add(new Text("" + allele.charAt(allele.length() - 1)));
- }
-
- } else {
- textNodes
- .add(new Text(specString + (match ? ("" + allele.charAt(allele.length() - 1)) : "")));
- }
-
- textNodes.add(
- new Text(" (" + alleleType.locus().name() + alleleType.equivSafe().specString() + ")"));
- }
-
- private static class SimpleTableObjectListCell extends ListCell> {
-
- // private Function, Supplier> tooltipProvider;
- private Function, String> tooltipProvider;
- private Tooltip tooltip = new Tooltip();
-
- public SimpleTableObjectListCell(Function, String> tooltipProvider) {
- // Function