Skip to content

Package: DataHead$ReadMultiStream

DataHead$ReadMultiStream

nameinstructionbranchcomplexitylinemethod
DataHead.ReadMultiStream(DataHead)
M: 0 C: 22
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
adjustInMemoryUsage()
M: 0 C: 1
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
close()
M: 0 C: 9
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
fetch()
M: 0 C: 64
100%
M: 0 C: 12
100%
M: 0 C: 7
100%
M: 0 C: 15
100%
M: 0 C: 1
100%
read()
M: 0 C: 18
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
read(byte[], int, int)
M: 0 C: 29
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 6
100%
M: 0 C: 1
100%

Coverage

1: /*
2: * Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Distribution License v. 1.0, which is available at
6: * http://www.eclipse.org/org/documents/edl-v10.php.
7: *
8: * SPDX-License-Identifier: BSD-3-Clause
9: */
10:
11: package org.jvnet.mimepull;
12:
13: import java.io.*;
14: import java.nio.ByteBuffer;
15:
16: /**
17: * Represents an attachment part in a MIME message. MIME message parsing is done
18: * lazily using a pull parser, so the part may not have all the data. {@link #read}
19: * and {@link #readOnce} may trigger the actual parsing the message. In fact,
20: * parsing of an attachment part may be triggered by calling {@link #read} methods
21: * on some other attachment parts. All this happens behind the scenes so the
22: * application developer need not worry about these details.
23: *
24: * @author Jitendra Kotamraju
25: */
26: final class DataHead {
27:
28: /**
29: * Linked list to keep the part's content
30: */
31: volatile Chunk head, tail;
32:
33: /**
34: * If the part is stored in a file, non-null.
35: */
36: DataFile dataFile;
37:
38: private final MIMEPart part;
39:
40: boolean readOnce;
41: volatile long inMemory;
42:
43: /**
44: * Used only for debugging. This records where readOnce() is called.
45: */
46: private Throwable consumedAt;
47:
48: DataHead(MIMEPart part) {
49: this.part = part;
50: }
51:
52: void addBody(ByteBuffer buf) {
53: synchronized(this) {
54: inMemory += buf.limit();
55: }
56: if (tail != null) {
57: tail = tail.createNext(this, buf);
58: } else {
59: head = tail = new Chunk(new MemoryData(buf, part.msg.config));
60: }
61: }
62:
63: void doneParsing() {
64: }
65:
66: void moveTo(File f) {
67: if (dataFile != null) {
68: dataFile.renameTo(f);
69: } else {
70: try {
71: try (OutputStream os = new FileOutputStream(f)) {
72: InputStream in = readOnce();
73: byte[] buf = new byte[8192];
74: int len;
75: while ((len = in.read(buf)) != -1) {
76: os.write(buf, 0, len);
77: }
78: }
79: } catch(IOException ioe) {
80: throw new MIMEParsingException(ioe);
81: }
82: }
83: }
84:
85: void close() {
86: head = tail = null;
87: if (dataFile != null) {
88: dataFile.close();
89: }
90: }
91:
92:
93: /**
94: * Can get the attachment part's content multiple times. That means
95: * the full content needs to be there in memory or on the file system.
96: * Calling this method would trigger parsing for the part's data. So
97: * do not call this unless it is required(otherwise, just wrap MIMEPart
98: * into a object that returns InputStream for e.g DataHandler)
99: *
100: * @return data for the part's content
101: */
102: public InputStream read() {
103: if (readOnce) {
104: throw new IllegalStateException("readOnce() is called before, read() cannot be called later.");
105: }
106:
107: // Trigger parsing for the part
108: while(tail == null) {
109: if (!part.msg.makeProgress()) {
110: throw new IllegalStateException("No such MIME Part: "+part);
111: }
112: }
113:
114: if (head == null) {
115: throw new IllegalStateException("Already read. Probably readOnce() is called before.");
116: }
117: return new ReadMultiStream();
118: }
119:
120: /**
121: * Used for an assertion. Returns true when readOnce() is not already called.
122: * or otherwise throw an exception.
123: *
124: * <p>
125: * Calling this method also marks the stream as 'consumed'
126: *
127: * @return true if readOnce() is not called before
128: */
129: @SuppressWarnings("ThrowableInitCause")
130: private boolean unconsumed() {
131: if (consumedAt != null) {
132: AssertionError error = new AssertionError("readOnce() is already called before. See the nested exception from where it's called.");
133: error.initCause(consumedAt);
134: throw error;
135: }
136: consumedAt = new Exception().fillInStackTrace();
137: return true;
138: }
139:
140: /**
141: * Can get the attachment part's content only once. The content
142: * will be lost after the method. Content data is not be stored
143: * on the file system or is not kept in the memory for the
144: * following case:
145: * - Attachement parts contents are accessed sequentially
146: *
147: * In general, take advantage of this when the data is used only
148: * once.
149: *
150: * @return data for the part's content
151: */
152: public InputStream readOnce() {
153: assert unconsumed();
154: if (readOnce) {
155: throw new IllegalStateException("readOnce() is called before. It can only be called once.");
156: }
157: readOnce = true;
158: // Trigger parsing for the part
159: while(tail == null) {
160: if (!part.msg.makeProgress() && tail == null) {
161: throw new IllegalStateException("No such Part: "+part);
162: }
163: }
164: InputStream in = new ReadOnceStream();
165: head = null;
166: return in;
167: }
168:
169: class ReadMultiStream extends InputStream {
170: Chunk current;
171: int offset;
172: int len;
173: byte[] buf;
174: boolean closed;
175:
176: public ReadMultiStream() {
177: this.current = head;
178: len = current.data.size();
179: buf = current.data.read();
180: }
181:
182: @Override
183: public int read(byte[] b, int off, int sz) throws IOException {
184:• if (!fetch()) {
185: return -1;
186: }
187:
188: sz = Math.min(sz, len-offset);
189: System.arraycopy(buf,offset,b,off,sz);
190: offset += sz;
191: return sz;
192: }
193:
194: @Override
195: public int read() throws IOException {
196:• if (!fetch()) {
197: return -1;
198: }
199: return (buf[offset++] & 0xff);
200: }
201:
202: void adjustInMemoryUsage() {
203: // Nothing to do in this case.
204: }
205:
206: /**
207: * Gets to the next chunk if we are done with the current one.
208: * @return true if any data available
209: * @throws IOException when i/o error
210: */
211: private boolean fetch() throws IOException {
212:• if (closed) {
213: throw new IOException("Stream already closed");
214: }
215:• if (current == null) {
216: return false;
217: }
218:
219:• while(offset==len) {
220:• while(!part.parsed && current.next == null) {
221: part.msg.makeProgress();
222: }
223: current = current.next;
224:
225:• if (current == null) {
226: return false;
227: }
228: adjustInMemoryUsage();
229: this.offset = 0;
230: this.buf = current.data.read();
231: this.len = current.data.size();
232: }
233: return true;
234: }
235:
236: @Override
237: public void close() throws IOException {
238: super.close();
239: current = null;
240: closed = true;
241: }
242: }
243:
244: final class ReadOnceStream extends ReadMultiStream {
245:
246: @Override
247: void adjustInMemoryUsage() {
248: synchronized(DataHead.this) {
249: inMemory -= current.data.size(); // adjust current memory usage
250: }
251: }
252:
253: }
254:
255:
256: }