View Javadoc
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--&gt;object, 
60   * where name is the name of a variable 
61   * <li>
62   * {@link #readWithName(String)} yields a singleton map name--&gt;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 }