package com.framsticks.params; import java.io.IOException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Collection; import java.util.List; import com.framsticks.params.types.*; import org.apache.log4j.Logger; import java.util.Locale; /** * The Class SimpleAbstractAccess implements all the methods of AccessInterface * which actions can be implemented with usage of {@link AccessInterface} methods * or concern schema, which is stored in {@link #framsClass} * * Based on c++ class SimpleAbstractParam located in: cpp/gdk/param.* * * @author Jarek Szymczak , Mateusz Jarus (please * replace name and surname with my personal data) * * @author Piotr Sniegowski */ public abstract class SimpleAbstractAccess implements AccessInterface { private final static Logger LOGGER = Logger.getLogger(SimpleAbstractAccess.class.getName()); @Override public final FramsClass getFramsClass() { return framsClass; } public void setFramsClass(FramsClass framsClass) { this.framsClass = framsClass; } /** * Simple String key, value class. */ private static class Entry { String key; String value; Entry(String key, String value) { this.key = key; this.value = value; } @Override public String toString() { return key + " = " + value; } } protected FramsClass framsClass; @Override public String getId() { return framsClass.getId(); } @Override public int getParamCount() { return framsClass.getParamCount(); } @Override public Param getParam(int i) { return framsClass.getParamEntry(i); } @Override public Param getParam(String id) { return framsClass.getParamEntry(id); } @Override public Param getGroupMember(int gi, int n) { return framsClass.getGroupMember(gi, n); } @Override public T get(int i, Class type) { return get(getParam(i), type); } @Override public T get(String id, Class type) { return get(getParam(id), type); } @Override public int set(String id, T value) { return set(getParam(id), value); } @Override public int set(int i, T value) { return set(getParam(i), value); } @SuppressWarnings("unchecked") @Override public int set(Param param, T value) { int flags = 0; //String id = param.getEffectiveId(); try { Object oldValue = get(param, param.getStorageType()); Object casted = param.reassign(value, oldValue); if (casted != oldValue) { internalSet(param, casted); } } catch (CastFailure e) { LOGGER.error("casting failure while set: ", e); } return flags; } @Override public void setDefault(boolean numericOnly) { for (int i = 0; i < framsClass.getParamCount(); i++) setDefault(i, numericOnly); } @Override public void setDefault(int i, boolean numericOnly) { Param entry = framsClass.getParamEntry(i); if ((entry != null) && (!numericOnly || entry.isNumeric())) { set(i, entry.getDef(entry.getStorageType())); } } @Override public void setMin() { for (int i = 0; i < framsClass.getParamCount(); i++) { setMin(i); } } @SuppressWarnings("unchecked") @Override public void setMin(int i) { Param entry = framsClass.getParamEntry(i); Object min = entry.getMin(entry.getStorageType()); if (min != null) { set(i, min); } } @Override public void setMax() { for (int i = 0; i < framsClass.getParamCount(); i++) { setMax(i); } } @SuppressWarnings("unchecked") @Override public void setMax(int i) { Param entry = framsClass.getParamEntry(i); Object max = entry.getMax(entry.getStorageType()); if (max != null) { set(i, max); } } @Override public void copyFrom(AccessInterface src) { clearValues(); //TODO: iterate over self, and pull from src /* for (int i = 0; i < src.getFramsClass().size(); i++) { this.set(i, src.get(i, Object.class)); } */ } @Override public void save(SinkInterface sink) { assert framsClass != null; sink.print(framsClass.getId()).print(":").breakLine(); for (Param p : framsClass.getParamEntries()) { if (p instanceof ValueParam) { Object value = get(p, Object.class); if (value == null) { continue; } sink.print(p.getId()).print(":"); ((ValueParam)p).save(sink, value); sink.breakLine(); //continue; } /* if (p instanceof CompositeParam) { if (!(p instanceof ListParam)) { continue; } Collection c = get(p, Collection.class); if (c != null) { save(stream, p, c.size()); } } */ } sink.breakLine(); } @Override public String save2(boolean omitDefaultValues) { StringBuilder stringBuilder = new StringBuilder(); DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols( Locale.ENGLISH)); boolean canOmitName = false; int n = 0; for (int i = 0; i < framsClass.getParamCount(); i++) { Object value = this.get(i, Object.class); Object def = framsClass.getParamEntry(i).getDef(Object.class); if (value != null && (!omitDefaultValues || !value.equals(def))) { if ((n != i && !canOmitName) || (getParam(i).getFlags() & Flags.CANOMITNAME) == 0) { stringBuilder.append(getParam(i).getId()); stringBuilder.append("="); } n++; canOmitName = true; if (this.get(i, Object.class) instanceof String) { String valueString = this.get(i, String.class); valueString = valueString.replaceAll("\\\"", "\\\\\""); valueString = valueString.contains(",") ? "\"" + valueString + "\"" : valueString; // if ("".equals(valueString.trim())) // value = "\"\""; stringBuilder.append(valueString); } else if (this.get(i, Object.class) instanceof Double) { stringBuilder.append(df.format(this.get(i, Double.class))); } else stringBuilder.append(this.get(i, Object.class)); stringBuilder.append(", "); } else { canOmitName = false; } } String params = ""; if (stringBuilder.length() > 1) params = stringBuilder.substring(0, stringBuilder.length() - 2); return getId() + ":" + params; } /* private static void appendToValue(StringBuilder value, String line) { if (value.length() != 0) { value.append(System.getProperty("line.separator")); } value.append(line); } */ private Entry readEntry(SourceInterface source) throws IOException { String line; String key = null; StringBuilder value = null; while ((line = source.readLine()) != null) { if (key == null) { int colonIndex = line.indexOf(':'); if (colonIndex == -1) { return null; } key = line.substring(0, colonIndex); String inlineValue = line.substring(colonIndex + 1); if (!inlineValue.startsWith("~")) { return new Entry(key, inlineValue); } value = new StringBuilder(); value.append(inlineValue.substring(1)); continue; } if (value.length() != 0) { value.append(System.getProperty("line.separator")); } if (line.contains("~")) { value.append(line.substring(0, line.indexOf("~"))); return new Entry(key, value.toString()); } value.append(line); /* if (line.contains("~")) { String lastLine = line.substring(0, line.indexOf("~")); if (lastLine.length() > 0) { appendToValue(value, lastLine); } return new Entry(key, value.toString()); } appendToValue(value, line); */ } return null; } @Override public void load(SourceInterface source) throws Exception { //TODO not clearing values, because get from manager gives only fields, not children //this.clearValues(); Entry entry; while ((entry = readEntry(source)) != null) { Param param = getParam(entry.key); if (param == null) { continue; } if ((param.getFlags() & Flags.DONTLOAD) != 0) { LOGGER.debug("DontLoad flag was set - not loading..."); } else { int retFlags = this.set(param, entry.value); if ((retFlags & (Flags.PSET_HITMIN | Flags.PSET_HITMAX)) != 0) { String which = ((retFlags & Flags.PSET_HITMIN) != 0) ? "small" : "big"; LOGGER.warn("value of key '" + entry.key + "' was too " + which + ", adjusted"); } } } } @Override public List load2(String line) throws Exception { this.clearValues(); // list of not terminable exceptions that occured List exceptions = new ArrayList(); int indexOfColon = line.indexOf(':'); if (indexOfColon < 0) indexOfColon = line.length(); String classId = line.substring(0, indexOfColon).trim(); if (!getId().equals(classId)) throw new Exception( "Inappropriate getId of param interface (class), specified \"" + classId + "\" while should be \"" + getId() + "\""); String parameters; if (indexOfColon == line.length()) parameters = ""; else parameters = line.substring(indexOfColon + 1); // tokenize boolean doubleQuotes = false; char previousChar = ','; List result = new ArrayList(); StringBuilder stringBuilder = new StringBuilder(); String key = ""; if (parameters.trim().length() > 0) { for (char currentChar : parameters.toCharArray()) { if (!doubleQuotes && currentChar == '=' && "".equals(key)) { key = stringBuilder.toString(); stringBuilder = new StringBuilder(); } else if (!doubleQuotes && currentChar == ',') { if (previousChar == ',') { result.add(new Entry(key.trim(), null)); } else { result.add(new Entry(key.trim(), stringBuilder .toString().trim())); } stringBuilder = new StringBuilder(); key = ""; } else if (currentChar == '"') { if (previousChar == '\\') { stringBuilder.deleteCharAt(stringBuilder.length() - 1); stringBuilder.append(currentChar); } else doubleQuotes = !doubleQuotes; } else { stringBuilder.append(currentChar); } previousChar = currentChar; } String last = stringBuilder.toString().trim(); // if (last.length() > 0 || previousChar == '\"' // || previousChar == ':') result.add(new Entry(key.trim(), last)); if (doubleQuotes) throw new Exception( "Double quotes expected while end of line met"); } // if successfully parsed set all necessary values //TODO: name omitting Param currentParam = null; boolean illegallyOmittedName = false; for (Entry pair : result) { try { if (pair.key != null && !"".equals(pair.key)) { Param param = getParam(pair.key); if (param == null) { illegallyOmittedName = true; throw new Exception("No parameter with such id: " + pair.key); } else { currentParam = param; illegallyOmittedName = false; } } else if (illegallyOmittedName || (currentParam.getFlags() & Flags.CANOMITNAME) == 0) { throw new Exception( "Parameter with offset: " + currentParam + " is not set, " + "because it's definition or definition of one of the previous params " + "does not contain flag, which allow to skip the getName (flag 1024)"); } if (pair.value != null) { int setFlag = this.set(currentParam, pair.value); if ((setFlag & Flags.PSET_HITMIN) != 0) { exceptions.add(createBoundaryHitException(currentParam, pair.value, Flags.PSET_HITMIN)); } if ((setFlag & Flags.PSET_HITMAX) != 0) { exceptions.add(createBoundaryHitException(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 \"" + getId() + "\"")); } } } catch (Exception e) { exceptions.add(e); //} finally { // currentProperty++; } } return exceptions; } private Exception createBoundaryHitException(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 \"" + getId() + "\" to value which exceeds " + name + " (" + value + "), truncated to: " + boundary); } protected abstract void internalSet(Param param, T value); @Override public Collection getParams() { return framsClass.getParamEntries(); } /* protected > int setAndCut(Param param, Object value, Class type) { int flags = 0; T val = type.cast(value); T min = param.getMin(type); T max = param.getMax(type); if (min != null && val.compareTo(min) < 0) { val = min; flags |= Flags.PSET_HITMIN; } if (max != null && val.compareTo(max) > 0) { val = max; flags |= Flags.PSET_HITMAX; } internalSet(param, val); return flags; }*/ }