/* * Copyright (c) 2003-2009, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "amount.h" #include "commodity.h" #include "annotate.h" #include "pool.h" #include "quotes.h" namespace ledger { commodity_pool_t::commodity_pool_t() : default_commodity(NULL), keep_base(false), quote_leeway(86400), get_quotes(false), get_commodity_quote(commodity_quote_from_script) { TRACE_CTOR(commodity_pool_t, ""); null_commodity = create(""); null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); } commodity_t * commodity_pool_t::create(const string& symbol) { shared_ptr base_commodity(new commodity_t::base_t(symbol)); std::auto_ptr commodity(new commodity_t(this, base_commodity)); DEBUG("amounts.commodities", "Creating base commodity " << symbol); // 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 += "\""; } DEBUG("amounts.commodities", "Creating commodity '" << commodity->symbol() << "'"); std::pair result = commodities.insert(commodities_map::value_type(commodity->mapping_key(), commodity.get())); assert(result.second); return commodity.release(); } commodity_t * commodity_pool_t::find_or_create(const string& symbol) { DEBUG("amounts.commodities", "Find-or-create commodity " << symbol); commodity_t * commodity = find(symbol); if (commodity) return commodity; return create(symbol); } commodity_t * commodity_pool_t::find(const string& symbol) { DEBUG("amounts.commodities", "Find commodity " << symbol); commodities_map::const_iterator i = commodities.find(symbol); if (i != commodities.end()) return (*i).second; return NULL; } commodity_t * commodity_pool_t::create(const string& symbol, const annotation_t& details) { commodity_t * new_comm = create(symbol); if (! new_comm) return NULL; if (details) return find_or_create(*new_comm, details); else return new_comm; } namespace { string make_qualified_name(const commodity_t& comm, const annotation_t& details) { assert(details); if (details.price && details.price->sign() < 0) throw_(amount_error, _("A commodity's price may not be negative")); std::ostringstream name; comm.print(name); details.print(name, comm.parent().keep_base); DEBUG("amounts.commodities", "make_qualified_name for " << *comm.qualified_symbol << std::endl << details); DEBUG("amounts.commodities", "qualified_name is " << name.str()); return name.str(); } } commodity_t * commodity_pool_t::find(const string& symbol, const annotation_t& details) { commodity_t * comm = find(symbol); if (! comm) return NULL; if (details) { string name = make_qualified_name(*comm, details); if (commodity_t * ann_comm = find(name)) { assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); return ann_comm; } return NULL; } else { return comm; } } commodity_t * commodity_pool_t::find_or_create(const string& symbol, const annotation_t& details) { commodity_t * comm = find(symbol); if (! comm) return NULL; if (details) return find_or_create(*comm, details); else return comm; } 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()); std::auto_ptr commodity (new annotated_commodity_t(&comm, details)); commodity->qualified_symbol = comm.symbol(); assert(! commodity->qualified_symbol->empty()); DEBUG("amounts.commodities", "Creating annotated commodity " << "symbol " << commodity->symbol() << " key " << mapping_key << std::endl << details); // Add the fully annotated name to the map, so that this symbol may // quickly be found again. commodity->mapping_key_ = mapping_key; std::pair result = commodities.insert(commodities_map::value_type(mapping_key, commodity.get())); assert(result.second); return commodity.release(); } commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, const annotation_t& details) { assert(comm); assert(details); string name = make_qualified_name(comm, details); assert(! name.empty()); if (commodity_t * ann_comm = find(name)) { assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); return ann_comm; } return create(comm, details, name); } void commodity_pool_t::exchange(commodity_t& commodity, const amount_t& per_unit_cost, const datetime_t& moment) { DEBUG("commodity.prices.add", "exchanging commodity " << commodity << " at per unit cost " << per_unit_cost << " on " << moment); commodity_t& base_commodity (commodity.annotated ? as_annotated_commodity(commodity).referent() : commodity); base_commodity.add_price(moment, per_unit_cost); } cost_breakdown_t commodity_pool_t::exchange(const amount_t& amount, const amount_t& cost, const bool is_per_unit, const optional& moment, const optional& tag) { DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost); DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit); #if defined(DEBUG_ON) if (moment) DEBUG("commodity.prices.add", "exchange: moment = " << *moment); if (tag) DEBUG("commodity.prices.add", "exchange: tag = " << *tag); #endif commodity_t& commodity(amount.commodity()); annotation_t * current_annotation = NULL; if (commodity.annotated) current_annotation = &as_annotated_commodity(commodity).details; amount_t per_unit_cost = (is_per_unit || amount.is_realzero() ? cost : cost / amount).abs(); DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost); if (! per_unit_cost.is_realzero()) exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME()); cost_breakdown_t breakdown; breakdown.final_cost = ! is_per_unit ? cost : cost * amount; DEBUG("commodity.prices.add", "exchange: final-cost = " << breakdown.final_cost); if (current_annotation && current_annotation->price) breakdown.basis_cost = (*current_annotation->price * amount).unrounded(); else breakdown.basis_cost = breakdown.final_cost; DEBUG("commodity.prices.add", "exchange: basis-cost = " << breakdown.basis_cost); annotation_t annotation(per_unit_cost, moment ? moment->date() : optional(), tag); annotation.add_flags(ANNOTATION_PRICE_CALCULATED); if (moment) annotation.add_flags(ANNOTATION_DATE_CALCULATED); if (tag) annotation.add_flags(ANNOTATION_TAG_CALCULATED); breakdown.amount = amount_t(amount, annotation); DEBUG("commodity.prices.add", "exchange: amount = " << breakdown.amount); return breakdown; } optional commodity_pool_t::parse_price_directive(char * line) { char * date_field_ptr = line; char * time_field_ptr = next_element(date_field_ptr); if (! time_field_ptr) return none; string date_field = date_field_ptr; char * symbol_and_price; datetime_t datetime; if (std::isdigit(time_field_ptr[0])) { symbol_and_price = next_element(time_field_ptr); if (! symbol_and_price) return none; datetime = parse_datetime(date_field + " " + time_field_ptr); } else if (std::isdigit(date_field_ptr[0])) { symbol_and_price = time_field_ptr; datetime = datetime_t(parse_date(date_field)); } else { symbol_and_price = date_field_ptr; datetime = CURRENT_TIME(); } string symbol; commodity_t::parse_symbol(symbol_and_price, symbol); price_point_t point; point.when = datetime; point.price.parse(symbol_and_price); VERIFY(point.price.valid()); DEBUG("commodity.download", "Looking up symbol: " << symbol); if (commodity_t * commodity = amount_t::current_pool->find_or_create(symbol)) { DEBUG("commodity.download", "Adding price for " << symbol << ": " << point.when << " " << point.price); commodity->add_price(point.when, point.price, true); commodity->add_flags(COMMODITY_KNOWN); return point; } return none; } commodity_t * commodity_pool_t::parse_price_expression(const std::string& str, const bool add_prices, const optional& moment) { scoped_array buf(new char[str.length() + 1]); std::strcpy(buf.get(), str.c_str()); char * price = std::strchr(buf.get(), '='); if (price) *price++ = '\0'; if (commodity_t * commodity = find_or_create(trim_ws(buf.get()))) { if (price && add_prices) { for (char * p = std::strtok(price, ";"); p; p = std::strtok(NULL, ";")) { commodity->add_price(moment ? *moment : CURRENT_TIME(), amount_t(p)); } } return commodity; } return NULL; } } // namespace ledger