| 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 | } |