From 01af68cd0d68561a1f46f856d4ff5dc5a95c20c9 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 01:15:13 +0800 Subject: [PATCH] add Table --- CHANGELOG.md | 4 +- .../cn/hutool/core/collection/IterUtil.java | 15 + .../java/cn/hutool/core/map/AbsEntry.java | 46 +++ .../java/cn/hutool/core/map/SimpleEntry.java | 31 ++ .../java/cn/hutool/core/map/TableMap.java | 49 +-- .../cn/hutool/core/map/multi/AbsTable.java | 236 ++++++++++++ .../cn/hutool/core/map/multi/RowKeyTable.java | 359 +++++++++--------- .../java/cn/hutool/core/map/multi/Table.java | 8 +- .../cn/hutool/core/map/RowKeyTableTest.java | 39 ++ 9 files changed, 552 insertions(+), 235 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java 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> supplier; + /** + * 列的Map创建器,用于定义Table中Value对应Map类型 + */ + final Builder> columnBuilder; + //region 构造 + + /** + * 构造 + */ + public RowKeyTable() { + this(new HashMap<>()); + } + + /** + * 构造 + * + * @param raw 原始Map + */ public RowKeyTable(Map> raw) { this(raw, HashMap::new); } - public RowKeyTable(Map> raw, Supplier> columnMapSupplier) { + /** + * 构造 + * + * @param raw 原始Map + * @param columnMapBuilder 列的map创建器 + */ + public RowKeyTable(Map> raw, Builder> 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 table) { - if (null != table) { - for (Table.Cell 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> mapIterator = raw.values().iterator(); + Iterator> entryIterator = IterUtil.empty(); + + @Override + protected C computeNext() { + while (true) { + if (entryIterator.hasNext()) { + Map.Entry entry = entryIterator.next(); + if (false == seen.containsKey(entry.getKey())) { + seen.put(entry.getKey(), entry.getValue()); + return entry.getKey(); + } + } else if (mapIterator.hasNext()) { + entryIterator = mapIterator.next().entrySet().iterator(); + } else { + return null; } } - return false; + } + } + //endregion + + //region getColumn + + @Override + public Map getColumn(C columnKey) { + return new Column(columnKey); + } + + private class Column extends AbstractMap { + final C columnKey; + + Column(C columnKey) { + this.columnKey = columnKey; } @Override - public boolean remove(Object o) { - if (contains(o)) { - @SuppressWarnings("unchecked") - final Cell cell = (Cell) o; - RowKeyTable.this.remove(cell.getRowKey(), cell.getColumnKey()); + public Set> entrySet() { + return new EntrySet(); + } + + private class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntrySetIterator(); + } + + @Override + public int size() { + int size = 0; + for (Map map : raw.values()) { + if (map.containsKey(columnKey)) { + size++; + } + } + return size; } - return false; } - @Override - public void clear() { - RowKeyTable.this.clear(); - } + private class EntrySetIterator extends ComputeIter> { + final Iterator>> iterator = raw.entrySet().iterator(); - @Override - public Iterator> iterator() { - return new CellIterator(); - } + @Override + protected Entry computeNext() { + while (iterator.hasNext()) { + final Entry> entry = iterator.next(); + if (entry.getValue().containsKey(columnKey)) { + return new AbsEntry() { + @Override + public R getKey() { + return entry.getKey(); + } - @Override - public int size() { - return RowKeyTable.this.size(); + @Override + public V getValue() { + return entry.getValue().get(columnKey); + } + + @Override + public V setValue(V value) { + return entry.getValue().put(columnKey, value); + } + }; + } + } + return null; + } } - }; + } + //endregion } 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 index cd14029a5..e0fb1c5f4 100644 --- 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 @@ -170,7 +170,13 @@ public interface Table extends Iterable> { * * @param table 其他table */ - void putAll(Table table); + default void putAll(Table table){ + if (null != table) { + for (Table.Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + } /** * 移除指定值 diff --git a/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java b/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java new file mode 100644 index 000000000..fdbaca398 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java @@ -0,0 +1,39 @@ +package cn.hutool.core.map; + +import cn.hutool.core.map.multi.RowKeyTable; +import cn.hutool.core.map.multi.Table; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class RowKeyTableTest { + + @Test + public void putGetTest(){ + final Table table = new RowKeyTable<>(); + table.put(1, 2, 3); + table.put(1, 6, 4); + + Assert.assertEquals(new Integer(3), table.get(1, 2)); + Assert.assertNull(table.get(1, 3)); + + //判断row和column确定的二维点是否存在 + Assert.assertTrue(table.contains(1, 2)); + Assert.assertFalse(table.contains(1, 3)); + + //判断列 + Assert.assertTrue(table.containsColumn(2)); + Assert.assertFalse(table.containsColumn(3)); + + // 判断行 + Assert.assertTrue(table.containsRow(1)); + Assert.assertFalse(table.containsRow(2)); + + + // 获取列 + Map column = table.getColumn(6); + Assert.assertEquals(1, column.size()); + Assert.assertEquals(new Integer(4), column.get(1)); + } +}