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-->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 }