1 /*
2 * Copyright 2007, 2008, 2009 Ange Optimization ApS
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package eu.simuline.octave.exec;
17
18 import java.io.BufferedReader;
19 import java.io.IOException;
20 import java.io.Reader;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24
25 import eu.simuline.octave.util.StringUtil;
26
27 /**
28 * Reader that passes the reading on to the output from the octave process
29 * until the spacer reached, then it returns EOF.
30 * When this reader is closed
31 * the underlying reader is slurped up to the spacer.
32 */
33 final class OctaveExecuteReader extends Reader {
34
35 private static final Log LOG = LogFactory.getLog(OctaveExecuteReader.class);
36
37 /**
38 * The wrapped reader.
39 */
40 private final BufferedReader octaveReader;
41
42 /**
43 * The string signifying end of stream.
44 */
45 private final String spacer;
46
47 /**
48 * The current line read from {@link #octaveReader}
49 * but not yet passed to a char-array by {@link #read(char[], int, int)}.
50 * If this buffer were empty, it is <code>null</code> instead,
51 * which is also the initial value.
52 * If this is not the first line, the line read from {@link #octaveReader}
53 * is preceeded by newline before being passed to {@link #buffer}.
54 */
55 private StringBuffer buffer = null;
56
57 /**
58 * Whether reading the first line.
59 * Initially, this is true.
60 * It is set to false, by {@link #read(char[], int, int)}
61 * if {@link #buffer} is filled for the first time.
62 */
63 private boolean firstLine = true;
64
65 /**
66 * Whether end of reader found.
67 * Initially, this is false.
68 * It is set to false, by {@link #read(char[], int, int)}
69 * if {@link #buffer} equals {@link #spacer},
70 * not really end of {@link #octaveReader}.
71 */
72 private boolean eof = false;
73
74 /**
75 * This reader will read from <code>octaveReader</code>
76 * until a single line equal() <code>spacer</code> is read,
77 * after that this reader will return eof.
78 * When this reader is closed it will update the state of octave to NONE.
79 *
80 * @param octaveReader
81 * the wrapped reader
82 * @param spacer
83 * the line signifying end of stream.
84 */
85 OctaveExecuteReader(final BufferedReader octaveReader,
86 final String spacer) {
87 this.octaveReader = octaveReader;
88 this.spacer = spacer;
89 }
90
91 /**
92 * Reads characters into a portion of an array.
93 * This method will block until some input is available,
94 * an I/O error occurs, or the end of the stream is reached.
95 *
96 * @param cbuf
97 * Destination buffer
98 * @param off
99 * Offset at which to start storing characters.
100 * @param len
101 * Maximum number of characters to read.
102 * @return
103 * The number of characters read,
104 * or -1 if the end of the stream has been reached.
105 * The latter is the case if {@link #eof} is set
106 * which means that line {@link #spacer} has been found.
107 * @throws IOException
108 * If an I/O error occurs.
109 * This is true in particular,
110 * if null-line has been read from {@link #octaveReader}.
111 */
112 @Override
113 public int read(final char[] cbuf, final int off, final int len)
114 throws IOException {
115 if (this.eof) {
116 return -1;
117 }
118 if (this.buffer == null) {
119 final String line = this.octaveReader.readLine();
120 if (LOG.isTraceEnabled()) {
121 LOG.trace("octaveReader.readLine() = " +
122 StringUtil.jQuote(line));
123 }
124 if (line == null) {
125 throw new IOException("Pipe to octave-process broken");
126 }
127 if (this.spacer.equals(line)) {
128 this.eof = true;
129 return -1;
130 }
131
132 // line possibly preceeded by \n
133 this.buffer = new StringBuffer(line.length() + 1);
134 if (this.firstLine) {
135 this.firstLine = false;
136 } else {
137 this.buffer.append('\n');
138 }
139 this.buffer.append(line);
140 }
141 assert this.buffer != null;
142
143 final int charsRead = Math.min(this.buffer.length(), len);
144 this.buffer.getChars(0, charsRead, cbuf, off);
145 if (charsRead == buffer.length()) {
146 this.buffer = null;
147 } else {
148 this.buffer.delete(0, charsRead);
149 }
150 return charsRead;
151 }
152
153 @Override
154 @SuppressWarnings({"checkstyle:magicnumber", "checkstyle:emptyblock"})
155 // length of buffer is immaterial for function
156 public void close() throws IOException {
157 final char[] buffer1 = new char[4096];
158 // Slurp the rest of the wrapped input
159 while (read(buffer1) != -1) { // NOPMD
160 // Do nothing
161 }
162 if (this.octaveReader.ready()) {
163 throw new IOException("octaveReader is ready()");
164 }
165 LOG.debug("Reader closed()");
166 }
167
168 }