/** * Copyright (C) 2019 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package sevenUnits.utils; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.Set; /** * A class that can be used to specify names and a symbol for a unit. * * @author Adrien Hopkins * @since 2019-10-21 */ public final class NameSymbol { /** The {@code NameSymbol} with all fields empty. */ public static final NameSymbol EMPTY = new NameSymbol(Optional.empty(), Optional.empty(), new HashSet<>()); /** * Creates a {@code NameSymbol}, ensuring that if primaryName is null and * otherNames is not empty, one name is moved from otherNames to primaryName * * Ensure that otherNames is not a copy of the inputted argument. */ private static final NameSymbol create(final String name, final String symbol, final Set otherNames) { final Optional primaryName; if (name == null && !otherNames.isEmpty()) { // get primary name and remove it from savedNames final Iterator it = otherNames.iterator(); assert it.hasNext(); primaryName = Optional.of(it.next()); otherNames.remove(primaryName.get()); } else { primaryName = Optional.ofNullable(name); } return new NameSymbol(primaryName, Optional.ofNullable(symbol), otherNames); } /** * Gets a {@code NameSymbol} with a primary name, a symbol and no other * names. * * @param name name to use * @param symbol symbol to use * @return NameSymbol instance * @since 2019-10-21 * @throws NullPointerException if name or symbol is null */ public static final NameSymbol of(final String name, final String symbol) { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>()); } /** * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use * @return NameSymbol instance * @since 2019-10-21 * @throws NullPointerException if any argument is null */ public static final NameSymbol of(final String name, final String symbol, final Set otherNames) { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>(Objects.requireNonNull(otherNames, "otherNames must not be null."))); } /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use * @return NameSymbol instance * @since 2019-10-21 * @throws NullPointerException if any argument is null */ public static final NameSymbol of(final String name, final String symbol, final String... otherNames) { return new NameSymbol(Optional.of(name), Optional.of(symbol), new HashSet<>(Arrays.asList(Objects.requireNonNull(otherNames, "otherNames must not be null.")))); } /** * Gets a {@code NameSymbol} with a primary name, no symbol, and no other * names. * * @param name name to use * @return NameSymbol instance * @since 2019-10-21 * @throws NullPointerException if name is null */ public static final NameSymbol ofName(final String name) { return new NameSymbol(Optional.of(name), Optional.empty(), new HashSet<>()); } /** * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. *

* If any argument is null, this static factory replaces it with an empty * Optional or empty Set. *

* If {@code name} is null and {@code otherNames} is not empty, a primary * name will be picked from {@code otherNames}. This name will not appear in * getOtherNames(). * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use * @return NameSymbol instance * @since 2019-11-26 */ public static final NameSymbol ofNullable(final String name, final String symbol, final Set otherNames) { return NameSymbol.create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(otherNames)); } /** * h * Gets a {@code NameSymbol} with a primary name, a symbol and additional * names. *

* If any argument is null, this static factory replaces it with an empty * Optional or empty Set. *

* If {@code name} is null and {@code otherNames} is not empty, a primary * name will be picked from {@code otherNames}. This name will not appear in * getOtherNames(). * * @param name name to use * @param symbol symbol to use * @param otherNames other names to use * @return NameSymbol instance * @since 2019-11-26 */ public static final NameSymbol ofNullable(final String name, final String symbol, final String... otherNames) { return create(name, symbol, otherNames == null ? new HashSet<>() : new HashSet<>(Arrays.asList(otherNames))); } /** * Gets a {@code NameSymbol} with a symbol and no names. * * @param symbol symbol to use * @return NameSymbol instance * @since 2019-10-21 * @throws NullPointerException if symbol is null */ public static final NameSymbol ofSymbol(final String symbol) { return new NameSymbol(Optional.empty(), Optional.of(symbol), new HashSet<>()); } private final Optional primaryName; private final Optional symbol; private final Set otherNames; /** * Creates the {@code NameSymbol}. * * @param primaryName primary name of unit * @param symbol symbol used to represent unit * @param otherNames other names and/or spellings, should be a mutable copy * of the argument * @since 2019-10-21 */ NameSymbol(final Optional primaryName, final Optional symbol, final Set otherNames) { this.primaryName = primaryName; this.symbol = symbol; if (otherNames != null) { otherNames.remove(null); this.otherNames = Collections.unmodifiableSet(otherNames); } else { this.otherNames = Set.of(); } if (this.primaryName == null || this.primaryName.isEmpty()) { assert this.otherNames.isEmpty(); } } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof NameSymbol)) return false; final NameSymbol other = (NameSymbol) obj; if (this.otherNames == null) { if (other.otherNames != null) return false; } else if (!this.otherNames.equals(other.otherNames)) return false; if (this.primaryName == null) { if (other.primaryName != null) return false; } else if (!this.primaryName.equals(other.primaryName)) return false; if (this.symbol == null) { if (other.symbol != null) return false; } else if (!this.symbol.equals(other.symbol)) return false; return true; } /** * @return otherNames * @since 2019-10-21 */ public final Set getOtherNames() { return this.otherNames; } /** * @return primaryName * @since 2019-10-21 */ public final Optional getPrimaryName() { return this.primaryName; } /** * @return symbol * @since 2019-10-21 */ public final Optional getSymbol() { return this.symbol; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (this.otherNames == null ? 0 : this.otherNames.hashCode()); result = prime * result + (this.primaryName == null ? 0 : this.primaryName.hashCode()); result = prime * result + (this.symbol == null ? 0 : this.symbol.hashCode()); return result; } /** * @return true iff this {@code NameSymbol} contains no names or symbols. */ public final boolean isEmpty() { // if primaryName is empty, otherNames must also be empty return this.primaryName.isEmpty() && this.symbol.isEmpty(); } @Override public String toString() { if (this.isEmpty()) return "NameSymbol.EMPTY"; else if (this.primaryName.isPresent() && this.symbol.isPresent()) return this.primaryName.orElseThrow() + " (" + this.symbol.orElseThrow() + ")"; else return this.primaryName.orElseGet(this.symbol::orElseThrow); } /** * Creates and returns a copy of this {@code NameSymbol} with the provided * extra name. If this {@code NameSymbol} has a primary name, the provided * name will become an other name, otherwise it will become the primary name. * * @param name additional name to add * @return copy of this NameSymbol with the additional name * * @since v0.4.0 * @since 2022-04-19 */ public final NameSymbol withExtraName(String name) { if (this.primaryName.isPresent()) { final var otherNames = new HashSet<>(this.otherNames); otherNames.add(name); return NameSymbol.ofNullable(this.primaryName.orElse(null), this.symbol.orElse(null), otherNames); } else return NameSymbol.ofNullable(name, this.symbol.orElse(null)); } }