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.util.Collections; 023 import java.util.LinkedList; 024 import java.util.List; 025 026 import org.apache.james.mime4j.util.ByteSequence; 027 import org.apache.james.mime4j.util.ContentUtil; 028 029 /** 030 * Represents a MIME multipart body (see RFC 2045).A multipart body has a 031 * ordered list of body parts. The multipart body also has a preamble and 032 * epilogue. The preamble consists of whatever characters appear before the 033 * first body part while the epilogue consists of whatever characters come after 034 * the last body part. 035 */ 036 public class Multipart implements Body { 037 038 private List<BodyPart> bodyParts = new LinkedList<BodyPart>(); 039 private Entity parent = null; 040 041 private ByteSequence preamble; 042 private transient String preambleStrCache; 043 private ByteSequence epilogue; 044 private transient String epilogueStrCache; 045 046 private String subType; 047 048 /** 049 * Creates a new empty <code>Multipart</code> instance. 050 */ 051 public Multipart(String subType) { 052 preamble = ByteSequence.EMPTY; 053 preambleStrCache = ""; 054 epilogue = ByteSequence.EMPTY; 055 epilogueStrCache = ""; 056 057 this.subType = subType; 058 } 059 060 /** 061 * Creates a new <code>Multipart</code> from the specified 062 * <code>Multipart</code>. The <code>Multipart</code> instance is 063 * initialized with copies of preamble, epilogue, sub type and the list of 064 * body parts of the specified <code>Multipart</code>. The parent entity 065 * of the new multipart is <code>null</code>. 066 * 067 * @param other 068 * multipart to copy. 069 * @throws UnsupportedOperationException 070 * if <code>other</code> contains a {@link SingleBody} that 071 * does not support the {@link SingleBody#copy() copy()} 072 * operation. 073 * @throws IllegalArgumentException 074 * if <code>other</code> contains a <code>Body</code> that 075 * is neither a {@link Message}, {@link Multipart} or 076 * {@link SingleBody}. 077 */ 078 public Multipart(Multipart other) { 079 preamble = other.preamble; 080 preambleStrCache = other.preambleStrCache; 081 epilogue = other.epilogue; 082 epilogueStrCache = other.epilogueStrCache; 083 084 for (BodyPart otherBodyPart : other.bodyParts) { 085 BodyPart bodyPartCopy = new BodyPart(otherBodyPart); 086 addBodyPart(bodyPartCopy); 087 } 088 089 subType = other.subType; 090 } 091 092 /** 093 * Gets the multipart sub-type. E.g. <code>alternative</code> (the 094 * default) or <code>parallel</code>. See RFC 2045 for common sub-types 095 * and their meaning. 096 * 097 * @return the multipart sub-type. 098 */ 099 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 }