View Javadoc
1   /*
2    * Copyright (C) 2012-2024 RRiBbit.org
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.rribbit.retrieval;
17  
18  import java.util.Collection;
19  import java.util.Map;
20  import java.util.concurrent.ConcurrentHashMap;
21  
22  import org.rribbit.ListenerObject;
23  import org.rribbit.creation.ListenerObjectCreator;
24  import org.rribbit.creation.notification.ListenerObjectCreationObserver;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  /**
29   * This {@link ListenerObjectRetriever} implements caching to be able to retrieve {@link ListenerObject}s more quickly. It keeps a local {@link Map} where requests are
30   * mapped to {@link Collection}s of {@link ListenerObject}s. This class extends from {@link DefaultListenerObjectRetriever} and adds caching to the retrieval methods, except
31   * {@link #getListenerObjects()}, because that one simply returns all {@link ListenerObject}s.
32   *
33   * 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
34   * that are associated with this {@link CachedListenerObjectRetriever}. Also, if you add a {@link ListenerObjectCreator} to this {@link CachedListenerObjectRetriever}, the cache will
35   * be cleared as well.
36   *
37   * @author G.J. Schouten
38   *
39   */
40  public class CachedListenerObjectRetriever extends DefaultListenerObjectRetriever implements ListenerObjectCreationObserver {
41  
42  	private static final Logger log = LoggerFactory.getLogger(CachedListenerObjectRetriever.class);
43  
44  	/**
45  	 * The cache of {@link Collection}s of {@link ListenerObject}s.
46  	 */
47  	protected Map<RetrievalRequest, Collection<ListenerObject>> cache;
48  
49  	/**
50  	 * Whenever you use this constructor, be sure to set the {@link ListenerObjectCreator} with the setter provided by this class.
51  	 * If you don't, runtime {@link NullPointerException}s will occur.
52  	 */
53  	public CachedListenerObjectRetriever() {
54  		this.init();
55  	}
56  
57  	/**
58  	 * This constructor is recommended, since it forces you to specify the {@link ListenerObjectCreator}. Passing a null value for this
59  	 * will result in runtime {@link NullPointerException}s.
60  	 *
61  	 * @param listenerObjectCreators
62  	 */
63  	public CachedListenerObjectRetriever(ListenerObjectCreator... listenerObjectCreators) {
64  		super(listenerObjectCreators);
65  		this.init();
66  	}
67  
68  	/**
69  	 * Initializes the cache of this {@link CachedListenerObjectRetriever}.
70  	 */
71  	protected void init() {
72  		cache = new ConcurrentHashMap<>();
73  	}
74  
75  	/**
76  	 * Clears the cache.
77  	 *
78  	 * @param addedClass
79  	 */
80  	@Override
81  	public void onClassAdded(Class<?> addedClass) {
82  
83  		log.debug("Class was added by ListenerObjectCreator, clearing cache...");
84  		this.clearCache();
85  	}
86  
87  	@Override
88  	public Collection<ListenerObject> getListenerObjects(Class<?> returnType) {
89  
90  		this.checkReturnType(returnType);
91  
92  		log.debug("Inspecting cache for matches");
93  		RetrievalRequest request = new RetrievalRequest(null, returnType);
94  		Collection<ListenerObject> listenerObjects = cache.get(request);
95  		if(listenerObjects == null) {
96  			log.debug("No match found, retrieving ListenerObject from DefaultRetriever and storing in cache");
97  			listenerObjects = super.getListenerObjects(returnType);
98  			cache.put(request, listenerObjects);
99  		}
100 		log.debug("Found {} ListenerObjects", listenerObjects.size());
101 		return listenerObjects;
102 	}
103 
104 	@Override
105 	public Collection<ListenerObject> getListenerObjects(String hint) {
106 
107 		this.checkHint(hint);
108 
109 		log.debug("Inspecting cache for matches");
110 		RetrievalRequest request = new RetrievalRequest(hint, null);
111 		Collection<ListenerObject> listenerObjects = cache.get(request);
112 		if(listenerObjects == null) {
113 			log.debug("No match found, retrieving ListenerObject from DefaultRetriever and storing in cache");
114 			listenerObjects = super.getListenerObjects(hint);
115 			cache.put(request, listenerObjects);
116 		}
117 		log.debug("Found {} ListenerObjects", listenerObjects.size());
118 		return listenerObjects;
119 	}
120 
121 	@Override
122 	public Collection<ListenerObject> getListenerObjects(Class<?> returnType, String hint) {
123 
124 		this.checkReturnType(returnType);
125 		this.checkHint(hint);
126 
127 		log.debug("Inspecting cache for matches");
128 		RetrievalRequest request = new RetrievalRequest(hint, returnType);
129 		Collection<ListenerObject> listenerObjects = cache.get(request);
130 		if(listenerObjects == null) {
131 			log.debug("No match found, retrieving ListenerObject from DefaultRetriever and storing in cache");
132 			listenerObjects = super.getListenerObjects(returnType, hint);
133 			cache.put(request, listenerObjects);
134 		}
135 		log.debug("Found {} ListenerObjects", listenerObjects.size());
136 		return listenerObjects;
137 	}
138 
139 	@Override
140 	public void addListenerObjectCreator(ListenerObjectCreator listenerObjectCreator) {
141 		super.addListenerObjectCreator(listenerObjectCreator);
142 		listenerObjectCreator.registerObserver(this);
143 		this.clearCache();
144 	}
145 
146 	@Override
147 	public void setListenerObjectCreator(ListenerObjectCreator listenerObjectCreator) {
148 		super.setListenerObjectCreator(listenerObjectCreator);
149 		listenerObjectCreator.registerObserver(this);
150 		this.clearCache();
151 	}
152 
153 	/**
154 	 * Checks whether the hint is null, in order to be able to fullfill the {@link ListenerObjectRetriever} contract.
155 	 *
156 	 * @param hint
157 	 */
158 	protected void checkHint(String hint) {
159 
160 		if(hint == null) {
161 			throw new IllegalArgumentException("hint cannot be null!");
162 		}
163 	}
164 
165 	/**
166 	 * Checks whether the returntype is null, in order to be able to fullfill the {@link ListenerObjectRetriever} contract.
167 	 *
168 	 * @param returnType
169 	 */
170 	protected void checkReturnType(Class<?> returnType) {
171 
172 		if(returnType == null) {
173 			throw new IllegalArgumentException("returnType cannot be null!");
174 		}
175 	}
176 
177 	protected void clearCache() {
178 
179 		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.
180 			cache.clear();
181 		}
182 	}
183 
184 	/**
185 	 * This class represents the request made to this {@link CachedListenerObjectRetriever}. It is used as a key in the {@link Map} in order to quickly
186 	 * retrieve {@link ListenerObject}s.
187 	 *
188 	 * @author G.J. Schouten
189 	 *
190 	 */
191 	protected static class RetrievalRequest {
192 
193 		private String hint;
194 		private Class<?> returnType;
195 
196 		public RetrievalRequest(String hint, Class<?> returnType) {
197 			this.hint = hint;
198 			this.returnType = returnType;
199 		}
200 
201 		@Override
202 		public int hashCode() {
203 
204 			int prime = 31;
205 			int result = 1;
206 			result = prime * result + ((hint == null) ? 0 : hint.hashCode());
207 			result = prime * result + ((returnType == null) ? 0 : returnType.hashCode());
208 			return result;
209 		}
210 
211 		@Override
212 		public boolean equals(Object obj) {
213 
214 			if(this == obj) {
215 				return true;
216 			}
217 			if(obj == null) {
218 				return false;
219 			}
220 			if(this.getClass() != obj.getClass()) {
221 				return false;
222 			}
223 
224 			RetrievalRequest other = (RetrievalRequest) obj;
225 			if(hint == null) {
226 				if(other.hint != null) {
227 					return false;
228 				}
229 			} else if(!hint.equals(other.hint)) {
230 				return false;
231 			}
232 			if(returnType == null) {
233 				if(other.returnType != null) {
234 					return false;
235 				}
236 			} else if(!returnType.equals(other.returnType)) {
237 				return false;
238 			}
239 			return true;
240 		}
241 	}
242 }