001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.mime4j.util;
021    
022    import java.io.Serializable;
023    import java.util.ArrayList;
024    import java.util.Collection;
025    import java.util.Collections;
026    import java.util.Enumeration;
027    import java.util.HashMap;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.NoSuchElementException;
031    
032    import org.apache.james.mime4j.message.Header;
033    import org.apache.james.mime4j.parser.ContentHandler;
034    import org.apache.james.mime4j.parser.Field;
035    
036    /**
037     * An object, which may be used to implement header, or parameter
038     * maps. The maps keys are the header or parameter names. The
039     * maps values are strings (single value), lists, or arrays.
040     * <p>
041     * Note that this class is not directly used anywhere in Mime4j.
042     * Instead a user might choose to use it instead of {@link Header}
043     * and {@link Field} in a custom {@link ContentHandler} implementation.
044     * See also MIME4j-24.
045     */
046    public class StringArrayMap implements Serializable {
047        private static final long serialVersionUID = -5833051164281786907L;
048        private final Map<String, Object> map = new HashMap<String, Object>();
049    
050        /**
051         * <p>Converts the given object into a string. The object may be either of:
052         * <ul>
053         *   <li>a string, which is returned without conversion</li>
054         *   <li>a list of strings, in which case the first element is returned</li>
055         *   <li>an array of strings, in which case the first element is returned</li>
056         * </ul>
057         */
058        public static String asString(Object pValue) {
059            if (pValue == null) {
060                return null;
061            }
062            if (pValue instanceof String) {
063                return (String) pValue;
064            }
065            if (pValue instanceof String[]) {
066                return ((String[]) pValue)[0];
067            }
068            if (pValue instanceof List) {
069                return (String) ((List<?>) pValue).get(0);
070            }
071            throw new IllegalStateException("Invalid parameter class: " + pValue.getClass().getName());
072        }
073    
074        /**
075         * <p>Converts the given object into a string array. The object may be either of:
076         * <ul>
077         *   <li>a string, which is returned as an array with one element</li>
078         *   <li>a list of strings, which is being converted into a string array</li>
079         *   <li>an array of strings, which is returned without conversion</li>
080         * </ul>
081         */
082        public static String[] asStringArray(Object pValue) {
083            if (pValue == null) {
084                return null;
085            }
086            if (pValue instanceof String) {
087                return new String[]{(String) pValue};
088            }
089            if (pValue instanceof String[]) {
090                return (String[]) pValue;
091            }
092            if (pValue instanceof List) {
093                final List<?> l = (List<?>) pValue;
094                return l.toArray(new String[l.size()]);
095            }
096            throw new IllegalStateException("Invalid parameter class: " + pValue.getClass().getName());
097        }
098    
099        /**
100         * <p>Converts the given object into a string enumeration. The object may be either of:
101         * <ul>
102         *   <li>a string, which is returned as an enumeration with one element</li>
103         *   <li>a list of strings, which is being converted into a string enumeration</li>
104         *   <li>an array of strings, which is being converted into a string enumeration</li>
105         * </ul>
106         */
107        public static Enumeration<String> asStringEnum(final Object pValue) {
108            if (pValue == null) {
109                return null;
110            }
111            if (pValue instanceof String) {
112                return new Enumeration<String>(){
113                    private Object value = pValue;
114                    public boolean hasMoreElements() {
115                        return value != null;
116                    }
117                    public String nextElement() {
118                        if (value == null) {
119                            throw new NoSuchElementException();
120                        }
121                        final String s = (String) value;
122                        value = null;
123                        return s;
124                    }
125                };
126            }
127            if (pValue instanceof String[]) {
128                final String[] values = (String[]) pValue;
129                return new Enumeration<String>() {
130                    private int offset;
131                    public boolean hasMoreElements() {
132                        return offset < values.length;
133                    }
134                    public String nextElement() {
135                        if (offset >= values.length) {
136                            throw new NoSuchElementException();
137                        }
138                        return values[offset++];
139                    }
140                };
141            }
142            if (pValue instanceof List) {
143                @SuppressWarnings("unchecked")
144                final List<String> stringList = (List<String>) pValue; 
145                return Collections.enumeration(stringList);
146            }
147            throw new IllegalStateException("Invalid parameter class: " + pValue.getClass().getName());
148        }
149    
150        /**
151         * Converts the given map into a string array map: The map values
152         * are string arrays.
153         */
154        public static Map<String, String[]> asMap(final Map<String, Object> pMap) {
155            Map<String, String[]> result = new HashMap<String, String[]>(pMap.size());
156            for (Map.Entry<String, Object> entry : pMap.entrySet()) {
157                final String[] value = asStringArray(entry.getValue());
158                result.put(entry.getKey(), value);
159            }
160            return Collections.unmodifiableMap(result);
161        }
162    
163        /**
164         * Adds a value to the given map.
165         */
166        protected void addMapValue(Map<String, Object> pMap, String pName, String pValue) {
167            Object o = pMap.get(pName);
168            if (o == null) {
169                o = pValue;
170            } else if (o instanceof String) {
171                final List<Object> list = new ArrayList<Object>();
172                list.add(o);
173                list.add(pValue);
174                o = list;
175            } else if (o instanceof List) {
176                @SuppressWarnings("unchecked")
177                final List<String> stringList = (List<String>) o; 
178                stringList.add(pValue);
179            } else if (o instanceof String[]) {
180                final List<String> list = new ArrayList<String>();
181                final String[] arr = (String[]) o;
182                for (String str : arr) {
183                    list.add(str);
184                }
185                list.add(pValue);
186                o = list;
187            } else {
188                throw new IllegalStateException("Invalid object type: " + o.getClass().getName());
189            }
190            pMap.put(pName, o);
191        }
192    
193        /**
194         * Lower cases the given name.
195         */
196        protected String convertName(String pName) {
197            return pName.toLowerCase();
198        }
199    
200        /**
201         * Returns the requested value.
202         */
203        public String getValue(String pName) {
204            return asString(map.get(convertName(pName)));
205        }
206    
207        /**
208         * Returns the requested values as a string array.
209         */
210        public String[] getValues(String pName) {
211            return asStringArray(map.get(convertName(pName)));
212        }
213    
214        /**
215         * Returns the requested values as an enumeration.
216         */
217        public Enumeration<String> getValueEnum(String pName) {
218            return asStringEnum(map.get(convertName(pName)));
219        }
220    
221        /**
222         * Returns the set of registered names as an enumeration.
223         * @see #getNameArray()
224         */
225        public Enumeration<String> getNames() {
226            return Collections.enumeration(map.keySet());
227        }
228    
229        /**
230         * Returns an unmodifiable map of name/value pairs. The map keys
231         * are the lower cased parameter/header names. The map values are
232         * string arrays.
233         */
234        public Map<String, String[]> getMap() {
235            return asMap(map);
236        }
237    
238        /**
239         * Adds a new name/value pair.
240         */
241        public void addValue(String pName, String pValue) {
242            addMapValue(map, convertName(pName), pValue);
243        }
244    
245        /**
246         * Returns the set of registered names.
247         * @see #getNames()
248         */
249        public String[] getNameArray() {
250            final Collection<String> c = map.keySet();
251            return c.toArray(new String[c.size()]);
252        }
253    }