1 /*******************************************************************************
2  * Copyright (c) 2000, 2016 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Terry Parker <tparker@google.com> (Google Inc.)  https://bugs.eclipse.org/365499
14  *     Stephan Herrmann - Contribution for
15  *								Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
16  *******************************************************************************/
17 package org.eclipse.jdt.internal.core;
18 import java.util.HashMap;
19 import java.util.Map;
20 
21 import org.eclipse.jdt.core.IJavaElement;
22 import org.eclipse.jdt.core.IJavaProject;
23 import org.eclipse.jdt.core.IPackageFragment;
24 import org.eclipse.jdt.core.IPackageFragmentRoot;
25 import org.eclipse.jdt.core.ITypeRoot;
26 import org.eclipse.jdt.internal.core.util.LRUCache;
27 import org.eclipse.jdt.internal.core.util.Util;
28 
29 /**
30  * The cache of java elements to their respective info.
31  */
32 public class JavaModelCache {
33 	public static boolean VERBOSE = false;
34 	public static boolean DEBUG_CACHE_INSERTIONS = false;
35 
36 	public static final int DEFAULT_PROJECT_SIZE = 5;  // average 25552 bytes per project.
37 	public static final int DEFAULT_ROOT_SIZE = 50; // average 2590 bytes per root -> maximum size : 25900*BASE_VALUE bytes
38 	public static final int DEFAULT_PKG_SIZE = 500; // average 1782 bytes per pkg -> maximum size : 178200*BASE_VALUE bytes
39 	public static final int DEFAULT_OPENABLE_SIZE = 250; // average 6629 bytes per openable (includes children) -> maximum size : 662900*BASE_VALUE bytes
40 	public static final int DEFAULT_CHILDREN_SIZE = 250*20; // average 20 children per openable
41 	public static final int DEFAULT_ACCESSRULE_SIZE = 1024;
42 	public static final String RATIO_PROPERTY = "org.eclipse.jdt.core.javamodelcache.ratio"; //$NON-NLS-1$
43 	public static final String JAR_TYPE_RATIO_PROPERTY = "org.eclipse.jdt.core.javamodelcache.jartyperatio"; //$NON-NLS-1$
44 
45 	public static final Object NON_EXISTING_JAR_TYPE_INFO = new Object();
46 
47 	/*
48 	 * The memory ratio that should be applied to the above constants.
49 	 */
50 	protected double memoryRatio = -1;
51 
52 	/**
53 	 * Active Java Model Info
54 	 */
55 	protected JavaElementInfo modelInfo;
56 
57 	/**
58 	 * Cache of open projects.
59 	 */
60 	protected HashMap<IJavaProject, JavaElementInfo> projectCache;
61 
62 	/**
63 	 * Cache of open package fragment roots.
64 	 */
65 	protected ElementCache<IPackageFragmentRoot> rootCache;
66 
67 	/**
68 	 * Cache of open package fragments
69 	 */
70 	protected ElementCache<IPackageFragment> pkgCache;
71 
72 	/**
73 	 * Cache of open compilation unit and class files
74 	 */
75 	protected ElementCache<ITypeRoot> openableCache;
76 
77 	/**
78 	 * Cache of open children of openable Java Model Java elements
79 	 */
80 	protected Map<IJavaElement, Object> childrenCache;
81 
82 	/**
83 	 * Cache of access rules
84 	 */
85 	protected LRUCache<ClasspathAccessRule, ClasspathAccessRule> accessRuleCache;
86 
87 
88 	/**
89 	 * Cache of open binary type (inside a jar) that have a non-open parent
90 	 * Values are either instance of IBinaryType or Object (see {@link #NON_EXISTING_JAR_TYPE_INFO})
91 	 */
92 	protected LRUCache<IJavaElement, Object> jarTypeCache;
93 
JavaModelCache()94 public JavaModelCache() {
95 	// set the size of the caches as a function of the maximum amount of memory available
96 	double ratio = getMemoryRatio();
97 	// adjust the size of the openable cache using the RATIO_PROPERTY property
98 	double openableRatio = getOpenableRatio();
99 	this.projectCache = new HashMap<>(DEFAULT_PROJECT_SIZE); // NB: Don't use a LRUCache for projects as they are constantly reopened (e.g. during delta processing)
100 	if (VERBOSE) {
101 		this.rootCache = new VerboseElementCache<>((int) (DEFAULT_ROOT_SIZE * ratio), "Root cache"); //$NON-NLS-1$
102 		this.pkgCache = new VerboseElementCache<>((int) (DEFAULT_PKG_SIZE * ratio), "Package cache"); //$NON-NLS-1$
103 		this.openableCache = new VerboseElementCache<>((int) (DEFAULT_OPENABLE_SIZE * ratio * openableRatio), "Openable cache"); //$NON-NLS-1$
104 	} else {
105 		this.rootCache = new ElementCache<>((int) (DEFAULT_ROOT_SIZE * ratio));
106 		this.pkgCache = new ElementCache<>((int) (DEFAULT_PKG_SIZE * ratio));
107 		this.openableCache = new ElementCache<>((int) (DEFAULT_OPENABLE_SIZE * ratio * openableRatio));
108 	}
109 	this.childrenCache = new HashMap<>((int) (DEFAULT_CHILDREN_SIZE * ratio * openableRatio));
110 	this.accessRuleCache = new LRUCache<>(DEFAULT_ACCESSRULE_SIZE);
111 	resetJarTypeCache();
112 }
113 
getOpenableRatio()114 private double getOpenableRatio() {
115 	return getRatioForProperty(RATIO_PROPERTY);
116 }
117 
getJarTypeRatio()118 private double getJarTypeRatio() {
119 	return getRatioForProperty(JAR_TYPE_RATIO_PROPERTY);
120 }
121 
getRatioForProperty(String propertyName)122 private double getRatioForProperty(String propertyName) {
123 	String property = System.getProperty(propertyName);
124 	if (property != null) {
125 		try {
126 			return Double.parseDouble(property);
127 		} catch (NumberFormatException e) {
128 			// ignore
129 			Util.log(e, "Could not parse value for " + propertyName + ": " + property); //$NON-NLS-1$ //$NON-NLS-2$
130 		}
131 	}
132 	return 1.0;
133 }
134 
135 /**
136  *  Returns the info for the element.
137  */
getInfo(IJavaElement element)138 public Object getInfo(IJavaElement element) {
139 	switch (element.getElementType()) {
140 		case IJavaElement.JAVA_MODEL:
141 			return this.modelInfo;
142 		case IJavaElement.JAVA_PROJECT:
143 			return this.projectCache.get(element);
144 		case IJavaElement.PACKAGE_FRAGMENT_ROOT:
145 			return this.rootCache.get((IPackageFragmentRoot) element);
146 		case IJavaElement.PACKAGE_FRAGMENT:
147 			return this.pkgCache.get((IPackageFragment) element);
148 		case IJavaElement.COMPILATION_UNIT:
149 		case IJavaElement.CLASS_FILE:
150 			return this.openableCache.get((ITypeRoot) element);
151 		case IJavaElement.TYPE:
152 			Object result = this.jarTypeCache.get(element);
153 			if (result != null)
154 				return result;
155 			else
156 				return this.childrenCache.get(element);
157 		default:
158 			return this.childrenCache.get(element);
159 	}
160 }
161 
162 /*
163  *  Returns the existing element that is equal to the given element if present in the cache.
164  *  Returns the given element otherwise.
165  */
getExistingElement(IJavaElement element)166 public IJavaElement getExistingElement(IJavaElement element) {
167 	switch (element.getElementType()) {
168 		case IJavaElement.JAVA_MODEL:
169 			return element;
170 		case IJavaElement.JAVA_PROJECT:
171 			return element; // projectCache is a Hashtable and Hashtables don't support getKey(...)
172 		case IJavaElement.PACKAGE_FRAGMENT_ROOT:
173 			return this.rootCache.getKey((IPackageFragmentRoot) element);
174 		case IJavaElement.PACKAGE_FRAGMENT:
175 			return this.pkgCache.getKey((IPackageFragment) element);
176 		case IJavaElement.COMPILATION_UNIT:
177 		case IJavaElement.CLASS_FILE:
178 			return this.openableCache.getKey((ITypeRoot) element);
179 		case IJavaElement.TYPE:
180 			return element; // jarTypeCache or childrenCache are Hashtables and Hashtables don't support getKey(...)
181 		default:
182 			return element; // childrenCache is a Hashtable and Hashtables don't support getKey(...)
183 	}
184 }
185 
getMemoryRatio()186 protected double getMemoryRatio() {
187 	if ((int) this.memoryRatio == -1) {
188 		long maxMemory = Runtime.getRuntime().maxMemory();
189 		// if max memory is infinite, set the ratio to 4d which corresponds to the 256MB that Eclipse defaults to
190 		// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=111299)
191 		this.memoryRatio = maxMemory == Long.MAX_VALUE ? 4d : ((double) maxMemory) / (64 * 0x100000); // 64MB is the base memory for most JVM
192 	}
193 	return this.memoryRatio;
194 }
195 
196 /**
197  *  Returns the info for this element without
198  *  disturbing the cache ordering.
199  */
peekAtInfo(IJavaElement element)200 protected Object peekAtInfo(IJavaElement element) {
201 	switch (element.getElementType()) {
202 		case IJavaElement.JAVA_MODEL:
203 			return this.modelInfo;
204 		case IJavaElement.JAVA_PROJECT:
205 			return this.projectCache.get(element);
206 		case IJavaElement.PACKAGE_FRAGMENT_ROOT:
207 			return this.rootCache.peek((IPackageFragmentRoot) element);
208 		case IJavaElement.PACKAGE_FRAGMENT:
209 			return this.pkgCache.peek((IPackageFragment) element);
210 		case IJavaElement.COMPILATION_UNIT:
211 		case IJavaElement.CLASS_FILE:
212 			return this.openableCache.peek((ITypeRoot) element);
213 		case IJavaElement.TYPE:
214 			Object result = this.jarTypeCache.peek(element);
215 			if (result != null)
216 				return result;
217 			else
218 				return this.childrenCache.get(element);
219 		default:
220 			return this.childrenCache.get(element);
221 	}
222 }
223 
224 /**
225  * Remember the info for the element.
226  */
putInfo(IJavaElement element, Object info)227 protected void putInfo(IJavaElement element, Object info) {
228 	if (DEBUG_CACHE_INSERTIONS) {
229 		System.out.println(Thread.currentThread() + " cache putInfo (" + getElementType(element) + " " + element.toString() + ", " + info + ")");  //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
230 	}
231 	switch (element.getElementType()) {
232 		case IJavaElement.JAVA_MODEL:
233 			this.modelInfo = (JavaElementInfo) info;
234 			break;
235 		case IJavaElement.JAVA_PROJECT:
236 			this.projectCache.put((IJavaProject) element, (JavaElementInfo) info);
237 			this.rootCache.ensureSpaceLimit((JavaElementInfo) info, element);
238 			break;
239 		case IJavaElement.PACKAGE_FRAGMENT_ROOT:
240 			this.rootCache.put((IPackageFragmentRoot) element, (JavaElementInfo) info);
241 			this.pkgCache.ensureSpaceLimit((JavaElementInfo) info, element);
242 			break;
243 		case IJavaElement.PACKAGE_FRAGMENT:
244 			this.pkgCache.put((IPackageFragment) element, (JavaElementInfo) info);
245 			this.openableCache.ensureSpaceLimit((JavaElementInfo) info, element);
246 			break;
247 		case IJavaElement.COMPILATION_UNIT:
248 		case IJavaElement.CLASS_FILE:
249 			this.openableCache.put((ITypeRoot) element, (JavaElementInfo) info);
250 			break;
251 		default:
252 			this.childrenCache.put(element, info);
253 	}
254 }
255 
getElementType(IJavaElement element)256 public static String getElementType(IJavaElement element) {
257 	String elementType;
258 	switch (element.getElementType()) {
259 		case IJavaElement.JAVA_PROJECT:
260 			elementType = "project"; //$NON-NLS-1$
261 			break;
262 		case IJavaElement.PACKAGE_FRAGMENT_ROOT:
263 			elementType = "root"; //$NON-NLS-1$
264 			break;
265 		case IJavaElement.PACKAGE_FRAGMENT:
266 			elementType = "package"; //$NON-NLS-1$
267 			break;
268 		case IJavaElement.CLASS_FILE:
269 			elementType = "class file"; //$NON-NLS-1$
270 			break;
271 		case IJavaElement.COMPILATION_UNIT:
272 			elementType = "compilation unit"; //$NON-NLS-1$
273 			break;
274 		default:
275 			elementType = "element"; //$NON-NLS-1$
276 	}
277 	return elementType;
278 }
279 
280 /**
281  * Removes the info of the element from the cache.
282  */
removeInfo(JavaElement element)283 protected void removeInfo(JavaElement element) {
284 	if (DEBUG_CACHE_INSERTIONS) {
285 		String elementToString = element.toString();
286 		System.out.println(Thread.currentThread() + " cache removeInfo " + getElementType(element) + " " + elementToString);  //$NON-NLS-1$//$NON-NLS-2$
287 	}
288 	switch (element.getElementType()) {
289 		case IJavaElement.JAVA_MODEL:
290 			this.modelInfo = null;
291 			break;
292 		case IJavaElement.JAVA_PROJECT:
293 			this.projectCache.remove((IJavaProject)element);
294 			this.rootCache.resetSpaceLimit((int) (DEFAULT_ROOT_SIZE * getMemoryRatio()), element);
295 			break;
296 		case IJavaElement.PACKAGE_FRAGMENT_ROOT:
297 			this.rootCache.remove((IPackageFragmentRoot) element);
298 			this.pkgCache.resetSpaceLimit((int) (DEFAULT_PKG_SIZE * getMemoryRatio()), element);
299 			break;
300 		case IJavaElement.PACKAGE_FRAGMENT:
301 			this.pkgCache.remove((IPackageFragment) element);
302 			this.openableCache.resetSpaceLimit((int) (DEFAULT_OPENABLE_SIZE * getMemoryRatio() * getOpenableRatio()), element);
303 			break;
304 		case IJavaElement.COMPILATION_UNIT:
305 		case IJavaElement.CLASS_FILE:
306 			this.openableCache.remove((ITypeRoot) element);
307 			break;
308 		default:
309 			this.childrenCache.remove(element);
310 	}
311 }
resetJarTypeCache()312 protected void resetJarTypeCache() {
313 	this.jarTypeCache = new LRUCache<>((int) (DEFAULT_OPENABLE_SIZE * getMemoryRatio() * getJarTypeRatio()));
314 }
removeFromJarTypeCache(BinaryType type)315 protected void removeFromJarTypeCache(BinaryType type) {
316 	this.jarTypeCache.flush(type);
317 }
318 @Override
toString()319 public String toString() {
320 	return toStringFillingRation(""); //$NON-NLS-1$
321 }
toStringFillingRation(String prefix)322 public String toStringFillingRation(String prefix) {
323 	StringBuffer buffer = new StringBuffer();
324 	buffer.append(prefix);
325 	buffer.append("Project cache: "); //$NON-NLS-1$
326 	buffer.append(this.projectCache.size());
327 	buffer.append(" projects\n"); //$NON-NLS-1$
328 	buffer.append(prefix);
329 	buffer.append(this.rootCache.toStringFillingRation("Root cache")); //$NON-NLS-1$
330 	buffer.append('\n');
331 	buffer.append(prefix);
332 	buffer.append(this.pkgCache.toStringFillingRation("Package cache")); //$NON-NLS-1$
333 	buffer.append('\n');
334 	buffer.append(prefix);
335 	buffer.append(this.openableCache.toStringFillingRation("Openable cache")); //$NON-NLS-1$
336 	buffer.append('\n');
337 	buffer.append(prefix);
338 	buffer.append(this.jarTypeCache.toStringFillingRation("Jar type cache")); //$NON-NLS-1$
339 	buffer.append('\n');
340 	return buffer.toString();
341 }
342 }
343