CachedListenerObjectRetriever.java
/*
* Copyright (C) 2012-2024 RRiBbit.org
*
* 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
*
* https://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 org.rribbit.retrieval;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.rribbit.ListenerObject;
import org.rribbit.creation.ListenerObjectCreator;
import org.rribbit.creation.notification.ListenerObjectCreationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This {@link ListenerObjectRetriever} implements caching to be able to retrieve {@link ListenerObject}s more quickly. It keeps a local {@link Map} where requests are
* mapped to {@link Collection}s of {@link ListenerObject}s. This class extends from {@link DefaultListenerObjectRetriever} and adds caching to the retrieval methods, except
* {@link #getListenerObjects()}, because that one simply returns all {@link ListenerObject}s.
*
* This class implements the {@link ListenerObjectCreationObserver} in order to clear the cache if any new {@link ListenerObject}s are created by any of the {@link ListenerObjectCreator}s
* that are associated with this {@link CachedListenerObjectRetriever}. Also, if you add a {@link ListenerObjectCreator} to this {@link CachedListenerObjectRetriever}, the cache will
* be cleared as well.
*
* @author G.J. Schouten
*
*/
public class CachedListenerObjectRetriever extends DefaultListenerObjectRetriever implements ListenerObjectCreationObserver {
private static final Logger log = LoggerFactory.getLogger(CachedListenerObjectRetriever.class);
/**
* The cache of {@link Collection}s of {@link ListenerObject}s.
*/
protected Map<RetrievalRequest, Collection<ListenerObject>> cache;
/**
* Whenever you use this constructor, be sure to set the {@link ListenerObjectCreator} with the setter provided by this class.
* If you don't, runtime {@link NullPointerException}s will occur.
*/
public CachedListenerObjectRetriever() {
this.init();
}
/**
* This constructor is recommended, since it forces you to specify the {@link ListenerObjectCreator}. Passing a null value for this
* will result in runtime {@link NullPointerException}s.
*
* @param listenerObjectCreators
*/
public CachedListenerObjectRetriever(ListenerObjectCreator... listenerObjectCreators) {
super(listenerObjectCreators);
this.init();
}
/**
* Initializes the cache of this {@link CachedListenerObjectRetriever}.
*/
protected void init() {
cache = new ConcurrentHashMap<>();
}
/**
* Clears the cache.
*
* @param addedClass
*/
@Override
public void onClassAdded(Class<?> addedClass) {
log.debug("Class was added by ListenerObjectCreator, clearing cache...");
this.clearCache();
}
@Override
public Collection<ListenerObject> getListenerObjects(Class<?> returnType) {
this.checkReturnType(returnType);
log.debug("Inspecting cache for matches");
RetrievalRequest request = new RetrievalRequest(null, returnType);
Collection<ListenerObject> listenerObjects = cache.get(request);
if(listenerObjects == null) {
log.debug("No match found, retrieving ListenerObject from DefaultRetriever and storing in cache");
listenerObjects = super.getListenerObjects(returnType);
cache.put(request, listenerObjects);
}
log.debug("Found {} ListenerObjects", listenerObjects.size());
return listenerObjects;
}
@Override
public Collection<ListenerObject> getListenerObjects(String hint) {
this.checkHint(hint);
log.debug("Inspecting cache for matches");
RetrievalRequest request = new RetrievalRequest(hint, null);
Collection<ListenerObject> listenerObjects = cache.get(request);
if(listenerObjects == null) {
log.debug("No match found, retrieving ListenerObject from DefaultRetriever and storing in cache");
listenerObjects = super.getListenerObjects(hint);
cache.put(request, listenerObjects);
}
log.debug("Found {} ListenerObjects", listenerObjects.size());
return listenerObjects;
}
@Override
public Collection<ListenerObject> getListenerObjects(Class<?> returnType, String hint) {
this.checkReturnType(returnType);
this.checkHint(hint);
log.debug("Inspecting cache for matches");
RetrievalRequest request = new RetrievalRequest(hint, returnType);
Collection<ListenerObject> listenerObjects = cache.get(request);
if(listenerObjects == null) {
log.debug("No match found, retrieving ListenerObject from DefaultRetriever and storing in cache");
listenerObjects = super.getListenerObjects(returnType, hint);
cache.put(request, listenerObjects);
}
log.debug("Found {} ListenerObjects", listenerObjects.size());
return listenerObjects;
}
@Override
public void addListenerObjectCreator(ListenerObjectCreator listenerObjectCreator) {
super.addListenerObjectCreator(listenerObjectCreator);
listenerObjectCreator.registerObserver(this);
this.clearCache();
}
@Override
public void setListenerObjectCreator(ListenerObjectCreator listenerObjectCreator) {
super.setListenerObjectCreator(listenerObjectCreator);
listenerObjectCreator.registerObserver(this);
this.clearCache();
}
/**
* Checks whether the hint is null, in order to be able to fullfill the {@link ListenerObjectRetriever} contract.
*
* @param hint
*/
protected void checkHint(String hint) {
if(hint == null) {
throw new IllegalArgumentException("hint cannot be null!");
}
}
/**
* Checks whether the returntype is null, in order to be able to fullfill the {@link ListenerObjectRetriever} contract.
*
* @param returnType
*/
protected void checkReturnType(Class<?> returnType) {
if(returnType == null) {
throw new IllegalArgumentException("returnType cannot be null!");
}
}
protected void clearCache() {
if(cache != null) { //Cache might be null while constructor of superclass is running. This method could be called indirectly from there, so checking for null.
cache.clear();
}
}
/**
* This class represents the request made to this {@link CachedListenerObjectRetriever}. It is used as a key in the {@link Map} in order to quickly
* retrieve {@link ListenerObject}s.
*
* @author G.J. Schouten
*
*/
protected static class RetrievalRequest {
private String hint;
private Class<?> returnType;
public RetrievalRequest(String hint, Class<?> returnType) {
this.hint = hint;
this.returnType = returnType;
}
@Override
public int hashCode() {
int prime = 31;
int result = 1;
result = prime * result + ((hint == null) ? 0 : hint.hashCode());
result = prime * result + ((returnType == null) ? 0 : returnType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj == null) {
return false;
}
if(this.getClass() != obj.getClass()) {
return false;
}
RetrievalRequest other = (RetrievalRequest) obj;
if(hint == null) {
if(other.hint != null) {
return false;
}
} else if(!hint.equals(other.hint)) {
return false;
}
if(returnType == null) {
if(other.returnType != null) {
return false;
}
} else if(!returnType.equals(other.returnType)) {
return false;
}
return true;
}
}
}