diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java index bb7c7e46c..e68ccdf38 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java @@ -70,7 +70,7 @@ public class EqualsBuilder implements Builder { * Converters value pair into a register pair. *

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @return the pair */ @@ -82,15 +82,15 @@ public class EqualsBuilder implements Builder { /** *

- * Returns true if the registry contains the given object pair. + * Returns {@code true} if the registry contains the given object pair. * Used by the reflection methods to avoid infinite loops. * Objects might be swapped therefore a check is needed if the object pair * is registered in given or swapped order. *

* - * @param lhs this object to lookup in registry + * @param lhs {@code this} object to lookup in registry * @param rhs the other object to lookup on registry - * @return boolean true if the registry contains the given object. + * @return boolean {@code true} if the registry contains the given object. * @since 3.0 */ static boolean isRegistered(final Object lhs, final Object rhs) { @@ -108,7 +108,7 @@ public class EqualsBuilder implements Builder { * Used by the reflection methods to avoid infinite loops. *

* - * @param lhs this object to register + * @param lhs {@code this} object to register * @param rhs the other object to register */ static void register(final Object lhs, final Object rhs) { @@ -131,7 +131,7 @@ public class EqualsBuilder implements Builder { *

* Used by the reflection methods to avoid infinite loops. * - * @param lhs this object to unregister + * @param lhs {@code this} object to unregister * @param rhs the other object to unregister * @since 3.0 */ @@ -170,7 +170,7 @@ public class EqualsBuilder implements Builder { * @param lhs 此对象 * @param rhs 另一个对象 * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true + * @return 两个对象是否equals,是返回{@code true} */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); @@ -182,62 +182,62 @@ public class EqualsBuilder implements Builder { * @param lhs 此对象 * @param rhs 另一个对象 * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true + * @return 两个对象是否equals,是返回{@code true} */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { return reflectionEquals(lhs, rhs, false, null, excludeFields); } /** - *

This method uses reflection to determine if the two Objects + *

This method uses reflection to determine if the two {@code Object}s * are equal.

* - *

It uses AccessibleObject.setAccessible to gain access to private + *

It uses {@code AccessibleObject.setAccessible} to gain access to private * fields. This means that it will throw a security exception if run under * a security manager, if the permissions are not set up correctly. It is also * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

+ * {@code equals()}.

* - *

If the TestTransients parameter is set to true, transient + *

If the TestTransients parameter is set to {@code true}, transient * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

+ * derived fields, and not part of the value of the {@code Object}.

* *

Static fields will not be tested. Superclass fields will be included.

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @param testTransients whether to include transient fields - * @return true if the two Objects have tested equals. + * @return {@code true} if the two Objects have tested equals. */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { return reflectionEquals(lhs, rhs, testTransients, null); } /** - *

This method uses reflection to determine if the two Objects + *

This method uses reflection to determine if the two {@code Object}s * are equal.

* - *

It uses AccessibleObject.setAccessible to gain access to private + *

It uses {@code AccessibleObject.setAccessible} to gain access to private * fields. This means that it will throw a security exception if run under * a security manager, if the permissions are not set up correctly. It is also * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

+ * {@code equals()}.

* - *

If the testTransients parameter is set to true, transient + *

If the testTransients parameter is set to {@code true}, transient * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

+ * derived fields, and not part of the value of the {@code Object}.

* *

Static fields will not be included. Superclass fields will be appended * up to and including the specified superclass. A null superclass is treated * as java.lang.Object.

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @param testTransients whether to include transient fields * @param reflectUpToClass the superclass to reflect up to (inclusive), - * may be null + * may be {@code null} * @param excludeFields array of field names to exclude from testing - * @return true if the two Objects have tested equals. + * @return {@code true} if the two Objects have tested equals. * @since 2.0 */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, @@ -343,9 +343,9 @@ public class EqualsBuilder implements Builder { //------------------------------------------------------------------------- /** - *

Adds the result of super.equals() to this builder.

+ *

Adds the result of {@code super.equals()} to this builder.

* - * @param superEquals the result of calling super.equals() + * @param superEquals the result of calling {@code super.equals()} * @return EqualsBuilder - used to chain calls. * @since 2.0 */ @@ -360,8 +360,8 @@ public class EqualsBuilder implements Builder { //------------------------------------------------------------------------- /** - *

Test if two Objects are equal using their - * equals method.

+ *

Test if two {@code Object}s are equal using their + * {@code equals} method.

* * @param lhs the left hand object * @param rhs the right hand object @@ -388,11 +388,11 @@ public class EqualsBuilder implements Builder { /** *

- * Test if two long s are equal. + * Test if two {@code long} s are equal. *

* - * @param lhs the left hand long - * @param rhs the right hand long + * @param lhs the left hand {@code long} + * @param rhs the right hand {@code long} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final long lhs, final long rhs) { @@ -404,10 +404,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two ints are equal.

+ *

Test if two {@code int}s are equal.

* - * @param lhs the left hand int - * @param rhs the right hand int + * @param lhs the left hand {@code int} + * @param rhs the right hand {@code int} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final int lhs, final int rhs) { @@ -419,10 +419,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two shorts are equal.

+ *

Test if two {@code short}s are equal.

* - * @param lhs the left hand short - * @param rhs the right hand short + * @param lhs the left hand {@code short} + * @param rhs the right hand {@code short} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final short lhs, final short rhs) { @@ -434,10 +434,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two chars are equal.

+ *

Test if two {@code char}s are equal.

* - * @param lhs the left hand char - * @param rhs the right hand char + * @param lhs the left hand {@code char} + * @param rhs the right hand {@code char} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final char lhs, final char rhs) { @@ -449,10 +449,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two bytes are equal.

+ *

Test if two {@code byte}s are equal.

* - * @param lhs the left hand byte - * @param rhs the right hand byte + * @param lhs the left hand {@code byte} + * @param rhs the right hand {@code byte} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final byte lhs, final byte rhs) { @@ -464,16 +464,16 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two doubles are equal by testing that the - * pattern of bits returned by doubleToLong are equal.

+ *

Test if two {@code double}s are equal by testing that the + * pattern of bits returned by {@code doubleToLong} are equal.

* - *

This handles NaNs, Infinities, and -0.0.

+ *

This handles NaNs, Infinities, and {@code -0.0}.

* *

It is compatible with the hash code generated by - * HashCodeBuilder.

+ * {@code HashCodeBuilder}.

* - * @param lhs the left hand double - * @param rhs the right hand double + * @param lhs the left hand {@code double} + * @param rhs the right hand {@code double} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final double lhs, final double rhs) { @@ -484,16 +484,16 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two floats are equal byt testing that the + *

Test if two {@code float}s are equal byt testing that the * pattern of bits returned by doubleToLong are equal.

* - *

This handles NaNs, Infinities, and -0.0.

+ *

This handles NaNs, Infinities, and {@code -0.0}.

* *

It is compatible with the hash code generated by - * HashCodeBuilder.

+ * {@code HashCodeBuilder}.

* - * @param lhs the left hand float - * @param rhs the right hand float + * @param lhs the left hand {@code float} + * @param rhs the right hand {@code float} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final float lhs, final float rhs) { @@ -504,10 +504,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two booleanss are equal.

+ *

Test if two {@code booleans}s are equal.

* - * @param lhs the left hand boolean - * @param rhs the right hand boolean + * @param lhs the left hand {@code boolean} + * @param rhs the right hand {@code boolean} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final boolean lhs, final boolean rhs) { @@ -519,7 +519,7 @@ public class EqualsBuilder implements Builder { } /** - *

Returns true if the fields that have been checked + *

Returns {@code true} if the fields that have been checked * are all equal.

* * @return boolean @@ -529,11 +529,11 @@ public class EqualsBuilder implements Builder { } /** - *

Returns true if the fields that have been checked + *

Returns {@code true} if the fields that have been checked * are all equal.

* - * @return true if all of the fields that have been checked - * are equal, false otherwise. + * @return {@code true} if all of the fields that have been checked + * are equal, {@code false} otherwise. * @since 3.0 */ @Override @@ -542,7 +542,7 @@ public class EqualsBuilder implements Builder { } /** - * Sets the isEquals value. + * Sets the {@code isEquals} value. * * @param isEquals The value to set. * @return this diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java new file mode 100644 index 000000000..0e8854896 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java @@ -0,0 +1,272 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.collection.TransIter; +import cn.hutool.core.util.ObjectUtil; +import com.sun.istack.internal.Nullable; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; + +public class RowKeyTable implements Table { + + final Map> raw; + final Supplier> supplier; + + public RowKeyTable(Map> raw) { + this(raw, HashMap::new); + } + + public RowKeyTable(Map> raw, Supplier> columnMapSupplier) { + this.raw = raw; + this.supplier = null == columnMapSupplier ? HashMap::new : columnMapSupplier; + } + + @Override + public Map> rowMap() { + return raw; + } + + @Override + public Map> columnMap() { + // TODO 实现columnMap + throw new UnsupportedOperationException("TODO implement this method"); + } + + @Override + public Collection values() { + return this.values; + } + + @Override + public Set> cellSet() { + return this.cellSet; + } + + @Override + public V put(R rowKey, C columnKey, V value) { + return raw.computeIfAbsent(rowKey, (key) -> supplier.get()).put(columnKey, value); + } + + @Override + public void putAll(Table table) { + if (null != table) { + for (Table.Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + } + + @Override + public V remove(R rowKey, C columnKey) { + final Map map = getRow(rowKey); + if (null == map) { + return null; + } + final V value = map.remove(columnKey); + if (map.isEmpty()) { + raw.remove(rowKey); + } + return value; + } + + @Override + public boolean isEmpty() { + return raw.isEmpty(); + } + + @Override + public void clear() { + this.raw.clear(); + } + + @Override + public Iterator> iterator() { + return new CellIterator(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Table) { + final Table that = (Table) obj; + return this.cellSet().equals(that.cellSet()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return cellSet().hashCode(); + } + + @Override + public String toString() { + return rowMap().toString(); + } + + /** + * 基于{@link Cell}的{@link Iterator}实现 + */ + private class CellIterator implements Iterator> { + final Iterator>> rowIterator = raw.entrySet().iterator(); + Map.Entry> rowEntry; + Iterator> columnIterator = IterUtil.empty(); + + @Override + public boolean hasNext() { + return rowIterator.hasNext() || columnIterator.hasNext(); + } + + @Override + public Cell next() { + if (false == columnIterator.hasNext()) { + rowEntry = rowIterator.next(); + columnIterator = rowEntry.getValue().entrySet().iterator(); + } + final Map.Entry columnEntry = columnIterator.next(); + return new SimpleCell<>(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + } + + @Override + public void remove() { + columnIterator.remove(); + if (rowEntry.getValue().isEmpty()) { + rowIterator.remove(); + } + } + } + + /** + * 简单{@link Cell} 实现 + * + * @param 行类型 + * @param 列类型 + * @param 值类型 + */ + private static class SimpleCell implements Cell, Serializable { + private static final long serialVersionUID = 1L; + + private final R rowKey; + private final C columnKey; + private final V value; + + SimpleCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { + this.rowKey = rowKey; + this.columnKey = columnKey; + this.value = value; + } + + @Override + public R getRowKey() { + return rowKey; + } + + @Override + public C getColumnKey() { + return columnKey; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Cell) { + Cell other = (Cell) obj; + return ObjectUtil.equal(rowKey, other.getRowKey()) + && ObjectUtil.equal(columnKey, other.getColumnKey()) + && ObjectUtil.equal(value, other.getValue()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(rowKey, columnKey, value); + } + + @Override + public String toString() { + return "(" + rowKey + "," + columnKey + ")=" + value; + } + } + + private final Collection values = new AbstractCollection() { + @Override + public Iterator iterator() { + return new TransIter<>(cellSet().iterator(), Cell::getValue); + } + + @Override + public boolean contains(Object o) { + //noinspection unchecked + return containsValue((V) o); + } + + @Override + public void clear() { + RowKeyTable.this.clear(); + } + + @Override + public int size() { + return RowKeyTable.this.size(); + } + }; + + private final Set> cellSet = new AbstractSet>() { + @Override + public boolean contains(Object o) { + if (o instanceof Cell) { + @SuppressWarnings("unchecked") final + Cell cell = (Cell) o; + Map row = getRow(cell.getRowKey()); + if (null != row) { + return ObjectUtil.equals(row.get(cell.getColumnKey()), cell.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + @SuppressWarnings("unchecked") + final Cell cell = (Cell) o; + RowKeyTable.this.remove(cell.getRowKey(), cell.getColumnKey()); + } + return false; + } + + @Override + public void clear() { + RowKeyTable.this.clear(); + } + + @Override + public Iterator> iterator() { + return new CellIterator(); + } + + @Override + public int size() { + return RowKeyTable.this.size(); + } + }; +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java new file mode 100644 index 000000000..cd14029a5 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java @@ -0,0 +1,253 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.func.Consumer3; +import cn.hutool.core.map.MapUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * 表格数据结构定义
+ * 此结构类似于Guava的Table接口,使用两个键映射到一个值,类似于表格结构。 + * + * @param 行键类型 + * @param 列键类型 + * @param 值类型 + * @since 5.7.23 + */ +public interface Table extends Iterable> { + + /** + * 是否包含指定行列的映射
+ * 行和列任意一个不存在都会返回{@code false},如果行和列都存在,值为{@code null},也会返回{@code true} + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 是否包含映射 + */ + default boolean contains(R rowKey, C columnKey) { + return Opt.ofNullable(getRow(rowKey)).map((map) -> map.containsKey(columnKey)).get(); + } + + //region Row + + /** + * 行是否存在 + * + * @param rowKey 行键 + * @return 行是否存在 + */ + default boolean containsRow(R rowKey) { + return Opt.ofNullable(rowMap()).map((map) -> map.containsKey(rowKey)).get(); + } + + /** + * 获取行 + * + * @param rowKey 行键 + * @return 行映射,返回的键为列键,值为表格的值 + */ + default Map getRow(R rowKey) { + return Opt.ofNullable(rowMap()).map((map) -> map.get(rowKey)).get(); + } + + /** + * 返回所有行的key,行的key不可重复 + * + * @return 行键 + */ + default Set rowKeySet() { + return Opt.ofNullable(rowMap()).map(Map::keySet).get(); + } + + /** + * 返回行列对应的Map + * + * @return map,键为行键,值为列和值的对应map + */ + Map> rowMap(); + //endregion + + //region Column + + /** + * 列是否存在 + * + * @param columnKey 列键 + * @return 列是否存在 + */ + default boolean containsColumn(C columnKey) { + return Opt.ofNullable(columnMap()).map((map) -> map.containsKey(columnKey)).get(); + } + + /** + * 获取列 + * + * @param columnKey 列键 + * @return 列映射,返回的键为行键,值为表格的值 + */ + default Map getColumn(C columnKey) { + return Opt.ofNullable(columnMap()).map((map) -> map.get(columnKey)).get(); + } + + /** + * 返回所有列的key,列的key不可重复 + * + * @return 列set + */ + default Set columnKeySet() { + return Opt.ofNullable(columnMap()).map(Map::keySet).get(); + } + + /** + * 返回列-行对应的map + * + * @return map,键为列键,值为行和值的对应map + */ + Map> columnMap(); + //endregion + + //region value + + /** + * 指定值是否存在 + * + * @param value 值 + * @return 值 + */ + default boolean containsValue(V value){ + final Collection> rows = Opt.ofNullable(rowMap()).map(Map::values).get(); + if(null != rows){ + for (Map row : rows) { + if (row.containsValue(value)) { + return true; + } + } + } + return false; + } + + /** + * 获取指定值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 值,如果值不存在,返回{@code null} + */ + default V get(R rowKey, C columnKey) { + return Opt.ofNullable(getRow(rowKey)).map((map) -> map.get(columnKey)).get(); + } + + /** + * 所有行列值的集合 + * + * @return 值的集合 + */ + Collection values(); + //endregion + + /** + * 所有单元格集合 + * + * @return 单元格集合 + */ + Set> cellSet(); + + /** + * 为表格指定行列赋值,如果不存在,创建之,存在则替换之,返回原值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @param value 值 + * @return 原值,不存在返回{@code null} + */ + V put(R rowKey, C columnKey, V value); + + /** + * 批量加入 + * + * @param table 其他table + */ + void putAll(Table table); + + /** + * 移除指定值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 移除的值,如果值不存在,返回{@code null} + */ + V remove(R rowKey, C columnKey); + + /** + * 表格是否为空 + * + * @return 是否为空 + */ + boolean isEmpty(); + + /** + * 表格大小,一般为单元格的个数 + * + * @return 表格大小 + */ + default int size(){ + final Map> rowMap = rowMap(); + if(MapUtil.isEmpty(rowMap)){ + return 0; + } + int size = 0; + for (Map map : rowMap.values()) { + size += map.size(); + } + return size; + } + + /** + * 清空表格 + */ + void clear(); + + /** + * 遍历表格的单元格,处理值 + * + * @param consumer 单元格值处理器 + */ + default void forEach(Consumer3 consumer) { + for (Cell cell : this) { + consumer.accept(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + + /** + * 单元格,用于表示一个单元格的行、列和值 + * + * @param 行键类型 + * @param 列键类型 + * @param 值类型 + */ + interface Cell { + /** + * 获取行键 + * + * @return 行键 + */ + R getRowKey(); + + /** + * 获取列键 + * + * @return 列键 + */ + C getColumnKey(); + + /** + * 获取值 + * + * @return 值 + */ + V getValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java index b0a2bcd20..2bf5b685f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java @@ -1,7 +1,7 @@ /** - * 列表类型值的Map实现 + * 多参数类型的Map实现,包括集合类型值的Map和Table * * @author looly * */ -package cn.hutool.core.map.multi; \ No newline at end of file +package cn.hutool.core.map.multi;