/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.common.model;
import java.util.logging.Logger;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import org.w3c.dom.Element;
/**
* Objects of this class hold the market data for a particular type of
* good.
*/
public class MarketData extends FreeColGameObject {
private static final Logger logger = Logger.getLogger(MarketData.class.getName());
/**
* Bounds on price movements.
*/
public static final int MINIMUM_PRICE = 1;
public static final int MAXIMUM_PRICE = 19;
/**
* What type of goods is this.
*/
private GoodsType goodsType;
/**
* Purchase price.
*/
private int costToBuy;
/**
* Sale price.
*/
private int paidForSale;
/**
* Amount of this goods in the market.
*/
private int amountInMarket;
/**
* The initial price.
*/
private int initialPrice;
/**
* Arrears owed to the crown.
*/
private int arrears;
/**
* Total sales.
*/
private int sales;
/**
* Total income before taxes.
*/
private int incomeBeforeTaxes;
/**
* Total income after taxes.
*/
private int incomeAfterTaxes;
/**
* Place to save to old price so as to be able to tell when a price change
* message should be generated. Not necessary to serialize.
*/
private int oldPrice;
/**
* Has this good been traded?
*/
private boolean traded;
/**
* Creates a new <code>MarketData</code> instance.
*
* @param goodsType a <code>GoodsType</code> value
*/
public MarketData(Game game, GoodsType goodsType) {
super(game);
this.goodsType = goodsType;
paidForSale = goodsType.getInitialSellPrice();
costToBuy = goodsType.getInitialBuyPrice();
amountInMarket = goodsType.getInitialAmount();
initialPrice = goodsType.getInitialSellPrice();
arrears = 0;
sales = 0;
incomeBeforeTaxes = 0;
incomeAfterTaxes = 0;
oldPrice = costToBuy;
traded = false;
}
/**
* Instantiate a new <code>MarketData</code> from an
* XML representation.
*
* @param game The <code>Game</code> this object belongs to.
* @param in The input stream containing the XML.
* @throws XMLStreamException if an error occured during parsing.
*/
public MarketData(Game game, XMLStreamReader in) throws XMLStreamException {
super(game, in);
readFromXML(in);
}
/**
* Instantiates a new <code>MarketData</code> with the given
* ID. The object should later be initialized by calling either
* {@link #readFromXML(XMLStreamReader)} or
* {@link #readFromXMLElement(Element)}.
*
* @param game The <code>Game</code> in which this object belong.
* @param id The unique identifier for this object.
*/
public MarketData(Game game, String id) {
super(game, id);
}
/**
* Adjust the prices. Sets the costToBuy and paidForSale fields
* from the amount in the market, initial price and goods-type
* specific information.
*/
public void price() {
if (!goodsType.isStorable()) return;
int diff = goodsType.getPriceDifference();
float amountPrice = initialPrice * (goodsType.getInitialAmount()
/ (float) amountInMarket);
int newSalePrice = Math.round(amountPrice);
int newPrice = newSalePrice + diff;
// Work-around to limit prices of new world goods
// and related manufactured goods.
if ((goodsType.isNewWorldGoodsType()
|| (goodsType.getRawMaterial() != null
&& goodsType.getRawMaterial().isNewWorldGoodsType()))
&& newSalePrice > initialPrice + 2) {
newSalePrice = initialPrice + 2;
newPrice = newSalePrice + diff;
}
// Another hack to prevent price changing too fast in one hit.
// Push the amount in market back as well to keep this stable.
//
// Prices that change by more than the buy/sell difference
// allow big traders to exploit the market and extract free
// money... not sure I want to be fighting economic reality
// but game balance demands it here.
if (costToBuy > 0) {
if (newPrice > costToBuy + diff) {
amountPrice -= newPrice - (costToBuy + diff);
amountInMarket = Math.round(goodsType.getInitialAmount()
* (initialPrice / amountPrice));
logger.info("Clamped price rise for " + getId()
+ " from " + newPrice
+ " to " + (costToBuy + diff));
newPrice = costToBuy + diff;
} else if (newPrice < costToBuy - diff) {
amountPrice += (costToBuy - diff) - newPrice;
amountInMarket = Math.round(goodsType.getInitialAmount()
* (initialPrice / amountPrice));
logger.info("Clamped price fall for " + getId()
+ " from " + newPrice
+ " to " + (costToBuy - diff));
newPrice = costToBuy - diff;
}
newSalePrice = newPrice - diff;
}
// Clamp extremes.
if (newPrice > MAXIMUM_PRICE) {
newPrice = MAXIMUM_PRICE;
newSalePrice = newPrice - diff;
} else if (newSalePrice < MINIMUM_PRICE) {
newSalePrice = MINIMUM_PRICE;
newPrice = newSalePrice + diff;
}
costToBuy = newPrice;
paidForSale = newSalePrice;
}
/**
* Get the type of goods of this <code>MarketData</code>.
*
* @return The goods type for this data.
*/
public final GoodsType getGoodsType() {
return goodsType;
}
/**
* Get the <code>CostToBuy</code> value.
*
* @return an <code>int</code> value
*/
public final int getCostToBuy() {
return costToBuy;
}
/**
* Set the <code>CostToBuy</code> value.
*
* @param newCostToBuy The new CostToBuy value.
*/
public final void setCostToBuy(final int newCostToBuy) {
this.costToBuy = newCostToBuy;
}
/**
* Get the <code>PaidForSale</code> value.
*
* @return an <code>int</code> value
*/
public final int getPaidForSale() {
return paidForSale;
}
/**
* Set the <code>PaidForSale</code> value.
*
* @param newPaidForSale The new PaidForSale value.
*/
public final void setPaidForSale(final int newPaidForSale) {
this.paidForSale = newPaidForSale;
}
/**
* Get the <code>AmountInMarket</code> value.
*
* @return an <code>int</code> value
*/
public final int getAmountInMarket() {
return amountInMarket;
}
/**
* Set the <code>AmountInMarket</code> value.
*
* @param newAmountInMarket The new AmountInMarket value.
*/
public final void setAmountInMarket(final int newAmountInMarket) {
this.amountInMarket = newAmountInMarket;
}
/**
* Get the <code>InitialPrice</code> value.
*
* @return an <code>int</code> value
*/
public final int getInitialPrice() {
return initialPrice;
}
/**
* Set the <code>InitialPrice</code> value.
*
* @param newInitialPrice The new InitialPrice value.
*/
public final void setInitialPrice(final int newInitialPrice) {
this.initialPrice = newInitialPrice;
}
/**
* Get the <code>Arrears</code> value.
*
* @return an <code>int</code> value
*/
public final int getArrears() {
return arrears;
}
/**
* Set the <code>Arrears</code> value.
*
* @param newArrears The new Arrears value.
*/
public final void setArrears(final int newArrears) {
this.arrears = newArrears;
}
/**
* Get the <code>Sales</code> value.
*
* @return an <code>int</code> value
*/
public final int getSales() {
return sales;
}
/**
* Set the <code>Sales</code> value.
*
* @param newSales The new Sales value.
*/
public final void setSales(final int newSales) {
this.sales = newSales;
}
/**
* Get the <code>IncomeBeforeTaxes</code> value.
*
* @return an <code>int</code> value
*/
public final int getIncomeBeforeTaxes() {
return incomeBeforeTaxes;
}
/**
* Set the <code>IncomeBeforeTaxes</code> value.
*
* @param newIncomeBeforeTaxes The new IncomeBeforeTaxes value.
*/
public final void setIncomeBeforeTaxes(final int newIncomeBeforeTaxes) {
this.incomeBeforeTaxes = newIncomeBeforeTaxes;
}
/**
* Get the <code>IncomeAfterTaxes</code> value.
*
* @return an <code>int</code> value
*/
public final int getIncomeAfterTaxes() {
return incomeAfterTaxes;
}
/**
* Set the <code>IncomeAfterTaxes</code> value.
*
* @param newIncomeAfterTaxes The new IncomeAfterTaxes value.
*/
public final void setIncomeAfterTaxes(final int newIncomeAfterTaxes) {
this.incomeAfterTaxes = newIncomeAfterTaxes;
}
/**
* Get the old price in this <code>MarketData</code>.
*
* @return The old price.
*/
public final int getOldPrice() {
return oldPrice;
}
/**
* Set the old price in this <code>MarketData</code>.
*
* @param oldPrice A `new' old price.
*/
public void setOldPrice(int oldPrice) {
this.oldPrice = oldPrice;
}
/**
* Has there been trading in this <code>MarketData</code>?
*
* @return Whether trading has occurred.
**/
public final boolean getTraded() {
return traded;
}
/**
* Set the trade status of this <code>MarketData</code>.
*
* @param traded The trade status to set.
**/
public void setTraded(boolean traded) {
this.traded = traded;
}
/**
* This method writes an XML-representation of this object to
* the given stream.
*
* Only attributes visible to the given <code>Player</code> will
* be added to that representation if <code>showAll</code> is
* set to <code>false</code>.
*
* @param out The target stream.
* @param player The <code>Player</code> this XML-representation
* should be made for, or <code>null</code> if
* <code>showAll == true</code>.
* @param showAll Only attributes visible to <code>player</code>
* will be added to the representation if <code>showAll</code>
* is set to <i>false</i>.
* @param toSavedGame If <code>true</code> then information that
* is only needed when saving a game is added.
* @throws XMLStreamException if there are any problems writing
* to the stream.
*/
protected void toXMLImpl(XMLStreamWriter out, Player player,
boolean showAll, boolean toSavedGame)
throws XMLStreamException {
// Start element:
out.writeStartElement(getXMLElementTagName());
out.writeAttribute(ID_ATTRIBUTE, getId());
out.writeAttribute("goods-type", goodsType.getId());
out.writeAttribute("amount", Integer.toString(amountInMarket));
out.writeAttribute("initialPrice", Integer.toString(initialPrice));
out.writeAttribute("arrears", Integer.toString(arrears));
out.writeAttribute("sales", Integer.toString(sales));
out.writeAttribute("incomeBeforeTaxes",
Integer.toString(incomeBeforeTaxes));
out.writeAttribute("incomeAfterTaxes",
Integer.toString(incomeAfterTaxes));
out.writeAttribute("traded", Boolean.toString(traded));
out.writeEndElement();
}
/**
* Initialize this object from an XML-representation of this object.
*
* @param in The input stream with the XML.
*/
public void readAttributes(XMLStreamReader in) throws XMLStreamException {
String goodsTypeStr = in.getAttributeValue(null, "goods-type");
if (goodsTypeStr == null) { // @compat 0.9.x
goodsTypeStr = in.getAttributeValue(null, ID_ATTRIBUTE);
setDefaultId(getGame());
// end compatibility code
} else {
setId(in.getAttributeValue(null, ID_ATTRIBUTE));
}
if (goodsTypeStr == null) {
throw new XMLStreamException("Missing goods-type");
}
goodsType = getSpecification().getGoodsType(goodsTypeStr);
amountInMarket = getAttribute(in, "amount", 0);
initialPrice = getAttribute(in, "initialPrice", -1);
arrears = getAttribute(in, "arrears", 0);
sales = getAttribute(in, "sales", 0);
incomeBeforeTaxes = getAttribute(in, "incomeBeforeTaxes", 0);
incomeAfterTaxes = getAttribute(in, "incomeAfterTaxes", 0);
traded = getAttribute(in, "traded", sales != 0);
costToBuy = -1; // Disable price change clamping
price();
oldPrice = costToBuy;
}
/**
* Returns the tag name of the root element representing this object.
*
* @return "marketData"
*/
public static String getXMLElementTagName() {
return "marketData";
}
}