/* Copyright 2009-2015 David Hadka
*
* This file is part of the MOEA Framework.
*
* The MOEA Framework is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* The MOEA Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the MOEA Framework. If not, see <http://www.gnu.org/licenses/>.
*/
package org.moeaframework.analysis.sensitivity;
import static org.moeaframework.analysis.sensitivity.ResultFileWriter.ENCODING_WARNING;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Properties;
import org.apache.commons.codec.binary.Base64;
import org.moeaframework.core.FrameworkException;
import org.moeaframework.core.NondominatedPopulation;
import org.moeaframework.core.Problem;
import org.moeaframework.core.Solution;
import org.moeaframework.core.Variable;
import org.moeaframework.core.variable.BinaryVariable;
import org.moeaframework.core.variable.Permutation;
import org.moeaframework.core.variable.RealVariable;
/**
* Reads result files created by {@link ResultFileWriter}. See the documentation
* for {@code ResultWriter} for a description of the file format.
* <p>
* This reader is expected to gracefully recover from incomplete or improperly
* formatted files. Unless a serious I/O error occurred, this reader will
* attempt to load the file to the last valid entry. This requirement enables a
* {@code ResultWriter} to resume processing at a valid state.
*
* @see ResultFileWriter
*/
public class ResultFileReader implements Closeable, Iterator<ResultEntry>,
Iterable<ResultEntry> {
/**
* The internal stream for reading data from the file.
*/
private final BufferedReader reader;
/**
* The last line read from the internal stream.
*/
private String line;
/**
* The problem.
*/
private final Problem problem;
/**
* The next entry to be returned; or {@code null} if the next entry has not
* yet been read.
*/
private ResultEntry nextEntry;
/**
* {@code true} if an error occurred parsing the result file; {@code false}
* otherwise.
*/
private boolean error;
/**
* {@code true} if the warning for unsupported decision variables was
* displayed; {@code false} otherwise.
*/
private boolean printedWarning;
/**
* Constructs a result file reader for reading the approximation sets from
* the specified result file.
*
* @param problem the problem
* @param file the file containing the results
* @throws IOException if an I/O error occurred
*/
public ResultFileReader(Problem problem, File file) throws IOException {
super();
this.problem = problem;
reader = new BufferedReader(new FileReader(file));
// prime the reader by reading the first line
line = reader.readLine();
}
@Override
public void close() throws IOException {
reader.close();
}
@Override
public ResultEntry next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
ResultEntry result = nextEntry;
nextEntry = null;
return result;
}
@Override
public Iterator<ResultEntry> iterator() {
return this;
}
/**
* Returns the next population in the file; or {@code null} if the end of
* the file is reached. If the last entry in the file is incomplete,
* {@code null} is returned.
*
* @return the next population in the file; or {@code null} if the end of
* the file is reached
* @throws NumberFormatException if an error occurred parsing the objectives
* @throws IOException if an I/O error occurred
*/
private ResultEntry readNextEntry() throws NumberFormatException,
IOException {
NondominatedPopulation population = new NondominatedPopulation();
StringWriter stringBuffer = new StringWriter();
// ignore any comment lines separating entries
while ((line != null) && line.startsWith("#")) {
line = reader.readLine();
}
// read next entry, terminated by #
while ((line != null) && !line.startsWith("#")) {
if (line.startsWith("//")) {
stringBuffer.write(line.substring(2));
stringBuffer.write('\n');
} else {
Solution solution = parseSolution(line);
if (solution == null) {
System.err.println("unable to parse solution, ignoring remaining entries in the file");
return null;
} else {
population.add(solution);
}
}
line = reader.readLine();
}
Properties properties = new Properties();
properties.load(new StringReader(stringBuffer.toString()));
// return population only if non-empty and terminated by a #
if ((line == null) || !line.startsWith("#")) {
return null;
} else {
return new ResultEntry(population, properties);
}
}
/**
* Parses the solution encoded in the specified line from the result file.
*
* @param line the line containing the encoded solution
* @return the solution
*/
private Solution parseSolution(String line) {
String[] entries = line.trim().split("\\s+");
Solution solution = null;
if (entries.length < problem.getNumberOfObjectives()) {
error = true;
return null;
}
try {
if (entries.length == (problem.getNumberOfVariables() +
problem.getNumberOfObjectives())) {
solution = problem.newSolution();
// read decision variables
for (int i = 0; i < problem.getNumberOfVariables(); i++) {
solution.setVariable(i, decode(solution.getVariable(i),
entries[i]));
}
} else {
solution = new Solution(0, problem.getNumberOfObjectives());
}
// read objectives
for (int i = 0; i < problem.getNumberOfObjectives(); i++) {
solution.setObjective(i, Double.parseDouble(
entries[entries.length -
problem.getNumberOfObjectives() + i]));
}
} catch (Exception e) {
e.printStackTrace();
error = true;
return null;
}
return solution;
}
@Override
public boolean hasNext() {
try {
if (error) {
return false;
}
if (nextEntry == null) {
nextEntry = readNextEntry();
}
return nextEntry != null;
} catch (IOException e) {
throw new FrameworkException(e);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Decodes string representations of decision variables, returning the
* variable with the decoded value. Depending on the implementation and
* variable type, the same variable as provided in the arguments or a new
* variable will be returned.
*
* @param variable the decision variable
* @param string the string representation of the decision variable
* @return the variable with the decoded value
* @see ResultFileWriter#encode(Variable)
*/
public Variable decode(Variable variable, String string) {
if (variable instanceof RealVariable) {
RealVariable rv = (RealVariable)variable;
rv.setValue(Double.parseDouble(string));
return rv;
} else if (variable instanceof BinaryVariable) {
BinaryVariable bv = (BinaryVariable)variable;
if (bv.getNumberOfBits() != string.length()) {
throw new FrameworkException("invalid bit string");
}
for (int i=0; i<bv.getNumberOfBits(); i++) {
char c = string.charAt(i);
if (c == '0') {
bv.set(i, false);
} else if (c == '1') {
bv.set(i, true);
} else {
throw new FrameworkException("invalid bit string");
}
}
return bv;
} else if (variable instanceof Permutation) {
Permutation p = (Permutation)variable;
String[] tokens = string.split(",");
int[] array = new int[tokens.length];
for (int i=0; i<tokens.length; i++) {
array[i] = Integer.parseInt(tokens[i]);
}
try {
p.fromArray(array);
} catch (IllegalArgumentException e) {
throw new FrameworkException("invalid permutation", e);
}
return p;
} else {
if (string.equals("-")) {
if (!printedWarning) {
System.err.println(ENCODING_WARNING);
printedWarning = true;
}
return variable;
} else {
try {
return deserialize(string);
} catch (Exception e) {
throw new FrameworkException("deserialization failed", e);
}
}
}
}
/**
* Returns the variable represented by the Base64 encoded string.
*
* @param string the Base64 encoded representation of the variable
* @return the variable represented by the Base64 encoded string
* @throws IOException if the variable could not be deserialized
* @throws ClassNotFoundException if the class of the deserialized variable
* could not be found
*/
private Variable deserialize(String string) throws IOException,
ClassNotFoundException {
ObjectInputStream ois = null;
try {
byte[] encoding = Base64.decodeBase64(string);
ByteArrayInputStream baos = new ByteArrayInputStream(encoding);
ois = new ObjectInputStream(baos);
return (Variable)ois.readObject();
} finally {
if (ois != null) {
ois.close();
}
}
}
}