diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74943728c..4c9993b9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,13 +2,15 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
-# 5.7.23 (2022-03-05)
+# 5.7.23 (2022-03-08)
### 🐣新特性
* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee)
* 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@Gitee)
* 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee)
* 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee)
+* 【core 】 增加Table实现(issue#2179@Github)
+*
### 🐞Bug修复
* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee)
diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java
index f03451bc3..2634eefae 100644
--- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java
@@ -906,4 +906,19 @@ public class IterUtil {
// 当两个Iterable长度不一致时返回false
return false == (it1.hasNext() || it2.hasNext());
}
+
+ /**
+ * 清空指定{@link Iterator},此方法遍历后调用{@link Iterator#remove()}移除每个元素
+ *
+ * @param iterator {@link Iterator}
+ * @since 5.7.23
+ */
+ public static void clear(Iterator> iterator) {
+ if (null != iterator) {
+ while (iterator.hasNext()) {
+ iterator.next();
+ iterator.remove();
+ }
+ }
+ }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java b/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java
new file mode 100644
index 000000000..a2ab5885f
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java
@@ -0,0 +1,46 @@
+package cn.hutool.core.map;
+
+import cn.hutool.core.util.ObjectUtil;
+
+import java.util.Map;
+
+/**
+ * 抽象的{@link Map.Entry}实现,来自Guava
+ * 实现了默认的{@link #equals(Object)}、{@link #hashCode()}、{@link #toString()}方法。
+ * 默认{@link #setValue(Object)}抛出异常。
+ *
+ * @param 键类型
+ * @param 值类型
+ * @author Guava
+ * @since 5.7.23
+ */
+public abstract class AbsEntry implements Map.Entry {
+
+ @Override
+ public V setValue(V value) {
+ throw new UnsupportedOperationException("Entry is read only.");
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof Map.Entry) {
+ final Map.Entry, ?> that = (Map.Entry, ?>) object;
+ return ObjectUtil.equals(this.getKey(), that.getKey())
+ && ObjectUtil.equals(this.getValue(), that.getValue());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ //copy from 1.8 HashMap.Node
+ K k = getKey();
+ V v = getValue();
+ return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return getKey() + "=" + getValue();
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java b/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java
new file mode 100644
index 000000000..e414636c0
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java
@@ -0,0 +1,31 @@
+package cn.hutool.core.map;
+
+/**
+ * {@link java.util.Map.Entry}简单实现。
+ * 键值对使用不可变字段表示。
+ *
+ * @param 键类型
+ * @param 值类型
+ * @author looly
+ * @since 5.7.23
+ */
+public class SimpleEntry extends AbsEntry {
+
+ private final K key;
+ private final V value;
+
+ public SimpleEntry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ @Override
+ public V getValue() {
+ return value;
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java
index 596ec5160..783f1e98a 100644
--- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java
+++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java
@@ -13,7 +13,6 @@ import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
/**
@@ -173,7 +172,7 @@ public class TableMap implements Map, Iterable>, Ser
public Set> entrySet() {
final Set> hashSet = new LinkedHashSet<>();
for (int i = 0; i < size(); i++) {
- hashSet.add(new Entry<>(keys.get(i), values.get(i)));
+ hashSet.add(new SimpleEntry<>(keys.get(i), values.get(i)));
}
return hashSet;
}
@@ -191,7 +190,7 @@ public class TableMap implements Map, Iterable>, Ser
@Override
public Map.Entry next() {
- return new Entry<>(keysIter.next(), valuesIter.next());
+ return new SimpleEntry<>(keysIter.next(), valuesIter.next());
}
@Override
@@ -209,48 +208,4 @@ public class TableMap implements Map, Iterable>, Ser
", values=" + values +
'}';
}
-
- private static class Entry implements Map.Entry {
-
- private final K key;
- private final V value;
-
- public Entry(K key, V value) {
- this.key = key;
- this.value = value;
- }
-
- @Override
- public K getKey() {
- return key;
- }
-
- @Override
- public V getValue() {
- return value;
- }
-
- @Override
- public V setValue(V value) {
- throw new UnsupportedOperationException("setValue not supported.");
- }
-
- @Override
- public final boolean equals(Object o) {
- if (o == this)
- return true;
- if (o instanceof Map.Entry) {
- Map.Entry, ?> e = (Map.Entry, ?>) o;
- return Objects.equals(key, e.getKey()) &&
- Objects.equals(value, e.getValue());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- //copy from 1.8 HashMap.Node
- return Objects.hashCode(key) ^ Objects.hashCode(value);
- }
- }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java
new file mode 100644
index 000000000..a98e40286
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java
@@ -0,0 +1,236 @@
+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 java.io.Serializable;
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 抽象{@link Table}接口实现
+ * 默认实现了:
+ *
+ * - {@link #equals(Object)}
+ * - {@link #hashCode()}
+ * - {@link #toString()}
+ * - {@link #values()}
+ * - {@link #cellSet()}
+ * - {@link #iterator()}
+ *
+ *
+ * @param 行类型
+ * @param 列类型
+ * @param 值类型
+ * @author Guava, Looly
+ * @since 5.7.23
+ */
+public abstract class AbsTable implements Table {
+
+ @Override
+ public boolean equals(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();
+ }
+
+ //region values
+ @Override
+ public Collection values() {
+ Collection result = values;
+ return (result == null) ? values = new Values() : result;
+ }
+
+ private Collection values;
+ private class Values extends 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() {
+ AbsTable.this.clear();
+ }
+
+ @Override
+ public int size() {
+ return AbsTable.this.size();
+ }
+ }
+ //endregion
+
+ //region cellSet
+ @Override
+ public Set> cellSet() {
+ Set> result = cellSet;
+ return (result == null) ? cellSet = new CellSet() : result;
+ }
+
+ private Set> cellSet;
+
+ private class CellSet extends 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;
+ AbsTable.this.remove(cell.getRowKey(), cell.getColumnKey());
+ }
+ return false;
+ }
+
+ @Override
+ public void clear() {
+ AbsTable.this.clear();
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return new AbsTable.CellIterator();
+ }
+
+ @Override
+ public int size() {
+ return AbsTable.this.size();
+ }
+ }
+ //endregion
+
+ //region iterator
+ @Override
+ public Iterator> iterator() {
+ return new CellIterator();
+ }
+
+ /**
+ * 基于{@link Cell}的{@link Iterator}实现
+ */
+ private class CellIterator implements Iterator> {
+ final Iterator>> rowIterator = rowMap().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();
+ }
+ }
+ }
+ //endregion
+
+ /**
+ * 简单{@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(R rowKey, C columnKey, 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;
+ }
+ }
+}
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
index 0e8854896..6094300ac 100644
--- 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
@@ -1,68 +1,75 @@
package cn.hutool.core.map.multi;
+import cn.hutool.core.builder.Builder;
+import cn.hutool.core.collection.ComputeIter;
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 cn.hutool.core.map.AbsEntry;
+import cn.hutool.core.map.SimpleEntry;
-import java.io.Serializable;
-import java.util.AbstractCollection;
+import java.util.AbstractMap;
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 {
+/**
+ * 将行的键作为主键的{@link Table}实现
+ * 此结构为: 行=(列=值)
+ *
+ * @param 行类型
+ * @param 列类型
+ * @param 值类型
+ * @author Guava, Looly
+ * @since 5.7.23
+ */
+public class RowKeyTable extends AbsTable {
final Map> raw;
- final Supplier extends Map> supplier;
+ /**
+ * 列的Map创建器,用于定义Table中Value对应Map类型
+ */
+ final Builder extends Map> columnBuilder;
+ //region 构造
+
+ /**
+ * 构造
+ */
+ public RowKeyTable() {
+ this(new HashMap<>());
+ }
+
+ /**
+ * 构造
+ *
+ * @param raw 原始Map
+ */
public RowKeyTable(Map> raw) {
this(raw, HashMap::new);
}
- public RowKeyTable(Map> raw, Supplier extends Map> columnMapSupplier) {
+ /**
+ * 构造
+ *
+ * @param raw 原始Map
+ * @param columnMapBuilder 列的map创建器
+ */
+ public RowKeyTable(Map> raw, Builder extends Map> columnMapBuilder) {
this.raw = raw;
- this.supplier = null == columnMapSupplier ? HashMap::new : columnMapSupplier;
+ this.columnBuilder = null == columnMapBuilder ? HashMap::new : columnMapBuilder;
}
+ //endregion
@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 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());
- }
- }
+ return raw.computeIfAbsent(rowKey, (key) -> columnBuilder.build()).put(columnKey, value);
}
@Override
@@ -89,184 +96,164 @@ public class RowKeyTable implements Table {
}
@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 {
+ public boolean containsColumn(C columnKey) {
+ if (columnKey == null) {
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) {
+ for (Map map : raw.values()) {
+ if (null != map && map.containsKey(columnKey)) {
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;
}
+ return false;
+ }
- @Override
- public int hashCode() {
- return Objects.hash(rowKey, columnKey, value);
- }
+ //region columnMap
+ @Override
+ public Map> columnMap() {
+ Map> result = columnMap;
+ return (result == null) ? columnMap = new ColumnMap() : result;
+ }
+ private Map> columnMap;
+
+ private class ColumnMap extends AbstractMap> {
@Override
- public String toString() {
- return "(" + rowKey + "," + columnKey + ")=" + value;
+ public Set>> entrySet() {
+ return new ColumnMapEntrySet();
}
}
- private final Collection values = new AbstractCollection() {
- @Override
- public Iterator iterator() {
- return new TransIter<>(cellSet().iterator(), Cell::getValue);
- }
+ private class ColumnMapEntrySet extends AbstractSet>> {
+ private final Set columnKeySet = columnKeySet();
@Override
- public boolean contains(Object o) {
- //noinspection unchecked
- return containsValue((V) o);
- }
-
- @Override
- public void clear() {
- RowKeyTable.this.clear();
+ public Iterator>> iterator() {
+ return new TransIter<>(columnKeySet.iterator(),
+ c -> new SimpleEntry<>(c, getColumn(c)));
}
@Override
public int size() {
- return RowKeyTable.this.size();
+ return columnKeySet.size();
}
- };
+ }
+ //endregion
+
+
+ //region columnKeySet
+ @Override
+ public Set columnKeySet() {
+ Set result = columnKeySet;
+ return (result == null) ? columnKeySet = new ColumnKeySet() : result;
+ }
+
+ private Set columnKeySet;
+
+ private class ColumnKeySet extends AbstractSet {
- 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());
+ public Iterator iterator() {
+ return new ColumnKeyIterator();
+ }
+
+ @Override
+ public int size() {
+ return IterUtil.size(iterator());
+ }
+ }
+
+ private class ColumnKeyIterator extends ComputeIter {
+ final Map seen = columnBuilder.build();
+ final Iterator | | | | | | | | | | |