Skip to content

Instantly share code, notes, and snippets.

@soc
Last active July 7, 2021 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soc/fb71876be910fbc52233fb34077855fe to your computer and use it in GitHub Desktop.
Save soc/fb71876be910fbc52233fb34077855fe to your computer and use it in GitHub Desktop.
package org.geotools.data.util;
import static javax.measure.MetricPrefix.*;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.measure.MetricPrefix;
import javax.measure.Unit;
import javax.measure.format.UnitFormat;
import org.junit.Test;
import tech.units.indriya.AbstractUnit;
import tech.units.indriya.format.SimpleUnitFormat;
import tech.units.indriya.unit.Units;
public class UnitFormatterTest {
@Test
public void unitsOnlyInOld() throws Exception {
SimpleUnitFormat simpleUnitFormat = SimpleUnitFormat.getInstance();
List<Map.Entry<Unit<?>, String>> unitToName = toSortedList1(getUnitToNameMap(simpleUnitFormat));
List<Map.Entry<Unit<?>, String>> unitToSymbol = toSortedList1(formatter.getUnitToSymbol());
// indriya (2.0.2) has an inconsistent mix of μ and µ; this doesn't.
/*
List<Map.Entry<Unit<?>, String>> olds181 = unitToName.stream().filter(unit -> unit.getValue().charAt(0) == 181).collect(Collectors.toList());
List<Map.Entry<Unit<?>, String>> olds956 = unitToName.stream().filter(unit -> unit.getValue().charAt(0) == 956).collect(Collectors.toList());
List<Map.Entry<Unit<?>, String>> news181 = unitToSymbol.stream().filter(unit -> unit.getValue().charAt(0) == 181).collect(Collectors.toList());
List<Map.Entry<Unit<?>, String>> news956 = unitToSymbol.stream().filter(unit -> unit.getValue().charAt(0) == 956).collect(Collectors.toList());
System.out.println("old 181: " + olds181);
System.out.println("new 181: " + news181);
System.out.println("old 956: " + olds956);
System.out.println("new 956: " + news956);
*/
List<Map.Entry<Unit<?>, String>> unitsOnlyInOld = unitToName.stream().filter(entry -> !unitToSymbol.contains(entry)).collect(Collectors.toList());
List<Map.Entry<? extends Unit<?>, String>> indriyaBug = List.of(
Map.entry(Units.GRAM.prefix(MICRO), "µg"),
Map.entry(Units.LITRE.prefix(MICRO), "µl"),
Map.entry(Units.CELSIUS.prefix(MICRO), "µ℃"));
assertEquals(unitsOnlyInOld.size() + " units only in old: " + unitsOnlyInOld + "\n", indriyaBug, unitsOnlyInOld);
}
@Test
public void unitsOnlyInNew() throws Exception {
SimpleUnitFormat simpleUnitFormat = SimpleUnitFormat.getInstance();
List<Map.Entry<Unit<?>, String>> unitToNameMap = toSortedList1(getUnitToNameMap(simpleUnitFormat));
List<Map.Entry<Unit<?>, String>> unitToSymbol = toSortedList1(formatter.getUnitToSymbol());
List<Map.Entry<Unit<?>, String>> unitsOnlyInNew = unitToSymbol.stream().filter(entry -> !unitToNameMap.contains(entry)).collect(Collectors.toList());
List<Map.Entry<? extends Unit<?>, String>> indriyaBug = List.of(
Map.entry(Units.GRAM.prefix(MICRO), "μg"),
Map.entry(Units.LITRE.prefix(MICRO), "μl"),
Map.entry(Units.CELSIUS.prefix(MICRO), "μ℃"));
assertEquals(unitsOnlyInNew.size() + " units only in new: " + unitsOnlyInNew + "\n", indriyaBug, unitsOnlyInNew);
}
@Test
public void namesOnlyInOld() throws Exception {
SimpleUnitFormat simpleUnitFormat = SimpleUnitFormat.getInstance();
List<Map.Entry<String, Unit<?>>> nameToUnitMap = toSortedList2(getNameToUnitMap(simpleUnitFormat));
List<Map.Entry<String, Unit<?>>> symbolToUnit = toSortedList2(formatter.getSymbolToUnit());
List<Map.Entry<String, Unit<?>>> unitsOnlyInOld = nameToUnitMap.stream().filter(entry -> !symbolToUnit.contains(entry)).collect(Collectors.toList());
assertEquals(unitsOnlyInOld.size() + " names only in old: " + unitsOnlyInOld + "\n", List.of(), unitsOnlyInOld);
}
@Test
public void namesOnlyInNew() throws Exception {
SimpleUnitFormat simpleUnitFormat = SimpleUnitFormat.getInstance();
List<Map.Entry<String, Unit<?>>> nameToUnitMap = toSortedList2(getNameToUnitMap(simpleUnitFormat));
List<Map.Entry<String, Unit<?>>> symbolToUnit = toSortedList2(formatter.getSymbolToUnit());
List<Map.Entry<String, Unit<?>>> unitsOnlyInNew = symbolToUnit.stream().filter(entry -> !nameToUnitMap.contains(entry)).collect(Collectors.toList());
// only one kind of µ is added for those special-cased units in indriya:
List<Map.Entry<String, Unit<?>>> indriyaBug = List.of(
Map.entry("μg", Units.GRAM.prefix(MICRO)),
Map.entry("μl", Units.LITRE.prefix(MICRO)),
Map.entry("μ℃", Units.CELSIUS.prefix(MICRO)),
Map.entry("μ°C", Units.CELSIUS.prefix(MICRO)),
Map.entry("μOhm", Units.OHM.prefix(MICRO)));
assertEquals(unitsOnlyInNew.size() + " names only in new: " + unitsOnlyInNew + "\n", indriyaBug, unitsOnlyInNew);
}
@SuppressWarnings("unchecked") // reflection in use, cannot be type safe
private static HashMap<Unit<?>, String> getUnitToNameMap(UnitFormat instance) throws Exception {
Field unitToNameField = instance.getClass().getDeclaredField("unitToName");
unitToNameField.setAccessible(true);
return (HashMap<Unit<?>, String>) unitToNameField.get(instance);
}
@SuppressWarnings("unchecked") // reflection in use, cannot be type safe
private static HashMap<String, Unit<?>> getNameToUnitMap(UnitFormat instance) throws Exception {
Field nameToUnitField = instance.getClass().getDeclaredField("nameToUnit");
nameToUnitField.setAccessible(true);
return (HashMap<String, Unit<?>>) nameToUnitField.get(instance);
}
private static List<Map.Entry<Unit<?>, String>> toSortedList1(HashMap<Unit<?>, String> map) {
return map.entrySet().stream()
.sorted(Comparator.nullsFirst(Comparator.comparing(x -> x.getKey().toString())))
.collect(Collectors.toList());
}
private static List<Map.Entry<String, Unit<?>>> toSortedList2(HashMap<String, Unit<?>> map) {
return map.entrySet().stream()
.sorted(Comparator.nullsFirst(Comparator.comparing(x -> x.getValue().toString())))
.collect(Collectors.toList());
}
private static final UnitFormatter formatter = UnitFormatter.of(Stream.of(
UnitDefinitions.DIMENSIONLESS,
UnitDefinitions.BASE,
UnitDefinitions.DERIVED)
.flatMap(Collection::stream)
.collect(Collectors.toList()));
}
class UnitFormatter {
/** associates units with their symbols */
private final HashMap<Unit<?>, String> unitToSymbol = new HashMap<>();
/** associates symbols with their units */
private final HashMap<String, Unit<?>> symbolToUnit = new HashMap<>();
public static UnitFormatter of(UnitDefinition... unitDefinitions) {
return new UnitFormatter(List.of(unitDefinitions));
}
public static UnitFormatter of(List<UnitDefinition> unitDefinitions) {
return new UnitFormatter(unitDefinitions);
}
// use this as a formatter/to initialize a formatter (like SimpleUnitFormat)
private UnitFormatter(List<UnitDefinition> unitDefinitions) {
for (UnitDefinition unitDefinition : unitDefinitions) {
Unit<?> unit = unitDefinition.getUnit();
String unitSymbol = unitDefinition.getSymbolOverride() != null ? unitDefinition.getSymbolOverride() : unit.getSymbol();
// add units
this.unitToSymbol.put(unit, unitSymbol);
this.symbolToUnit.put(unitSymbol, unit);
for (PrefixDefinition prefix : unitDefinition.getPrefixes()) {
addUnit(unit, unitSymbol, prefix);
}
// add labels
for(String alias : unitDefinition.getAliases()) {
symbolToUnit.put(alias, unit);
}
for (PrefixDefinition prefix : unitDefinition.getPrefixes()) {
for(String alias : unitDefinition.getAliases()) {
addAlias(unit, alias, prefix);
}
}
}
}
private void addUnit(Unit<?> unit, String unitSymbol, PrefixDefinition prefix) {
Unit<?> prefixedUnit = unit.prefix(prefix.getPrefix());
String prefixString = prefix.getPrefixOverride() != null ? prefix.getPrefixOverride() : prefix.getPrefix().getSymbol();
String prefixedSymbol = prefixString + unitSymbol;
this.unitToSymbol.put(prefixedUnit, prefixedSymbol);
this.symbolToUnit.put(prefixedSymbol, prefixedUnit);
for (String prefixAlias : prefix.getPrefixAliases()) {
this.unitToSymbol.put(prefixedUnit, prefixAlias + unitSymbol);
this.symbolToUnit.put(prefixAlias + unitSymbol, prefixedUnit);
}
}
private void addAlias(Unit<?> unit, String unitSymbol, PrefixDefinition prefix) {
Unit<?> prefixedUnit = unit.prefix(prefix.getPrefix());
String prefixString = prefix.getPrefixOverride() != null ? prefix.getPrefixOverride() : prefix.getPrefix().getSymbol();
String prefixedSymbol = prefixString + unitSymbol;
this.symbolToUnit.put(prefixedSymbol, prefixedUnit);
for (String prefixAlias : prefix.getPrefixAliases()) {
this.symbolToUnit.put(prefixAlias + unitSymbol, prefixedUnit);
}
}
public HashMap<Unit<?>, String> getUnitToSymbol() {
return unitToSymbol;
}
public HashMap<String, Unit<?>> getSymbolToUnit() {
return symbolToUnit;
}
}
class UnitDefinitions {
public static List<UnitDefinition> DIMENSIONLESS = List.of(
UnitDefinition.of(AbstractUnit.ONE, List.of(), "one", List.of()),
UnitDefinition.of(Units.PERCENT, List.of(), "%", List.of()));
public static List<UnitDefinition> BASE = List.of(
UnitDefinition.withStandardPrefixes(Units.AMPERE),
UnitDefinition.withStandardPrefixes(Units.CANDELA),
UnitDefinition.withStandardPrefixes(Units.KELVIN),
UnitDefinition.of(Units.KILOGRAM, List.of(), null, List.of()),
UnitDefinition.withStandardPrefixes(Units.METRE),
UnitDefinition.withStandardPrefixes(Units.MOLE),
UnitDefinition.withStandardPrefixes(Units.SECOND));
public static List<UnitDefinition> DERIVED = List.of(
UnitDefinition.withStandardPrefixes(Units.BECQUEREL),
UnitDefinition.of(Units.CELSIUS, PrefixDefinitions.STANDARD, "℃", List.of("°C")),
UnitDefinition.withStandardPrefixes(Units.COULOMB),
// "m3" was added later, as well as (Units.SQUARE_METRE, "m2"), but not m²
UnitDefinition.of(Units.CUBIC_METRE, List.of(), "㎥", List.of()),
UnitDefinition.of(Units.DAY, List.of(), "day", List.of("d")),
UnitDefinition.withStandardPrefixes(Units.FARAD),
UnitDefinition.withPrefixesAndSymbolOverride(Units.GRAM, PrefixDefinitions.GRAM, "g"),
UnitDefinition.withStandardPrefixes(Units.GRAY),
UnitDefinition.withStandardPrefixes(Units.HENRY),
UnitDefinition.withStandardPrefixes(Units.HERTZ),
UnitDefinition.of(Units.HOUR, List.of(), "h", List.of()),
UnitDefinition.withStandardPrefixes(Units.JOULE),
UnitDefinition.withStandardPrefixes(Units.KATAL),
UnitDefinition.of(Units.KILOMETRE_PER_HOUR, List.of(), "km/h", List.of()),
UnitDefinition.withStandardPrefixes(Units.LITRE),
UnitDefinition.withStandardPrefixes(Units.LUMEN),
UnitDefinition.withStandardPrefixes(Units.LUX),
UnitDefinition.of(Units.MINUTE, List.of(), "min", List.of()),
UnitDefinition.withStandardPrefixes(Units.NEWTON),
UnitDefinition.of(Units.OHM, PrefixDefinitions.STANDARD, null, List.of("Ohm")),
UnitDefinition.withStandardPrefixes(Units.PASCAL),
UnitDefinition.withStandardPrefixes(Units.RADIAN),
UnitDefinition.withStandardPrefixes(Units.SIEMENS),
UnitDefinition.withStandardPrefixes(Units.SIEVERT),
UnitDefinition.withStandardPrefixes(Units.STERADIAN),
UnitDefinition.withStandardPrefixes(Units.TESLA),
UnitDefinition.withStandardPrefixes(Units.VOLT),
UnitDefinition.withStandardPrefixes(Units.WATT),
UnitDefinition.withStandardPrefixes(Units.WEBER),
UnitDefinition.of(Units.WEEK, List.of(), "week", List.of()),
UnitDefinition.of(Units.YEAR, List.of(), "year", List.of("days365")));
}
class UnitDefinition {
private final Unit<?> unit;
private final List<PrefixDefinition> prefixes;
private final String symbolOverride;
private final List<String> aliases;
static UnitDefinition of(Unit<?> unit, List<PrefixDefinition> prefixes, String symbolOverride, List<String> aliases) {
return new UnitDefinition(unit, prefixes, symbolOverride, aliases);
}
static UnitDefinition withStandardPrefixes(Unit<?> unit) {
return new UnitDefinition(unit, PrefixDefinitions.STANDARD, null, List.of());
}
static UnitDefinition withPrefixesAndSymbolOverride(Unit<?> unit, List<PrefixDefinition> prefixes, String symbolOverride) {
return new UnitDefinition(unit, prefixes, symbolOverride, List.of());
}
private UnitDefinition(Unit<?> unit, List<PrefixDefinition> prefixes, String symbolOverride, List<String> aliases) {
this.unit = unit;
this.prefixes = prefixes;
this.symbolOverride = symbolOverride;
this.aliases = aliases;
}
public Unit<?> getUnit() {
return unit;
}
public List<PrefixDefinition> getPrefixes() {
return prefixes;
}
public String getSymbolOverride() {
return symbolOverride;
}
public List<String> getAliases() {
return aliases;
}
}
class PrefixDefinition {
private final MetricPrefix prefix;
private final String prefixOverride;
private final List<String> prefixAliases;
static PrefixDefinition of(MetricPrefix prefix, List<String> prefixAlias) {
return new PrefixDefinition(prefix, null, prefixAlias);
}
static PrefixDefinition of(MetricPrefix prefix, String... prefixAlias) {
return new PrefixDefinition(prefix, null, List.of(prefixAlias));
}
private PrefixDefinition(MetricPrefix prefix, String prefixOverride, List<String> prefixAliases) {
this.prefix = prefix;
this.prefixOverride = prefixOverride;
this.prefixAliases = prefixAliases;
}
public MetricPrefix getPrefix() {
return prefix;
}
public String getPrefixOverride() {
return prefixOverride;
}
public List<String> getPrefixAliases() {
return prefixAliases;
}
}
class PrefixDefinitions {
public static List<PrefixDefinition> STANDARD = List.of(
PrefixDefinition.of(YOTTA),
PrefixDefinition.of(ZETTA),
PrefixDefinition.of(EXA),
PrefixDefinition.of(PETA),
PrefixDefinition.of(TERA),
PrefixDefinition.of(GIGA),
PrefixDefinition.of(MEGA),
PrefixDefinition.of(KILO),
PrefixDefinition.of(HECTO),
PrefixDefinition.of(DEKA),
PrefixDefinition.of(DECI),
PrefixDefinition.of(CENTI),
PrefixDefinition.of(MILLI),
PrefixDefinition.of(MICRO, "\u03BC"),
PrefixDefinition.of(NANO),
PrefixDefinition.of(PICO),
PrefixDefinition.of(FEMTO),
PrefixDefinition.of(ATTO),
PrefixDefinition.of(ZEPTO),
PrefixDefinition.of(YOCTO));
public static List<PrefixDefinition> GRAM = List.of(
PrefixDefinition.of(YOTTA),
PrefixDefinition.of(ZETTA),
PrefixDefinition.of(EXA),
PrefixDefinition.of(PETA),
PrefixDefinition.of(TERA),
PrefixDefinition.of(GIGA),
PrefixDefinition.of(MEGA),
// no kilo
PrefixDefinition.of(HECTO),
PrefixDefinition.of(DEKA),
PrefixDefinition.of(DECI),
PrefixDefinition.of(CENTI),
PrefixDefinition.of(MILLI),
PrefixDefinition.of(MICRO, "\u03BC"),
PrefixDefinition.of(NANO),
PrefixDefinition.of(PICO),
PrefixDefinition.of(FEMTO),
PrefixDefinition.of(ATTO),
PrefixDefinition.of(ZEPTO),
PrefixDefinition.of(YOCTO));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment