diff options
author | John Wiegley <johnw@newartisans.com> | 2007-05-03 06:11:04 +0000 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-04-13 03:38:36 -0400 |
commit | c59018c29ddfc7a46aeb951fbcd5cb5b93f47ec0 (patch) | |
tree | 204d28bfa2bdbfe8d7f550877faa114c1e93859f /src/commodity.cc | |
parent | f9f24fab933266ab8e12da7eef4cc2a906f77350 (diff) | |
download | fork-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.cc | 543 |
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 |