/** * Copyright 2012-2013 Maciej Jaworski, Mariusz Kapcia, Paweł Kędzia, Mateusz Kubuszok * * <p>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</p> * * <p>http://www.apache.org/licenses/LICENSE-2.0</p> * * <p>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.</p> */ package com.autoupdater.server.utils.cache; import java.util.ArrayList; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; /** * Contains output cache (O type) for specified input (i type). * * Output is obtained through CacheSource<I,O> object. * * @param <I> * input type * @param <O> * output type */ public class Cache<I extends Comparable<I>, O> { /** * Runtime instance. */ private Runtime rt; /** * Source object. */ private final CacheSource<I, O> Source; /** * Maximal number of objects kept after cleanup. */ private int MaxKeep; /** * Amount of memory in bytes above which runCleanup() is called. */ private long MaxMemory; /** * Interval between cleanups. */ private int GCDelay; /** * Cleanup cycles before request list reset. */ private int ResetCounter; /** * Cleanups since reset. */ private int CleanupCounter; /** * Contains data about input and output. * * @see CacheLeaf */ private final Map<I, CacheLeaf> OutputData; /** * Contains data about requests. * * @see RequestData */ private final ArrayList<RequestData> OutputRequests; /** * Creates Cache instance using passed source. * * @param source * CacheSource object */ public Cache(CacheSource<I, O> source) { this(source, true); } /** * Creates Cache instance using passed source. * * @param source * CacheSource object * @param createCleaner */ public Cache(CacheSource<I, O> source, boolean createCleaner) { Source = source; setGCDelay(1000 * 3600); setMaxKeep(100); setResetCounter(24); setMaxMemory(0); OutputData = new TreeMap<I, CacheLeaf>(); OutputRequests = new ArrayList<RequestData>(); CleanupCounter = 0; rt = Runtime.getRuntime(); if (createCleaner) new Cleaner().start(); } /** * Sets interval between garbage collector calls. * * @param gCDelay * interval between garbage collector calls in ms * @return this object */ public Cache<I, O> setGCDelay(int gCDelay) { GCDelay = gCDelay; return this; } /** * Sets maximal amount of cached object kept after cleaning. * * @param maxKeep * maximal amount of cached object kept after cleaning * @return this object */ public Cache<I, O> setMaxKeep(int maxKeep) { MaxKeep = maxKeep; return this; } /** * Sets maximal amount of memory in bytes above which runCleanup() is * called. * * @param maxMemory * maximal amount of memory in bytes above which runCleanup() is * called * @return this object */ public Cache<I, O> setMaxMemory(long maxMemory) { MaxMemory = maxMemory; return this; } /** * Sets number of cleaning cycles after which request list is reset. * * @param resetCounter * number of cycles after which list is reset * @return this object */ public Cache<I, O> setResetCounter(int resetCounter) { ResetCounter = resetCounter; return this; } /** * Obtains output for passed input. * * @param input * input data * @return output data */ public O getDataFor(I input) { O output; synchronized (getMutex(input)) { refreshDataFor(input); output = OutputData.get(input).getOutput(); } if (MaxMemory > 0 && rt.totalMemory() - rt.freeMemory() > MaxMemory) runCleanup(); return output; } /** * Force reload of an input. * * For cases like change of the original value. * * @param input * some input that should be reloaded */ public void forceReload(I input) { synchronized (getMutex(input)) { getLeaf(input).setOutput(null); } } /** * Run cache cleanup - after certain cleanup also run a request reset. * * @see Cache#runGC() * @see Cache#runReset() */ public void runCleanup() { runGC(); if (++CleanupCounter >= ResetCounter) { runReset(); CleanupCounter = 0; } } /** * Cache cleanup. * * Saves first MaxKeep elements. * * @see Cache#setMaxKeep(int) */ public void runGC() { if (OutputRequests.size() > MaxKeep) { for (int i = MaxKeep > 0 ? MaxKeep : 0; i < OutputRequests.size(); i++) { CacheLeaf Leaf = OutputRequests.get(i).getLeaf(); synchronized (getMutex(Leaf.getInput())) { Leaf.setOutput(null); } } Runtime.getRuntime().gc(); } } /** * Request list reset. * * @see Cache#setResetCounter(int) */ public void runReset() { for (int i = 0; i < OutputRequests.size(); i++) synchronized (OutputRequests) { OutputRequests.get(i).setRequests(0); } } /** * Obtains leaf from ObjectData tree (or create it) for passed input. * * @param input * input data * @return leaf with information */ private CacheLeaf getLeaf(I input) { if (OutputData.containsKey(input)) return OutputData.get(input); CacheLeaf Leaf = new CacheLeaf(input); OutputData.put(input, Leaf); return Leaf; } /** * Obtains mutex for passed input to separate * * @param input * input data * @return mutex */ private synchronized Object getMutex(I input) { return getLeaf(input); } /** * Updates request data and load object into cache if necessary. * * @param input * input data */ private void refreshDataFor(I input) { CacheLeaf Leaf = getLeaf(input); int CurrentPosition = Leaf.getCurrentPosition(); synchronized (OutputRequests) { if (CurrentPosition < 0) { OutputRequests.add(new RequestData(Leaf)); Leaf.setCurrentPosition(OutputRequests.size() - 1); CurrentPosition = Leaf.getCurrentPosition(); } OutputRequests.get(CurrentPosition).increment(); while (CurrentPosition > 0 && OutputRequests.get(CurrentPosition - 1).getRequests() < OutputRequests.get( CurrentPosition).getRequests()) { Collections.swap(OutputRequests, CurrentPosition - 1, CurrentPosition); OutputRequests.get(CurrentPosition - 1).getLeaf() .setCurrentPosition(CurrentPosition); OutputRequests.get(CurrentPosition).getLeaf() .setCurrentPosition(CurrentPosition - 1); CurrentPosition--; } } if (Leaf.getOutput() == null) Leaf.setOutput(Source.getElement(input)); } /** * Displays current state of cache. */ public void debugContent() { System.out.println("Requests data:"); System.out.println("\tInput\t\tOutput (null for deleted cache)"); for (Entry<I, CacheLeaf> Leaf : OutputData.entrySet()) System.out.println("\t" + Leaf.getKey() + "" + "\t->\t" + Leaf.getValue().getOutput()); System.out.println("Requests list:"); System.out.println("\tReq.\t\tInput\t\tOutput (null for deleted cache)"); for (RequestData Element : OutputRequests) System.out.println("\t" + Element.getRequests() + "\t->\t" + Element.getLeaf().getInput() + "\t->\t" + Element.getLeaf().getOutput()); } /** * Leaf in tree (OutputData). */ private class CacheLeaf { /** * Input data. */ private I Input; /** * Output data (actual cache) */ private O Output; /** * Current position in request list. */ private int CurrentPosition; /** * Creates leaf. * * @param input * input data */ public CacheLeaf(I input) { initialize(input); } /** * Input getter. * * @return input data */ public I getInput() { return Input; } /** * Output getter. * * @return output data */ public O getOutput() { return Output; } /** * Output setter. * * @param output * output data */ public void setOutput(O output) { Output = output; } /** * Gets current position of input in request list. * * @return current position */ public int getCurrentPosition() { return CurrentPosition; } /** * Sets current position of input in request list. * * @param currentPosition * current position */ public void setCurrentPosition(int currentPosition) { CurrentPosition = currentPosition; } /** * Called by constructor. * * @param input * input data */ private void initialize(I input) { Input = input; Output = null; setCurrentPosition(-1); } } /** * Element of request list (OutputRequest). */ private class RequestData { /** * Leaf with data. * * @see CacheLeaf */ private final CacheLeaf Leaf; /** * Number of requests. */ private int Requests; /** * Creates RequestData. * * @param leaf * leaf for a request */ public RequestData(CacheLeaf leaf) { Leaf = leaf; Requests = 0; } /** * Returns leaf for a request. * * @return leaf with data */ public CacheLeaf getLeaf() { return Leaf; } /** * Gets number of requests. * * @return number of requests */ public int getRequests() { return Requests; } /** * Sets requests to specified number. * * @param requests * number of requests */ public void setRequests(int requests) { Requests = requests; } /** * Increment requests. */ public void increment() { Requests++; } } /** * Calls runGC() and runReset() after specified time. * * @see Cache#runGC() * @see Cache#runReset() */ private class Cleaner extends Thread { /** * Thread's body. */ @Override public void run() { while (true) { if (GCDelay > 0) { try { sleep(GCDelay); } catch (InterruptedException e) { } runCleanup(); } else try { sleep(1000); } catch (InterruptedException e) { } } } } }