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
020package org.apache.james.mime4j.message;
021
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.james.mime4j.dom.Header;
030import org.apache.james.mime4j.stream.Field;
031
032/**
033 * Abstract MIME header.
034 */
035public 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}