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.util.Collections;
23 import java.util.LinkedList;
24 import java.util.List;
25
26 import org.apache.james.mime4j.util.ByteSequence;
27 import org.apache.james.mime4j.util.ContentUtil;
28
29 /**
30 * Represents a MIME multipart body (see RFC 2045).A multipart body has a
31 * ordered list of body parts. The multipart body also has a preamble and
32 * epilogue. The preamble consists of whatever characters appear before the
33 * first body part while the epilogue consists of whatever characters come after
34 * the last body part.
35 */
36 public class Multipart implements Body {
37
38 private List<BodyPart> bodyParts = new LinkedList<BodyPart>();
39 private Entity parent = null;
40
41 private ByteSequence preamble;
42 private transient String preambleStrCache;
43 private ByteSequence epilogue;
44 private transient String epilogueStrCache;
45
46 private String subType;
47
48 /**
49 * Creates a new empty <code>Multipart</code> instance.
50 */
51 public Multipart(String subType) {
52 preamble = ByteSequence.EMPTY;
53 preambleStrCache = "";
54 epilogue = ByteSequence.EMPTY;
55 epilogueStrCache = "";
56
57 this.subType = subType;
58 }
59
60 /**
61 * Creates a new <code>Multipart</code> from the specified
62 * <code>Multipart</code>. The <code>Multipart</code> instance is
63 * initialized with copies of preamble, epilogue, sub type and the list of
64 * body parts of the specified <code>Multipart</code>. The parent entity
65 * of the new multipart is <code>null</code>.
66 *
67 * @param other
68 * multipart to copy.
69 * @throws UnsupportedOperationException
70 * if <code>other</code> contains a {@link SingleBody} that
71 * does not support the {@link SingleBody#copy() copy()}
72 * operation.
73 * @throws IllegalArgumentException
74 * if <code>other</code> contains a <code>Body</code> that
75 * is neither a {@link Message}, {@link Multipart} or
76 * {@link SingleBody}.
77 */
78 public Multipart(Multipart other) {
79 preamble = other.preamble;
80 preambleStrCache = other.preambleStrCache;
81 epilogue = other.epilogue;
82 epilogueStrCache = other.epilogueStrCache;
83
84 for (BodyPart otherBodyPart : other.bodyParts) {
85 BodyPart bodyPartCopy = new BodyPart(otherBodyPart);
86 addBodyPart(bodyPartCopy);
87 }
88
89 subType = other.subType;
90 }
91
92 /**
93 * Gets the multipart sub-type. E.g. <code>alternative</code> (the
94 * default) or <code>parallel</code>. See RFC 2045 for common sub-types
95 * and their meaning.
96 *
97 * @return the multipart sub-type.
98 */
99 public String getSubType() {
100 return subType;
101 }
102
103 /**
104 * Sets the multipart sub-type. E.g. <code>alternative</code> or
105 * <code>parallel</code>. See RFC 2045 for common sub-types and their
106 * meaning.
107 *
108 * @param subType
109 * the sub-type.
110 */
111 public void setSubType(String subType) {
112 this.subType = subType;
113 }
114
115 /**
116 * @see org.apache.james.mime4j.message.Body#getParent()
117 */
118 public Entity getParent() {
119 return parent;
120 }
121
122 /**
123 * @see org.apache.james.mime4j.message.Body#setParent(org.apache.james.mime4j.message.Entity)
124 */
125 public void setParent(Entity parent) {
126 this.parent = parent;
127 for (BodyPart bodyPart : bodyParts) {
128 bodyPart.setParent(parent);
129 }
130 }
131
132 /**
133 * Returns the number of body parts.
134 *
135 * @return number of <code>BodyPart</code> objects.
136 */
137 public int getCount() {
138 return bodyParts.size();
139 }
140
141 /**
142 * Gets the list of body parts. The list is immutable.
143 *
144 * @return the list of <code>BodyPart</code> objects.
145 */
146 public List<BodyPart> getBodyParts() {
147 return Collections.unmodifiableList(bodyParts);
148 }
149
150 /**
151 * Sets the list of body parts.
152 *
153 * @param bodyParts
154 * the new list of <code>BodyPart</code> objects.
155 */
156 public void setBodyParts(List<BodyPart> bodyParts) {
157 this.bodyParts = bodyParts;
158 for (BodyPart bodyPart : bodyParts) {
159 bodyPart.setParent(parent);
160 }
161 }
162
163 /**
164 * Adds a body part to the end of the list of body parts.
165 *
166 * @param bodyPart
167 * the body part.
168 */
169 public void addBodyPart(BodyPart bodyPart) {
170 if (bodyPart == null)
171 throw new IllegalArgumentException();
172
173 bodyParts.add(bodyPart);
174 bodyPart.setParent(parent);
175 }
176
177 /**
178 * Inserts a body part at the specified position in the list of body parts.
179 *
180 * @param bodyPart
181 * the body part.
182 * @param index
183 * index at which the specified body part is to be inserted.
184 * @throws IndexOutOfBoundsException
185 * if the index is out of range (index < 0 || index >
186 * getCount()).
187 */
188 public void addBodyPart(BodyPart bodyPart, int index) {
189 if (bodyPart == null)
190 throw new IllegalArgumentException();
191
192 bodyParts.add(index, bodyPart);
193 bodyPart.setParent(parent);
194 }
195
196 /**
197 * Removes the body part at the specified position in the list of body
198 * parts.
199 *
200 * @param index
201 * index of the body part to be removed.
202 * @return the removed body part.
203 * @throws IndexOutOfBoundsException
204 * if the index is out of range (index < 0 || index >=
205 * getCount()).
206 */
207 public BodyPart removeBodyPart(int index) {
208 BodyPart bodyPart = bodyParts.remove(index);
209 bodyPart.setParent(null);
210 return bodyPart;
211 }
212
213 /**
214 * Replaces the body part at the specified position in the list of body
215 * parts with the specified body part.
216 *
217 * @param bodyPart
218 * body part to be stored at the specified position.
219 * @param index
220 * index of body part to replace.
221 * @return the replaced body part.
222 * @throws IndexOutOfBoundsException
223 * if the index is out of range (index < 0 || index >=
224 * getCount()).
225 */
226 public BodyPart replaceBodyPart(BodyPart bodyPart, int index) {
227 if (bodyPart == null)
228 throw new IllegalArgumentException();
229
230 BodyPart replacedBodyPart = bodyParts.set(index, bodyPart);
231 if (bodyPart == replacedBodyPart)
232 throw new IllegalArgumentException(
233 "Cannot replace body part with itself");
234
235 bodyPart.setParent(parent);
236 replacedBodyPart.setParent(null);
237
238 return replacedBodyPart;
239 }
240
241 // package private for now; might become public someday
242 ByteSequence getPreambleRaw() {
243 return preamble;
244 }
245
246 void setPreambleRaw(ByteSequence preamble) {
247 this.preamble = preamble;
248 this.preambleStrCache = null;
249 }
250
251 /**
252 * Gets the preamble.
253 *
254 * @return the preamble.
255 */
256 public String getPreamble() {
257 if (preambleStrCache == null) {
258 preambleStrCache = ContentUtil.decode(preamble);
259 }
260 return preambleStrCache;
261 }
262
263 /**
264 * Sets the preamble.
265 *
266 * @param preamble
267 * the preamble.
268 */
269 public void setPreamble(String preamble) {
270 this.preamble = ContentUtil.encode(preamble);
271 this.preambleStrCache = preamble;
272 }
273
274 // package private for now; might become public someday
275 ByteSequence getEpilogueRaw() {
276 return epilogue;
277 }
278
279 void setEpilogueRaw(ByteSequence epilogue) {
280 this.epilogue = epilogue;
281 this.epilogueStrCache = null;
282 }
283
284 /**
285 * Gets the epilogue.
286 *
287 * @return the epilogue.
288 */
289 public String getEpilogue() {
290 if (epilogueStrCache == null) {
291 epilogueStrCache = ContentUtil.decode(epilogue);
292 }
293 return epilogueStrCache;
294 }
295
296 /**
297 * Sets the epilogue.
298 *
299 * @param epilogue
300 * the epilogue.
301 */
302 public void setEpilogue(String epilogue) {
303 this.epilogue = ContentUtil.encode(epilogue);
304 this.epilogueStrCache = epilogue;
305 }
306
307 /**
308 * Disposes of the BodyParts of this Multipart. Note that the dispose call
309 * does not get forwarded to the parent entity of this Multipart.
310 *
311 * @see org.apache.james.mime4j.message.Disposable#dispose()
312 */
313 public void dispose() {
314 for (BodyPart bodyPart : bodyParts) {
315 bodyPart.dispose();
316 }
317 }
318
319 }