/**
* Implementation of a set-associative cache.
*
* @author Mosharaf Chowdhury (http://www.mosharaf.com)
* @author Prashanth Mohan (http://www.cs.berkeley.edu/~prmohan)
*
* Copyright (c) 2012, University of California at Berkeley
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of University of California, Berkeley nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package edu.berkeley.cs162;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.LinkedList;
import java.io.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
/**
* A set-associate cache which has a fixed maximum number of sets (numSets).
* Each set has a maximum number of elements (MAX_ELEMS_PER_SET).
* If a set is full and another entry is added, an entry is dropped based on the eviction policy.
*/
public class KVCache implements KeyValueInterface {
private int numSets = 100;
private int maxElemsPerSet = 10;
// added variables:
private int cacheSize = 1000;
private LinkedList<KVSet> cacheData;
/**
* Creates a new LRU cache.
* @param cacheSize the maximum number of entries that will be kept in this cache.
*/
public KVCache(int numSets, int maxElemsPerSet) {
this.numSets = numSets;
this.maxElemsPerSet = maxElemsPerSet;
// TODO: Implement Me!
cacheSize = numSets * maxElemsPerSet;
cacheData = new LinkedList<KVSet>(); // records numSets KVSets
for (int i = 0; i < numSets; i++) { // instantiates KVSets in cacheData
KVSet set = new KVSet();
cacheData.add(set);
}
}
/**
* Added: inner class that keeps track of a key-value entry.
*/
public class KVEntry {
public String key;
public String value;
public boolean isReferenced; // helps implement second chance algorithm
public KVEntry(String key, String value, boolean isReferenced) {
this.key = key;
this.value = value;
this.isReferenced = isReferenced;
}
}
/**
* Added: inner class that keeps track of a key-value set and its WriteLock.
*/
public class KVSet {
public LinkedList<KVEntry> cacheSet; // records up to maxElemsPerSet KVEntries
public WriteLock setLock; // used for concurrency; retrieved by other classes with KVCache.getWriteLock()
public KVSet( ) {
cacheSet = new LinkedList<KVEntry>();
setLock = new ReentrantReadWriteLock().writeLock();
}
}
/**
* Retrieves an entry from the cache.
* Assumes the corresponding set has already been locked for writing.
* @param key the key whose associated value is to be returned.
* @return the value associated to this key, or null if no value with this key exists in the cache.
*/
public String get(String key) {
// Must be called before anything else
AutoGrader.agCacheGetStarted(key);
AutoGrader.agCacheGetDelay();
// TODO: Implement Me!
String rv = ""; // to store return value
if (key == null || key.length() > 256 || key.length() == 0) { // check validity of key
System.out.println("Key error");
}
else {
int setID = getSetId(key);
KVSet set = cacheData.get(setID); // get set corresponding to key
LinkedList<KVEntry> setList = set.cacheSet;
for (int i = 0; i < setList.size(); i++) {
if (setList.get(i).key.equals(key)) {
rv = setList.get(i).value; // return value associated to key if it exists
setList.get(i).isReferenced = true; // recently used page!
break;
}
}
}
// Must be called before returning
AutoGrader.agCacheGetFinished(key);
if (!rv.equals("")) {
return rv;
}
return null;
}
/**
* Adds an entry to this cache.
* If an entry with the specified key already exists in the cache, it is replaced by the new entry.
* If the cache is full, an entry is removed from the cache based on the eviction policy
* Assumes the corresponding set has already been locked for writing.
* @param key the key with which the specified value is to be associated.
* @param value a value to be associated with the specified key.
* @return true is something has been overwritten
*/
public void put(String key, String value) {
// Must be called before anything else
AutoGrader.agCachePutStarted(key, value);
AutoGrader.agCachePutDelay();
// TODO: Implement Me!
if (key == null || key.length() > 256 || key.length() == 0) { // check validity of key
System.out.println("Key error");
}
else {
boolean added = false;
int setID = getSetId(key);
KVSet set = cacheData.get(setID); // get set corresponding to key
LinkedList<KVEntry> setList = set.cacheSet;
KVEntry putEntry = new KVEntry(key, value, false); // instantiate new KVEntry
for (int i = 0; i < setList.size(); i++) {
if (setList.get(i).key.equals(key)) {
putEntry.isReferenced = true;
setList.set(i, putEntry); // if key already exists, replace entry and set isReferenced to true
added = true;
break;
}
}
if (!added && setList.size() < maxElemsPerSet) {
setList.add(putEntry); // if key doesn't already exist and set is not full, add new entry to back of set
added = true;
}
else if (setList.size() == maxElemsPerSet) { // set is full; use second chance algorithm for eviction
while (!added) { // loop algorithm until entry has been added
KVEntry entry = setList.peek();
if (entry.isReferenced) { // if referenced, give entry a second chance and try the next entry
setList.remove(); // remove...
entry.isReferenced = false; // clear isReferenced bit
setList.add(entry); // ... and re-add to tail
}
else {
setList.remove(); // if !isReferenced, remove and shift entries, then add new entry to back of set
setList.add(putEntry);
added = true;
}
}
}
}
// Must be called before returning
AutoGrader.agCachePutFinished(key, value);
}
/**
* Removes an entry from this cache.
* Assumes the corresponding set has already been locked for writing.
* @param key the key with which the specified value is to be associated.
*/
public void del (String key) {
// Must be called before anything else
AutoGrader.agCacheDelStarted(key);
AutoGrader.agCacheDelDelay();
// TODO: Implement Me!
if (key == null || key.length() > 256 || key.length() == 0) { // check validity of key
System.out.println("Key error");
}
else {
int setID = getSetId(key);
KVSet set = cacheData.get(setID); // get set corresponding to key
LinkedList<KVEntry> setList = set.cacheSet;
for (int i = 0; i < setList.size(); i++) {
if (setList.get(i).key.equals(key)) {
setList.remove(i);
break;
}
}
}
// Must be called before returning
AutoGrader.agCacheDelFinished(key);
}
/**
* @param key
* @return the write lock of the set that contains key.
*/
public WriteLock getWriteLock(String key) {
// TODO: Implement Me!
if (key == null || key.length() > 256 || key.length() == 0) { // check validity of key
return null;
}
int setID = getSetId(key);
KVSet set = cacheData.get(setID); // get set corresponding to key
return set.setLock;
}
/**
*
* @param key
* @return set of the key
*/
private int getSetId(String key) {
return Math.abs(key.hashCode()) % numSets;
}
public String toXML() {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = null;
try{
docBuilder = docFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
Document doc = docBuilder.newDocument();
doc.setXmlStandalone(true);
Element rootElement = doc.createElement("KVCache");
doc.appendChild(rootElement);
for (int i = 0; i < numSets; i++){
Element element = doc.createElement("Set");
Attr attr = doc.createAttribute("id");
attr.setValue(Integer.toString(i));
element.setAttributeNode(attr);
rootElement.appendChild(element);
KVSet set = cacheData.get(i); // get set corresponding to key
LinkedList<KVEntry> setList = set.cacheSet;
for (int j = 0; j < setList.size(); j++){
KVEntry entry = setList.get(j);
Element cacheElement = doc.createElement("CacheEntry");
Element keyElement = doc.createElement("Key");
Element valueElement = doc.createElement("Value");
if (entry == null) {
Attr ref = doc.createAttribute("isReferenced");
ref.setValue(Boolean.toString(false));
Attr valid = doc.createAttribute("isValid");
valid.setValue(Boolean.toString(false));
element.appendChild(cacheElement);
cacheElement.appendChild(keyElement);
keyElement.appendChild(doc.createTextNode(null));
cacheElement.appendChild(valueElement);
valueElement.appendChild(doc.createTextNode(null));
} else {
Attr ref = doc.createAttribute("isReferenced");
ref.setValue(Boolean.toString(entry.isReferenced));
Attr valid = doc.createAttribute("isValid");
valid.setValue(Boolean.toString(true));
element.appendChild(cacheElement);
cacheElement.appendChild(keyElement);
keyElement.appendChild(doc.createTextNode(entry.key));
cacheElement.appendChild(valueElement);
valueElement.appendChild(doc.createTextNode(entry.value));
}
}
}
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = null;
try{
transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StringWriter strwrite = new StringWriter();
StreamResult result = new StreamResult(strwrite);
transformer.transform(source, result);
String output = strwrite.getBuffer().toString();
return output;
} catch (TransformerException ex){
ex.printStackTrace();
}
return null;
}
}