package com.framsticks.parsers; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import com.framsticks.model.Model; import static com.framsticks.params.SimpleAbstractAccess.*; import com.framsticks.params.Flags; import com.framsticks.params.Param; import com.framsticks.util.Exceptions; import com.framsticks.util.Pair; import com.framsticks.util.Strings; import org.apache.log4j.Logger; import com.framsticks.params.FramsClass; import com.framsticks.params.AccessInterface; /** * The class Parser is used to parse genotype encoded in f0 representation. */ public class F0Parser { private final static Logger LOGGER = Logger.getLogger(F0Parser.class); /** The schema proper for f0 representation. */ protected final Schema schema; protected final InputStream is; protected final List result = new ArrayList(); int lineNumber = 0; public F0Parser(Schema schema, InputStream is) { assert schema != null; assert is != null; this.schema = schema; this.is = is; } protected AccessInterface processLine(String line) { try { Pair p = Strings.splitIntoPair(line, ':', ""); String classId = p.first.trim(); FramsClass framsClass = schema.getRegistry().getInfoFromCache(classId); if (framsClass == null) { throw new Exception("unknown class id: " + classId); } AccessInterface access = schema.getRegistry().createAccess(classId, framsClass); access.select(access.createAccessee()); for (Exception e : loadFromLine(access, p.second)) { warn(lineNumber, "however entry was added", e); } return access; } catch (Exception e) { warn(lineNumber, "entry was not added", e); } return null; } /** * Parses the stream with genotype in f0 representation. The correctness of * genotype is checked. IO and syntax exceptions interrupts parsing and no * result is returned. Other exceptions, connected with schema validation * cause that certain object or it's parameter is ignored (appropriate * communicate informs user about it). Inappropriate values in numeric * fields (bigger than maximum or smaller than minimum values) are * communicated by warnings and set to minimum / maximum value. * * @return the list * @throws IOException * Signals that an I/O exception has occurred. * @throws ParseException * the parse exception */ public List parse() throws IOException, ParseException { InputStreamReader reader = null; try { reader = new InputStreamReader(is, "UTF-8"); BufferedReader br = new BufferedReader(reader); while (br.ready()) { ++lineNumber; String line = br.readLine(); line = (line == null ? "" : line.trim()); if (lineNumber == 1) { if (!"//0".equals(line)) { LOGGER.warn("stream should begin with \"//0\" in the first line"); } else { continue; } } if (line.equals("")) { continue; } if (line.startsWith("#")) { continue; } AccessInterface access = processLine(line); if (access != null) { result.add(access); } } /** If no 'm' (Model) line was found, than simulate it on the beginning of the result.*/ if (result.isEmpty() || !(result.get(0) instanceof Model)) { result.add(0, processLine("m:")); } } finally { if (reader != null) { reader.close(); } } return result; } private static void warn(int lineNumber, String message, Exception e) { LOGGER.warn("in line " + lineNumber + " the following error occurred (" + message + "): " + e + "\n" + Exceptions.printStackTrace(e)); } /** Breaks string into entries.*/ public List breakIntoEntries(String parameters) throws Exception { // tokenize boolean inQuotes = false; char previousChar = ','; List result = new ArrayList(); StringBuilder stringBuilder = new StringBuilder(); String key = null; if (parameters.trim().length() > 0) { for (char currentChar : parameters.toCharArray()) { if (!inQuotes && (currentChar == '=') && (key == null)) { key = stringBuilder.toString().trim(); stringBuilder = new StringBuilder(); } else if (!inQuotes && currentChar == ',') { if (previousChar == ',') { result.add(new Entry(key, null)); } else { result.add(new Entry(key, stringBuilder.toString().trim())); } stringBuilder = new StringBuilder(); key = null; } else if (currentChar == '"') { if (previousChar == '\\') { stringBuilder.deleteCharAt(stringBuilder.length() - 1); stringBuilder.append(currentChar); } else { inQuotes = !inQuotes; } } else { stringBuilder.append(currentChar); } previousChar = currentChar; } result.add(new Entry(key, stringBuilder.toString().trim())); if (inQuotes) { throw new Exception("Double quotes expected while end of line met"); } } return result; } public List loadFromLine(AccessInterface access, String parameters) throws Exception { List entries = breakIntoEntries(parameters); List exceptions = new ArrayList(); Param[] params = access.getParams().toArray(new Param[] {null}); if (params.length == 0) { return exceptions; } for (Param p : params) { Object def = p.getDef(Object.class); if (def != null) { access.set(p, def); } } int number = -1; Integer nextParamNumber = 0; for (Entry pair : entries) { ++number; try { Param currentParam; if (pair.key != null) { currentParam = access.getParam(pair.key); if (currentParam == null) { nextParamNumber = null; throw new Exception("no parameter with such id: " + pair.key); } } else { if (nextParamNumber == null || ((params[nextParamNumber].getFlags() & Flags.CANOMITNAME) == 0)) { nextParamNumber = null; throw new Exception( "parameter with offset: " + number + " is not set, " + "because it's definition or definition of the previous param " + "does not contain flag, which allows to skip the name (flag 1024)"); } currentParam = params[nextParamNumber]; } if (currentParam != null) { if (pair.value != null) { int setFlag = access.set(currentParam, pair.value); if ((setFlag & Flags.PSET_HITMIN) != 0) { exceptions.add(createBoundaryHitException(access, currentParam, pair.value, Flags.PSET_HITMIN)); } if ((setFlag & Flags.PSET_HITMAX) != 0) { exceptions.add(createBoundaryHitException(access, currentParam, pair.value, Flags.PSET_HITMAX)); } if ((setFlag & Flags.PSET_RONLY) != 0) { throw (new Exception("tried to set a read-only attribute \"" + currentParam.getId() + "\" in class \"" + access.getId() + "\"")); } } nextParamNumber = null; for (int j = params.length - 1; j > 0; --j) { if (params[j - 1] == currentParam) { nextParamNumber = j; } } } } catch (Exception e) { exceptions.add(e); } } return exceptions; } private static Exception createBoundaryHitException(AccessInterface access, Param param, String value, int flag) { boolean minimum = (flag & Flags.PSET_HITMIN) != 0; String boundary = (minimum ? param.getMin(Object.class) : param.getMax(Object.class)).toString(); String name = (minimum ? "minimum" : "maximum"); return new Exception("Tried to set attribute \"" + param.getId() + "\" in class \"" + access.getId() + "\" to value which exceeds " + name + " (" + value + "), truncated to: " + boundary); } }