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.message;
021    
022    import java.util.Collections;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.LinkedList;
026    import java.util.List;
027    import java.util.Map;
028    
029    import org.apache.james.mime4j.dom.Header;
030    import org.apache.james.mime4j.stream.Field;
031    
032    /**
033     * Abstract MIME header.
034     */
035    public abstract class AbstractHeader implements Header {
036    
037        private List<Field> fields = new LinkedList<Field>();
038        private Map<String, List<Field>> fieldMap = new HashMap<String, List<Field>>();
039    
040        /**
041         * Creates a new empty <code>Header</code>.
042         */
043        public AbstractHeader() {
044        }
045    
046        /**
047         * Creates a new <code>Header</code> from the specified
048         * <code>Header</code>. The <code>Header</code> instance is initialized
049         * with a copy of the list of {@link Field}s of the specified
050         * <code>Header</code>. The <code>Field</code> objects are not copied
051         * because they are immutable and can safely be shared between headers.
052         *
053         * @param other
054         *            header to copy.
055         */
056        public AbstractHeader(Header other) {
057            for (Field otherField : other.getFields()) {
058                addField(otherField);
059            }
060        }
061    
062        /**
063         * Adds a field to the end of the list of fields.
064         *
065         * @param field the field to add.
066         */
067        public void addField(Field field) {
068            List<Field> values = fieldMap.get(field.getName().toLowerCase());
069            if (values == null) {
070                values = new LinkedList<Field>();
071                fieldMap.put(field.getName().toLowerCase(), values);
072            }
073            values.add(field);
074            fields.add(field);
075        }
076    
077        /**
078         * Gets the fields of this header. The returned list will not be
079         * modifiable.
080         *
081         * @return the list of <code>Field</code> objects.
082         */
083        public List<Field> getFields() {
084            return Collections.unmodifiableList(fields);
085        }
086    
087        /**
088         * Gets a <code>Field</code> given a field name. If there are multiple
089         * such fields defined in this header the first one will be returned.
090         *
091         * @param name the field name (e.g. From, Subject).
092         * @return the field or <code>null</code> if none found.
093         */
094        public Field getField(String name) {
095            List<Field> l = fieldMap.get(name.toLowerCase());
096            if (l != null && !l.isEmpty()) {
097                return l.get(0);
098            }
099            return null;
100        }
101    
102        /**
103         * Gets all <code>Field</code>s having the specified field name.
104         *
105         * @param name the field name (e.g. From, Subject).
106         * @return the list of fields.
107         */
108        public List<Field> getFields(final String name) {
109            final String lowerCaseName = name.toLowerCase();
110            final List<Field> l = fieldMap.get(lowerCaseName);
111            final List<Field> results;
112            if (l == null || l.isEmpty()) {
113                results = Collections.emptyList();
114            } else {
115                results = Collections.unmodifiableList(l);
116            }
117            return results;
118        }
119    
120        /**
121         * Returns an iterator over the list of fields of this header.
122         *
123         * @return an iterator.
124         */
125        public Iterator<Field> iterator() {
126            return Collections.unmodifiableList(fields).iterator();
127        }
128    
129        /**
130         * Removes all <code>Field</code>s having the specified field name.
131         *
132         * @param name
133         *            the field name (e.g. From, Subject).
134         * @return number of fields removed.
135         */
136        public int removeFields(String name) {
137            final String lowerCaseName = name.toLowerCase();
138            List<Field> removed = fieldMap.remove(lowerCaseName);
139            if (removed == null || removed.isEmpty())
140                return 0;
141    
142            for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext();) {
143                Field field = iterator.next();
144                if (field.getName().equalsIgnoreCase(name))
145                    iterator.remove();
146            }
147    
148            return removed.size();
149        }
150    
151        /**
152         * Sets or replaces a field. This method is useful for header fields such as
153         * Subject or Message-ID that should not occur more than once in a message.
154         *
155         * If this <code>Header</code> does not already contain a header field of
156         * the same name as the given field then it is added to the end of the list
157         * of fields (same behavior as {@link #addField(Field)}). Otherwise the
158         * first occurrence of a field with the same name is replaced by the given
159         * field and all further occurrences are removed.
160         *
161         * @param field the field to set.
162         */
163        public void setField(Field field) {
164            final String lowerCaseName = field.getName().toLowerCase();
165            List<Field> l = fieldMap.get(lowerCaseName);
166            if (l == null || l.isEmpty()) {
167                addField(field);
168                return;
169            }
170    
171            l.clear();
172            l.add(field);
173    
174            int firstOccurrence = -1;
175            int index = 0;
176            for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext(); index++) {
177                Field f = iterator.next();
178                if (f.getName().equalsIgnoreCase(field.getName())) {
179                    iterator.remove();
180    
181                    if (firstOccurrence == -1)
182                        firstOccurrence = index;
183                }
184            }
185    
186            fields.add(firstOccurrence, field);
187        }
188    
189        /**
190         * Return Header Object as String representation. Each headerline is
191         * seperated by "\r\n"
192         *
193         * @return headers
194         */
195        @Override
196        public String toString() {
197            StringBuilder str = new StringBuilder(128);
198            for (Field field : fields) {
199                str.append(field.toString());
200                str.append("\r\n");
201            }
202            return str.toString();
203        }
204    
205    }