Skip to content

Commit

Permalink
Add UCMap
Browse files Browse the repository at this point in the history
Make looking up printers and printer-types by name
work case insensitively.
  • Loading branch information
khelwood committed Nov 30, 2023
1 parent a10bdbc commit 6e8f23e
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 18 deletions.
14 changes: 7 additions & 7 deletions src/main/java/uk/ac/sanger/sprint/config/Config.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package uk.ac.sanger.sprint.config;

import uk.ac.sanger.sprint.model.*;
import uk.ac.sanger.sprint.utils.UCMap;

import java.util.List;
import java.util.Map;

/**
* The config for the application.
* The printers, printer types and label types, with references to each other.
*/
public class Config {
private Map<String, Printer> printers;
private List<LabelType> labelTypes;
private Map<String, PrinterType> printerTypes;
private final UCMap<Printer> printers;
private final List<LabelType> labelTypes;
private final UCMap<PrinterType> printerTypes;

public Config(Map<String, Printer> printers, List<LabelType> labelTypes, Map<String, PrinterType> printerTypes) {
public Config(UCMap<Printer> printers, List<LabelType> labelTypes, UCMap<PrinterType> printerTypes) {
this.printers = printers;
this.labelTypes = labelTypes;
this.printerTypes = printerTypes;
Expand All @@ -24,7 +24,7 @@ public Config(Map<String, Printer> printers, List<LabelType> labelTypes, Map<Str
* The map from printer hostname to printer.
* @return a map from hostname to printer
*/
public Map<String, Printer> getPrinters() {
public UCMap<Printer> getPrinters() {
return printers;
}

Expand All @@ -40,7 +40,7 @@ public List<LabelType> getLabelTypes() {
* The map from printer type name to printer type.
* @return a map from name to printer type
*/
public Map<String, PrinterType> getPrinterTypes() {
public UCMap<PrinterType> getPrinterTypes() {
return printerTypes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import uk.ac.sanger.sprint.model.*;
import uk.ac.sanger.sprint.utils.UCMap;

import javax.xml.bind.JAXBException;
import java.io.IOException;
Expand Down Expand Up @@ -59,14 +60,14 @@ public Config loadConfig(Collection<Path> paths) {
final Map<String, LabelType> labelTypes = configs.stream()
.flatMap(cf -> cf.getLabelTypes().stream())
.collect(Collectors.toMap(LabelType::getName, Function.identity()));
final Map<String, PrinterType> printerTypes = configs.stream()
UCMap<PrinterType> printerTypes = configs.stream()
.flatMap(cf -> cf.getPrinterTypes().stream())
.collect(Collectors.toMap(PrinterType::getName, Function.identity()));
.collect(UCMap.toUCMap(PrinterType::getName));
final String volumePath = appConfig.getVolume();
Map<String, Printer> printers = configs.stream()
UCMap<Printer> printers = configs.stream()
.flatMap(cf -> cf.getEntries().stream())
.map(e -> toPrinter(e, printerTypes, labelTypes, volumePath))
.collect(Collectors.toMap(Printer::getHostname, Function.identity()));
.collect(UCMap.toUCMap(Printer::getHostname));

for (Printer printer: printers.values()) {
printer.getLabelType().getPrinters().add(printer);
Expand Down
33 changes: 31 additions & 2 deletions src/main/java/uk/ac/sanger/sprint/utils/BasicUtils.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package uk.ac.sanger.sprint.utils;

import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
* @author dr6
Expand Down Expand Up @@ -34,4 +36,31 @@ public static <E> List<E> nullToEmpty(List<E> list) {
public static String nullToEmpty(String string) {
return (string==null ? "" : string);
}

/**
* Collector to a map where the values are the input objects
* @param keyMapper a mapping function to produce keys
* @param mapFactory a supplier providing a new empty {@code Map}
* into which the results will be inserted
* @param <T> the type of the input elements
* @param <K> the output type of the key mapping function
* @param <M> the type of the resulting {@code Map}
* @return a {@code Collector} which collects elements into a {@code Map}
* whose keys are the result of applying a key mapping function to the input
* elements, and whose values are input elements
*/
public static <T, K, M extends Map<K, T>> Collector<T, ?, M> inMap(Function<? super T, ? extends K> keyMapper,
Supplier<M> mapFactory) {
return Collectors.toMap(keyMapper, Function.identity(), illegalStateMerge(), mapFactory);
}

/**
* A binary operator that throws an illegal state exception. Used as the merge function for collecting
* to a map whose incoming keys are expected to be unique.
* @param <U> the type of value
* @return a binary operator that throws an {@link IllegalStateException}
*/
public static <U> BinaryOperator<U> illegalStateMerge() {
return (a, b) -> {throw new IllegalStateException("Duplicate keys found in map.");};
}
}
206 changes: 206 additions & 0 deletions src/main/java/uk/ac/sanger/sprint/utils/UCMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package uk.ac.sanger.sprint.utils;

import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;

/**
* A map with upper case strings for keys.
* Keys are converted to upper case when they are put into the map.
* Keys are converted to upper case when they are being looked up in the map.
* @author dr6
*/
public class UCMap<V> implements Map<String, V> {
private final Map<String, V> inner;

public UCMap(int initialCapacity) {
inner = new HashMap<>(initialCapacity);
}

public UCMap() {
inner = new HashMap<>();
}

public UCMap(Map<String, V> contents) {
this(contents.size());
this.putAll(contents);
}

@Override
public int size() {
return inner.size();
}

@Override
public boolean isEmpty() {
return inner.isEmpty();
}

@Override
public boolean containsKey(Object key) {
return inner.containsKey(upcase(key));
}

@Override
public boolean containsValue(Object value) {
return inner.containsValue(value);
}

@Override
public V get(Object key) {
return inner.get(upcase(key));
}

@Override
public V put(String key, V value) {
return inner.put(upcase(key), value);
}

@Override
public V remove(Object key) {
return inner.remove(upcase(key));
}

@Override
public void putAll(Map<? extends String, ? extends V> m) {
for (Entry<? extends String, ? extends V> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}

@Override
public void clear() {
inner.clear();
}


@Override
public Set<String> keySet() {
return inner.keySet();
}


@Override
public Collection<V> values() {
return inner.values();
}


@Override
public Set<Entry<String, V>> entrySet() {
return inner.entrySet();
}

@Override
public V getOrDefault(Object key, V defaultValue) {
return inner.getOrDefault(upcase(key), defaultValue);
}

@Override
public void forEach(BiConsumer<? super String, ? super V> action) {
inner.forEach(action);
}

@Override
public void replaceAll(BiFunction<? super String, ? super V, ? extends V> function) {
inner.replaceAll(function);
}


@Override
public V putIfAbsent(String key, V value) {
return inner.putIfAbsent(upcase(key), value);
}

@Override
public boolean remove(Object key, Object value) {
return inner.remove(upcase(key), value);
}

@Override
public boolean replace(String key, V oldValue, V newValue) {
return inner.replace(upcase(key), oldValue, newValue);
}


@Override
public V replace(String key, V value) {
return inner.replace(upcase(key), value);
}

@Override
public V computeIfAbsent(String key, Function<? super String, ? extends V> mappingFunction) {
return inner.computeIfAbsent(upcase(key), mappingFunction);
}

@Override
public V computeIfPresent(String key, BiFunction<? super String, ? super V, ? extends V> remappingFunction) {
return inner.computeIfPresent(upcase(key), remappingFunction);
}

@Override
public V compute(String key, BiFunction<? super String, ? super V, ? extends V> remappingFunction) {
return inner.compute(upcase(key), remappingFunction);
}

@Override
public V merge(String key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return inner.merge(upcase(key), value, remappingFunction);
}

@Override
public boolean equals(Object obj) {
return (obj==this || obj instanceof Map && this.inner.equals(obj));
}

@Override
public int hashCode() {
return inner.hashCode();
}

private static <K> K upcase(K key) {
if (key instanceof String) {
//noinspection unchecked
key = (K) ((String) key).toUpperCase();
}
return key;
}

@Override
public String toString() {
return "UCMap("+inner+")";
}

/**
* Collector to collect to a {@code UCMap} from strings to the input objects.
* @param keyMapper mapper from input objects to strings
* @param <T> the type of input object
* @return a collector to collect to a {@code UCMap}
*/
public static <T> Collector<T, ?, UCMap<T>> toUCMap(Function<? super T, String> keyMapper) {
return BasicUtils.inMap(keyMapper, UCMap::new);
}

public static <T> UCMap<T> from(Function<T, String> keyMapper, T value) {
UCMap<T> map = new UCMap<>(1);
map.put(keyMapper.apply(value), value);
return map;
}

@SafeVarargs
public static <T> UCMap<T> from(Function<T, String> keyMapper, T... values) {
UCMap<T> map = new UCMap<>(values.length);
for (T value : values) {
map.put(keyMapper.apply(value), value);
}
return map;
}

public static <T> UCMap<T> from(Collection<? extends T> items, Function<T, String> keyMapper) {
UCMap<T> map = new UCMap<>(items.size());
for (T item : items) {
map.put(keyMapper.apply(item), item);
}
return map;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import org.junit.jupiter.api.*;
import uk.ac.sanger.sprint.model.*;
import uk.ac.sanger.sprint.utils.UCMap;

import javax.xml.bind.JAXBException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;
Expand Down Expand Up @@ -82,10 +82,8 @@ private PrinterConfig loadPrinterConfig(Path path) throws JAXBException {
@Test
public void testLoadConfig() {
Config config = configLoader.loadConfig(paths);
Map<String, PrinterType> printerTypeMap = printerTypes.stream()
.collect(Collectors.toMap(PrinterType::getName, Function.identity()));
Map<String, Printer> printerMap = printers.stream()
.collect(Collectors.toMap(Printer::getHostname, Function.identity()));
UCMap<PrinterType> printerTypeMap = UCMap.from(printerTypes, PrinterType::getName);
UCMap<Printer> printerMap = UCMap.from(printers, Printer::getHostname);
assertEquals(config.getPrinterTypes(), printerTypeMap);
assertEquals(config.getLabelTypes().size(), labelTypes.size());
assertEquals(new HashSet<>(config.getLabelTypes()), new HashSet<>(labelTypes));
Expand Down

0 comments on commit 6e8f23e

Please sign in to comment.