/**
* 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.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.freecolandroid.xml.stream.XMLStreamConstants;
import org.freecolandroid.xml.stream.XMLStreamException;
import org.freecolandroid.xml.stream.XMLStreamReader;
import org.freecolandroid.xml.stream.XMLStreamWriter;
import net.sf.freecol.common.util.Utils;
import org.w3c.dom.Element;
/**
* This class implements a simple economic model where a market holds
* all goods to be sold and the price of a particular goods type is
* determined solely by its availability in that market.
*/
public final class Market extends FreeColGameObject implements Ownable {
/**
* European markets are bottomless. Goods present never decrease
* below this threshold.
*/
public static final int MINIMUM_AMOUNT = 100;
/**
* Constant for specifying the access to this <code>Market</code>
* when selling goods.
*/
public static enum Access {
EUROPE,
CUSTOM_HOUSE,
}
private final Map<GoodsType, MarketData> marketData
= new HashMap<GoodsType, MarketData>();
private Player owner;
private ArrayList<TransactionListener> transactionListeners
= new ArrayList<TransactionListener>();
/**
* Main constructor for creating a market for a new player.
*/
public Market(Game game, Player player) {
super(game);
this.owner = player;
/*
* Create the market data containers for each type of goods
* and seed these objects with the initial amount of each type
* of goods.
*/
for (GoodsType goodsType : getSpecification().getGoodsTypeList()) {
if (goodsType.isStorable()) {
marketData.put(goodsType, new MarketData(game, goodsType));
}
}
}
/**
* Initiates a new <code>Market</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 a problem was encountered
* during parsing.
*/
public Market(Game game, XMLStreamReader in) throws XMLStreamException {
super(game, in);
readFromXML(in);
}
/**
* Initiates a new <code>Market</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 Market(Game game, String id) {
super(game, id);
}
/**
* Describe <code>putMarketData</code> method here.
*
* @param goodsType a <code>GoodsType</code> value
* @param data a <code>MarketData</code> value
*/
private void putMarketData(GoodsType goodsType, MarketData data) {
marketData.put(goodsType, data);
}
/**
* Gets the market data for a specified goods type, creating it
* if it does not exist yet.
*
* @param goodsType The <code>GoodsType</code> to query.
* @return The <code>MarketData</code> required.
*/
private MarketData requireMarketData(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
if (data == null) {
data = new MarketData(getGame(), goodsType);
putMarketData(goodsType, data);
}
return data;
}
// ------------------------------------------------------------ API methods
/**
* Makes a slight randomization to all the new world and luxury goods
* types. Used at the start of game by the PreGameController.
*
* @param random A pseudo-random number source.
*/
public void randomizeInitialPrice(Random random) {
for (GoodsType type : getGame().getSpecification().getGoodsTypeList()) {
if (type.isNewWorldGoodsType() || type.isNewWorldLuxuryType()) {
int add = Utils.randomInt(null, null, random, 3);
if (add > 0) {
setInitialPrice(type, add + getInitialPrice(type));
}
}
}
}
/**
* Return the market data for a type of goods. This one is public
* so the server can send individual MarketData updates.
*
* @param goodsType a <code>GoodsType</code> value
* @return a <code>MarketData</code> value
*/
public MarketData getMarketData(GoodsType goodsType) {
return marketData.get(goodsType);
}
/**
* Gets the owner of this <code>Market</code>.
*
* @return The owner of this <code>Market</code>.
*/
public Player getOwner() {
return owner;
}
/**
* Sets the owner of this <code>Market</code>.
*
* @param owner The <code>Player</code> to own this <code>Market</code>.
*/
public void setOwner(Player owner) {
this.owner = owner;
}
/**
* Has a type of goods been traded in this market?
*
* @param type The type of goods to consider.
* @return True if the goods type has been traded.
*/
public boolean hasBeenTraded(GoodsType type) {
MarketData data = getMarketData(type);
return data != null && data.getTraded();
}
/**
* Determines the cost to buy a single unit of a particular type of good.
*
* @param type A <code>GoodsType</code> value.
* @return The cost to buy a single unit of the given type of goods.
*/
public int getCostToBuy(GoodsType type) {
MarketData data = getMarketData(type);
return (data == null) ? 0 : data.getCostToBuy();
}
/**
* Determines the price paid for the sale of a single unit of a particular
* type of goods.
*
* @param type A <code>GoodsType</code> value.
* @return The price for a single unit of the given type of goods
* if it is for sale.
*/
public int getPaidForSale(GoodsType type) {
MarketData data = getMarketData(type);
return (data == null) ? 0 : data.getPaidForSale();
}
/**
* Add (or remove) some goods to this market.
*
* @param goodsType The <code>GoodsType</code> to add.
* @param amount The amount of goods.
*/
public void addGoodsToMarket(GoodsType goodsType, int amount) {
MarketData data = requireMarketData(goodsType);
// Markets are bottomless, amount can not go below the threshold
data.setAmountInMarket(Math.max(MINIMUM_AMOUNT,
data.getAmountInMarket() + amount));
data.setTraded(true);
data.price();
}
/**
* Gets the initial price of a given goods type.
*
* @param goodsType The <code>GoodsType</code> to get the initial price of.
* @return The initial price.
*/
public int getInitialPrice(GoodsType goodsType) {
MarketData data = requireMarketData(goodsType);
return data.getInitialPrice();
}
/**
* Sets the initial price of a given goods type.
*
* @param goodsType The <code>GoodsType</code> to set the initial price of.
* @param amount The new initial price.
*/
public void setInitialPrice(GoodsType goodsType, int amount) {
MarketData data = requireMarketData(goodsType);
data.setInitialPrice(amount);
}
/**
* Gets the price of a given goods when the <code>Player</code> buys.
*
* @param type a <code>GoodsType</code> value
* @param amount The amount of goods.
* @return The bid price of the given goods.
*/
public int getBidPrice(GoodsType type, int amount) {
MarketData data = getMarketData(type);
return (data == null) ? 0 : amount * data.getCostToBuy();
}
/**
* Gets the price of a given goods when the <code>Player</code> sells.
*
* @param type a <code>GoodsType</code> value
* @param amount The amount of goods.
* @return The sale price of the given goods.
*/
public int getSalePrice(GoodsType type, int amount) {
MarketData data = getMarketData(type);
return (data == null) ? 0 : amount * data.getPaidForSale();
}
/**
* Gets the price of a given goods when the <code>Player</code> sells.
*
* @param goods a <code>Goods</code> value
* @return an <code>int</code> value
*/
public int getSalePrice(Goods goods) {
return getSalePrice(goods.getType(), goods.getAmount());
}
/**
* Gets the arrears for of a given goods type.
*
* @param goodsType The <code>GoodsType</code> to get arrears for.
* @return The arrears.
*/
public int getArrears(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
return (data == null) ? 0 : data.getArrears();
}
/**
* Sets the arrears associated with a type of goods.
*
* @param goodsType The <code>GoodsType</code> to set the arrears for.
* @param value The amount of arrears to set.
*/
public void setArrears(GoodsType goodsType, int value) {
MarketData data = requireMarketData(goodsType);
data.setArrears(value);
}
/**
* Gets the sales of a type of goods.
*
* @param goodsType The <code>GoodsType</code> to get the sales for.
* @return The current sales amount.
*/
public int getSales(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
return (data == null) ? 0 : data.getSales();
}
/**
* Modifies the sales of a type of goods.
*
* @param goodsType The <code>GoodsType</code> to set the sales for.
* @param amount The amount of sales to add to the current amount.
*/
public void modifySales(GoodsType goodsType, int amount) {
if (amount != 0) {
MarketData data = requireMarketData(goodsType);
data.setSales(data.getSales() + amount);
data.setTraded(true);
}
}
/**
* Gets the income before taxes for a type of goods.
*
* @param goodsType The <code>GoodsType</code> to get the income for.
* @return The current income before taxes.
*/
public int getIncomeBeforeTaxes(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
return (data == null) ? 0 : data.getIncomeBeforeTaxes();
}
/**
* Modifies the income before taxes on sales of a type of goods.
*
* @param goodsType The <code>GoodsType</code> to set the income for.
* @param amount The amount of tax income to add to the current amount.
*/
public void modifyIncomeBeforeTaxes(GoodsType goodsType, int amount) {
MarketData data = requireMarketData(goodsType);
data.setIncomeBeforeTaxes(data.getIncomeBeforeTaxes() + amount);
}
/**
* Gets the income after taxes for a type of goods.
*
* @param goodsType The <code>GoodsType</code> to get the income for.
* @return The current income after taxes.
*/
public int getIncomeAfterTaxes(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
return (data == null) ? 0 : data.getIncomeAfterTaxes();
}
/**
* Modifies the income after taxes on sales of a type of goods.
*
* @param goodsType The <code>GoodsType</code> to set the income for.
* @param amount The amount of tax income to add to the current amount.
*/
public void modifyIncomeAfterTaxes(GoodsType goodsType, int amount) {
MarketData data = requireMarketData(goodsType);
data.setIncomeAfterTaxes(data.getIncomeAfterTaxes() + amount);
}
/**
* Gets the amount of a goods type in the market.
*
* @param goodsType The <code>GoodsType</code> to get the amount of.
* @return The current amount of the goods in the market.
*/
public int getAmountInMarket(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
return (data == null) ? 0 : data.getAmountInMarket();
}
/**
* Has the price of a type of goods changed in this market?
*
* @param goodsType The type of goods to consider.
* @return True if the price has changed.
*/
public boolean hasPriceChanged(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
return data != null && data.getOldPrice() != 0
&& data.getOldPrice() != data.getCostToBuy();
}
/**
* Clear any price changes for a type of goods.
*
* @param goodsType The type of goods to consider.
*/
public void flushPriceChange(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
if (data != null) {
data.setOldPrice(data.getCostToBuy());
}
}
/**
* Make up a <code>ModelMessage</code> describing the change in this
* <code>Market</code> for a specified type of goods.
*
* @param goodsType The <code>GoodsType</code> that has changed price.
* @return A message describing the change.
*/
public ModelMessage makePriceChangeMessage(GoodsType goodsType) {
MarketData data = getMarketData(goodsType);
int oldPrice = data.getOldPrice();
int newPrice = data.getCostToBuy();
return (oldPrice == newPrice) ? null
: new ModelMessage(ModelMessage.MessageType.MARKET_PRICES,
((newPrice > oldPrice)
? "model.market.priceIncrease"
: "model.market.priceDecrease"),
this, goodsType)
.addStringTemplate("%market%", owner.getMarketName())
.add("%goods%", goodsType.getNameKey())
.addAmount("%buy%", newPrice)
.addAmount("%sell%", data.getPaidForSale());
}
/**
* Adds a transaction listener for notification of any transaction
*
* @param listener the listener
*/
public void addTransactionListener(TransactionListener listener) {
transactionListeners.add(listener);
}
/**
* Removes a transaction listener
*
* @param listener the listener
*/
public void removeTransactionListener(TransactionListener listener) {
transactionListeners.remove(listener);
}
/**
* Returns an array of all the TransactionListener added to this Market.
*
* @return all of the TransactionListener added or an empty array if no
* listeners have been added
*/
public TransactionListener[] getTransactionListener() {
return transactionListeners.toArray(new TransactionListener[0]);
}
/**
* 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("owner", owner.getId());
if (player == owner || showAll || toSavedGame) {
for (MarketData data : marketData.values()) {
data.toXML(out, player, showAll, toSavedGame);
}
}
out.writeEndElement();
}
/**
* Initialize this object from an XML-representation of this object.
*
* @param in The input stream with the XML.
* @throws XMLStreamException on problems with the stream.
* TODO: Get rid of the price() when the server sends all
* price changes.
*/
protected void readFromXMLImpl(XMLStreamReader in)
throws XMLStreamException {
Game game = getGame();
setId(in.getAttributeValue(null, FreeColObject.ID_ATTRIBUTE));
owner = getFreeColGameObject(in, "owner", Player.class);
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
if (in.getLocalName().equals(MarketData.getXMLElementTagName())) {
String id = in.getAttributeValue(null, FreeColObject.ID_ATTRIBUTE);
MarketData data = (MarketData) game.getFreeColGameObject(id);
if (data == null) {
data = new MarketData(game, in);
} else {
data.readFromXML(in);
}
putMarketData(data.getGoodsType(), data);
}
}
}
/**
* Returns the tag name of the root element representing this object.
*
* @return "market".
*/
public static String getXMLElementTagName() {
return "market";
}
}