001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.turbo;
042:
043: import java.util.*;
044:
045: /**
046: * Keeps entity key => attribute,value pairs without actually
047: * holding key's reference. It supports several size strategies:
048: * <ul>
049: * <li>minimal entry count (for caches with short lived keys),
050: * <li>maximum entry count (for caches with long lived keys) and
051: * <li>driven by key lifetime
052: * </ul>
053: * <p>
054: * It's synchronized for safety because at least
055: * <code>TrackingRef.run</code> comes from private OpenAPI
056: * thread.
057: *
058: * @author Petr Kuzel
059: */
060: final class Memory {
061:
062: /** Defines minimum entries count limit. */
063: private final int minimumSize;
064:
065: /** Defines maximum entries count limit. */
066: private final int maximumSize;
067:
068: /** Limited size Map<key, Map>. It defines minimal cache size. */
069: private final Map minimalMap;
070:
071: /** Unbound Map<key, Map<attributeName, attributeValue>>. Key identifies live entity. */
072: private final Map liveEntitiesMap = new WeakHashMap(3571);
073:
074: /** Special value, known null that does not invalidate but caches. */
075: public static final Object NULL = new Object();
076:
077: /** Keep reference to last isPrepared result. */
078: public static final ThreadLocal prepared = new ThreadLocal();
079:
080: private final Statistics statistics;
081:
082: private static final Random random = new Random(0);
083:
084: /**
085: * Creates memory map with given strategy.
086: * @param minSize minimum size
087: * @param maxSize maximum size or <code>-1</code> for unbound size
088: * defined by key instance lifetime
089: */
090: public Memory(Statistics statistics, int minSize, int maxSize) {
091: if (maxSize != -1) {
092: if (maxSize < minSize || maxSize < 1) {
093: throw new IllegalArgumentException();
094: }
095: }
096: minimumSize = minSize;
097: maximumSize = maxSize;
098: minimalMap = new LRU(minimumSize);
099: this .statistics = statistics;
100: }
101:
102: /**
103: * Makes best efford to store file object attributes obtainable by next {@link #get}.
104: * @param value updated value, <code>null</code> for removing memory entry
105: * or <code>Memory.NULL</code> for storing <code>null</code> value (it's known that
106: * value does not exist) later returned by {@link #get}.
107: */
108: public synchronized void put(Object key, String name, Object value) {
109:
110: // find values map
111:
112: Map attributes;
113: if (liveEntitiesMap.containsKey(key)) {
114: attributes = (Map) liveEntitiesMap.get(key);
115: } else {
116: attributes = (Map) minimalMap.get(key);
117: if (attributes == null) {
118: attributes = new HashMap(5);
119: }
120: }
121:
122: // update it
123:
124: if (value != null) {
125: attributes.put(name, normalizeValue(value));
126: } else {
127: attributes.remove(name);
128: }
129: putLive(key, attributes);
130: minimalMap.put(key, attributes);
131: Entry entry = (Entry) prepared.get();
132: if (entry != null) {
133: if (key.equals(entry.key) && name.equals(entry.name)) {
134: if (value != null) {
135: entry.value = normalizeValue(value);
136: } else {
137: prepared.set(null);
138: }
139: }
140: }
141: }
142:
143: private void putLive(Object key, Map attributes) {
144:
145: // enforce maximumSize strategy by randomly removing
146: // several (7) entries on reaching the max
147: if (maximumSize != -1 && liveEntitiesMap.size() >= maximumSize) {
148: Set keySet = liveEntitiesMap.keySet();
149: List l = new ArrayList(liveEntitiesMap.keySet());
150:
151: // liveEntitiesMap is a weakhashmap so l.size() should be the real size
152: if (l.size() == maximumSize) {
153: int limit = Math.min(7, maximumSize / 10);
154: for (int i = 0; i < limit; i++) {
155: int index = random.nextInt(maximumSize);
156: Object removed = l.get(index);
157: if (keySet.remove(removed)) {
158: statistics.keyRemoved(removed);
159: }
160: }
161: }
162:
163: }
164:
165: liveEntitiesMap.put(key, attributes);
166: statistics.keyAdded(key);
167: }
168:
169: private static Object normalizeValue(Object value) {
170: if (value == NULL)
171: return null;
172: return value;
173: }
174:
175: /**
176: * Looks for cached file atribute of given name.
177: * Return stored attributes or <code>null</code>.
178: */
179: public synchronized Object get(Object key, String name) {
180:
181: Map attributes = (Map) liveEntitiesMap.get(key);
182: if (attributes != null) {
183: return attributes.get(name);
184: }
185:
186: // try the minimal map
187: attributes = (Map) minimalMap.get(key);
188: if (attributes != null) {
189: putLive(key, attributes);
190: return attributes.get(name);
191: }
192:
193: // have not been promised by existsEntry but eliminated by GC?
194: Entry entry = (Entry) prepared.get();
195: if (entry != null) {
196: if (key.equals(entry.key) && name.equals(entry.name)) {
197: prepared.set(null); // here ends our promised contract
198: return entry.value;
199: }
200: }
201:
202: return null;
203: }
204:
205: /**
206: * Determines if given attribute has a cache entry.
207: * Note that the entry can contain info that attribute
208: * does not exist!
209: */
210: public synchronized boolean existsEntry(Object key, String name) {
211: Map attributes = (Map) liveEntitiesMap.get(key);
212: if (attributes == null) {
213: attributes = (Map) minimalMap.get(key);
214: }
215:
216: // keep promised value in tread local to survive paralell GC
217: boolean isPrepared = attributes != null
218: && attributes.keySet().contains(name);
219: if (isPrepared) {
220: Entry entry = (Entry) prepared.get();
221: if (entry == null) {
222: entry = new Entry();
223: }
224: entry.key = key;
225: entry.name = name;
226: entry.value = attributes.get(name);
227: prepared.set(entry);
228: } else {
229: statistics.computeRemoved(liveEntitiesMap.keySet());
230: prepared.set(null);
231: }
232: return isPrepared;
233: }
234:
235: public synchronized Object getMonitoredKey(Object key) {
236: Set keySet = liveEntitiesMap.keySet();
237: if (keySet.contains(key)) {
238: Iterator it = keySet.iterator();
239: while (it.hasNext()) {
240: Object next = it.next();
241: if (key.equals(next)) {
242: return next;
243: }
244: }
245: }
246: return null;
247: }
248:
249: /** Single entry structure. */
250: private class Entry {
251: private Object key;
252: private String name;
253: private Object value;
254: }
255:
256: /** Limited size LRU map implementation. */
257: private final static class LRU extends LinkedHashMap {
258:
259: private final int maxSize;
260:
261: public LRU(int maxSize) {
262: super (maxSize * 2, 0.5f, true);
263: this .maxSize = maxSize;
264: }
265:
266: protected boolean removeEldestEntry(Map.Entry eldest) {
267: return size() > maxSize;
268: }
269: }
270:
271: }
|