View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.james.mime4j.message;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.james.mime4j.MimeException;
32  import org.apache.james.mime4j.MimeIOException;
33  import org.apache.james.mime4j.parser.AbstractContentHandler;
34  import org.apache.james.mime4j.parser.Field;
35  import org.apache.james.mime4j.parser.MimeStreamParser;
36  
37  /**
38   * The header of an entity (see RFC 2045).
39   */
40  public class Header implements Iterable<Field> {
41  
42      private List<Field> fields = new LinkedList<Field>();
43      private Map<String, List<Field>> fieldMap = new HashMap<String, List<Field>>();
44      
45      /**
46       * Creates a new empty <code>Header</code>.
47       */
48      public Header() {
49      }
50  
51      /**
52       * Creates a new <code>Header</code> from the specified
53       * <code>Header</code>. The <code>Header</code> instance is initialized
54       * with a copy of the list of {@link Field}s of the specified
55       * <code>Header</code>. The <code>Field</code> objects are not copied
56       * because they are immutable and can safely be shared between headers.
57       * 
58       * @param other
59       *            header to copy.
60       */
61      public Header(Header other) {
62          for (Field otherField : other.fields) {
63              addField(otherField);
64          }
65      }
66  
67      /**
68       * Creates a new <code>Header</code> from the specified stream.
69       * 
70       * @param is the stream to read the header from.
71       * 
72       * @throws IOException on I/O errors.
73       * @throws MimeIOException on MIME protocol violations.
74       */
75      public Header(InputStream is) 
76              throws IOException, MimeIOException {
77          final MimeStreamParser parser = new MimeStreamParser();
78          parser.setContentHandler(new AbstractContentHandler() {
79              @Override
80              public void endHeader() {
81                  parser.stop();
82              }
83              @Override
84              public void field(Field field) throws MimeException {
85                  addField(field);
86              }
87          });
88          try {
89              parser.parse(is);
90          } catch (MimeException ex) {
91              throw new MimeIOException(ex);
92          }
93      }
94  
95      /**
96       * Adds a field to the end of the list of fields.
97       * 
98       * @param field the field to add.
99       */
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 }