1 /*
2 * Copyright 2007, 2008, 2009, 2012 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.io;
17
18 import java.io.BufferedReader;
19 import java.io.IOException;
20 import java.io.StringReader;
21 import java.io.StringWriter;
22 import java.io.Writer;
23 import java.util.Collections;
24 import java.util.Map;
25
26 import eu.simuline.octave.exception.OctaveClassCastException;
27 import eu.simuline.octave.exception.OctaveIOException;
28 import eu.simuline.octave.exception.OctaveParseException;
29 import eu.simuline.octave.exec.OctaveExec;
30 import eu.simuline.octave.exec.ReaderWriteFunctor;
31 import eu.simuline.octave.exec.WriteFunctor;
32 import eu.simuline.octave.exec.WriterReadFunctor;
33 import eu.simuline.octave.io.spi.OctaveDataReader;
34 import eu.simuline.octave.io.spi.OctaveDataWriter;
35 import eu.simuline.octave.type.OctaveObject;
36
37 // ER: Has only static methods or methods based on {@link #octaveExec}
38 /**
39 * The object controlling IO of Octave data of {@link #octaveExec}.
40 * The basic operations are to
41 * <ul>
42 * <li>
43 * set a map of variable names to their values
44 * via {@link #set(Map)} (no setting of a single value),
45 * <li>
46 * get the value for a variable name via {@link #get(String)},
47 * <li>
48 * check whether a variable with a given name exists
49 * via {@link #existsVar(String)}.
50 * </ul>
51 * The rest are static utility methods.
52 * Part is for reading objects from a reader:
53 * <ul>
54 * <li>
55 * {@link #readerReadLine(BufferedReader)} reads a line
56 * <li>
57 * {@link #read(BufferedReader)} reads an object
58 * <li>
59 * {@link #readWithName(BufferedReader)} yields a singleton map name-->object,
60 * where name is the name of a variable
61 * <li>
62 * {@link #readWithName(String)} yields a singleton map name-->object,
63 * as above but reading from a string.
64 * </ul>
65 * Part is for writing:
66 * <ul>
67 * <li>
68 * {@link #write(Writer, OctaveObject)}
69 write(Writer, String, OctaveObject)
70
71 toText(String, OctaveObject)
72 toText(OctaveObject)
73 * </ul>
74 */
75 public final class OctaveIO {
76
77 private static final String GLOBAL = "global ";
78 private static final String TYPE = "# type: ";
79 private static final String NAME = "# name: ";
80
81 private final OctaveExec octaveExec;
82
83 /**
84 * @param octaveExec
85 */
86 public OctaveIO(final OctaveExec octaveExec) {
87 this.octaveExec = octaveExec;
88 }
89
90 /**
91 * Sets the map <code>values</code>
92 * mapping variable names to according values.
93 *
94 * @param values
95 */
96 public void set(final Map<String, OctaveObject> values) {
97 final StringWriter outputWriter = new StringWriter();
98 this.octaveExec.evalRW(new DataWriteFunctor(values),
99 new WriterReadFunctor(outputWriter));
100
101 final String output = outputWriter.toString();
102 if (output.length() != 0) {
103 throw new IllegalStateException
104 ("Unexpected output: '" + output + "'");
105 }
106 }
107
108 /**
109 * Gets the value of the variable <code>name</code>
110 * or null if this variable does not exist
111 * according to {@link #existsVar(String)}.
112 *
113 * @param name
114 * the name of a variable
115 * @return
116 * the value of the variable <code>name</code> from octave
117 * or <code>null</code> if the variable does not exist.
118 * @throws OctaveClassCastException
119 * if the value can not be cast to T
120 */
121 public OctaveObject get(final String name) {
122 if (!existsVar(name)) {
123 return null;
124 }
125 final WriteFunctor writeFunctor =
126 new ReaderWriteFunctor(new StringReader("save -text - " + name));
127 final DataReadFunctor readFunctor = new DataReadFunctor(name);
128 this.octaveExec.evalRW(writeFunctor, readFunctor);
129 return readFunctor.getData();
130 }
131
132 /**
133 * Returns whether the variable <code>name</code> exists.
134 *
135 * @param name
136 * the name of a variable
137 * @return
138 * whether the variable <code>name</code> exists.
139 */
140 private boolean existsVar(final String name) {
141 StringReader checkCmd = new StringReader
142 ("printf('%d', exist('" + name + "','var'));");
143 final StringWriter existResult = new StringWriter();
144 this.octaveExec.evalRW(new ReaderWriteFunctor(checkCmd),
145 new WriterReadFunctor(existResult));
146 final String s = existResult.toString();
147 // switch (s) {
148 // case "1":
149 // return true;
150 // case "0":
151 // return false;
152 // default:
153 // throw new OctaveParseException("Unexpected output '" + s + "'");
154 // }
155
156 if ("1".equals(s)) {
157 return true;
158 } else if ("0".equals(s)) {
159 return false;
160 } else {
161 throw new OctaveParseException("Unexpected output '" + s + "'");
162 }
163 }
164
165 /**
166 * Reads a line from <code>reader</code> into a string if possible.
167 * Returns null at the end of the stream and throws an exception
168 * in case of io problems.
169 *
170 * @param reader
171 * the reader to read a line from.
172 * @return
173 * next line from <code>reader</code>, <code>null</code> at end of stream
174 * @throws OctaveIOException
175 * in case of IOException reading from <code>reader</code>.
176 */
177 public static String readerReadLine(final BufferedReader reader) {
178 try {
179 return reader.readLine();
180 } catch (IOException e) {
181 throw new OctaveIOException(e);
182 }
183 }
184
185 /**
186 * Read a single object from Reader.
187 * The first line read determines the type of object
188 * and the rest of reading is delegated to the OctaveDataReader
189 * associated with that type given by
190 * {@link OctaveDataReader#getOctaveDataReader(String)}.
191 *
192 * @param reader
193 * a reader starting with first line
194 * <code>{@link #TYPE}[global ]type</code>,
195 * i.e. <code>global </code> is optional
196 * and type is the type of the object to be read.
197 * @return
198 * OctaveObject read from Reader
199 * @throws OctaveParseException **** appropriate type?
200 * if the type read before is not registered
201 * and so there is no appropriate reader.
202 */
203 public static OctaveObject read(final BufferedReader reader) {
204 // may throw OctaveIOException
205 final String line = OctaveIO.readerReadLine(reader);
206 // line == null at end of stream
207 if (line == null || !line.startsWith(TYPE)) {
208 throw new OctaveParseException
209 ("Expected '" + TYPE + "' got '" + line + "'");
210 }
211 String typeGlobal = line.substring(TYPE.length());
212 // Ignore "global " prefix to type (it is not really a type)
213 String type = typeGlobal.startsWith(GLOBAL)
214 ? typeGlobal.substring(GLOBAL.length())
215 : typeGlobal;
216 final OctaveDataReader dataReader =
217 OctaveDataReader.getOctaveDataReader(type);
218 if (dataReader == null) {
219 throw new OctaveParseException
220 ("Unknown octave type, type='" + type + "'");
221 }
222 return dataReader.read(reader);
223 }
224
225 /**
226 * Read a single variable - object pair from Reader.
227 * The variable is given by its name.
228 *
229 * @param reader
230 * a reader starting with first line <code>{@link #NAME}name</code>,
231 * where name is the name of the variable.
232 * the following lines represent the object stored in that variable.
233 * @return
234 * a singleton map with the name of a variable and object stored therein.
235 */
236 // used in DataReadFunctor.doReads(Reader) only and tests.
237 public static
238 Map<String, OctaveObject> readWithName(final BufferedReader reader) {
239
240 // read name from the first line
241 // may throw OctaveIOException
242 final String line = OctaveIO.readerReadLine(reader);
243 if (!line.startsWith(NAME)) {
244 throw new OctaveParseException
245 ("Expected '" + NAME + "', but got '" + line + "'");
246 }
247 final String name = line.substring(NAME.length());
248 // read value and put into singleton map
249 return Collections.singletonMap(name, read(reader));
250 }
251
252 /**
253 * Read a single object from String,
254 * it is an error if there is data left after the object.
255 *
256 * @param input
257 *
258 * @return
259 * a singleton map with the name and object
260 * @throws OctaveParseException
261 * if there is data left after the object is read
262 */
263 public static Map<String, OctaveObject> readWithName(final String input) {
264 final BufferedReader bufferedReader =
265 new BufferedReader(new StringReader(input));
266 // may throw OctaveIOException
267 final Map<String, OctaveObject> map = readWithName(bufferedReader);
268 try {
269 final String line = bufferedReader.readLine();
270 if (line != null) {
271 throw new OctaveParseException
272 ("Too much data in input, first extra line is '" +
273 line + "'");
274 }
275 } catch (IOException e) {
276 throw new OctaveIOException(e);
277 }
278 return map;
279 }
280
281 // newly introduced, not yet used.
282 /**
283 * Writes a line given by <code>strWithNl</code> to <code>writer</code>
284 * if possible.
285 *
286 * @param writer
287 * @param strWithNl
288 * @throws OctaveIOException
289 * in case of IOException writing to <code>writer</code>.
290 */
291 public static void writerWriteLine(Writer writer, String strWithNl) {
292 try {
293 writer.write(strWithNl);
294 } catch (IOException e) {
295 throw new OctaveIOException(e);
296 }
297 }
298
299 /**
300 * ER:
301 * Writes the {@link OctaveObject} <code>octaveType</code> (****bad name)
302 * to the writer <code>writer</code>.
303 * To that end, fetch an {@link OctaveDataWriter}
304 * of the appropriate type given by <code>octaveType</code>
305 * and use this writer to write <code>octaveType</code>
306 * onto <code>writer</code>.
307
308 * @param <T>
309 * the type of {@link OctaveObject} to be written.
310 * @param writer
311 * the writer to write the object <code>octValue</code> onto.
312 * @param octValue
313 * the object to write to <code>writer</code>.
314 * @throws OctaveParseException **** appropriate type?
315 * if the type of <code>octValue</code> is not registered
316 * and so there is no appropriate writer.
317 * @throws IOException
318 * if the process of writing fails.
319 */
320 public static <T extends OctaveObject> void write(final Writer writer,
321 final T octValue)
322 throws IOException {
323 final OctaveDataWriter<T> dataWriter =
324 OctaveDataWriter.getOctaveDataWriter(octValue);
325 if (dataWriter == null) {
326 throw new OctaveParseException
327 ("No writer for java type " + octValue.getClass() + ". ");
328 }
329 // may throw IOException
330 dataWriter.write(writer, octValue);
331 }
332
333 /**
334 * ER:
335 * Writes the name <code>name</code>
336 * and the {@link OctaveObject} <code>octValue</code>
337 * to the writer <code>writer</code>
338 * using {@link #write(Writer, OctaveObject)}.
339
340 * @param writer
341 * the writer to write the object <code>octaveType</code> onto.
342 * @param name
343 * the name, **** of a variable
344 * @param octValue
345 * the object to write to <code>writer</code>.
346 * @throws OctaveParseException **** appropriate type?
347 * if the type of <code>octaveType</code> is not registered
348 * and so there is no appropriate writer.
349 * @throws IOException
350 * if the process of writing fails.
351 */
352 public static void write(final Writer writer,
353 final String name,
354 final OctaveObject octValue) throws IOException {
355 // may throw IOException
356 writer.write("# name: " + name + "\n"); // just a comment??? ****
357 // may throw IOException, OctaveParseException
358 write(writer, octValue);
359 }
360
361 /**
362 * Returns as a string how the variable <code>name</code>
363 * and the {@link OctaveObject} <code>octaveType</code> (****bad name)
364 * are written.
365 *
366 * @param name
367 * the name, **** of a variable
368 * @param octValue
369 * the object to write to <code>writer</code>.
370 * @return
371 * The result from saving the value octaveType
372 * in octave -text format
373 */
374 // seems to be used only locally and in tests
375 public static String toText(final String name,
376 final OctaveObject octValue) {
377 try {
378 final Writer writer = new StringWriter();
379 write(writer, name, octValue);
380 return writer.toString();
381 } catch (final IOException e) {
382 throw new OctaveIOException(e);
383 }
384 }
385
386 /**
387 * Returns as a string how the {@link OctaveObject} <code>octaveType</code>
388 * (****bad name) is written without variable,
389 * i.e. with variable <code>"ans"</code>.
390 *
391 * @param octValue
392 * @return toText("ans", octValue)
393 */
394 public static String toText(final OctaveObject octValue) {
395 return toText("ans", octValue);
396 }
397
398 }