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