From 003f571aad53123fd66c50a7b090e6614d92b65f Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 4 Sep 2020 00:29:36 +0800 Subject: [PATCH] =?UTF-8?q?[Add]=20=E6=B7=BB=E5=8A=A0=20ListCacheStore=20?= =?UTF-8?q?=E7=9A=84=E6=9C=AC=E5=9C=B0=E5=AE=9E=E7=8E=B0;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Add] CopyOnWriteArrayListCacheStore 添加基于 CopyOnWriteArrayList 的 ListCacheStore 实现; [Add] LocalCollectionCacheStore 添加集合型本地缓存存储容器父类, 已实现 CollectionCacheStore 接口; [Add] ListCacheStoreTest 添加对 CopyOnWriteArrayListCacheStore 的完整单元测试(同样包括了 LocalCollectionCacheStore 的测试); --- .../local/CopyOnWriteArrayListCacheStore.java | 91 ++++++++++++ .../local/LocalCollectionCacheStore.java | 110 ++++++++++++++ .../bot/cache/local/ListCacheStoreTest.java | 136 ++++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/CopyOnWriteArrayListCacheStore.java create mode 100644 ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/LocalCollectionCacheStore.java create mode 100644 ContentGrabbingJi-CacheStore-local/src/test/java/net/lamgc/cgj/bot/cache/local/ListCacheStoreTest.java diff --git a/ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/CopyOnWriteArrayListCacheStore.java b/ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/CopyOnWriteArrayListCacheStore.java new file mode 100644 index 0000000..23fb17f --- /dev/null +++ b/ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/CopyOnWriteArrayListCacheStore.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.cache.local; + +import net.lamgc.cgj.bot.cache.ListCacheStore; + +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * 基于 {@link CopyOnWriteArrayList} 的有序列表缓存存储容器. + * @param 元素类型. + * @author LamGC + */ +public class CopyOnWriteArrayListCacheStore extends LocalCollectionCacheStore> implements ListCacheStore { + + @Override + public E getElement(String key, int index) { + List itemCollection = getCacheItemCollection(key, false); + try { + return itemCollection == null ? null : itemCollection.get(index); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + @Override + public List getElementsByRange(String key, int index, int length) { + int listLength = elementsLength(key); + if (listLength == -1) { + return null; + } + List itemCollection = getCacheItemCollection(key, false); + List result = new ArrayList<>(); + + try { + ListIterator iterator = itemCollection.listIterator(index); + for (int i = 0; i < length && iterator.hasNext(); i++) { + result.add(iterator.next()); + } + } catch (IndexOutOfBoundsException ignored) { + // 正常情况来讲, 该 try-catch 块只有 listIterator 会抛出 IndexOutOfBoundsException, + // 而一旦抛出 IndexOutOfBoundsException, 就代表 index 溢出了, try 块后面代码没有继续执行, + // 既然抛出异常时, result 并没有添加任何元素, 为何要再 new 一个 List 浪费内存呢? :D + } + return result; + } + + @Override + public boolean removeElement(String key, int index) { + List itemCollection = getCacheItemCollection(key, false); + if (itemCollection != null) { + try { + itemCollection.remove(index); + return true; + } catch (ArrayIndexOutOfBoundsException e) { + return false; + } + } + return false; + } + + @Override + protected List getCacheItemCollection(String key, boolean create) { + Objects.requireNonNull(key); + Map>> cacheMap = getCacheMap(); + if (!cacheMap.containsKey(key)) { + if (create) { + cacheMap.put(key, new CacheItem<>(new CopyOnWriteArrayList<>())); + } else { + return null; + } + } + return cacheMap.get(key).getValue(); + } +} diff --git a/ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/LocalCollectionCacheStore.java b/ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/LocalCollectionCacheStore.java new file mode 100644 index 0000000..cb6f3a4 --- /dev/null +++ b/ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/LocalCollectionCacheStore.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.cache.local; + +import net.lamgc.cgj.bot.cache.CollectionCacheStore; + +import java.util.Collection; +import java.util.Objects; + +/** + * 本地集合缓存存储容器. + * @param 元素类型 + * @author LamGC + * @see net.lamgc.cgj.bot.cache.CacheStore + * @see net.lamgc.cgj.bot.cache.CollectionCacheStore + */ +public abstract class LocalCollectionCacheStore> +extends HashCacheStore +implements CollectionCacheStore { + + /** + * 获取缓存项集合对象. + * @param key 缓存项键名 + * @param create 如果不存在, 是否创建. + * @return 如果不存在且 create 为 false, 或添加失败, 返回 false, 添加成功返回 true. + */ + protected abstract C getCacheItemCollection(String key, boolean create); + + @Override + public boolean addElement(String key, E element) { + Objects.requireNonNull(key); + Objects.requireNonNull(element); + Collection itemCollection = getCacheItemCollection(key, true); + return itemCollection.add(element); + } + + @Override + public boolean addElements(String key, Collection elements) { + Objects.requireNonNull(key); + Objects.requireNonNull(elements); + Collection itemCollection = getCacheItemCollection(key, true); + return itemCollection.addAll(elements); + } + + @Override + public boolean containsElement(String key, E value) { + Objects.requireNonNull(key); + Objects.requireNonNull(value); + Collection itemCollection = getCacheItemCollection(key, false); + if (itemCollection == null) { + return false; + } + return itemCollection.contains(value); + } + + @Override + public boolean isEmpty(String key) { + Collection itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false); + if (itemCollection == null) { + return false; + } + return itemCollection.isEmpty(); + } + + @Override + public int elementsLength(String key) { + Collection itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false); + if (itemCollection == null) { + return -1; + } + return itemCollection.size(); + } + + @Override + public boolean clearCollection(String key) { + Collection itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false); + if (itemCollection == null) { + return false; + } + itemCollection.clear(); + return true; + } + + @Override + public boolean removeElement(String key, E element) { + Objects.requireNonNull(key); + Objects.requireNonNull(element); + Collection itemCollection = getCacheItemCollection(key, false); + if (itemCollection == null) { + return false; + } + return itemCollection.remove(element); + } + +} diff --git a/ContentGrabbingJi-CacheStore-local/src/test/java/net/lamgc/cgj/bot/cache/local/ListCacheStoreTest.java b/ContentGrabbingJi-CacheStore-local/src/test/java/net/lamgc/cgj/bot/cache/local/ListCacheStoreTest.java new file mode 100644 index 0000000..910689d --- /dev/null +++ b/ContentGrabbingJi-CacheStore-local/src/test/java/net/lamgc/cgj/bot/cache/local/ListCacheStoreTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.cache.local; + +import com.google.common.collect.Lists; +import net.lamgc.cgj.bot.cache.ListCacheStore; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * @see CopyOnWriteArrayListCacheStore + * @see LocalCollectionCacheStore + */ +public class ListCacheStoreTest { + + @Test + public void nullThrowTest() { + final ListCacheStore cacheStore = new CopyOnWriteArrayListCacheStore<>(); + + // LocalCollectionCacheStore + Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElement(null, "testValue")); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElement("testKey", null)); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElements(null, new ArrayList<>())); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.addElements("testKey", null)); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.elementsLength(null)); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.containsElement(null, "testValue")); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.containsElement("testKey", null)); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.isEmpty(null)); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearCollection(null)); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement(null, "testValue")); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement("testKey", null)); + + // CopyOnWriteArrayListCacheStore + Assert.assertThrows(NullPointerException.class, () -> cacheStore.getElement(null, 0)); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.getElementsByRange(null, 0, 0)); + Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeElement(null, 0)); + + } + + @Test + public void notExistCacheTest() { + final ListCacheStore cacheStore = new CopyOnWriteArrayListCacheStore<>(); + final String key = "testKey"; + Assert.assertFalse(cacheStore.clearCollection(key)); + Assert.assertFalse(cacheStore.isEmpty(key)); + Assert.assertEquals(-1, cacheStore.elementsLength(key)); + Assert.assertFalse(cacheStore.containsElement(key, "testValue")); + Assert.assertFalse(cacheStore.removeElement(key, "testValue")); + } + + @Test + public void addAndGetTest() { + final ListCacheStore cacheStore = new CopyOnWriteArrayListCacheStore<>(); + final String key = "test01"; + List numbers = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9); + // getElement/getElementsByRange Cache不存在测试 + Assert.assertNull(cacheStore.getElement(key, 0)); + Assert.assertNull(cacheStore.getElementsByRange(key, 0, 1)); + + // addElement/getElement 正常情况测试 + Assert.assertTrue("addElement operation failed!", cacheStore.addElement(key, 0)); + Assert.assertEquals(new Integer(0), cacheStore.getElement(key, 0)); + // 超出范围的 null 测试 + Assert.assertNull(cacheStore.getElement(key, cacheStore.elementsLength(key))); + + // addElements/getElementsByRange 正常情况测试 + Assert.assertTrue("addElements operation failed!", cacheStore.addElements(key, numbers)); + Assert.assertEquals(Lists.newArrayList(0, 1, 2), cacheStore.getElementsByRange(key, 0, 3)); + + // 不足长度的 getElementsByRange + Assert.assertEquals(Lists.newArrayList(7, 8, 9), cacheStore.getElementsByRange(key, 7, 8)); + + // 超出索引的 getElementsByRange + List result = cacheStore.getElementsByRange(key, cacheStore.elementsLength(key) + 1, 8); + Assert.assertNotNull("getElementsByRange returned null if index is out of range", result); + Assert.assertEquals("getElementsByRange returned a non empty list when the index was out of range", + 0, result.size()); + + // 不足长度的 getElementsByRange + Assert.assertEquals(Lists.newArrayList(), cacheStore.getElementsByRange(key, cacheStore.elementsLength(key), 0)); + } + + @Test + public void removeElementTest() { + // removeElement(String, E) / removeElement(String, int) + final ListCacheStore cacheStore = new CopyOnWriteArrayListCacheStore<>(); + final String key = "test01"; + Random random = new Random(); + List numbers = Lists.newArrayList("1", "2", "3", "4", "5", "6", "7", "8", "9"); + + // 删除不存在 Cache 返回 false + Assert.assertFalse(cacheStore.removeElement(key, 0)); + + Assert.assertTrue("addElements operation failed!", cacheStore.addElements(key, numbers)); + + int removeIndex = random.nextInt(cacheStore.elementsLength(key)); + numbers.remove(removeIndex); + Assert.assertTrue("removeElement operation failed!", cacheStore.removeElement(key, removeIndex)); + Assert.assertEquals(numbers, cacheStore.getElementsByRange(key, 0, cacheStore.elementsLength(key))); + + String removeTarget = cacheStore.getElement(key, random.nextInt(cacheStore.elementsLength(key))); + Assert.assertNotNull(removeTarget); + Assert.assertTrue(cacheStore.containsElement(key, removeTarget)); + numbers.remove(removeTarget); + Assert.assertTrue("removeElement operation failed!", cacheStore.removeElement(key, removeTarget)); + Assert.assertEquals(numbers, cacheStore.getElementsByRange(key, 0, cacheStore.elementsLength(key))); + + Assert.assertTrue("clearCollection operation failed!", cacheStore.clearCollection(key)); + Assert.assertTrue(cacheStore.exists(key)); + Assert.assertEquals(0, cacheStore.elementsLength(key)); + Assert.assertTrue(cacheStore.isEmpty(key)); + + // 删除不存在元素返回 false + Assert.assertFalse(cacheStore.removeElement(key, cacheStore.elementsLength(key))); + } + +} \ No newline at end of file