mirror of
				https://github.com/LamGC/ContentGrabbingJi.git
				synced 2025-10-31 08:36:56 +00:00 
			
		
		
		
	[Add] 添加 ListCacheStore 的本地实现;
[Add] CopyOnWriteArrayListCacheStore 添加基于 CopyOnWriteArrayList 的 ListCacheStore 实现; [Add] LocalCollectionCacheStore 添加集合型本地缓存存储容器父类, 已实现 CollectionCacheStore 接口; [Add] ListCacheStoreTest 添加对 CopyOnWriteArrayListCacheStore 的完整单元测试(同样包括了 LocalCollectionCacheStore 的测试);
This commit is contained in:
		| @ -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 <https://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | 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 <E> 元素类型. | ||||||
|  |  * @author LamGC | ||||||
|  |  */ | ||||||
|  | public class CopyOnWriteArrayListCacheStore<E> extends LocalCollectionCacheStore<E, List<E>> implements ListCacheStore<E> { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public E getElement(String key, int index) { | ||||||
|  |         List<E> itemCollection = getCacheItemCollection(key, false); | ||||||
|  |         try { | ||||||
|  |             return itemCollection == null ? null : itemCollection.get(index); | ||||||
|  |         } catch (IndexOutOfBoundsException e) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<E> getElementsByRange(String key, int index, int length) { | ||||||
|  |         int listLength = elementsLength(key); | ||||||
|  |         if (listLength == -1) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         List<E> itemCollection = getCacheItemCollection(key, false); | ||||||
|  |         List<E> result = new ArrayList<>(); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             ListIterator<E> 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<E> itemCollection = getCacheItemCollection(key, false); | ||||||
|  |         if (itemCollection != null) { | ||||||
|  |             try { | ||||||
|  |                 itemCollection.remove(index); | ||||||
|  |                 return true; | ||||||
|  |             } catch (ArrayIndexOutOfBoundsException e) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected List<E> getCacheItemCollection(String key, boolean create) { | ||||||
|  |         Objects.requireNonNull(key); | ||||||
|  |         Map<String, CacheItem<List<E>>> cacheMap = getCacheMap(); | ||||||
|  |         if (!cacheMap.containsKey(key)) { | ||||||
|  |             if (create) { | ||||||
|  |                 cacheMap.put(key, new CacheItem<>(new CopyOnWriteArrayList<>())); | ||||||
|  |             } else { | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return cacheMap.get(key).getValue(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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 <https://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package net.lamgc.cgj.bot.cache.local; | ||||||
|  |  | ||||||
|  | import net.lamgc.cgj.bot.cache.CollectionCacheStore; | ||||||
|  |  | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 本地集合缓存存储容器. | ||||||
|  |  * @param <E> 元素类型 | ||||||
|  |  * @author LamGC | ||||||
|  |  * @see net.lamgc.cgj.bot.cache.CacheStore | ||||||
|  |  * @see net.lamgc.cgj.bot.cache.CollectionCacheStore | ||||||
|  |  */ | ||||||
|  | public abstract class LocalCollectionCacheStore<E, C extends Collection<E>> | ||||||
|  | extends HashCacheStore<C> | ||||||
|  | implements CollectionCacheStore<E, C> { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取缓存项集合对象. | ||||||
|  |      * @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<E> itemCollection = getCacheItemCollection(key, true); | ||||||
|  |         return itemCollection.add(element); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean addElements(String key, Collection<E> elements) { | ||||||
|  |         Objects.requireNonNull(key); | ||||||
|  |         Objects.requireNonNull(elements); | ||||||
|  |         Collection<E> itemCollection = getCacheItemCollection(key, true); | ||||||
|  |         return itemCollection.addAll(elements); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean containsElement(String key, E value) { | ||||||
|  |         Objects.requireNonNull(key); | ||||||
|  |         Objects.requireNonNull(value); | ||||||
|  |         Collection<E> itemCollection = getCacheItemCollection(key, false); | ||||||
|  |         if (itemCollection == null) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return itemCollection.contains(value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEmpty(String key) { | ||||||
|  |         Collection<E> itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false); | ||||||
|  |         if (itemCollection == null) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return itemCollection.isEmpty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int elementsLength(String key) { | ||||||
|  |         Collection<E> itemCollection = getCacheItemCollection(Objects.requireNonNull(key), false); | ||||||
|  |         if (itemCollection == null) { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |         return itemCollection.size(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean clearCollection(String key) { | ||||||
|  |         Collection<E> 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<E> itemCollection = getCacheItemCollection(key, false); | ||||||
|  |         if (itemCollection == null) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return itemCollection.remove(element); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @ -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 <https://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | 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<String> 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<String> 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<Integer> cacheStore = new CopyOnWriteArrayListCacheStore<>(); | ||||||
|  |         final String key = "test01"; | ||||||
|  |         List<Integer> 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<Integer> 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<String> cacheStore = new CopyOnWriteArrayListCacheStore<>(); | ||||||
|  |         final String key = "test01"; | ||||||
|  |         Random random = new Random(); | ||||||
|  |         List<String> 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))); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user