Populate and serialize text options (Java and C++)

I find it convenient to have a unified text options, similar to those ini/rc files, with serializer/de-serializer for both Java and C++ and here is it.

Sample text file:

testmode=3
block_repeat=100
# floating point
scaling_factor=0.9
# arrays
c=1.0, -2.0, 1.0
b[0]=-1.5, 2.0, -0.5, 0.0
b[1]=0.0, 0.5
b[2]=0.00000000e+00, 6.78046882e-01, -8.91344100e-02
b[3]=0.00000000e+00, 7.75490344e-01, -1.71563059e-01, 2.25807708e-02
# strings
log_file=/tmp/test.log

Java source:

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Options {

  private void init() {
    opt_l = new HashMap<String, Long>();
    opt_f = new HashMap<String, Float>();
    opt_ff = new HashMap<String, Double>();
    opt_s = new HashMap<String, String>();
  }

  public Options() {
    init();
    doublePrecision = false;
  }

  public Options(Boolean dblPrecision) {
    init();
    doublePrecision = dblPrecision;
  }

  public Options(String text) {
    init();
    doublePrecision = false;
    from(text);
  }

  public Options(String text, Boolean dblPrecision) {
    init();
    doublePrecision = dblPrecision;
    from(text);
  }

  private HashMap<String, Long> opt_l;

  private HashMap<String, Float> opt_f;

  private HashMap<String, Double> opt_ff;

  private HashMap<String, String> opt_s;

  private List validKeys_s, validKeys_f, validKeys_l;

  private Boolean doublePrecision;

  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    StringBuilder str = new StringBuilder(512);
    str.append("_double_precision = " + (doublePrecision ? 1 : 0) + "\n");
    str.append("; ======== Integer Options ========\n");
    for (Map.Entry<String, Long> entry : opt_l.entrySet()) {
      str.append(entry.getKey() + " = " + entry.getValue() + "\n");
    }
    str.append("; ======== " + (doublePrecision ? "Double" : "Float") + " Options ========\n");
    for (Map.Entry<String, Float> entry : opt_f.entrySet()) {
      str.append(entry.getKey() + " = " + String.format("%.8e", entry.getValue()) + "\n");
    }
    for (Map.Entry<String, Double> entry : opt_ff.entrySet()) {
      str.append(entry.getKey() + " = " + String.format("%.16e", entry.getValue()) + "\n");
    }
    str.append("; ======== String Options ========\n");
    for (Map.Entry<String, String> entry : opt_s.entrySet()) {
      str.append(entry.getKey() + " = " + entry.getValue() + "\n");
    }

    return str.toString();
  }

  public void from(String text) {
    for (String line : text.split("[\\r\\n]+")) {
      parseLine(line);
    }
  }

  /**
   * Test the regex before using it
   */
  @Deprecated
  public static Boolean isNumber(String str) {
    return str.matches("\\d") && str.matches("^[+-]?\\d*\\.?\\d*([eE]\\d)?\\d*$");
  }

  public static Boolean isInteger(String str) {
    return str.matches("^[+-]?\\d+$");
  }

  public Boolean isDoublePrecision() {
    return doublePrecision;
  }

  public void setDoublePrecision(boolean state) {
    doublePrecision = state;
  }

  public void parseLine(String line) {
    line.trim();
    if (line.startsWith(";") || line.startsWith("#")) {
      return;
    }
    String[] pair = line.split("\\s*=\\s*", 2);
    if (pair.length < 2) {       return;     }     try {       long v = Long.parseLong(pair[1]);       if (pair[0].equals("_double_precison") && v > 0) {
        doublePrecision = true;
      } else {
        opt_l.put(pair[0], v);
      }
    } catch (NumberFormatException e1) {
      try {
        double v = Double.parseDouble(pair[1]);
        if (doublePrecision) {
          opt_ff.put(pair[0], v);
        } else {
          opt_f.put(pair[0], (float) v);
        }
      } catch (NumberFormatException e2) {
        opt_s.put(pair[0], pair[1]);
      }
    }
  }

  public void clear() {
    opt_l.clear();
    opt_f.clear();
    opt_ff.clear();
    opt_s.clear();
  }

  public long getl(String key, long... default_v) {
    long v = default_v.length > 0 ? default_v[0] : 0;
    return opt_l.containsKey(key) ? opt_l.get(key) : v;
  }

  /*
   * Does not return boolean, instead, an interger indicates how many alternative settings are there
   */
  public int countl(String key) {
    return opt_l.containsKey(key) ? 1 : 0;
  }

  public int geti(String key, int... default_v) {
    int v = default_v.length > 0 ? default_v[0] : 0;
    return (int) getl(key, v);
  }

  public int counti(String key) {
    return countl(key);
  }

  public double getff(String key, double... default_v) {
    double v = default_v.length > 0 ? default_v[0] : 0;
    return opt_ff.containsKey(key) ? opt_ff.get(key) : opt_f.containsKey(key) ? opt_f.get(key)
        : opt_l.containsKey(key) ? opt_l.get(key) : v;
  }

  public int countff(String key) {
    return (opt_ff.containsKey(key) ? 1 : 0) + (opt_f.containsKey(key) ? 1 : 0) + (opt_l.containsKey(key) ? 1 : 0);
  }

  public float getf(String key, float... default_v) {
    float v = default_v.length > 0 ? default_v[0] : 0;
    return opt_f.containsKey(key) ? opt_f.get(key) : opt_ff.containsKey(key) ? opt_ff.get(key).floatValue() : opt_l
        .containsKey(key) ? opt_l.get(key) : v;
  }

  public int countf(String key) {
    return countff(key);
  }

  public String gets(String key, String... default_v) {
    String v = default_v.length > 0 ? default_v[0] : "";
    return opt_s.containsKey(key) ? opt_s.get(key) : v;
  }

  public int counts(String key) {
    return opt_s.containsKey(key) ? 1 : 0;
  }

  public boolean containsLong(String key) {
    return countl(key) > 0;
  }

  public boolean containsInt(String key) {
    return counti(key) > 0;
  }

  public boolean containsDouble(String key) {
    return countff(key) > 0;
  }

  public boolean containsFloat(String key) {
    return countf(key) > 0;
  }

  public boolean containsString(String key) {
    return counts(key) > 0;
  }

  public void set(String key, long v) {
    opt_l.put(key, v);
  }

  public void set(String key, int v) {
    opt_l.put(key, (long) v);
  }

  public void set(String key, double v) {
    if (doublePrecision) {
      opt_ff.put(key, v);
    } else {
      opt_f.put(key, (float) v);
    }
  }

  public void set(String key, float v) {
    if (doublePrecision) {
      opt_ff.put(key, (double) v);
    } else {
      opt_f.put(key, v);
    }
  }

  public void set(String key, String v) {
    opt_s.put(key, v);
  }

  public static void main(String[] args) {
    Options opt = new Options("int1=1\nfloat2= 3.0 \nfloat3= .3e-5 \nstring_test = TEST STRING");
    System.out.println(opt);
    String text = opt.toString();
    opt = new Options(text, true);
    System.out.println(opt);
  }

  public static String floats2str(float[] v) {
    String val = String.format("%.8e", v[0]);
    for (int i = 1; i < v.length; i++) {
      val += String.format(", %.8e", v[i]);
    }
    return val;
  }

  public static String doubles2str(double[] v) {
    String val = String.format("%.16e", v[0]);
    for (int i = 1; i < v.length; i++) {
      val += String.format(", %.16e", v[i]);
    }
    return val;
  }

  /**
   * This method sets a string entry of the arrays with sufficient precisions
   * for simplicity, no { } to enclose it (no plan to support 2D array)
   */
  public void set(String key, float[] v) {
    set(key, floats2str(v));
  }

  public void set(String key, double[] v) {
    set(key, doubles2str(v));
  }

  /*
   * This method will fail if cannot parse properly
   */
  public static float[] str2floats(String str) {
    String[] strs = str.split(",\\s*");
    float[] floats = new float[strs.length];

    for (int i = 0; i < strs.length; i++) {
      floats[i] = Float.parseFloat(strs[i]);
    }
    return floats;
  }

  /*
   * This method will fail if cannot parse properly
   */
  public static double[] str2doubles(String str) {
    String[] strs = str.split(",\\s*");
    double[] doubles = new double[strs.length];

    for (int i = 0; i < strs.length; i++) {       doubles[i] = Double.parseDouble(strs[i]);     }     return doubles;   }   public float[] getFloats(String key, float[]... default_v) {     float[] v = default_v.length > 0 ? default_v[0] : null;
    return opt_s.containsKey(key) ? str2floats(opt_s.get(key)) : v;
  }

  public double[] getDoubles(String key, double[]... default_v) {
    double[] v = default_v.length > 0 ? default_v[0] : null;
    return opt_s.containsKey(key) ? str2doubles(opt_s.get(key)) : v;
  }

  public void print_validKeys() {
    System.out.printf("Valid int keys: %s\n", validKeys_l);
    System.out.printf("Valid float keys: %s\n", validKeys_f);
    System.out.printf("Valid string keys: %s\n", validKeys_s);
  }

  private boolean validate_keys(List validKeys, Set keys, String description) {
    Iterator it = keys.iterator();
    boolean valid = true;
    while (it.hasNext()) {
      String key = it.next();
      if (!key.startsWith("_") && !validKeys.contains(key)) { // keys start with _ are exempted from validation
        System.out.printf("ERROR: Invalid %s key for text options: %s\n", description, key);
        valid = false;
      }
    }

    return valid;
  }

  public boolean validate_keys() {
    boolean valid = true;
    valid &= validate_keys(validKeys_l, opt_l.keySet(), "int"); // a = a && b will short circuit, which we do not want
    valid &= validate_keys(validKeys_f, opt_f.keySet(), "float");
    valid &= validate_keys(validKeys_f, opt_ff.keySet(), "float");
    valid &= validate_keys(validKeys_s, opt_s.keySet(), "string");
    if (!valid) {
      print_validKeys();
    }
    return valid;
  }

  public void setValidStringKeys(List keys) {
    validKeys_s = keys;
  }

  public void setValidFloatKeys(List keys) {
    validKeys_f = keys;
  }

  public void setValidIntKeys(List keys) {
    validKeys_l = keys;
  }
}

C++ source:

Options.h:

#ifndef OPTIONS_H_
#define OPTIONS_H_

#include <map>
using std::string;
using std::map;
typedef map<string, int> map_i;
typedef map<string, long> map_l;
typedef map<string, float> map_f;
typedef map<string, double> map_ff;
typedef map<string, string> map_s;

class COptions {
public:
    map_l opt_l;
    map_f opt_f;
    map_ff opt_ff;
    map_s opt_s;
private:
    int doublePrecision;
public:
    COptions();
    COptions(const char* text);
    COptions(string str);
    virtual ~COptions();

    COptions& operator<<(const char* text);
    COptions& operator<<(const string& str);
    COptions& operator<<(std::istream& is);     COptions& operator>>(std::ostream& os);
    friend std::istream& operator>>(std::istream& is, COptions&);
    friend std::ostream& operator<<(std::ostream& os, const COptions&);

    void clear();
    void reset();
    virtual void init_defs(); // for overriding ...
    string to_string();
//    const char * c_str(); // NO NO!!!

    int isDoublePrecision();
    int geti(const char* key, int def = 0);
    long getl(const char* key, long def = 0L);
    float getf(const char* key, float def = 0.0f);
    double getff(const char* key, double def = 0.0);
    string gets(const char* key, string def = "");

    vector getFloats(const char* key);
    vector getFloats(const char* key, vector& def);
    vector getDoubles(const char* key);
    vector getDoubles(const char* key, vector& def);

    int counti(const char* key);
    int countl(const char* key);
    int countf(const char* key);
    int countff(const char* key);
    int counts(const char* key);

    void set(const char* key, int val);
    void set(const char* key, long val);
    void set(const char* key, float val);
    void set(const char* key, double val);
    void set(const char* key, vector vec);
    void set(const char* key, vector vec);
    void set(const char* key, string val);

    void parse_line(string& line);
    void chop_space(string& str);

    static vector str2floats(string& str);
    static vector str2doubles(string& str);
    static string floats2str(vector& vec);
    static string doubles2str(vector& vec);
    static int test();
};

#endif /* OPTIONS_H_ */

Options.cpp:

#include "Options.h"
#include <sstream>
#include <iostream>
#include <iterator>
#include <iomanip>
#include <limits>

COptions::COptions(const char* text) {
  init_defs();
  *this << text;
}

COptions::COptions(string str) {
  init_defs();
  *this << str;
}

COptions::COptions() {
  init_defs();
}

COptions::~COptions() {

}

void COptions::clear() {
  opt_l.clear();
  opt_f.clear();
  opt_ff.clear();
  opt_s.clear();
}

void COptions::reset() {
  clear();
  init_defs();
}

void COptions::init_defs() {
  doublePrecision = 0;
}

COptions& COptions::operator <<(const char* text) {
  if (!text) return *this;

  std::stringstream ss(text);
  return (*this << ss);
}

COptions& COptions::operator <<(const string& str) {
  if (str.empty()) return *this;

  std::stringstream ss(str);
  return (*this << ss);
}

COptions& COptions::operator <<(std::istream& is) {   string line;   while (std::getline(is, line))     parse_line(line);   return *this; } COptions& COptions::operator >>(std::ostream& os) {
  os << *this;   return *this; } std::istream& operator >>(std::istream& is, COptions& me) {
  me << is;
  return is;
}

std::ostream& operator <<(std::ostream& os, const COptions& me) {
  os << "_double_precision = " << (me.doublePrecision ? 1 : 0) << std::endl;
  os << "; =======Integer options=======" << std::endl;
  for (map_l::const_iterator iter = me.opt_l.begin(); iter != me.opt_l.end(); iter++)
    os << iter->first << " = " << iter->second << std::endl;

  os << "; =======" << (me.doublePrecision ? "Double" : "Float") << " options=======" << std::endl;   for (map_f::const_iterator iter = me.opt_f.begin(); iter != me.opt_f.end(); iter++) {     float f = iter->second;
    //if ((float) ((int) nearbyintf(f)) == f) os << iter->first << " = " << iter->second << ".0" << std::endl;
    //else os << iter->first << " = " << iter->second << std::endl;
    os << iter->first << " = " << std::scientific
        << std::setprecision(std::numeric_limits::digits10 + 1) // 7+1, same as %.8e, totally 9 digits
        << f << std::endl;   }   for (map_ff::const_iterator iter = me.opt_ff.begin(); iter != me.opt_ff.end(); iter++) {     double f = iter->second;
    //if ((double) ((int) nearbyint(f)) == f) os << iter->first << " = " << iter->second << ".0" << std::endl;
    //else os << iter->first << " = " << iter->second << std::endl;
    os << iter->first << " = " << std::scientific
        << std::setprecision(std::numeric_limits::digits10 + 1) // 15+1, same as %.16e, totally 17 digits
        << f << std::endl;
  }

  os << "; =======String options=======" << std::endl;
  for (map_s::const_iterator iter = me.opt_s.begin(); iter != me.opt_s.end(); iter++)
    os << iter->first << " = " << iter->second << std::endl;
  return os;
}

void COptions::parse_line(string& line) {
  if (line[0] == ';' || line[0] == '#') return; // comment line
  size_t pos = line.find('=');
  if (pos == string::npos) return;
  string key = line.substr(0, pos);
  string val = line.substr(pos + 1);
  chop_space(key);
  chop_space(val);
  if (key.empty() || val.empty()) return;
  // std::cout << "key = " << key << ", val = " << val << std::endl;   if (val.find_first_not_of("+-0123456789") == string::npos) {     if (key.compare("_double_precision") == 0) doublePrecision = atol(val.c_str());     else opt_l[key] = atol(val.c_str()); // integer   }   // floating point, not fool-proof but sufficient ...   else if ((val.find_first_of("0123456789") != string::npos)       && (val.find_first_not_of("+-.0123456789eE") == string::npos)) {     if (doublePrecision) opt_ff[key] = atof(val.c_str());     else opt_f[key] = atof(val.c_str());   } else opt_s[key] = val; // string } void COptions::chop_space(string& str) {   size_t pos;   if ((pos = str.find_first_not_of(" \t\n\r")) > 0) str = str.substr(pos); // remove leading spaces
  if ((pos = str.find_last_not_of(" \t\n\r")) < str.size() - 1) str = str.substr(0, pos + 1); // remove trailing spaces
}

string COptions::to_string() {
  std::stringstream ss(std::stringstream::out);

  ss << *this;

  return ss.str();
}

/* should not do like this, to_string() will be free'ed and c_str() will be meaningless
 const char* COptions::c_str() {
 return to_string().c_str();
 }
 */

int COptions::geti(const char* key, int def) {
  return opt_l.count(key) ? opt_l[key] : def;
}

long COptions::getl(const char* key, long def) {
  return opt_l.count(key) ? opt_l[key] : def;
}

float COptions::getf(const char* key, float def) {
  return opt_f.count(key) ? opt_f[key] : opt_ff.count(key) ? opt_ff[key] : opt_l.count(key) ? opt_l[key] : def;
}

double COptions::getff(const char* key, double def) {
  return opt_ff.count(key) ? opt_ff[key] : opt_f.count(key) ? opt_f[key] : opt_l.count(key) ? opt_l[key] : def;
}

string COptions::gets(const char* key, string def) {
  return opt_s.count(key) ? opt_s[key] : def;
}

void COptions::set(const char* key, int val) {
  opt_l[key] = val;
}

void COptions::set(const char* key, long val) {
  opt_l[key] = val;
}

void COptions::set(const char* key, float val) {
  opt_f[key] = val;
}

void COptions::set(const char* key, double val) {
  opt_ff[key] = val;
}

void COptions::set(const char* key, string val) {
  opt_s[key] = val;
}

int COptions::test() {
  const char text[] = "_double_precision = 1\nint1= 2\n float2=0.0 \n string3 = TEST STRING  \n float4 = 0.01";
  COptions opt;
  opt.clear();
  opt << text;
  std::cout << opt << std::endl;

  string str = opt.to_string();
  opt.reset();
  opt << str;
  std::cout << opt << std::endl;

  COptions opt2(text);
  std::cout << opt2 << std::endl;

  return 0;
}

int COptions::isDoublePrecision() {
  return doublePrecision;
}

vector COptions::getFloats(const char* key) {
  if (opt_s.count(key)) return str2floats(opt_s[key]);
  vector v;
  if (countf(key)) v.push_back(getf(key)); // handles size=1
  return v;
}

vector COptions::getFloats(const char* key, vector& def) {
  if (opt_s.count(key)) return str2floats(opt_s[key]);
  if (countf(key)) { // handles size=1
    vector v;
    v.push_back(getf(key));
    return v;
  }
  return def;
}

vector COptions::getDoubles(const char* key) {
  if (opt_s.count(key)) return str2doubles(opt_s[key]);
  vector v;
  if (countff(key)) v.push_back(getff(key)); // handles size=1
  return v;
}

vector COptions::getDoubles(const char* key, vector& def) {
  if (opt_s.count(key)) return str2doubles(opt_s[key]);
  if (countff(key)) { // handles size=1
    vector v;
    v.push_back(getff(key));
    return v;
  }
  return def;
}

int COptions::counti(const char* key) {
  return opt_l.count(key);
}

int COptions::countl(const char* key) {
  return opt_l.count(key);
}

int COptions::countf(const char* key) {
  return opt_f.count(key) + opt_ff.count(key) + opt_l.count(key);
}

int COptions::countff(const char* key) {
  return opt_ff.count(key) + opt_f.count(key) + opt_l.count(key);
}

int COptions::counts(const char* key) {
  return opt_s.count(key);
}

vector COptions::str2floats(string& str) {
  std::stringstream iss(str);
  vector v;

  float val;
  char c;
  while (iss >> val) {
    v.push_back(val);
    while ((c = iss.peek()) == ',' || c == ' ')
      iss.get();
  }
  return v;
}

vector COptions::str2doubles(string& str) {
  std::stringstream iss(str);
  vector v;

  double val;
  char c;
  while (iss >> val) {
    v.push_back(val);
    while ((c = iss.peek()) == ',' || c == ' ')
      iss.get();
  }
  return v;
}

string COptions::floats2str(vector& vec) {
  std::ostringstream ss;
  ss << std::scientific << std::setprecision(std::numeric_limits::digits10 + 1) // 7+1, same as %.8e, totally 9 digits
      << vec[0];
  for (size_t i = 1; i < vec.size(); i++) {
    ss << ", " << std::scientific << std::setprecision(std::numeric_limits::digits10 + 1) // 7+1, same as %.8e, totally 9 digits
        << vec[i];
  }
  return ss.str();
}

string COptions::doubles2str(vector& vec) {
  std::ostringstream ss;
  ss << std::scientific << std::setprecision(std::numeric_limits::digits10 + 1) // 15+1, same as %.16e, totally 17 digits
      << vec[0];
  for (size_t i = 1; i < vec.size(); i++) {
    ss << ", " << std::scientific << std::setprecision(std::numeric_limits::digits10 + 1) // 15+1, same as %.16e, totally 17 digits
        << vec[i];
  }
  return ss.str();
}

void COptions::set(const char* key, vector vec) {
  set(key, floats2str(vec));
}

void COptions::set(const char* key, vector vec) {
  set(key, doubles2str(vec));
}

This entry was posted in programming. Bookmark the permalink.

2 Responses to Populate and serialize text options (Java and C++)

  1. I will immediately grab your rss as I can’t to find your e-mail subscription link or
    e-newsletter service. Do you’ve any? Please let me know in order that I could subscribe.

    Thanks.

  2. I’ve been surfing on-line greater than 3 hours as of late,
    but I never found any fascinating article like yours.
    It’s beautiful price sufficient for me. In my view, if all site owners and bloggers made
    good content as you did, the web might be a lot more useful than ever before.

Leave a Reply

Your email address will not be published. Required fields are marked *