001package com.randomnoun.common;
002
003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
005 */
006
007import java.util.*;
008
009
010/**
011 * This class is used to convert the Maps and Lists returned by Spring DAO into
012 * camelCase versions, which can be used when dealing with some databases 
013 * that return all column names in upper case. 
014 * 
015 * <p>Maps and Lists passed to this object will be modified in-place.
016 *
017 * <p>This object should be considered thread-safe, i.e. many threads may use a
018 * single instance of this object at a time.
019 *
020 * @author knoxg
021 */
022public class CamelCaser {
023    
024    
025
026    /** Internal map whose keys are ALLUPPERCASE text strings, and values are camelCase versions */
027    private Map<String, String> namingMap;
028
029    /**
030     * Create a new map renamer. The list supplied to this constructor contains
031     * a list of Strings that may be used as keys in the maps to be renamed. Each
032     * String is the camelCase version to return.
033     *
034     * @param nameList
035     */
036    public CamelCaser(List<String> nameList) {
037        this.populateNamingMap(nameList);
038    }
039
040    /**
041     * Creates a new CamelCaser object
042     *
043     * @param camelCaseNames A list of camelcased names, separated by commas.
044     *
045     * @throws NullPointerException if there was a problem loading the resource property list,
046     *   or the propertyKey does not exist in the resource.
047     */
048    public CamelCaser(String camelCaseNames) {
049        List<String> nameList = new ArrayList<String>();
050        StringTokenizer tokenizer = new StringTokenizer(camelCaseNames, ",");
051        String token;
052
053        while (tokenizer.hasMoreElements()) {
054            token = tokenizer.nextToken();
055            nameList.add(token.trim());
056        }
057
058        populateNamingMap(nameList);
059    }
060
061    /** Private method used to create the namingMap object */
062    private void populateNamingMap(List<String> nameList) {
063        // don't croak if we're passed a null list 
064        if (nameList == null) {
065            nameList = new ArrayList<String>(0);
066        }
067
068        namingMap = new HashMap<String, String>();
069
070        String name;
071
072        for (Iterator<String> i = nameList.iterator(); i.hasNext();) {
073            name = (String) i.next();
074            namingMap.put(name.toLowerCase(), name);
075        }
076    }
077
078    /** Renames an individual string. If the camelCase version of the string is not
079     *  known, then it is returned without modification.
080     *
081     * @param string The String to be renamed
082     * @return The camelCase version of the String
083     */
084    public String renameString(String string) {
085        String result = (String) namingMap.get(string.toLowerCase());
086
087        if (result == null) {
088            result = string;
089        }
090
091        return result;
092    }
093
094    /** Renames all the keys in the map. The map may itself contain Maps and Lists,
095     * which will also be renamed.
096     *
097     * @param map The map to rename
098     */
099    @SuppressWarnings("unchecked")
100        public void renameMap(Map<String, Object> map) {
101        Object key;
102        Object value;
103        String newKey;
104
105
106        // we iterator on this object rather than map.keySet().iterator(), 
107        // since we are modifying the keys in the map
108        // as we iterate. 
109        List<String> keyList = new ArrayList<String>(map.keySet());
110
111        for (Iterator<String> i = keyList.iterator(); i.hasNext();) {
112            key = i.next();
113            value = map.get(key);
114
115            if (key instanceof String) {
116                newKey = renameString((String) key);
117                map.remove(key);
118                map.put(newKey, value);
119            }
120
121            // recurse through this structure
122            if (value instanceof Map) {
123                renameMap((Map<String, Object>) value);
124            } else if (value instanceof List) {
125                renameList((List<?>) value);
126            }
127        }
128    }
129
130    /** Renames all the objects in a list. Any Maps or Lists contained with the List
131     * will be recursed into.
132     *
133     * @param list The list containing maps to rename.
134     */
135    @SuppressWarnings("unchecked")
136        public void renameList(List<? extends Object> list) {
137        Object obj;
138
139        for (Iterator<? extends Object> i = list.iterator(); i.hasNext();) {
140            obj = i.next();
141
142            // recurse through this structure
143            if (obj instanceof Map) {
144                renameMap((Map<String, Object>) obj);
145            } else if (obj instanceof List) {
146                renameList((List<?>) obj);
147            }
148        }
149    }
150}