001// Copyright 2007 by Basil Vandegriend.  All rights reserved.
002
003package com.basilv.envgen;
004
005import java.util.HashMap;
006import java.util.Iterator;
007import java.util.Map;
008
009import com.basilv.core.Assert;
010
011import freemarker.template.TemplateHashModel;
012import freemarker.template.TemplateModel;
013import freemarker.template.TemplateScalarModel;
014
015/**
016 * Adapts a Map of property names and values to Freemarker's TemplateModel interface.
017 * Properties of the form "foo.bar" need special handling, as FreeMarker considers a period
018 * to be a dereference operator, and thus first looks up property "foo", and then in the result looks up
019 * property "bar". So we store these properties in the same fashion.
020 * 
021 * This class can represent both a simple string value for a given property, or a map of properties. 
022 * This allows it to store the properties "foo.bar=one" and "foo.bar.baz=two" as follows. The property "foo" 
023 * is mapped to instance #2 of this class. Instance #2 has property "bar", which maps to instance #3 
024 * that holds the string value "one" and holds the property "baz" which maps to instance #4 that just holds
025 * the string value "two". 
026 */
027public class TemplateMapModel implements TemplateHashModel, TemplateScalarModel
028{
029
030        private Map map = new HashMap();
031        private String stringValue;
032
033        private TemplateMapModel() {
034        }
035
036        public TemplateMapModel(Map properties) {
037                Assert.notNull("properties", properties);
038                
039                for (Iterator i = properties.entrySet().iterator(); i.hasNext(); ) {
040                        Map.Entry entry = (Map.Entry) i.next();
041                        String key = (String) entry.getKey();
042                        Assert.notNullOrEmpty("key", key);
043                        put(key, (String)entry.getValue());
044
045                }
046        }
047        
048        public TemplateMapModel getAsTemplateMapModel(String key) {
049                return (TemplateMapModel) map.get(key);
050        }
051        
052        public TemplateModel get(String key) {
053                return getAsTemplateMapModel(key);
054        }
055
056        public boolean isEmpty() {
057                return map.isEmpty();
058        }
059
060        public String getAsString() {
061                return stringValue;
062        }
063        
064        private void put(String key, String value) {
065                Assert.notNullOrEmpty("key", key);
066
067                if (key.indexOf(".") != -1) {
068                        putDereferencingKey(key, value);                                                
069                } else {
070                        putRegularKey(key, value);
071                }
072                
073        }
074
075        private void putDereferencingKey(String key, String value) {
076                // Key is of the form foo.bar, which we want to split into the first 'foo' plus the remainder
077                // The remainder may also contain a period, so this recursively calls put. 
078                String[] parts = key.split("\\.", 2);
079                Assert.isTrue("Key [" + key + "] was not properly split two parts.", parts.length==2);
080                String firstKey = parts[0];
081                String remainingKey = parts[1];
082                
083                TemplateMapModel modelForKey = getModelForKeyCreatingIfNotExists(firstKey);
084                modelForKey.put(remainingKey, value);           
085        }
086
087        private void putRegularKey(String key, String value) {
088                TemplateMapModel modelForKey = getModelForKeyCreatingIfNotExists(key);
089                modelForKey.stringValue = value;
090        }
091
092        private TemplateMapModel getModelForKeyCreatingIfNotExists(String key) {
093                
094                TemplateMapModel modelForKey = (TemplateMapModel) map.get(key);
095                
096                if (modelForKey == null) {
097                        modelForKey = new TemplateMapModel();
098                        map.put(key, modelForKey);
099                }
100                return modelForKey;
101        }
102
103}