Coverage Report - eu.simuline.octave.io.OctaveIO
 
Classes in this File Line Coverage Branch Coverage Complexity
OctaveIO
76%
56/73
72%
16/22
3.385
 
 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  106
     public OctaveIO(final OctaveExec octaveExec) {
 87  106
         this.octaveExec = octaveExec;
 88  106
     }
 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  504
         final StringWriter outputWriter = new StringWriter();
 98  504
         this.octaveExec.evalRW(new DataWriteFunctor(values),
 99  
                                new WriterReadFunctor(outputWriter));
 100  
         
 101  500
         final String output = outputWriter.toString();
 102  500
         if (output.length() != 0) {
 103  0
             throw new IllegalStateException
 104  
                 ("Unexpected output: '" + output + "'");
 105  
         }
 106  500
     }
 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  600
         if (!existsVar(name)) {
 123  420
             return null;
 124  
         }
 125  180
         final WriteFunctor writeFunctor = 
 126  
             new ReaderWriteFunctor(new StringReader("save -text - " + name));
 127  180
         final DataReadFunctor readFunctor = new DataReadFunctor(name);
 128  180
         this.octaveExec.evalRW(writeFunctor, readFunctor);
 129  176
         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  600
         StringReader checkCmd = new StringReader
 142  
             ("printf('%d', exist('" + name + "','var'));");
 143  600
         final StringWriter existResult = new StringWriter();
 144  600
         this.octaveExec.evalRW(new ReaderWriteFunctor(checkCmd),
 145  
                                new WriterReadFunctor(existResult));
 146  600
         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  600
         if ("1".equals(s)) {
 157  180
             return true;
 158  420
         } else if ("0".equals(s)) {
 159  420
             return false;
 160  
         } else {
 161  0
             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  12500
             return reader.readLine();
 180  0
         } catch (IOException e) {
 181  0
             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  752
         final String line = OctaveIO.readerReadLine(reader);
 206  
         // line == null at end of stream 
 207  752
         if (line == null || !line.startsWith(TYPE)) {
 208  0
             throw new OctaveParseException
 209  
                 ("Expected '" + TYPE + "' got '" + line + "'");
 210  
         }
 211  752
         String typeGlobal = line.substring(TYPE.length());
 212  
         // Ignore "global " prefix to type (it is not really a type)
 213  752
         String type = typeGlobal.startsWith(GLOBAL)
 214  2
             ? typeGlobal.substring(GLOBAL.length())
 215  
             : typeGlobal;
 216  752
         final OctaveDataReader dataReader = 
 217  752
             OctaveDataReader.getOctaveDataReader(type);
 218  752
         if (dataReader == null) {
 219  2
             throw new OctaveParseException
 220  
                 ("Unknown octave type, type='" + type + "'");
 221  
         }
 222  750
         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  192
         final String line = OctaveIO.readerReadLine(reader);
 243  192
         if (!line.startsWith(NAME)) {
 244  0
             throw new OctaveParseException
 245  
                 ("Expected '" + NAME + "', but got '" + line + "'");
 246  
         }
 247  192
         final String name = line.substring(NAME.length());
 248  
         // read value and put into singleton map 
 249  192
         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  12
         final BufferedReader bufferedReader = 
 265  
             new BufferedReader(new StringReader(input));
 266  
         // may throw OctaveIOException 
 267  12
         final Map<String, OctaveObject> map = readWithName(bufferedReader);
 268  
         try {
 269  12
             final String line = bufferedReader.readLine();
 270  12
             if (line != null) {
 271  2
                 throw new OctaveParseException
 272  
                     ("Too much data in input, first extra line is '" + 
 273  
                      line + "'");
 274  
             }
 275  0
         } catch (IOException e) {
 276  0
             throw new OctaveIOException(e);
 277  10
         }
 278  10
         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  0
             writer.write(strWithNl);
 294  0
         } catch (IOException e) {
 295  0
             throw new OctaveIOException(e);
 296  0
         }
 297  0
     }
 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  1002
         final OctaveDataWriter<T> dataWriter = 
 324  1002
             OctaveDataWriter.getOctaveDataWriter(octValue);
 325  1002
         if (dataWriter == null) {
 326  0
             throw new OctaveParseException
 327  0
                 ("No writer for java type " + octValue.getClass() + ". ");
 328  
         }
 329  
         // may throw IOException 
 330  1002
         dataWriter.write(writer, octValue);
 331  1002
     }
 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  1002
         writer.write("# name: " + name + "\n"); // just a comment??? **** 
 357  
         // may throw IOException, OctaveParseException
 358  1002
         write(writer, octValue);
 359  1002
     }
 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  130
             final Writer writer = new StringWriter();
 379  130
             write(writer, name, octValue);
 380  130
             return writer.toString();
 381  0
         } catch (final IOException e) {
 382  0
             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  34
         return toText("ans", octValue);
 396  
     }
 397  
 
 398  
 }