1 | /******************************************************************************* |
2 | * Copyright (c) 2009 IBM Corporation and others. |
3 | * All rights reserved. This program and the accompanying materials |
4 | * are made available under the terms of the Eclipse Public License v1.0 |
5 | * which accompanies this distribution, and is available at |
6 | * http://www.eclipse.org/legal/epl-v10.html |
7 | * |
8 | * Contributors: |
9 | * IBM Corporation - initial API and implementation |
10 | *******************************************************************************/ |
11 | package org.eclipse.pde.api.tools.internal.model; |
12 | |
13 | import org.eclipse.core.runtime.CoreException; |
14 | import org.eclipse.jdt.internal.core.OverflowingLRUCache; |
15 | import org.eclipse.jdt.internal.core.util.LRUCache; |
16 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; |
17 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; |
18 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement; |
19 | |
20 | /** |
21 | * Manages the caches of {@link IApiElement}s |
22 | * |
23 | * @since 1.0.2 |
24 | */ |
25 | public final class ApiModelCache { |
26 | |
27 | /** |
28 | * Cache used for {@link IApiElement}s |
29 | */ |
30 | class Cache extends OverflowingLRUCache { |
31 | |
32 | /** |
33 | * Constructor |
34 | * @param size |
35 | * @param overflow |
36 | */ |
37 | public Cache(int size, int overflow) { |
38 | super(size, overflow); |
39 | } |
40 | |
41 | /* (non-Javadoc) |
42 | * @see org.eclipse.jdt.internal.core.OverflowingLRUCache#close(org.eclipse.jdt.internal.core.util.LRUCache.LRUCacheEntry) |
43 | */ |
44 | protected boolean close(LRUCacheEntry entry) { |
45 | return true; |
46 | } |
47 | |
48 | /* (non-Javadoc) |
49 | * @see org.eclipse.jdt.internal.core.OverflowingLRUCache#newInstance(int, int) |
50 | */ |
51 | protected LRUCache newInstance(int size, int newOverflow) { |
52 | return new Cache(size, newOverflow); |
53 | } |
54 | |
55 | /** |
56 | * Returns if the cache has any elements in it or not |
57 | * |
58 | * @return true if the cache has no entries, false otherwise |
59 | */ |
60 | public boolean isEmpty() { |
61 | return !keys().hasMoreElements(); |
62 | } |
63 | } |
64 | |
65 | static final int DEFAULT_CACHE_SIZE = 100; |
66 | static final int DEFAULT_OVERFLOW = (int)(DEFAULT_CACHE_SIZE * 0.1f); |
67 | static ApiModelCache fInstance = null; |
68 | |
69 | Cache fRootCache = null; |
70 | Cache fMemberTypeCache = null; |
71 | |
72 | /** |
73 | * Constructor - no instantiation |
74 | */ |
75 | private ApiModelCache() {} |
76 | |
77 | /** |
78 | * Returns the singleton instance of this cache |
79 | * |
80 | * @return the cache |
81 | */ |
82 | public static synchronized ApiModelCache getCache() { |
83 | if(fInstance == null) { |
84 | fInstance = new ApiModelCache(); |
85 | } |
86 | return fInstance; |
87 | } |
88 | |
89 | /** |
90 | * Returns the key to use in a cache. The key is of the form: |
91 | * <code>[baselineid].[componentid].[typename]</code><br> |
92 | * |
93 | * @param baseline |
94 | * @param component |
95 | * @param typename |
96 | * @return the member type cache key to use |
97 | */ |
98 | private String getCacheKey(String baseline, String component, String typename) { |
99 | StringBuffer buffer = new StringBuffer(); |
100 | buffer.append(baseline).append('.').append(component).append('.').append(typename); |
101 | return buffer.toString(); |
102 | } |
103 | |
104 | /** |
105 | * Caches the given {@link IApiElement} in the correct cache based on its type. |
106 | * |
107 | * @param element the element to cache |
108 | * @throws CoreException if there is a problem accessing any of the {@link IApiElement} info |
109 | * in order to cache it - pass the exception along. |
110 | */ |
111 | public void cacheElementInfo(IApiElement element) throws CoreException { |
112 | switch(element.getType()) { |
113 | case IApiElement.TYPE: { |
114 | if(fRootCache == null) { |
115 | fRootCache = new Cache(DEFAULT_CACHE_SIZE, DEFAULT_OVERFLOW); |
116 | } |
117 | IApiComponent comp = element.getApiComponent(); |
118 | if(comp != null) { |
119 | IApiBaseline baseline = comp.getBaseline(); |
120 | String id = comp.getId(); |
121 | if(id == null) { |
122 | return; |
123 | } |
124 | Cache compcache = (Cache) fRootCache.get(baseline.getName()); |
125 | if(compcache == null) { |
126 | compcache = new Cache(DEFAULT_CACHE_SIZE, DEFAULT_OVERFLOW); |
127 | fRootCache.put(baseline.getName(), compcache); |
128 | } |
129 | Cache typecache = (Cache) compcache.get(id); |
130 | if(typecache == null) { |
131 | typecache = new Cache(DEFAULT_CACHE_SIZE, DEFAULT_OVERFLOW); |
132 | compcache.put(comp.getId(), typecache); |
133 | } |
134 | ApiType type = (ApiType) element; |
135 | if(type.isMemberType() || isMemberType(type.getName()) /*cache even a root type with a '$' in its name here as well*/) { |
136 | if(this.fMemberTypeCache == null) { |
137 | this.fMemberTypeCache = new Cache(DEFAULT_CACHE_SIZE, DEFAULT_OVERFLOW); |
138 | } |
139 | String key = getCacheKey(baseline.getName(), id, getRootName(type.getName())); |
140 | Cache mcache = (Cache) this.fMemberTypeCache.get(key); |
141 | if(mcache == null) { |
142 | mcache = new Cache(DEFAULT_CACHE_SIZE, DEFAULT_OVERFLOW); |
143 | this.fMemberTypeCache.put(key, mcache); |
144 | } |
145 | mcache.put(type.getName(), type); |
146 | } |
147 | else { |
148 | typecache.put(element.getName(), element); |
149 | } |
150 | } |
151 | break; |
152 | } |
153 | } |
154 | } |
155 | |
156 | /** |
157 | * Returns the root type name assuming that the '$' char is a member type boundary |
158 | * @param typename |
159 | * @return the pruned name or the original name |
160 | */ |
161 | private String getRootName(String typename) { |
162 | int idx = typename.indexOf('$'); |
163 | if(idx > -1) { |
164 | return typename.substring(0, idx); |
165 | } |
166 | return typename; |
167 | } |
168 | |
169 | /** |
170 | * Method to see if the type boundary char appears in the type name |
171 | * @param typename |
172 | * @return true if the type name contains '$' false otherwise |
173 | */ |
174 | private boolean isMemberType(String typename) { |
175 | return typename.indexOf('$') > -1; |
176 | } |
177 | |
178 | /** |
179 | * Returns the {@link IApiElement} infos for the element referenced by the given |
180 | * identifier and of the given type. |
181 | * |
182 | * @param baselineid the id of the baseline the component + element belongs to |
183 | * @param componentid the id of the {@link IApiComponent} the element resides in |
184 | * @param identifier for example the qualified name of the type or the id of an API component |
185 | * @param type the kind of the element to look for info for |
186 | * |
187 | * @return the cached {@link IApiElement} or <code>null</code> if no such element is cached |
188 | */ |
189 | public IApiElement getElementInfo(String baselineid, String componentid, String identifier, int type) { |
190 | if(baselineid == null || componentid == null) { |
191 | return null; |
192 | } |
193 | switch(type) { |
194 | case IApiElement.TYPE: { |
195 | if(isMemberType(identifier)) { |
196 | if(this.fMemberTypeCache != null) { |
197 | Cache mcache = (Cache) this.fMemberTypeCache.get(getCacheKey(baselineid, componentid, getRootName(identifier))); |
198 | if(mcache != null) { |
199 | return (IApiElement) mcache.get(identifier); |
200 | } |
201 | } |
202 | } |
203 | else { |
204 | if(this.fRootCache != null) { |
205 | Cache compcache = (Cache) fRootCache.get(baselineid); |
206 | if(compcache != null) { |
207 | Cache typecache = (Cache) compcache.get(componentid); |
208 | if(typecache != null && identifier != null) { |
209 | return (IApiElement) typecache.get(identifier); |
210 | } |
211 | } |
212 | } |
213 | } |
214 | break; |
215 | } |
216 | } |
217 | return null; |
218 | } |
219 | |
220 | /** |
221 | * Removes the {@link IApiElement} from the given component (given its id) with |
222 | * the given identifier and of the given type. |
223 | * |
224 | * @param componentid the id of the component the element resides in |
225 | * @param identifier the id (name) of the element to remove |
226 | * @param type the type of the element (TYPE, METHOD, FIELD, etc) |
227 | * |
228 | * @return true if the element was removed, false otherwise |
229 | */ |
230 | public boolean removeElementInfo(String baselineid, String componentid, String identifier, int type) { |
231 | if(baselineid == null) { |
232 | return false; |
233 | } |
234 | switch(type) { |
235 | case IApiElement.TYPE: { |
236 | if(componentid != null && identifier != null) { |
237 | boolean removed = true; |
238 | //clean member type cache |
239 | if(this.fMemberTypeCache != null) { |
240 | if(isMemberType(identifier)) { |
241 | Cache mcache = (Cache) this.fMemberTypeCache.get(getCacheKey(baselineid, componentid, getRootName(identifier))); |
242 | if(mcache != null) { |
243 | return mcache.remove(identifier) != null; |
244 | } |
245 | } |
246 | else { |
247 | this.fMemberTypeCache.remove(getCacheKey(baselineid, componentid, getRootName(identifier))); |
248 | } |
249 | } |
250 | if(fRootCache != null) { |
251 | Cache compcache = (Cache) fRootCache.get(baselineid); |
252 | if(compcache != null) { |
253 | Cache typecache = (Cache) compcache.get(componentid); |
254 | if(typecache != null) { |
255 | removed &= typecache.remove(identifier) != null; |
256 | if(typecache.isEmpty()) { |
257 | removed &= compcache.remove(componentid) != null; |
258 | } |
259 | if(compcache.isEmpty()) { |
260 | removed &= fRootCache.remove(baselineid) != null; |
261 | } |
262 | return removed; |
263 | } |
264 | |
265 | } |
266 | } |
267 | else { |
268 | return false; |
269 | } |
270 | } |
271 | break; |
272 | } |
273 | case IApiElement.COMPONENT: { |
274 | flushMemberCache(); |
275 | if(fRootCache != null && componentid != null) { |
276 | Cache compcache = (Cache) fRootCache.get(baselineid); |
277 | if(compcache != null) { |
278 | boolean removed = compcache.remove(componentid) != null; |
279 | if(compcache.isEmpty()) { |
280 | removed &= fRootCache.remove(baselineid) != null; |
281 | } |
282 | return removed; |
283 | } |
284 | } |
285 | break; |
286 | } |
287 | case IApiElement.BASELINE: { |
288 | flushMemberCache(); |
289 | if(fRootCache != null) { |
290 | return fRootCache.remove(baselineid) != null; |
291 | } |
292 | break; |
293 | } |
294 | } |
295 | return false; |
296 | } |
297 | |
298 | /** |
299 | * Removes the given {@link IApiElement} info from the cache and returns it if present |
300 | * @param element |
301 | * @return true if the {@link IApiElement} was removed false otherwise |
302 | * @throws CoreException if there is a problem accessing any of the {@link IApiElement} info |
303 | * in order to remove it from the cache - pass the exception along. |
304 | */ |
305 | public boolean removeElementInfo(IApiElement element) { |
306 | if(element == null) { |
307 | return false; |
308 | } |
309 | switch(element.getType()) { |
310 | case IApiElement.COMPONENT: |
311 | case IApiElement.TYPE: { |
312 | if(fRootCache != null) { |
313 | IApiComponent comp = element.getApiComponent(); |
314 | if(comp != null) { |
315 | try { |
316 | IApiBaseline baseline = comp.getBaseline(); |
317 | return removeElementInfo(baseline.getName(), comp.getId(), element.getName(), element.getType()); |
318 | } |
319 | catch(CoreException ce) {} |
320 | } |
321 | } |
322 | break; |
323 | } |
324 | case IApiElement.BASELINE: { |
325 | flushMemberCache(); |
326 | if(fRootCache != null) { |
327 | IApiBaseline baseline = (IApiBaseline) element; |
328 | return fRootCache.remove(baseline.getName()) != null; |
329 | } |
330 | break; |
331 | } |
332 | } |
333 | return false; |
334 | } |
335 | |
336 | /** |
337 | * Clears out all cached information. |
338 | */ |
339 | public void flushCaches() { |
340 | if(fRootCache != null) { |
341 | fRootCache.flush(); |
342 | } |
343 | flushMemberCache(); |
344 | } |
345 | |
346 | /** |
347 | * Flushes the cache of member types |
348 | */ |
349 | private void flushMemberCache() { |
350 | if(this.fMemberTypeCache != null) { |
351 | this.fMemberTypeCache.flush(); |
352 | } |
353 | } |
354 | |
355 | /** |
356 | * Returns if the cache has any elements in it or not |
357 | * |
358 | * @return true if the cache has no entries, false otherwise |
359 | */ |
360 | public boolean isEmpty() { |
361 | boolean empty = true; |
362 | if(fRootCache != null) { |
363 | empty &= fRootCache.isEmpty(); |
364 | } |
365 | if(this.fMemberTypeCache != null) { |
366 | empty &= this.fMemberTypeCache.isEmpty(); |
367 | } |
368 | return empty; |
369 | } |
370 | } |