diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8cf4617
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.iml
+*.xml
diff --git a/SoftCache/.gitignore b/SoftCache/.gitignore
new file mode 100644
index 0000000..ffd2a4a
--- /dev/null
+++ b/SoftCache/.gitignore
@@ -0,0 +1 @@
+*.idea
diff --git a/SoftCache/pom.xml b/SoftCache/pom.xml
new file mode 100644
index 0000000..d2a7ca6
--- /dev/null
+++ b/SoftCache/pom.xml
@@ -0,0 +1,100 @@
+
+
+
+ 4.0.0
+
+ maven
+ softCache
+ jar
+ 1.0
+
+ A Camel Route
+
+
+ UTF-8
+ UTF-8
+
+
+
+
+
+
+ org.apache.camel
+ camel-parent
+ 2.18.3
+ import
+ pom
+
+
+
+
+
+
+
+ org.apache.camel
+ camel-core
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ runtime
+
+
+ org.apache.logging.log4j
+ log4j-core
+ runtime
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ runtime
+
+
+
+
+ org.apache.camel
+ camel-test
+ test
+
+
+
+
+ install
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+ 1.8
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.0.1
+
+ UTF-8
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.5.0
+
+ maven.MainApp
+ false
+
+
+
+
+
+
+
diff --git a/SoftCache/src/main/java/maven/Cache.java b/SoftCache/src/main/java/maven/Cache.java
new file mode 100644
index 0000000..ab5fcb1
--- /dev/null
+++ b/SoftCache/src/main/java/maven/Cache.java
@@ -0,0 +1,23 @@
+package maven;
+
+public interface Cache {
+ /**
+ * Возвращает соответствующее значение, если оно ещё в кэше, иначе null
+ */
+ V getIfPresent(K key);
+
+ /**
+ * Сохраняет value по соответствующему ключу key
+ */
+ void put(K key, V value);
+
+ /**
+ * Удаляет соответствующее ключу key значение
+ */
+ V remove(K key);
+
+ /**
+ * Очищает кэш
+ */
+ void clear();
+}
diff --git a/SoftCache/src/main/java/maven/SoftCacheMap.java b/SoftCache/src/main/java/maven/SoftCacheMap.java
new file mode 100644
index 0000000..4b4616c
--- /dev/null
+++ b/SoftCache/src/main/java/maven/SoftCacheMap.java
@@ -0,0 +1,111 @@
+package maven;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+public class SoftCacheMap implements Cache {
+
+ private HashMap> cache;
+ private HashMap, HashSet> subsidiaryMap;
+ private ReferenceQueue referenceQueue;
+ private Deque recentlyUsed;
+ private int maxSize;
+ private int cleaningFrequency;
+ private int numOfPuts;
+
+ protected SoftCacheMap(int size, int frequency) {
+ cache = new HashMap<>();
+ /**
+ * второй hashMap - вспомогательный, используется при удалении из кэша
+ * тех ссылок, которые попали в referenceQueue. Чтобы не искать все ключи,
+ * соответствующие одной ссылке, храню Set из таких ключей в качестве значения
+ * во втором hashMap-e.
+ */
+ subsidiaryMap = new HashMap<>();
+ referenceQueue = new ReferenceQueue<>();
+ recentlyUsed = new LinkedList<>();
+ maxSize = size;
+ cleaningFrequency = frequency;
+ numOfPuts = 0;
+ }
+
+ private void putInQueue(V object) {
+ if (maxSize > 0) {
+ if (object != null) {
+ recentlyUsed.remove(object);
+ if (recentlyUsed.size() < maxSize) {
+ recentlyUsed.addLast(object);
+ } else {
+ recentlyUsed.addLast(object);
+ recentlyUsed.removeFirst();
+ }
+ }
+ }
+ }
+
+
+ private void clean() {
+ boolean done = false;
+ while (!done) {
+ SoftReference reference = (SoftReference) referenceQueue.poll();
+ if (reference != null) {
+ if (subsidiaryMap.containsKey(reference)) {
+ HashSet localSet = subsidiaryMap.remove(reference);
+ for (K local : localSet) {
+ if (reference.get() == null) {
+ cache.remove(local);
+ }
+ }
+ }
+ } else {
+ done = true;
+ }
+ }
+ }
+
+ @Override
+ public V getIfPresent(K key) {
+ if (cache.containsKey(key)) {
+ putInQueue(cache.get(key).get());
+ return cache.get(key).get();
+ }
+ return null;
+ }
+
+ @Override
+ public void put(K key, V value) {
+ ++numOfPuts;
+ SoftReference reference = new SoftReference<>(value, referenceQueue);
+ cache.put(key, reference);
+ putInQueue(value);
+ if (subsidiaryMap.containsKey(reference)) {
+ subsidiaryMap.get(reference).add(key);
+ } else {
+ subsidiaryMap.put(reference, new HashSet<>());
+ subsidiaryMap.get(reference).add(key);
+ }
+ if (numOfPuts % cleaningFrequency == 0) {
+ clean();
+ }
+ }
+
+ @Override
+ public V remove(K key) {
+ if ((cache.containsKey(key)) && (cache.get(key).get() != null)) {
+ subsidiaryMap.remove(cache.get(key));
+ return cache.remove(key).get();
+ }
+ return null;
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ subsidiaryMap.clear();
+ }
+
+}
diff --git a/SoftCache/src/main/resources/log4j2.properties b/SoftCache/src/main/resources/log4j2.properties
new file mode 100644
index 0000000..328db35
--- /dev/null
+++ b/SoftCache/src/main/resources/log4j2.properties
@@ -0,0 +1,7 @@
+
+appender.out.type = Console
+appender.out.name = out
+appender.out.layout.type = PatternLayout
+appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n
+rootLogger.level = INFO
+rootLogger.appenderRef.out.ref = out
diff --git a/SoftCache/src/test/java/maven/SoftCacheMapTest.java b/SoftCache/src/test/java/maven/SoftCacheMapTest.java
new file mode 100644
index 0000000..4cfd06a
--- /dev/null
+++ b/SoftCache/src/test/java/maven/SoftCacheMapTest.java
@@ -0,0 +1,93 @@
+package maven;
+
+import org.junit.Assert;
+import org.junit.Test;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class SoftCacheMapTest {
+
+ private SoftCacheMap getCache(int size, int frequency) {
+ return new SoftCacheMap<>(size, frequency);
+ }
+
+ private void testBefore(int size, int frequency, ArrayList values,
+ ArrayList expectedBefore) throws Exception {
+
+ String errorMessage = "Bad result";
+ SoftCacheMap cache = getCache(size, frequency);
+ for (int i = 0; i < values.size(); ++i) {
+ cache.put(i, values.get(i));
+ }
+ for (int i = 0; i < values.size(); ++i) {
+ Assert.assertEquals(errorMessage, expectedBefore.get(i), cache.getIfPresent(i));
+ }
+ }
+
+ private void testAfter(int size, int frequency, ArrayList values,
+ ArrayList expectedAfter) {
+ String errorMessage = "Bad result";
+ SoftCacheMap cache = getCache(size, frequency);
+ for (int i = 0; i < values.size(); ++i) {
+ cache.put(i, new Integer(values.get(i)));
+ }
+ try {
+ Object[] big = new Object[(int) Runtime.getRuntime().maxMemory()];
+ } catch (OutOfMemoryError e) {
+ // ignore
+ }
+
+ int counter = 0;
+ for (int i = 0; i < values.size(); ++i) {
+ if (cache.getIfPresent(i) != null) {
+ Assert.assertEquals(errorMessage, expectedAfter.get(counter), cache.getIfPresent(i));
+ ++counter;
+ }
+ }
+ }
+
+ @Test
+ public void testReferenceQueueBefore() throws Exception {
+ ArrayList values = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5));
+ ArrayList expectedBefore = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5));
+ testBefore(0, 1, values, expectedBefore);
+ }
+
+ @Test
+ public void testReferenceQueueAfter() throws Exception {
+ ArrayList values = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5));
+ testAfter(0, 1, values, null);
+ }
+
+ @Test
+ public void testRecentlyUsedQueueAfter0() throws Exception {
+ ArrayList values = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5));
+ ArrayList expectedAfter = new ArrayList<>(Arrays.asList(4, 5));
+ testAfter(2, 1, values, expectedAfter);
+ }
+
+ @Test
+ public void testRecentlyUsedQueueAfter1() throws Exception {
+ ArrayList values = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5));
+ ArrayList expectedAfter = new ArrayList<>(Arrays.asList(2, 5));
+ String errorMessage = "Bad result";
+ SoftCacheMap cache = getCache(2, 1);
+ for (int i = 0; i < values.size(); ++i) {
+ cache.put(i, new Integer(values.get(i)));
+ }
+ cache.getIfPresent(2);
+ try {
+ Object[] big = new Object[(int) Runtime.getRuntime().maxMemory()];
+ } catch (OutOfMemoryError e) {
+ // ignore
+ }
+
+ int counter = 0;
+ for (int i = 0; i < values.size(); ++i) {
+ if (cache.getIfPresent(i) != null) {
+ Assert.assertEquals(errorMessage, expectedAfter.get(counter), cache.getIfPresent(i));
+ ++counter;
+ }
+ }
+ }
+}