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 }