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 }