summaryrefslogtreecommitdiff
path: root/src/commodity.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2007-05-03 06:11:04 +0000
committerJohn Wiegley <johnw@newartisans.com>2008-04-13 03:38:36 -0400
commitc59018c29ddfc7a46aeb951fbcd5cb5b93f47ec0 (patch)
tree204d28bfa2bdbfe8d7f550877faa114c1e93859f /src/commodity.cc
parentf9f24fab933266ab8e12da7eef4cc2a906f77350 (diff)
downloadfork-ledger-c59018c29ddfc7a46aeb951fbcd5cb5b93f47ec0.tar.gz
fork-ledger-c59018c29ddfc7a46aeb951fbcd5cb5b93f47ec0.tar.bz2
fork-ledger-c59018c29ddfc7a46aeb951fbcd5cb5b93f47ec0.zip
Revised how commodities are dealt with.
Diffstat (limited to 'src/commodity.cc')
-rw-r--r--src/commodity.cc543
1 files changed, 301 insertions, 242 deletions
diff --git a/src/commodity.cc b/src/commodity.cc
index dba1eb98..1fac4a4e 100644
--- a/src/commodity.cc
+++ b/src/commodity.cc
@@ -43,61 +43,98 @@
namespace ledger {
-#ifndef THREADSAFE
-base_commodities_map commodity_base_t::commodities;
-
-commodity_base_t::updater_t * commodity_base_t::updater = NULL;
-
-commodities_map commodity_t::commodities;
-commodities_array * commodity_t::commodities_by_ident;
-bool commodity_t::commodities_sorted = false;
-commodity_t * commodity_t::null_commodity;
-commodity_t * commodity_t::default_commodity = NULL;
-#endif
-
-void commodity_base_t::add_price(const moment_t& date,
- const amount_t& price)
+void commodity_t::add_price(const moment_t& date,
+ const amount_t& price)
{
- if (! history)
- history = history_t();
+ if (! base->history)
+ base->history = history_t();
- history_map::iterator i = history->prices.find(date);
- if (i != history->prices.end()) {
+ history_map::iterator i = base->history->prices.find(date);
+ if (i != base->history->prices.end()) {
(*i).second = price;
} else {
std::pair<history_map::iterator, bool> result
- = history->prices.insert(history_pair(date, price));
+ = base->history->prices.insert(history_pair(date, price));
assert(result.second);
}
}
-bool commodity_base_t::remove_price(const moment_t& date)
+bool commodity_t::remove_price(const moment_t& date)
{
- if (history) {
- history_map::size_type n = history->prices.erase(date);
+ if (base->history) {
+ history_map::size_type n = base->history->prices.erase(date);
if (n > 0) {
- if (history->prices.empty())
- history.reset();
+ if (base->history->prices.empty())
+ base->history.reset();
return true;
}
}
return false;
}
-commodity_base_t * commodity_base_t::create(const string& symbol)
+optional<amount_t> commodity_t::value(const optional<moment_t>& moment)
{
- commodity_base_t * commodity = new commodity_base_t(symbol);
+ optional<moment_t> age;
+ optional<amount_t> price;
- DEBUG("amounts.commodities", "Creating base commodity " << symbol);
+ if (base->history) {
+ assert(base->history->prices.size() > 0);
+
+ if (! moment) {
+ history_map::reverse_iterator r = base->history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ history_map::iterator i = base->history->prices.lower_bound(*moment);
+ if (i == base->history->prices.end()) {
+ history_map::reverse_iterator r = base->history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ age = (*i).first;
+ if (*moment != *age) {
+ if (i != base->history->prices.begin()) {
+ --i;
+ age = (*i).first;
+ price = (*i).second;
+ } else {
+ age = optional<moment_t>();
+ }
+ } else {
+ price = (*i).second;
+ }
+ }
+ }
+ }
+
+ if (! (flags() & COMMODITY_STYLE_NOMARKET)) {
+ if (optional<amount_t> quote = parent().get_quote
+ (*this, age, moment,
+ (base->history && base->history->prices.size() > 0 ?
+ (*base->history->prices.rbegin()).first : optional<moment_t>())))
+ return *quote;
+ }
+ return price;
+}
+
+commodity_t::operator bool() const
+{
+ return this != parent().null_commodity;
+}
- std::pair<base_commodities_map::iterator, bool> result
- = commodities.insert(base_commodities_pair(symbol, commodity));
- assert(result.second);
+annotated_commodity_t& commodity_t::as_annotated()
+{
+ assert(annotated);
+ return *polymorphic_downcast<annotated_commodity_t *>(this);
+}
- return commodity;
+const annotated_commodity_t& commodity_t::as_annotated() const
+{
+ assert(annotated);
+ return *polymorphic_downcast<const annotated_commodity_t *>(this);
}
-bool commodity_t::needs_quotes(const string& symbol)
+bool commodity_t::symbol_needs_quotes(const string& symbol)
{
for (const char * p = symbol.c_str(); *p; p++)
if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.')
@@ -108,7 +145,7 @@ bool commodity_t::needs_quotes(const string& symbol)
bool commodity_t::valid() const
{
- if (symbol().empty() && this != null_commodity) {
+ if (symbol().empty() && this != parent().null_commodity) {
DEBUG("ledger.validate",
"commodity_t: symbol().empty() && this != null_commodity");
return false;
@@ -127,204 +164,225 @@ bool commodity_t::valid() const
return true;
}
-commodity_t * commodity_t::create(const string& symbol)
+bool annotated_commodity_t::operator==(const commodity_t& comm) const
{
- std::auto_ptr<commodity_t> commodity(new commodity_t);
-
- commodity->base = commodity_base_t::create(symbol);
-
- if (needs_quotes(symbol)) {
- commodity->qualified_symbol = "\"";
- commodity->qualified_symbol += symbol;
- commodity->qualified_symbol += "\"";
- } else {
- commodity->qualified_symbol = symbol;
- }
-
- DEBUG("amounts.commodities",
- "Creating commodity " << commodity->qualified_symbol);
-
- std::pair<commodities_map::iterator, bool> result
- = commodities.insert(commodities_pair(symbol, commodity.get()));
- if (! result.second)
- return NULL;
+ // If the base commodities don't match, the game's up.
+ if (base != comm.base)
+ return false;
- commodity->ident = commodities_by_ident->size();
- commodities_by_ident->push_back(commodity.get());
+ assert(annotated);
+ if (! comm.annotated)
+ return false;
- // Start out the new commodity with the default commodity's flags
- // and precision, if one has been defined.
- if (default_commodity)
- commodity->drop_flags(COMMODITY_STYLE_THOUSANDS |
- COMMODITY_STYLE_NOMARKET);
+ if (details != comm.as_annotated().details)
+ return false;
- return commodity.release();
+ return true;
}
-commodity_t * commodity_t::find_or_create(const string& symbol)
+void
+annotated_commodity_t::write_annotations(std::ostream& out,
+ const annotation_t& info)
{
- DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);
+ if (info.price)
+ out << " {" << *info.price << '}';
- commodity_t * commodity = find(symbol);
- if (commodity)
- return commodity;
- return create(symbol);
+ if (info.date)
+ out << " [" << *info.date << ']';
+
+ if (info.tag)
+ out << " (" << *info.tag << ')';
}
-commodity_t * commodity_t::find(const string& symbol)
+bool compare_amount_commodities::operator()(const amount_t * left,
+ const amount_t * right) const
{
- DEBUG("amounts.commodities", "Find commodity " << symbol);
+ commodity_t& leftcomm(left->commodity());
+ commodity_t& rightcomm(right->commodity());
- commodities_map::const_iterator i = commodities.find(symbol);
- if (i != commodities.end())
- return (*i).second;
- return NULL;
-}
+ int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
+ if (cmp != 0)
+ return cmp < 0;
-amount_t commodity_base_t::value(const moment_t& moment)
-{
- moment_t age;
- amount_t price;
+ if (! leftcomm.annotated) {
+ assert(rightcomm.annotated);
+ return true;
+ }
+ else if (! rightcomm.annotated) {
+ assert(leftcomm.annotated);
+ return false;
+ }
+ else {
+ annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm));
+ annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm));
- if (history) {
- assert(history->prices.size() > 0);
+ if (! aleftcomm.details.price && arightcomm.details.price)
+ return true;
+ if (aleftcomm.details.price && ! arightcomm.details.price)
+ return false;
- if (! is_valid_moment(moment)) {
- history_map::reverse_iterator r = history->prices.rbegin();
- age = (*r).first;
- price = (*r).second;
- } else {
- history_map::iterator i = history->prices.lower_bound(moment);
- if (i == history->prices.end()) {
- history_map::reverse_iterator r = history->prices.rbegin();
- age = (*r).first;
- price = (*r).second;
+ if (aleftcomm.details.price && arightcomm.details.price) {
+ amount_t leftprice(*aleftcomm.details.price);
+ leftprice.in_place_reduce();
+ amount_t rightprice(*arightcomm.details.price);
+ rightprice.in_place_reduce();
+
+ if (leftprice.commodity() == rightprice.commodity()) {
+ return (leftprice - rightprice).sign() < 0;
} else {
- age = (*i).first;
- if (moment != age) {
- if (i != history->prices.begin()) {
- --i;
- age = (*i).first;
- price = (*i).second;
- } else {
- age = moment_t();
- }
- } else {
- price = (*i).second;
- }
+ // Since we have two different amounts, there's really no way
+ // to establish a true sorting order; we'll just do it based
+ // on the numerical values.
+ leftprice.clear_commodity();
+ rightprice.clear_commodity();
+ return (leftprice - rightprice).sign() < 0;
}
}
+
+ if (! aleftcomm.details.date && arightcomm.details.date)
+ return true;
+ if (aleftcomm.details.date && ! arightcomm.details.date)
+ return false;
+
+ if (aleftcomm.details.date && arightcomm.details.date) {
+ duration_t diff = *aleftcomm.details.date - *arightcomm.details.date;
+ return diff.is_negative();
+ }
+
+ if (! aleftcomm.details.tag && arightcomm.details.tag)
+ return true;
+ if (aleftcomm.details.tag && ! arightcomm.details.tag)
+ return false;
+
+ if (aleftcomm.details.tag && arightcomm.details.tag)
+ return *aleftcomm.details.tag < *arightcomm.details.tag;
+
+ assert(false);
+ return true;
}
+}
- if (updater && ! (flags & COMMODITY_STYLE_NOMARKET))
- (*updater)(*this, moment, age,
- (history && history->prices.size() > 0 ?
- (*history->prices.rbegin()).first : moment_t()), price);
+commodity_pool_t::commodity_pool_t() : default_commodity(NULL)
+{
+ null_commodity = create("");
+ null_commodity->add_flags(COMMODITY_STYLE_NOMARKET |
+ COMMODITY_STYLE_BUILTIN);
- return price;
+ // Add time commodity conversions, so that timelog's may be parsed
+ // in terms of seconds, but reported as minutes or hours.
+ commodity_t * commodity = create("s");
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN);
+
+ amount_t::parse_conversion(*this, "1.0m", "60s");
+ amount_t::parse_conversion(*this, "1.0h", "60m");
}
-bool annotated_commodity_t::operator==(const commodity_t& comm) const
+commodity_t * commodity_pool_t::create(const string& symbol)
{
- // If the base commodities don't match, the game's up.
- if (base != comm.base)
- return false;
+ shared_ptr<commodity_base_t> base_commodity(new commodity_base_t(symbol));
+ std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
- if (price &&
- (! comm.annotated ||
- price != static_cast<const annotated_commodity_t&>(comm).price))
- return false;
+ DEBUG("amounts.commodities", "Creating base commodity " << symbol);
- if (date &&
- (! comm.annotated ||
- date != static_cast<const annotated_commodity_t&>(comm).date))
- return false;
+ // Create the "qualified symbol" version of this commodity's symbol
+ if (commodity_t::symbol_needs_quotes(symbol)) {
+ commodity->qualified_symbol = "\"";
+ *commodity->qualified_symbol += symbol;
+ *commodity->qualified_symbol += "\"";
+ }
- if (tag &&
- (! comm.annotated ||
- tag != static_cast<const annotated_commodity_t&>(comm).tag))
- return false;
+ DEBUG("amounts.commodities",
+ "Creating commodity '" << commodity->symbol() << "'");
- return true;
+ // Start out the new commodity with the default commodity's flags
+ // and precision, if one has been defined.
+#if 0
+ // jww (2007-05-02): This doesn't do anything currently!
+ if (default_commodity)
+ commodity->drop_flags(COMMODITY_STYLE_THOUSANDS |
+ COMMODITY_STYLE_NOMARKET);
+#endif
+
+ commodity->ident = commodities.size();
+
+ std::pair<commodities_t::iterator, bool> result =
+ commodities.insert(commodity.get());
+ if (! result.second) {
+ assert(false);
+ return NULL;
+ } else {
+ return commodity.release();
+ }
}
-void
-annotated_commodity_t::write_annotations(std::ostream& out,
- const optional<amount_t>& price,
- const optional<moment_t>& date,
- const optional<string>& tag)
+commodity_t * commodity_pool_t::find_or_create(const string& symbol)
{
- if (price)
- out << " {" << *price << '}';
-
- if (date)
- out << " [" << *date << ']';
+ DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);
- if (tag)
- out << " (" << *tag << ')';
+ commodity_t * commodity = find(symbol);
+ if (commodity)
+ return commodity;
+ return create(symbol);
}
-commodity_t *
-annotated_commodity_t::create(const commodity_t& comm,
- const optional<amount_t>& price,
- const optional<moment_t>& date,
- const optional<string>& tag,
- const string& mapping_key)
+commodity_t * commodity_pool_t::find(const string& symbol)
{
- std::auto_ptr<annotated_commodity_t> commodity(new annotated_commodity_t);
+ DEBUG("amounts.commodities", "Find commodity " << symbol);
- // Set the annotated bits
- commodity->price = price;
- commodity->date = date;
- commodity->tag = tag;
+ typedef commodity_pool_t::commodities_t::nth_index<1>::type
+ commodities_by_name;
- commodity->ptr = &comm;
- assert(commodity->ptr);
- commodity->base = comm.base;
- assert(commodity->base);
+ commodities_by_name& name_index = commodities.get<1>();
+ commodities_by_name::const_iterator i = name_index.find(symbol);
+ if (i != name_index.end())
+ return *i;
+ else
+ return NULL;
+}
- commodity->qualified_symbol = comm.symbol();
+commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident)
+{
+ DEBUG("amounts.commodities", "Find commodity by ident " << ident);
- DEBUG("amounts.commodities", "Creating annotated commodity "
- << "symbol " << commodity->symbol()
- << " key " << mapping_key << std::endl
- << " price " << (price ? price->to_string() : "NONE") << " "
- << " date " << (date ? *date : moment_t()) << " "
- << " tag " << (tag ? *tag : "NONE"));
+ typedef commodity_pool_t::commodities_t::nth_index<0>::type
+ commodities_by_ident;
- // Add the fully annotated name to the map, so that this symbol may
- // quickly be found again.
- std::pair<commodities_map::iterator, bool> result
- = commodities.insert(commodities_pair(mapping_key, commodity.get()));
- if (! result.second)
+ commodities_by_ident& ident_index = commodities.get<0>();
+ commodities_by_ident::iterator i = ident_index.find(ident);
+ if (i != ident_index.end())
+ return *i;
+ else
return NULL;
+}
- commodity->ident = commodities_by_ident->size();
- commodities_by_ident->push_back(commodity.get());
+commodity_t *
+commodity_pool_t::create(const string& symbol, const annotation_t& details)
+{
+ commodity_t * new_comm = create(symbol);
+ if (! new_comm)
+ return NULL;
- return commodity.release();
+ if (details)
+ return find_or_create(*new_comm, details);
+ else
+ return new_comm;
}
namespace {
- string make_qualified_name(const commodity_t& comm,
- const optional<amount_t>& price,
- const optional<moment_t>& date,
- const optional<string>& tag)
+ string make_qualified_name(const commodity_t& comm,
+ const annotation_t& details)
{
- if (price && price->sign() < 0)
+ assert(details);
+
+ if (details.price && details.price->sign() < 0)
throw_(amount_error, "A commodity's price may not be negative");
std::ostringstream name;
-
comm.write(name);
- annotated_commodity_t::write_annotations(name, price, date, tag);
+ annotated_commodity_t::write_annotations(name, details);
DEBUG("amounts.commodities", "make_qualified_name for "
- << comm.qualified_symbol << std::endl
- << " price " << (price ? price->to_string() : "NONE") << " "
- << " date " << (date ? *date : moment_t()) << " "
- << " tag " << (tag ? *tag : "NONE"));
-
+ << comm.qualified_symbol << std::endl << details);
DEBUG("amounts.commodities", "qualified_name is " << name.str());
return name.str();
@@ -332,87 +390,88 @@ namespace {
}
commodity_t *
-annotated_commodity_t::find_or_create(const commodity_t& comm,
- const optional<amount_t>& price,
- const optional<moment_t>& date,
- const optional<string>& tag)
+commodity_pool_t::find(const string& symbol, const annotation_t& details)
{
- string name = make_qualified_name(comm, price, date, tag);
+ commodity_t * comm = find(symbol);
+ if (! comm)
+ return NULL;
- commodity_t * ann_comm = commodity_t::find(name);
- if (ann_comm) {
- assert(ann_comm->annotated);
- return ann_comm;
+ if (details) {
+ string name = make_qualified_name(*comm, details);
+
+ if (commodity_t * ann_comm = find(name)) {
+ assert(ann_comm->annotated &&
+ ann_comm->as_annotated().details);
+ return ann_comm;
+ }
+ return NULL;
+ } else {
+ return comm;
}
- return create(comm, price, date, tag, name);
}
-bool compare_amount_commodities::operator()(const amount_t * left,
- const amount_t * right) const
+commodity_t *
+commodity_pool_t::find_or_create(const string& symbol,
+ const annotation_t& details)
{
- commodity_t& leftcomm(left->commodity());
- commodity_t& rightcomm(right->commodity());
+ commodity_t * comm = find(symbol);
+ if (! comm)
+ return NULL;
- int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
- if (cmp != 0)
- return cmp < 0;
+ if (details)
+ return find_or_create(*comm, details);
+ else
+ return comm;
+}
- if (! leftcomm.annotated) {
- assert(rightcomm.annotated);
- return true;
- }
- else if (! rightcomm.annotated) {
- assert(leftcomm.annotated);
- return false;
- }
- else {
- annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm));
- annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm));
+commodity_t *
+commodity_pool_t::create(commodity_t& comm,
+ const annotation_t& details,
+ const string& mapping_key)
+{
+ assert(comm);
+ assert(details);
+ assert(! mapping_key.empty());
- if (! aleftcomm.price && arightcomm.price)
- return true;
- if (aleftcomm.price && ! arightcomm.price)
- return false;
+ std::auto_ptr<commodity_t> commodity
+ (new annotated_commodity_t(&comm, details));
- if (aleftcomm.price && arightcomm.price) {
- amount_t leftprice(*aleftcomm.price);
- leftprice.in_place_reduce();
- amount_t rightprice(*arightcomm.price);
- rightprice.in_place_reduce();
+ commodity->qualified_symbol = comm.symbol();
+ assert(! commodity->qualified_symbol->empty());
- if (leftprice.commodity() == rightprice.commodity()) {
- return (leftprice - rightprice).sign() < 0;
- } else {
- // Since we have two different amounts, there's really no way
- // to establish a true sorting order; we'll just do it based
- // on the numerical values.
- leftprice.clear_commodity();
- rightprice.clear_commodity();
- return (leftprice - rightprice).sign() < 0;
- }
- }
+ DEBUG("amounts.commodities", "Creating annotated commodity "
+ << "symbol " << commodity->symbol()
+ << " key " << mapping_key << std::endl << details);
- if (! aleftcomm.date && arightcomm.date)
- return true;
- if (aleftcomm.date && ! arightcomm.date)
- return false;
+ // Add the fully annotated name to the map, so that this symbol may
+ // quickly be found again.
+ commodity->ident = commodities.size();
+ commodity->mapping_key_ = mapping_key;
- if (aleftcomm.date && arightcomm.date) {
- duration_t diff = *aleftcomm.date - *arightcomm.date;
- return diff.is_negative();
- }
+ std::pair<commodities_t::iterator, bool> result
+ = commodities.insert(commodity.get());
+ if (! result.second) {
+ assert(false);
+ return NULL;
+ } else {
+ return commodity.release();
+ }
+}
- if (! aleftcomm.tag && arightcomm.tag)
- return true;
- if (aleftcomm.tag && ! arightcomm.tag)
- return false;
+commodity_t * commodity_pool_t::find_or_create(commodity_t& comm,
+ const annotation_t& details)
+{
+ assert(comm);
+ assert(details);
- if (aleftcomm.tag && arightcomm.tag)
- return *aleftcomm.tag < *arightcomm.tag;
+ string name = make_qualified_name(comm, details);
+ assert(! name.empty());
- assert(false);
- return true;
+ if (commodity_t * ann_comm = find(name)) {
+ assert(ann_comm->annotated && ann_comm->as_annotated().details);
+ return ann_comm;
}
+ return create(comm, details, name);
}
} // namespace ledger