001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.template;
054:
055: import java.io.Serializable;
056: import java.util.Collections;
057: import java.util.ConcurrentModificationException;
058: import java.util.HashMap;
059: import java.util.Iterator;
060: import java.util.Map;
061: import java.util.SortedMap;
062: import java.util.TreeMap;
063:
064: import freemarker.ext.beans.BeansWrapper;
065:
066: /**
067: * <p>A simple implementation of the <tt>TemplateHashModelEx</tt>
068: * interface, using an underlying {@link Map} or {@link SortedMap}.</p>
069: *
070: * <p>This class is thread-safe if you don't call the <tt>put</tt> or <tt>remove</tt> methods
071: * after you have made the object available for multiple threads.
072: *
073: * <p><b>Note:</b><br />
074: * As of 2.0, this class is unsynchronized by default.
075: * To obtain a synchronized wrapper, call the {@link #synchronizedWrapper} method.</p>
076: *
077: * @version $Id: SimpleHash.java,v 1.72.2.2 2006/02/26 18:26:18 revusky Exp $
078: * @see SimpleSequence
079: * @see SimpleScalar
080: */
081: public class SimpleHash extends WrappingTemplateModel implements
082: TemplateHashModelEx, Serializable {
083:
084: private Map map;
085: private boolean putFailed;
086: private Map unwrappedMap;
087:
088: /**
089: * Constructs an empty hash that uses the default wrapper set in
090: * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}.
091: */
092: public SimpleHash() {
093: this ((ObjectWrapper) null);
094: }
095:
096: /**
097: * Creates a new simple hash with the copy of the underlying map and the
098: * default wrapper set in
099: * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}.
100: * @param map The Map to use for the key/value pairs. It makes a copy for
101: * internal use. If the map implements the {@link SortedMap} interface, the
102: * internal copy will be a {@link TreeMap}, otherwise it will be a
103: * {@link HashMap}.
104: */
105: public SimpleHash(Map map) {
106: this (map, null);
107: }
108:
109: /**
110: * Creates an empty simple hash using the specified object wrapper.
111: * @param wrapper The object wrapper to use to wrap objects into
112: * {@link TemplateModel} instances. If null, the default wrapper set in
113: * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)} is
114: * used.
115: */
116: public SimpleHash(ObjectWrapper wrapper) {
117: super (wrapper);
118: map = new HashMap();
119: }
120:
121: /**
122: * Creates a new simple hash with the copy of the underlying map and
123: * either the default wrapper set in
124: * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}, or
125: * the {@link freemarker.ext.beans.BeansWrapper JavaBeans wrapper}.
126: * @param map The Map to use for the key/value pairs. It makes a copy for
127: * internal use. If the map implements the {@link SortedMap} interface, the
128: * internal copy will be a {@link TreeMap}, otherwise it will be a
129: * @param wrapper The object wrapper to use to wrap objects into
130: * {@link TemplateModel} instances. If null, the default wrapper set in
131: * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)} is
132: * used.
133: */
134: public SimpleHash(Map map, ObjectWrapper wrapper) {
135: super (wrapper);
136: try {
137: this .map = copyMap(map);
138: } catch (ConcurrentModificationException cme) {
139: //This will occur extremely rarely.
140: //If it does, we just wait 5 ms and try again. If
141: // the ConcurrentModificationException
142: // is thrown again, we just let it bubble up this time.
143: // TODO: Maybe we should log here.
144: try {
145: Thread.sleep(5);
146: } catch (InterruptedException ie) {
147: }
148: synchronized (map) {
149: this .map = copyMap(map);
150: }
151: }
152: }
153:
154: protected Map copyMap(Map map) {
155: if (map instanceof HashMap) {
156: return (Map) ((HashMap) map).clone();
157: }
158: if (map instanceof SortedMap) {
159: if (map instanceof TreeMap) {
160: return (Map) ((TreeMap) map).clone();
161: } else {
162: return new TreeMap((SortedMap) map);
163: }
164: }
165: return new HashMap(map);
166: }
167:
168: /**
169: * Adds a key-value entry to the map.
170: *
171: * @param key the name by which the object is
172: * identified in the template.
173: * @param obj the object to store.
174: */
175: public void put(String key, Object obj) {
176: map.put(key, obj);
177: unwrappedMap = null;
178: }
179:
180: /**
181: * Puts a boolean in the map
182: *
183: * @param key the name by which the resulting <tt>TemplateModel</tt>
184: * is identified in the template.
185: * @param b the boolean to store.
186: */
187: public void put(String key, boolean b) {
188: put(key, b ? TemplateBooleanModel.TRUE
189: : TemplateBooleanModel.FALSE);
190: }
191:
192: public TemplateModel get(String key) throws TemplateModelException {
193: Object result = map.get(key);
194: if (result == null && key.length() == 1) {
195: // just check for Character key if this is a single-character string
196: try {
197: result = map.get(new Character(key.charAt(0)));
198: } catch (Exception e) {
199: }
200: }
201: if (result instanceof TemplateModel) {
202: return (TemplateModel) result;
203: }
204: TemplateModel tm = wrap(result);
205: if (!putFailed)
206: try {
207: map.put(key, tm);
208: } catch (Exception e) {
209: // If it's immutable or something, we just keep going.
210: putFailed = true;
211: }
212: return tm;
213: }
214:
215: /**
216: * Removes the given key from the underlying map.
217: *
218: * @param key the key to be removed
219: */
220: public void remove(String key) {
221: map.remove(key);
222: }
223:
224: /**
225: * Adds all the key/value entries in the map
226: * @param m the map with the entries to add, the keys are assumed to be strings.
227: */
228:
229: public void putAll(Map m) {
230: for (Iterator it = m.entrySet().iterator(); it.hasNext();) {
231: Map.Entry entry = (Map.Entry) it.next();
232: this .put((String) entry.getKey(), entry.getValue());
233: }
234: }
235:
236: /**
237: * Note that this method creates and returns a deep-copy of the underlying hash used
238: * internally. This could be a gotcha for some people
239: * at some point who want to alter something in the data model,
240: * but we should maintain our immutability semantics (at least using default SimpleXXX wrappers)
241: * for the data model. It will recursively unwrap the stuff in the underlying container.
242: */
243: public Map toMap() throws TemplateModelException {
244: if (unwrappedMap == null) {
245: Class mapClass = this .map.getClass();
246: Map m = null;
247: try {
248: m = (Map) mapClass.newInstance();
249: } catch (Exception e) {
250: throw new TemplateModelException(
251: "Error instantiating map of type "
252: + mapClass.getName() + "\n"
253: + e.getMessage());
254: }
255: // Create a copy to maintain immutability semantics and
256: // Do nested unwrapping of elements if necessary.
257: BeansWrapper bw = BeansWrapper.getDefaultInstance();
258: for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
259: Map.Entry entry = (Map.Entry) it.next();
260: Object key = entry.getKey();
261: Object value = entry.getValue();
262: if (value instanceof TemplateModel) {
263: value = bw.unwrap((TemplateModel) value);
264: }
265: m.put(key, value);
266: }
267: unwrappedMap = m;
268: }
269: return unwrappedMap;
270: }
271:
272: /**
273: * Convenience method for returning the <tt>String</tt> value of the
274: * underlying map.
275: */
276: public String toString() {
277: return map.toString();
278: }
279:
280: public int size() {
281: return map.size();
282: }
283:
284: public boolean isEmpty() {
285: return map == null || map.isEmpty();
286: }
287:
288: public TemplateCollectionModel keys() {
289: return new SimpleCollection(map.keySet(), getObjectWrapper());
290: }
291:
292: public TemplateCollectionModel values() {
293: return new SimpleCollection(map.values(), getObjectWrapper());
294: }
295:
296: public SimpleHash synchronizedWrapper() {
297: return new SynchronizedHash();
298: }
299:
300: private class SynchronizedHash extends SimpleHash {
301:
302: public boolean isEmpty() {
303: synchronized (SimpleHash.this ) {
304: return SimpleHash.this .isEmpty();
305: }
306: }
307:
308: public void put(String key, Object obj) {
309: synchronized (SimpleHash.this ) {
310: SimpleHash.this .put(key, obj);
311: }
312: }
313:
314: public TemplateModel get(String key)
315: throws TemplateModelException {
316: synchronized (SimpleHash.this ) {
317: return SimpleHash.this .get(key);
318: }
319: }
320:
321: public void remove(String key) {
322: synchronized (SimpleHash.this ) {
323: SimpleHash.this .remove(key);
324: }
325: }
326:
327: public int size() {
328: synchronized (SimpleHash.this ) {
329: return SimpleHash.this .size();
330: }
331: }
332:
333: public TemplateCollectionModel keys() {
334: synchronized (SimpleHash.this ) {
335: return SimpleHash.this .keys();
336: }
337: }
338:
339: public TemplateCollectionModel values() {
340: synchronized (SimpleHash.this ) {
341: return SimpleHash.this .values();
342: }
343: }
344:
345: public Map toMap() throws TemplateModelException {
346: synchronized (SimpleHash.this) {
347: return SimpleHash.this.toMap();
348: }
349: }
350:
351: }
352: }
|