This commit is contained in:
Looly 2022-03-07 02:39:05 +08:00
parent 04dc6d2b73
commit 820db7fa32
4 changed files with 588 additions and 63 deletions

View File

@ -70,7 +70,7 @@ public class EqualsBuilder implements Builder<Boolean> {
* Converters value pair into a register pair.
* </p>
*
* @param lhs <code>this</code> object
* @param lhs {@code this} object
* @param rhs the other object
* @return the pair
*/
@ -82,15 +82,15 @@ public class EqualsBuilder implements Builder<Boolean> {
/**
* <p>
* Returns <code>true</code> 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.
* </p>
*
* @param lhs <code>this</code> 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 <code>true</code> 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<Boolean> {
* Used by the reflection methods to avoid infinite loops.
* </p>
*
* @param lhs <code>this</code> 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<Boolean> {
* <p>
* Used by the reflection methods to avoid infinite loops.
*
* @param lhs <code>this</code> 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<Boolean> {
* @param lhs 此对象
* @param rhs 另一个对象
* @param excludeFields 排除的字段集合如果有不参与计算equals的字段加入此集合即可
* @return 两个对象是否equals是返回<code>true</code>
* @return 两个对象是否equals是返回{@code true}
*/
public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection<String> excludeFields) {
return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class));
@ -182,62 +182,62 @@ public class EqualsBuilder implements Builder<Boolean> {
* @param lhs 此对象
* @param rhs 另一个对象
* @param excludeFields 排除的字段集合如果有不参与计算equals的字段加入此集合即可
* @return 两个对象是否equals是返回<code>true</code>
* @return 两个对象是否equals是返回{@code true}
*/
public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) {
return reflectionEquals(lhs, rhs, false, null, excludeFields);
}
/**
* <p>This method uses reflection to determine if the two <code>Object</code>s
* <p>This method uses reflection to determine if the two {@code Object}s
* are equal.</p>
*
* <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
* <p>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
* <code>equals()</code>.</p>
* {@code equals()}.</p>
*
* <p>If the TestTransients parameter is set to <code>true</code>, transient
* <p>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 <code>Object</code>.</p>
* derived fields, and not part of the value of the {@code Object}.</p>
*
* <p>Static fields will not be tested. Superclass fields will be included.</p>
*
* @param lhs <code>this</code> object
* @param lhs {@code this} object
* @param rhs the other object
* @param testTransients whether to include transient fields
* @return <code>true</code> 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);
}
/**
* <p>This method uses reflection to determine if the two <code>Object</code>s
* <p>This method uses reflection to determine if the two {@code Object}s
* are equal.</p>
*
* <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private
* <p>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
* <code>equals()</code>.</p>
* {@code equals()}.</p>
*
* <p>If the testTransients parameter is set to <code>true</code>, transient
* <p>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 <code>Object</code>.</p>
* derived fields, and not part of the value of the {@code Object}.</p>
*
* <p>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.</p>
*
* @param lhs <code>this</code> 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 <code>null</code>
* may be {@code null}
* @param excludeFields array of field names to exclude from testing
* @return <code>true</code> 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<Boolean> {
//-------------------------------------------------------------------------
/**
* <p>Adds the result of <code>super.equals()</code> to this builder.</p>
* <p>Adds the result of {@code super.equals()} to this builder.</p>
*
* @param superEquals the result of calling <code>super.equals()</code>
* @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<Boolean> {
//-------------------------------------------------------------------------
/**
* <p>Test if two <code>Object</code>s are equal using their
* <code>equals</code> method.</p>
* <p>Test if two {@code Object}s are equal using their
* {@code equals} method.</p>
*
* @param lhs the left hand object
* @param rhs the right hand object
@ -388,11 +388,11 @@ public class EqualsBuilder implements Builder<Boolean> {
/**
* <p>
* Test if two <code>long</code> s are equal.
* Test if two {@code long} s are equal.
* </p>
*
* @param lhs the left hand <code>long</code>
* @param rhs the right hand <code>long</code>
* @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<Boolean> {
}
/**
* <p>Test if two <code>int</code>s are equal.</p>
* <p>Test if two {@code int}s are equal.</p>
*
* @param lhs the left hand <code>int</code>
* @param rhs the right hand <code>int</code>
* @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<Boolean> {
}
/**
* <p>Test if two <code>short</code>s are equal.</p>
* <p>Test if two {@code short}s are equal.</p>
*
* @param lhs the left hand <code>short</code>
* @param rhs the right hand <code>short</code>
* @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<Boolean> {
}
/**
* <p>Test if two <code>char</code>s are equal.</p>
* <p>Test if two {@code char}s are equal.</p>
*
* @param lhs the left hand <code>char</code>
* @param rhs the right hand <code>char</code>
* @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<Boolean> {
}
/**
* <p>Test if two <code>byte</code>s are equal.</p>
* <p>Test if two {@code byte}s are equal.</p>
*
* @param lhs the left hand <code>byte</code>
* @param rhs the right hand <code>byte</code>
* @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<Boolean> {
}
/**
* <p>Test if two <code>double</code>s are equal by testing that the
* pattern of bits returned by <code>doubleToLong</code> are equal.</p>
* <p>Test if two {@code double}s are equal by testing that the
* pattern of bits returned by {@code doubleToLong} are equal.</p>
*
* <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p>
* <p>This handles NaNs, Infinities, and {@code -0.0}.</p>
*
* <p>It is compatible with the hash code generated by
* <code>HashCodeBuilder</code>.</p>
* {@code HashCodeBuilder}.</p>
*
* @param lhs the left hand <code>double</code>
* @param rhs the right hand <code>double</code>
* @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<Boolean> {
}
/**
* <p>Test if two <code>float</code>s are equal byt testing that the
* <p>Test if two {@code float}s are equal byt testing that the
* pattern of bits returned by doubleToLong are equal.</p>
*
* <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p>
* <p>This handles NaNs, Infinities, and {@code -0.0}.</p>
*
* <p>It is compatible with the hash code generated by
* <code>HashCodeBuilder</code>.</p>
* {@code HashCodeBuilder}.</p>
*
* @param lhs the left hand <code>float</code>
* @param rhs the right hand <code>float</code>
* @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<Boolean> {
}
/**
* <p>Test if two <code>booleans</code>s are equal.</p>
* <p>Test if two {@code booleans}s are equal.</p>
*
* @param lhs the left hand <code>boolean</code>
* @param rhs the right hand <code>boolean</code>
* @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<Boolean> {
}
/**
* <p>Returns <code>true</code> if the fields that have been checked
* <p>Returns {@code true} if the fields that have been checked
* are all equal.</p>
*
* @return boolean
@ -529,11 +529,11 @@ public class EqualsBuilder implements Builder<Boolean> {
}
/**
* <p>Returns <code>true</code> if the fields that have been checked
* <p>Returns {@code true} if the fields that have been checked
* are all equal.</p>
*
* @return <code>true</code> if all of the fields that have been checked
* are equal, <code>false</code> 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<Boolean> {
}
/**
* Sets the <code>isEquals</code> value.
* Sets the {@code isEquals} value.
*
* @param isEquals The value to set.
* @return this

View File

@ -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<R, C, V> implements Table<R, C, V> {
final Map<R, Map<C, V>> raw;
final Supplier<? extends Map<C, V>> supplier;
public RowKeyTable(Map<R, Map<C, V>> raw) {
this(raw, HashMap::new);
}
public RowKeyTable(Map<R, Map<C, V>> raw, Supplier<? extends Map<C, V>> columnMapSupplier) {
this.raw = raw;
this.supplier = null == columnMapSupplier ? HashMap::new : columnMapSupplier;
}
@Override
public Map<R, Map<C, V>> rowMap() {
return raw;
}
@Override
public Map<C, Map<R, V>> columnMap() {
// TODO 实现columnMap
throw new UnsupportedOperationException("TODO implement this method");
}
@Override
public Collection<V> values() {
return this.values;
}
@Override
public Set<Cell<R, C, V>> 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<? extends R, ? extends C, ? extends V> table) {
if (null != table) {
for (Table.Cell<? extends R, ? extends C, ? extends V> cell : table.cellSet()) {
put(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
}
}
}
@Override
public V remove(R rowKey, C columnKey) {
final Map<C, V> 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<Cell<R, C, V>> 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<Cell<R, C, V>> {
final Iterator<Map.Entry<R, Map<C, V>>> rowIterator = raw.entrySet().iterator();
Map.Entry<R, Map<C, V>> rowEntry;
Iterator<Map.Entry<C, V>> columnIterator = IterUtil.empty();
@Override
public boolean hasNext() {
return rowIterator.hasNext() || columnIterator.hasNext();
}
@Override
public Cell<R, C, V> next() {
if (false == columnIterator.hasNext()) {
rowEntry = rowIterator.next();
columnIterator = rowEntry.getValue().entrySet().iterator();
}
final Map.Entry<C, V> 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 <R> 行类型
* @param <C> 列类型
* @param <V> 值类型
*/
private static class SimpleCell<R, C, V> implements Cell<R, C, V>, 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<V> values = new AbstractCollection<V>() {
@Override
public Iterator<V> 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<Cell<R, C, V>> cellSet = new AbstractSet<Cell<R, C, V>>() {
@Override
public boolean contains(Object o) {
if (o instanceof Cell) {
@SuppressWarnings("unchecked") final
Cell<R, C, V> cell = (Cell<R, C, V>) o;
Map<C, V> 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<R, C, V> cell = (Cell<R, C, V>) o;
RowKeyTable.this.remove(cell.getRowKey(), cell.getColumnKey());
}
return false;
}
@Override
public void clear() {
RowKeyTable.this.clear();
}
@Override
public Iterator<Table.Cell<R, C, V>> iterator() {
return new CellIterator();
}
@Override
public int size() {
return RowKeyTable.this.size();
}
};
}

View File

@ -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;
/**
* 表格数据结构定义<br>
* 此结构类似于Guava的Table接口使用两个键映射到一个值类似于表格结构
*
* @param <R> 行键类型
* @param <C> 列键类型
* @param <V> 值类型
* @since 5.7.23
*/
public interface Table<R, C, V> extends Iterable<Table.Cell<R, C, V>> {
/**
* 是否包含指定行列的映射<br>
* 行和列任意一个不存在都会返回{@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<C, V> getRow(R rowKey) {
return Opt.ofNullable(rowMap()).map((map) -> map.get(rowKey)).get();
}
/**
* 返回所有行的key行的key不可重复
*
* @return 行键
*/
default Set<R> rowKeySet() {
return Opt.ofNullable(rowMap()).map(Map::keySet).get();
}
/**
* 返回行列对应的Map
*
* @return map键为行键值为列和值的对应map
*/
Map<R, Map<C, V>> 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<R, V> getColumn(C columnKey) {
return Opt.ofNullable(columnMap()).map((map) -> map.get(columnKey)).get();
}
/**
* 返回所有列的key列的key不可重复
*
* @return 列set
*/
default Set<C> columnKeySet() {
return Opt.ofNullable(columnMap()).map(Map::keySet).get();
}
/**
* 返回列-行对应的map
*
* @return map键为列键值为行和值的对应map
*/
Map<C, Map<R, V>> columnMap();
//endregion
//region value
/**
* 指定值是否存在
*
* @param value
* @return
*/
default boolean containsValue(V value){
final Collection<Map<C, V>> rows = Opt.ofNullable(rowMap()).map(Map::values).get();
if(null != rows){
for (Map<C, V> 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<V> values();
//endregion
/**
* 所有单元格集合
*
* @return 单元格集合
*/
Set<Cell<R, C, V>> cellSet();
/**
* 为表格指定行列赋值如果不存在创建之存在则替换之返回原值
*
* @param rowKey 行键
* @param columnKey 列键
* @param value
* @return 原值不存在返回{@code null}
*/
V put(R rowKey, C columnKey, V value);
/**
* 批量加入
*
* @param table 其他table
*/
void putAll(Table<? extends R, ? extends C, ? extends V> table);
/**
* 移除指定值
*
* @param rowKey 行键
* @param columnKey 列键
* @return 移除的值如果值不存在返回{@code null}
*/
V remove(R rowKey, C columnKey);
/**
* 表格是否为空
*
* @return 是否为空
*/
boolean isEmpty();
/**
* 表格大小一般为单元格的个数
*
* @return 表格大小
*/
default int size(){
final Map<R, Map<C, V>> rowMap = rowMap();
if(MapUtil.isEmpty(rowMap)){
return 0;
}
int size = 0;
for (Map<C, V> map : rowMap.values()) {
size += map.size();
}
return size;
}
/**
* 清空表格
*/
void clear();
/**
* 遍历表格的单元格处理值
*
* @param consumer 单元格值处理器
*/
default void forEach(Consumer3<? super R, ? super C, ? super V> consumer) {
for (Cell<R, C, V> cell : this) {
consumer.accept(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
}
}
/**
* 单元格用于表示一个单元格的行列和值
*
* @param <R> 行键类型
* @param <C> 列键类型
* @param <V> 值类型
*/
interface Cell<R, C, V> {
/**
* 获取行键
*
* @return 行键
*/
R getRowKey();
/**
* 获取列键
*
* @return 列键
*/
C getColumnKey();
/**
* 获取值
*
* @return
*/
V getValue();
}
}

View File

@ -1,7 +1,7 @@
/**
* 列表类型值的Map实现
* 多参数类型的Map实现包括集合类型值的Map和Table
*
* @author looly
*
*/
package cn.hutool.core.map.multi;
package cn.hutool.core.map.multi;