All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.broadinstitute.hellbender.utils.tsv.TableColumnCollection Maven / Gradle / Ivy

There is a newer version: 4.6.0.0
Show newest version
package org.broadinstitute.hellbender.utils.tsv;

import org.broadinstitute.hellbender.utils.Utils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Represents a list of table columns.
 *
 * @author Valentin Ruano-Rubio <[email protected]>
 */
public final class TableColumnCollection {

    /**
     * List of column names sorted by their index.
     */
    private final List names;

    /**
     * Map from column name to its index, the first column has index 0, the second 1 and so forth.
     */
    private final Map indexByName;

    /**
     * Creates a new table-column names collection.
     * 

* The new instance will have its own copy of the input name array, * thus is safe to modify such an array after calling this constructor; * it won't modify the state of this object. *

* * @param names the column names. * @throws IllegalArgumentException if {@code names} is not a valid column name iterable * of names as determined by {@link #checkNames checkNames}. */ public TableColumnCollection(final Iterable names) { this(Utils.stream(Utils.nonNull(names, "the names cannot be null")).toArray(String[]::new)); } /** * Creates a new table-column names collection. *

* The new instance will have its own copy of the input name array, * thus is safe to modify such an array after calling this constructor; * it won't modify the state of this object. *

* * @param names the column names. * @throws IllegalArgumentException if {@code names} is not a valid column name array * of names as determined by {@link #checkNames checkNames}. */ public TableColumnCollection(final String... names) { this.names = Collections.unmodifiableList(Arrays.asList(checkNames(names.clone(), IllegalArgumentException::new))); this.indexByName = IntStream.range(0, names.length).boxed() .collect(Collectors.toMap(this.names::get, Function.identity())); } /** * Creates a new table-column names collection. *

* The new instance will have its own copy of the input name array, * thus is safe to modify such an array after calling this constructor; * it won't modify the state of this object. *

* * @param names the column names. They will be transformed into strings using {@link Object#toString() toString}. * @throws IllegalArgumentException if {@code names} is not a valid column name array * of names as determined by {@link #checkNames checkNames}. */ public TableColumnCollection(final Object... names) { this.names = Collections.unmodifiableList(Arrays.asList(checkNames(names, IllegalArgumentException::new))); this.indexByName = IntStream.range(0, names.length).boxed() .collect(Collectors.toMap(this.names::get, Function.identity())); } /** * Creates a new table-column collection from a set of enum constants. * *

* The new instance will have one column for enum value in {@code enumClass}, in their * ordinal order. *

* *

* The {@link Object#toString() toString} transformation of each constant is used * as the column name. Notice that this might differ from the constant name if * this method is overloaded by the enum. *

* @param enumClass the input enum class. * @throws IllegalArgumentException if {@code enumClass} is {@code null} or is actually * not an enum class or the corresponding names are illegal. */ public TableColumnCollection(final Class> enumClass) { Utils.nonNull(enumClass); // Despite the generic annotation, due to erasure this might not be a enum class // in run-time. if (!enumClass.isEnum()) { throw new IllegalArgumentException("the input class must be an enum class"); } names = Collections.unmodifiableList( Stream.of(enumClass.getEnumConstants()) .map(Object::toString) .collect(Collectors.toList())); checkNames(names.toArray(new String[names.size()]), IllegalArgumentException::new); indexByName = IntStream.range(0, names.size()).boxed() .collect(Collectors.toMap(names::get, Function.identity())); } /** * Returns the column names ordered by column index. * * @return never {@code null}, a unmodifiable view to this collection column names. */ public List names() { return names; } /** * Returns the name of a column by its index. *

* Column indexes are 0-based. *

* * @param index the target column index. * @return never {@code null}. * @throws IllegalArgumentException if {@code index} is not a valid column index. */ public String nameAt(final int index) { Utils.validIndex(index, names.size()); return names.get(index); } /** * Returns the index of a column by its name. * * @param name the query column name. * @return {@code -1} if there is not such a column, 0 or greater otherwise. */ public int indexOf(final String name) { Utils.nonNull(name, "the column name cannot be null"); return indexByName.getOrDefault(name, -1); } /** * Check whether there is such a column by name. * * @param name the query name. * @return {@code true} iff there is a column with such a {@code name}. * @throws IllegalArgumentException if {@code name} is {@code null}. */ public boolean contains(final String name) { return indexByName.containsKey(Utils.nonNull(name, "cannot be null")); } /** * Checks whether columns contain all the names in an array. * * @param names the names to test. * @return {@code true} iff all the names in {@code names} correspond to columns in this collection. * @throws IllegalArgumentException if {@code names} is {@code null} or contains any {@code null}. */ public boolean containsAll(final String... names) { return Stream.of(Utils.nonNull(names, "names cannot be null")).allMatch(this::contains); } /** * Checks whether columns contain all the names in an array. * * @param names the names to test. * @return {@code true} iff all the names in {@code names} correspond to columns in this collection. * @throws IllegalArgumentException if {@code names} is {@code null} or contains any {@code null}. */ public boolean containsAll(final Iterable names) { for (final String name : Utils.nonNull(names, "names cannot be null")) { if (!contains(name)) { return false; } } return true; } /** * Checks whether columns contain all the names in an array and no other. * * @param names the names to test. * @return {@code true} iff all the names in {@code names} correspond to columns in this collection, and there * is no other column name. * @throws IllegalArgumentException if {@code names} is {@code null} or contains {@code null}. */ public boolean containsExactly(final String... names) { return containsAll(names) && columnCount() == names.length; } /** * Checks a column names matches given one. *

* A match against a column index beyond the last column returns {@code false}. *

* * @param index the column index to test. * @param name the expected column name. * @return {@code true} iff the the column at {@code index}'s name is equal to the input {@code name}. * @throws IllegalArgumentException if {@code name} is {@code null} or {@code index} is negative. */ public boolean matches(final int index, final String name) { if (index >= names.size()) { return false; } else { return names.get(Utils.validIndex(index, names.size())).equals(Utils.nonNull(name, "name cannot be null")); } } /** * Checks an array of column matches the column names starting from a given index. * * @param offset the column index to match to the first name in {@code names} test. * @param names the expected column names. * @return {@code true} iff the the column names starting from {@code index} is equal to the input {@code names}. * @throws IllegalArgumentException if {@code names} is {@code null}, contains any {@code null} * or {@code index} is not a valid index. */ public boolean matchesAll(final int offset, final String... names) { Utils.validIndex(offset, this.names.size() + 1); final int toIndex = offset + names.length; if (toIndex > this.names.size()) { return false; } for (int i = 0; i < names.length; i++) { if (!this.names.get(offset + i).equals(names[i])) { return false; } } return true; } /** * Checks whether columns contain all the names in an array and no other and in the very same order. * * @param names the names to test. * @return {@code true} iff all the names in {@code names} correspond to columns in this collection in the same * order, and there is no other column. * @throws IllegalArgumentException if {@code names} is {@code null} or contains {@code null}. */ public boolean matchesExactly(final String... names) { return matchesAll(0, names) && names.length == this.names.size(); } /** * Returns the number of columns. * * @return never {@code column} */ public int columnCount() { return names.size(); } /** * Checks that a column name, as objects, array is valid. *

* The actual column names to test are obtained by mapping its object to its string representation using * {@link Object#toString() toString}. *

* *

* Assuming that it is null-value free, a column name array is invalid if: *

    *
  • has length 0,
  • *
  • the first name contains the comment prefix,
  • *
  • or contains repeats
  • *
*

*

When the input array is invalid, an exception is thrown using the exception factory function provided.

*

The message passed to the factory explains why the column name array is invalid

. *

Notice that a {@code null} array or a {@code null} containing array is considered a illegal argument caused by * a bug and a {@code IllegalArgumentException} will be thrown instead.

* * @throws IllegalArgumentException if {@code columnNames} is {@code null}, or it contains any {@code null}, or {@code exceptionFactory} is {@code null} or ir returns a {@code null} * when invoked. * @throws RuntimeException if {@code columnNames} does not contain a valid list of column names. The exact type will depend on the * input {@code exceptionFactory}. * @return never {@code null}, the same reference as the input column name array {@code columnNames}. */ public static String[] checkNames(final Object[] columnNames, final Function exceptionFactory) { Utils.nonNull(columnNames, "column names cannot be null"); final String[] stringNames = new String[columnNames.length]; for (int i = 0; i < columnNames.length; i++) { stringNames[i] = Utils.nonNull(columnNames[i],"no column name can be null: e.g. " + i + " element").toString(); } return checkNames(stringNames, exceptionFactory); } /** * Checks that a column name array is valid. * *

* Assuming that it is null-value free, a column name array is invalid if: *

    *
  • has length 0,
  • *
  • the first name contains the comment prefix,
  • *
  • or contains repeats
  • *
*

*

When the input array is invalid, an exception is thrown using the exception factory function provided.

*

The message passed to the factory explains why the column name array is invalid

. *

Notice that a {@code null} array or a {@code null} containing array is considered a illegal argument caused by * a bug an a {@code IllegalArgumentException} will be thrown instead.

* * @throws IllegalArgumentException if {@code columnNames} is {@code null}, or it contains any {@code null}, or {@code exceptionFactory} is {@code null} or ir returns a {@code null} * when invoked. * @throws RuntimeException if {@code columnNames} does not contain a valid list of column names. The exact type will depend on the * input {@code exceptionFactory}. * @return never {@code null}, the same reference as the input column name array {@code columnNames}. */ public static String[] checkNames(final String[] columnNames, final Function exceptionFactory) { Utils.nonNull(columnNames, "column names cannot be null"); Utils.nonNull(exceptionFactory, "exception factory cannot be null"); if (columnNames.length == 0) { throw Utils.nonNull(exceptionFactory.apply("there must be at least one column")); } final Set columnNameSet = new HashSet<>(columnNames.length); for (int i = 0; i < columnNames.length; i++) { final String columnName = Utils.nonNull(columnNames[i],"no column name can be null: e.g. " + i + " element"); if (!columnNameSet.add(columnName)) { throw Utils.nonNull(exceptionFactory.apply("more than one column have the same name: " + columnNames[i]), "exception factory produces null exceptions"); } } if (columnNames[0].startsWith(TableUtils.COMMENT_PREFIX)) { throw Utils.nonNull(exceptionFactory.apply("the first column name cannot start with the comment prefix"), "exception factory produces null exceptions"); } return columnNames; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final TableColumnCollection that = (TableColumnCollection) o; return names.equals(that.names) && indexByName.equals(that.indexByName); } @Override public int hashCode() { int result = names.hashCode(); result = 31 * result + indexByName.hashCode(); return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy