Replace method added in Java1.5. Contributed by Oleg Kalnichevski.
[mime4j.git] / src / main / java / org / apache / james / mime4j / AbstractEntity.java
blobb57dc0a93461465c61ec56181968369a279795fa
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 ****************************************************************/
20 package org.apache.james.mime4j;
22 import java.io.IOException;
23 import java.util.BitSet;
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.apache.james.mime4j.util.MessageUtils;
29 /**
30 * Abstract MIME entity.
32 public abstract class AbstractEntity implements EntityStateMachine {
34 protected final Log log;
36 protected final BodyDescriptor parent;
37 protected final int startState;
38 protected final int endState;
39 protected final boolean maximalBodyDescriptor;
40 protected final boolean strictParsing;
41 protected final MutableBodyDescriptor body;
43 protected int state;
45 private final ByteArrayBuffer linebuf;
46 private final CharArrayBuffer fieldbuf;
48 private int lineCount;
49 private String field, fieldName, fieldValue;
50 private boolean endOfHeader;
52 private static final BitSet fieldChars = new BitSet();
54 static {
55 for (int i = 0x21; i <= 0x39; i++) {
56 fieldChars.set(i);
58 for (int i = 0x3b; i <= 0x7e; i++) {
59 fieldChars.set(i);
63 /**
64 * Internal state, not exposed.
66 private static final int T_IN_BODYPART = -2;
67 /**
68 * Internal state, not exposed.
70 private static final int T_IN_MESSAGE = -3;
72 AbstractEntity(
73 BodyDescriptor parent,
74 int startState,
75 int endState,
76 boolean maximalBodyDescriptor,
77 boolean strictParsing) {
78 this.log = LogFactory.getLog(getClass());
79 this.parent = parent;
80 this.state = startState;
81 this.startState = startState;
82 this.endState = endState;
83 this.maximalBodyDescriptor = maximalBodyDescriptor;
84 this.strictParsing = strictParsing;
85 this.body = newBodyDescriptor(parent);
86 this.linebuf = new ByteArrayBuffer(64);
87 this.fieldbuf = new CharArrayBuffer(64);
88 this.lineCount = 0;
89 this.endOfHeader = false;
92 public int getState() {
93 return state;
96 /**
97 * Creates a new instance of {@link BodyDescriptor}. Subclasses may override
98 * this in order to create body descriptors, that provide more specific
99 * information.
101 protected MutableBodyDescriptor newBodyDescriptor(BodyDescriptor pParent) {
102 final MutableBodyDescriptor result;
103 if (maximalBodyDescriptor) {
104 result = new MaximalBodyDescriptor(pParent);
105 } else {
106 result = new DefaultBodyDescriptor(pParent);
108 return result;
111 protected abstract int getLineNumber();
113 protected abstract BufferingInputStream getDataStream();
115 private void fillFieldBuffer() throws IOException, MimeException {
116 if (endOfHeader) {
117 return;
119 BufferingInputStream instream = getDataStream();
120 fieldbuf.clear();
121 for (;;) {
122 // If there's still data stuck in the line buffer
123 // copy it to the field buffer
124 int len = linebuf.length();
125 if (len > 0) {
126 fieldbuf.append(linebuf, 0, len);
128 linebuf.clear();
129 if (instream.readLine(linebuf) == -1) {
130 monitor(Event.HEADERS_PREMATURE_END);
131 endOfHeader = true;
132 break;
134 len = linebuf.length();
135 if (len > 0 && linebuf.byteAt(len - 1) == '\n') {
136 len--;
138 if (len > 0 && linebuf.byteAt(len - 1) == '\r') {
139 len--;
141 if (len == 0) {
142 // empty line detected
143 endOfHeader = true;
144 break;
146 lineCount++;
147 if (lineCount > 1) {
148 int ch = linebuf.byteAt(0);
149 if (ch != MessageUtils.SP && ch != MessageUtils.HT) {
150 // new header detected
151 break;
157 protected boolean parseField() throws IOException {
158 for (;;) {
159 if (endOfHeader) {
160 return false;
162 fillFieldBuffer();
164 // Strip away line delimiter
165 int len = fieldbuf.length();
166 if (len > 0 && fieldbuf.charAt(len - 1) == '\n') {
167 len--;
169 if (len > 0 && fieldbuf.charAt(len - 1) == '\r') {
170 len--;
172 fieldbuf.setLength(len);
174 boolean valid = true;
175 field = fieldbuf.toString();
176 int pos = fieldbuf.indexOf(':');
177 if (pos == -1) {
178 monitor(Event.INALID_HEADER);
179 valid = false;
180 } else {
181 fieldName = fieldbuf.substring(0, pos);
182 for (int i = 0; i < fieldName.length(); i++) {
183 if (!fieldChars.get(fieldName.charAt(i))) {
184 monitor(Event.INALID_HEADER);
185 valid = false;
186 break;
189 fieldValue = fieldbuf.substring(pos + 1, fieldbuf.length());
191 if (valid) {
192 body.addField(fieldName, fieldValue);
193 return true;
199 * <p>Gets a descriptor for the current entity.
200 * This method is valid if {@link #getState()} returns:</p>
201 * <ul>
202 * <li>{@link #T_BODY}</li>
203 * <li>{@link #T_START_MULTIPART}</li>
204 * <li>{@link #T_EPILOGUE}</li>
205 * <li>{@link #T_PREAMBLE}</li>
206 * </ul>
207 * @return <code>BodyDescriptor</code>, not nulls
209 public BodyDescriptor getBodyDescriptor() {
210 switch (getState()) {
211 case EntityStates.T_BODY:
212 case EntityStates.T_START_MULTIPART:
213 case EntityStates.T_PREAMBLE:
214 case EntityStates.T_EPILOGUE:
215 case EntityStates.T_END_OF_STREAM:
216 return body;
217 default:
218 throw new IllegalStateException("Invalid state :" + stateToString(state));
223 * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
224 * @return String with the fields raw contents.
225 * @throws IllegalStateException {@link #getState()} returns another
226 * value than {@link #T_FIELD}.
228 public String getField() {
229 switch (getState()) {
230 case EntityStates.T_FIELD:
231 return field;
232 default:
233 throw new IllegalStateException("Invalid state :" + stateToString(state));
238 * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
239 * @return String with the fields name.
240 * @throws IllegalStateException {@link #getState()} returns another
241 * value than {@link #T_FIELD}.
243 public String getFieldName() {
244 switch (getState()) {
245 case EntityStates.T_FIELD:
246 return fieldName;
247 default:
248 throw new IllegalStateException("Invalid state :" + stateToString(state));
253 * This method is valid, if {@link #getState()} returns {@link #T_FIELD}.
254 * @return String with the fields value.
255 * @throws IllegalStateException {@link #getState()} returns another
256 * value than {@link #T_FIELD}.
258 public String getFieldValue() {
259 switch (getState()) {
260 case EntityStates.T_FIELD:
261 return fieldValue;
262 default:
263 throw new IllegalStateException("Invalid state :" + stateToString(state));
268 * Monitors the given event.
269 * Subclasses may override to perform actions upon events.
270 * Base implementation logs at warn.
271 * @param event <code>Event</code>, not null
272 * @throws MimeException subclasses may elect to throw this exception upon
273 * invalid content
274 * @throws IOException subclasses may elect to throw this exception
276 protected void monitor(Event event) throws MimeException, IOException {
277 if (strictParsing) {
278 throw new MimeParseEventException(event);
279 } else {
280 warn(event);
285 * Creates an indicative message suitable for display
286 * based on the given event and the current state of the system.
287 * @param event <code>Event</code>, not null
288 * @return message suitable for use as a message in an exception
289 * or for logging
291 protected String message(Event event) {
292 String preamble = "Line " + getLineNumber() + ": ";
293 final String message;
294 if (event == null) {
295 message = "Event is unexpectedly null.";
296 } else {
297 message = event.toString();
299 final String result = preamble + message;
300 return result;
304 * Logs (at warn) an indicative message based on the given event
305 * and the current state of the system.
306 * @param event <code>Event</code>, not null
308 protected void warn(Event event) {
309 if (log.isWarnEnabled()) {
310 log.warn(message(event));
315 * Logs (at debug) an indicative message based on the given event
316 * and the current state of the system.
317 * @param event <code>Event</code>, not null
319 protected void debug(Event event) {
320 if (log.isDebugEnabled()) {
321 log.debug(message(event));
325 public String toString() {
326 return getClass().getName() + " [" + stateToString(state)
327 + "][" + body.getMimeType() + "][" + body.getBoundary() + "]";
331 * Renders a state as a string suitable for logging.
332 * @param state
333 * @return rendered as string, not null
335 public static final String stateToString(int state) {
336 final String result;
337 switch (state) {
338 case EntityStates.T_END_OF_STREAM:
339 result = "End of stream";
340 break;
341 case EntityStates.T_START_MESSAGE:
342 result = "Start message";
343 break;
344 case EntityStates.T_END_MESSAGE:
345 result = "End message";
346 break;
347 case EntityStates.T_RAW_ENTITY:
348 result = "Raw entity";
349 break;
350 case EntityStates.T_START_HEADER:
351 result = "Start header";
352 break;
353 case EntityStates.T_FIELD:
354 result = "Field";
355 break;
356 case EntityStates.T_END_HEADER:
357 result = "End header";
358 break;
359 case EntityStates.T_START_MULTIPART:
360 result = "Start multipart";
361 break;
362 case EntityStates.T_END_MULTIPART:
363 result = "End multipart";
364 break;
365 case EntityStates.T_PREAMBLE:
366 result = "Premable";
367 break;
368 case EntityStates.T_EPILOGUE:
369 result = "Epilogue";
370 break;
371 case EntityStates.T_START_BODYPART:
372 result = "Start bodypart";
373 break;
374 case EntityStates.T_END_BODYPART:
375 result = "End bodypart";
376 break;
377 case EntityStates.T_BODY:
378 result = "Body";
379 break;
380 case T_IN_BODYPART:
381 result = "Bodypart";
382 break;
383 case T_IN_MESSAGE:
384 result = "In message";
385 break;
386 default:
387 result = "Unknown";
388 break;
390 return result;