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 }