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