001: /*
002: * Copyright 2002-2005 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.util;
018:
019: import java.io.Serializable;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.Map;
024: import java.util.Set;
025: import java.util.WeakHashMap;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: /**
031: * A simple decorator for a Map, encapsulating the workflow for caching
032: * expensive values in a target Map. Supports caching weak or strong keys.
033: *
034: * <p>This class is also an abstract template. Caching Map implementations
035: * should subclass and override the <code>create(key)</code> method which
036: * encapsulates expensive creation of a new object.
037: *
038: * @author Keith Donald
039: * @since 1.2.2
040: */
041: public abstract class CachingMapDecorator implements Map, Serializable {
042:
043: private static Object NULL_VALUE = new Object();
044:
045: private static final Log logger = LogFactory
046: .getLog(CachingMapDecorator.class);
047:
048: private final Map targetMap;
049:
050: /**
051: * Create a CachingMapDecorator with strong keys,
052: * using an underlying synchronized Map.
053: */
054: public CachingMapDecorator() {
055: this (false);
056: }
057:
058: /**
059: * Create a CachingMapDecorator,
060: * using an underlying synchronized Map.
061: * @param weakKeys whether to use weak references for keys
062: */
063: public CachingMapDecorator(boolean weakKeys) {
064: Map internalMap = weakKeys ? (Map) new WeakHashMap()
065: : new HashMap();
066: this .targetMap = Collections.synchronizedMap(internalMap);
067: }
068:
069: /**
070: * Create a CachingMapDecorator with initial size,
071: * using an underlying synchronized Map.
072: * @param weakKeys whether to use weak references for keys
073: * @param size the initial cache size
074: */
075: public CachingMapDecorator(boolean weakKeys, int size) {
076: Map internalMap = weakKeys ? (Map) new WeakHashMap(size)
077: : new HashMap(size);
078: this .targetMap = Collections.synchronizedMap(internalMap);
079: }
080:
081: /**
082: * Create a CachingMapDecorator for the given Map.
083: * <p>The passed-in Map won't get synchronized explicitly,
084: * so make sure to pass in a properly synchronized Map, if desired.
085: * @param targetMap the Map to decorate
086: */
087: public CachingMapDecorator(Map targetMap) {
088: Assert.notNull(targetMap, "Target Map is required");
089: this .targetMap = targetMap;
090: }
091:
092: public int size() {
093: return this .targetMap.size();
094: }
095:
096: public boolean isEmpty() {
097: return this .targetMap.isEmpty();
098: }
099:
100: public boolean containsKey(Object key) {
101: return this .targetMap.containsKey(key);
102: }
103:
104: public boolean containsValue(Object value) {
105: return this .targetMap.containsValue(value);
106: }
107:
108: public Object put(Object key, Object value) {
109: return this .targetMap.put(key, value);
110: }
111:
112: public Object remove(Object key) {
113: return this .targetMap.remove(key);
114: }
115:
116: public void putAll(Map t) {
117: this .targetMap.putAll(t);
118: }
119:
120: public void clear() {
121: this .targetMap.clear();
122: }
123:
124: public Set keySet() {
125: return this .targetMap.keySet();
126: }
127:
128: public Collection values() {
129: return this .targetMap.values();
130: }
131:
132: public Set entrySet() {
133: return this .targetMap.entrySet();
134: }
135:
136: /**
137: * Get value for key.
138: * Creates and caches value if it doesn't already exist in the cache.
139: * <p>This implementation is <i>not</i> synchronized: This is highly
140: * concurrent but does not guarantee unique instances in the cache,
141: * as multiple values for the same key could get created in parallel.
142: * Consider overriding this method to synchronize it, if desired.
143: * @see #create(Object)
144: */
145: public Object get(Object key) {
146: Object value = this .targetMap.get(key);
147: if (value == null) {
148: if (logger.isDebugEnabled()) {
149: logger.debug("Creating new expensive value for key '"
150: + key + "'");
151: }
152: value = create(key);
153: if (value == null) {
154: value = NULL_VALUE;
155: }
156: if (logger.isDebugEnabled()) {
157: logger.debug("Caching expensive value: " + value);
158: }
159: put(key, value);
160: } else {
161: if (logger.isDebugEnabled()) {
162: logger.debug("For key '" + key
163: + "', returning cached value: " + value);
164: }
165: }
166: return (value == NULL_VALUE) ? null : value;
167: }
168:
169: /**
170: * Create a value to cache for the given key.
171: * Called by <code>get</code> if there is no value cached already.
172: * @param key the cache key
173: * @see #get(Object)
174: */
175: protected abstract Object create(Object key);
176:
177: public String toString() {
178: return "CachingMapDecorator [" + getClass().getName() + "]:"
179: + this.targetMap;
180: }
181:
182: }
|