diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/application/DependenciesBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/application/DependenciesBean.java index 82b50ba2d807b585a3334dad83fa3b292b9851f9..65e9f66d86497f1b9310bba4160fe2a9e52a3a89 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/application/DependenciesBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/application/DependenciesBean.java @@ -83,7 +83,6 @@ public class DependenciesBean { final MIDependency dependency = FileManager.getInstance().uploadDependency(file); if (dependency != null) { // Is is possible that we already have a dependency with the same name and have to remove it first. - for (final MIDependency dep : this.dependencies) { if (dep.getFilePath().equals(dependency.getFilePath())) { this.dependencies.remove(dep); diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/LRUCache.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/LRUCache.java new file mode 100644 index 0000000000000000000000000000000000000000..0100d475af5f432477d803d742272ed07fac1e68 --- /dev/null +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/LRUCache.java @@ -0,0 +1,110 @@ +/*************************************************************************** + * Copyright 2012 by + * + Christian-Albrechts-University of Kiel + * + Department of Computer Science + * + Software Engineering Group + * and others. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package kieker.webgui.common; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +/** + * This is a thread-safe cache, which can be used to cache objects under a specific key. This class is a LRU cache. + * + * @author Nils Christian Ehmke + * + * @param <Key> + * The type for the keys. + * @param <Value> + * The type for the values. + */ +public class LRUCache<Key, Value> { + + /** + * This is the map which is used to store the entries. + */ + private final HashMap<Key, Value> hashMap; + private final int maxSize; + + /** + * Creates a new instance of this class using the given size as maximal cache size. + * + * @param size + * The maximal cache size. + */ + public LRUCache(final int size) { + this.maxSize = size; + this.hashMap = new FixedLinkedHashMap(); + } + + /** + * This method adds a given value within the cache, using the given key. If a value with the same key does already exist, it will be replaced. If the entry would + * excess the maximal cache size, the oldest entry will be removed. + * + * @param key + * The key used to store the value. + * @param value + * The value to be stored. + */ + public void add(final Key key, final Value value) { + synchronized (this.hashMap) { + this.hashMap.put(key, value); + } + } + + /** + * This method delivers the value to the key or null, if the value doesn't exist. + * + * @param key + * The key to the value. + * @return The corresponding value. + */ + public Value get(final Key key) { + synchronized (this.hashMap) { + return this.hashMap.get(key); + } + } + + /** + * A modified version of {@link LinkedHashMap} which has a fixed size. Each time the {@code put} or {@code putAll} method exceeds the size of the + * {@link LRUCache}, the oldest entry is being removed. + * + * @author Nils Christian Ehmke + */ + private class FixedLinkedHashMap extends LinkedHashMap<Key, Value> { + + /** + * The serial version uid. + */ + private static final long serialVersionUID = 1L; + + /** + * Creates a new instance of this class, using the LRUCache's maximal size as the size of the map. + */ + public FixedLinkedHashMap() { + super(LRUCache.this.maxSize); + } + + @Override + protected boolean removeEldestEntry(final Entry<Key, Value> eldest) { + return this.size() > LRUCache.this.maxSize; + } + + } +} diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java index 50bf53be616ade5405adde636ecfdd87cd106ace..c0f67511db60f66b4f4697b6b1388b4ddca081c5 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/PluginFinder.java @@ -31,7 +31,6 @@ import java.util.jar.JarFile; import kieker.analysis.plugin.AbstractPlugin; import kieker.analysis.plugin.annotation.Plugin; import kieker.analysis.repository.AbstractRepository; -import sun.misc.Cache; /** * This tool class can be used to find all plugins and repositories within a given jar file - assuming that the <code>PluginClassLoader</code> knows these jars. @@ -42,19 +41,19 @@ import sun.misc.Cache; */ public final class PluginFinder { - private static final Cache PLUGIN_CACHE; - private static final Cache REPOSITORY_CACHE; + private static final LRUCache<URL, List<Class<AbstractPlugin>>> PLUGIN_CACHE; + private static final LRUCache<URL, List<Class<AbstractRepository>>> REPOSITORY_CACHE; static { - PLUGIN_CACHE = new Cache(15); - REPOSITORY_CACHE = new Cache(15); + PLUGIN_CACHE = new LRUCache<URL, List<Class<AbstractPlugin>>>(15); + REPOSITORY_CACHE = new LRUCache<URL, List<Class<AbstractRepository>>>(15); } /** * Creates a new instance of this class. */ private PluginFinder() { - /* No code necessary. */ + // No code necessary. } /** @@ -66,7 +65,7 @@ public final class PluginFinder { */ public static List<Class<AbstractRepository>> getAllRepositoriesWithinJar(final URL url) { // TODO: Merge this with the other method - final List<Class<AbstractRepository>> repositoryClasses = (List<Class<AbstractRepository>>) PluginFinder.REPOSITORY_CACHE.get(url); + final List<Class<AbstractRepository>> repositoryClasses = PluginFinder.REPOSITORY_CACHE.get(url); if (repositoryClasses != null) { return repositoryClasses; } @@ -92,7 +91,7 @@ public final class PluginFinder { } } jarFile.close(); - PluginFinder.REPOSITORY_CACHE.put(url, result); + PluginFinder.REPOSITORY_CACHE.add(url, result); return result; } catch (final IOException ex) { ex.printStackTrace(); @@ -109,7 +108,7 @@ public final class PluginFinder { * @return A list containing all available plugin-classes or null, if an exception occurred. */ public static List<Class<AbstractPlugin>> getAllPluginsWithinJar(final URL url) { - final List<Class<AbstractPlugin>> pluginClasses = (List<Class<AbstractPlugin>>) PluginFinder.PLUGIN_CACHE.get(url); + final List<Class<AbstractPlugin>> pluginClasses = PluginFinder.PLUGIN_CACHE.get(url); if (pluginClasses != null) { return pluginClasses; } @@ -135,7 +134,7 @@ public final class PluginFinder { } } jarFile.close(); - PluginFinder.PLUGIN_CACHE.put(url, result); + PluginFinder.PLUGIN_CACHE.add(url, result); return result; } catch (final IOException ex) { ex.printStackTrace(); diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/converter/MIDependencyToCountPluginsConverter.java b/Kieker.WebGUI/src/main/java/kieker/webgui/converter/MIDependencyToCountPluginsConverter.java index 3883b0cf16190d0b796fbb7893057611d1d660c1..b7ced166e3c9b29ca7219f7e6d933a0fd239ccab 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/converter/MIDependencyToCountPluginsConverter.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/converter/MIDependencyToCountPluginsConverter.java @@ -21,8 +21,6 @@ package kieker.webgui.converter; import java.net.MalformedURLException; import java.net.URL; -import java.util.HashMap; -import java.util.Map; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; @@ -31,6 +29,7 @@ import javax.faces.convert.FacesConverter; import kieker.analysis.model.analysisMetaModel.MIDependency; import kieker.webgui.common.FileManager; +import kieker.webgui.common.LRUCache; import kieker.webgui.common.PluginFinder; /** @@ -57,13 +56,13 @@ public class MIDependencyToCountPluginsConverter implements Converter { * This map is the cache and contains the number of plugins within the * dependencies. */ - private final Map<MIDependency, String> cache; + private final LRUCache<MIDependency, String> cache; /** * Creates a new instance of this class. */ public MIDependencyToCountPluginsConverter() { - this.cache = new HashMap<MIDependency, String>(); + this.cache = new LRUCache<MIDependency, String>(MIDependencyToCountPluginsConverter.MAX_CACHE_SIZE); } /** @@ -102,24 +101,21 @@ public class MIDependencyToCountPluginsConverter implements Converter { if (!(o instanceof MIDependency)) { return ""; } else { - // Try to find the library within the cache. - if (this.cache.containsKey(o)) { - return this.cache.get(o); + final MIDependency dependency = (MIDependency) o; + // Try to find the string within the cache. + String result = this.cache.get(dependency); + if (result == null) { + // We have to create the string first + try { + result = Integer.toString(PluginFinder.getAllPluginsWithinJar(new URL("file", "localhost", FileManager.getInstance().getFullPath(dependency))) + .size()); + // Remember it for the next time + this.cache.add(dependency, result); + } catch (final MalformedURLException e) { + result = ""; + } } - // If the cache is too big, remove one of the objects. - if (this.cache.size() > MIDependencyToCountPluginsConverter.MAX_CACHE_SIZE) { - this.cache.remove(this.cache.keySet().iterator().next()); - } - String result; - try { - result = Integer.toString(PluginFinder.getAllPluginsWithinJar(new URL("file", "localhost", FileManager.getInstance().getFullPath((MIDependency) o))) - .size()); - } catch (final MalformedURLException ex) { - result = ""; - } catch (final NullPointerException ex) { - result = ""; - } - this.cache.put((MIDependency) o, result); + return result; } } diff --git a/Kieker.WebGUI/src/test/java/kieker/webgui/common/LRUCacheTest.java b/Kieker.WebGUI/src/test/java/kieker/webgui/common/LRUCacheTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e6bd40231bef34d599a0755c9713a360b2bf8811 --- /dev/null +++ b/Kieker.WebGUI/src/test/java/kieker/webgui/common/LRUCacheTest.java @@ -0,0 +1,70 @@ +package kieker.webgui.common; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.junit.Test; + +/** + * TestCase for {@link LRUCache}. + * + * @author Nils Christian Ehmke + * @version 1.0 + */ +public class LRUCacheTest extends TestCase { + + /** + * Creates a new instance of this class. + */ + public LRUCacheTest() {} + + @Test + public void testEntryStorage() { + final int n = 5; + final LRUCache<Integer, Integer> cache = new LRUCache<Integer, Integer>(n); + + // Make sure that there is nothing stored under the given values yet. + for (int i = 0; i < n; i++) { + Assert.assertNull(cache.get(i)); + } + + // Store everything + for (int i = 0; i < n; i++) { + cache.add(i, n - i); + } + + // Make sure that the values are still available + for (int i = 0; i < n; i++) { + Assert.assertEquals((Integer) (n - i), cache.get(i)); + } + + // Overwrite an entry + cache.add(0, 0); + // Make sure that it worked + Assert.assertEquals((Integer) 0, cache.get(0)); + } + + @Test + public void testMaximalStorage() { + final int n = 5; + final LRUCache<Integer, Integer> cache = new LRUCache<Integer, Integer>(n); + + // Make sure that there is nothing stored under the given values yet. + for (int i = 0; i < n; i++) { + Assert.assertNull(cache.get(i)); + } + + // Store everything and exceed the size + for (int i = 0; i < n + 1; i++) { + cache.add(i, n - i); + } + + // Make sure that the values are still available + for (int i = 1; i < n + 1; i++) { + Assert.assertEquals((Integer) (n - i), cache.get(i)); + } + + // Make sure that the oldest entry isn't available + Assert.assertNull(cache.get(0)); + } +}