/**
* 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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import net.sf.freecol.common.model.Ownable;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import org.w3c.dom.Element;
/**
* Contains goods and can be used by a {@link Location} to make certain
* tasks easier.
*/
public class GoodsContainer extends FreeColGameObject implements Ownable {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(Location.class.getName());
public static final int CARGO_SIZE = 100;
public static final String STORED_GOODS_TAG = "storedGoods";
public static final String OLD_STORED_GOODS_TAG = "oldStoredGoods";
/** The list of Goods stored in this <code>GoodsContainer</code>. */
private Map<GoodsType, Integer> storedGoods = new HashMap<GoodsType, Integer>();
/** The previous list of Goods stored in this <code>GoodsContainer</code>. */
private Map<GoodsType, Integer> oldStoredGoods = new HashMap<GoodsType, Integer>();
/** The owner of this <code>GoodsContainer</code>. */
private final Location parent;
/**
* Creates an empty <code>GoodsContainer</code>.
*
* @param game The <code>Game</code> in which this <code>GoodsContainer</code> belong.
* @param parent The <code>Location</code> this <code>GoodsContainer</code> will be containg goods for.
*/
public GoodsContainer(Game game, Location parent) {
super(game);
if (parent == null) {
throw new IllegalArgumentException("Location of GoodsContainer must not be null!");
}
this.parent = parent;
}
/**
* Initiates a new <code>GoodsContainer</code> from an <code>Element</code>.
*
* @param game The <code>Game</code> in which this <code>GoodsContainer</code>
* belong.
* @param parent The object using this <code>GoodsContainer</code>
* for storing it's goods.
* @param in The input stream containing the XML.
* @throws XMLStreamException if a problem was encountered
* during parsing.
*/
public GoodsContainer(Game game, Location parent, XMLStreamReader in) throws XMLStreamException {
super(game, in);
if (parent == null) {
throw new IllegalArgumentException("Location of GoodsContainer must not be null!");
}
this.parent = parent;
readFromXML(in);
}
/**
* Initiates a new <code>GoodsContainer</code> from an <code>Element</code>.
*
* @param game The <code>Game</code> in which this <code>GoodsContainer</code>
* belong.
* @param parent The object using this <code>GoodsContainer</code>
* for storing it's goods.
* @param e An XML-element that will be used to initialize
* this object.
*/
public GoodsContainer(Game game, Location parent, Element e) {
super(game, e);
if (parent == null) {
throw new IllegalArgumentException("Location of GoodsContainer must not be null!");
}
this.parent = parent;
readFromXMLElement(e);
}
/**
* Gets the owner of this <code>GoodsContainer</code>.
*
* @return The <code>Player</code> controlling this
* {@link Ownable}.
*/
public Player getOwner() {
return (parent instanceof Ownable) ? ((Ownable) parent).getOwner()
: null;
}
/**
* Sets the owner of this <code>Ownable</code>.
*
* @param p The <code>Player</code> that should take ownership
* of this {@link Ownable}.
* @exception UnsupportedOperationException if not implemented.
*/
public void setOwner(Player p) {
throw new UnsupportedOperationException("Can not set GoodsContainer owner");
}
/**
* Removes all references to this object.
*
* @return A list of disposed objects.
*/
public List<FreeColGameObject> disposeList() {
storedGoods.clear();
List<FreeColGameObject> objects = new ArrayList<FreeColGameObject>();
objects.addAll(super.disposeList());
return objects;
}
/**
* Dispose of this GoodsContainer.
*/
public void dispose() {
disposeList();
}
/**
* Adds a <code>Goods</code> to this containter.
* @param g The Goods to add to this container.
*/
public boolean addGoods(AbstractGoods g) {
return addGoods(g.getType(), g.getAmount());
}
/**
* Adds the given amount of the given type of goods.
* @param type The type of goods to add.
* @param amount The type of amount to add.
*/
public boolean addGoods(GoodsType type, int amount) {
int oldAmount = getGoodsCount(type);
int newAmount = oldAmount + amount;
if (newAmount < 0) {
throw new IllegalStateException("Operation would leave "
+ newAmount + " goods of type "
+ type.getNameKey() + " in Location " + parent);
} else if (newAmount == 0) {
storedGoods.remove(type);
} else {
storedGoods.put(type, newAmount);
}
return true;
}
/**
* Removes Goods from this containter.
* @param g The Goods to remove from this container.
*/
public Goods removeGoods(AbstractGoods g) {
return removeGoods(g.getType(), g.getAmount());
}
public Goods removeGoods(GoodsType type) {
return removeGoods(type, INFINITY);
}
/**
* Removes the given amount of the given type of goods.
*
* @param type The type of goods to remove.
* @param amount The type of amount to remove.
* @return A Goods with the requested or available amount that has been removed
*/
public Goods removeGoods(GoodsType type, int amount) {
int oldAmount = getGoodsCount(type);
int newAmount = oldAmount - amount;
Goods removedGoods;
if (newAmount > 0) {
removedGoods = new Goods(getGame(), parent, type, amount);
storedGoods.put(type, newAmount);
} else {
removedGoods = new Goods(getGame(), parent, type, oldAmount);
storedGoods.remove(type);
}
return removedGoods;
}
/**
* Removes all goods above given amount, provided that the goods
* are storable and do not ignore warehouse limits.
*
* @param newAmount The treshold.
*/
public void removeAbove(int newAmount) {
for (GoodsType goodsType : storedGoods.keySet()) {
if (goodsType.isStorable() && !goodsType.limitIgnored() &&
storedGoods.get(goodsType) > newAmount) {
setAmount(goodsType, newAmount);
}
}
}
/**
* Set the amount of goods in this container.
*
* @param goodsType The <code>GoodsType</code> to set the amount of.
* @param newAmount The new amount.
*/
public void setAmount(GoodsType goodsType, int newAmount) {
if (newAmount == 0) {
storedGoods.remove(goodsType);
} else {
storedGoods.put(goodsType, newAmount);
}
}
/**
* Removes all goods.
*
*/
public void removeAll() {
storedGoods.clear();
}
/**
* Checks if any storable type of goods has reached the given
* amount.
*
* @param amount The amount.
* @return <code>true</code> if any type of goods,
* except for <code>Goods.FOOD</code>, has reached
* the given amount.
*/
public boolean hasReachedCapacity(int amount) {
for (GoodsType goodsType : storedGoods.keySet()) {
if (goodsType.isStorable() && !goodsType.limitIgnored() &&
storedGoods.get(goodsType) > amount) {
return true;
}
}
return false;
}
/**
* Checks if the specified <code>Goods</code> is in this container.
*
* @param g The <code>Goods</code> to test the presence of.
* @return The result.
*/
public boolean contains(Goods g) {
throw new UnsupportedOperationException();
}
/**
* Gets the amount of one type of goods in this container.
*
* @param type The <code>GoodsType</code> being looked for.
* @return The amount of this type of goods in this container.
*/
public int getGoodsCount(GoodsType type) {
return (storedGoods.containsKey(type))
? storedGoods.get(type).intValue()
: 0;
}
/**
* Gets the amount of one type of goods at the beginning of the turn.
*
* @param type The <code>GoodsType</code> being looked for.
* @return The amount of this type of goods in this container at
* the beginning of the turn
*/
public int getOldGoodsCount(GoodsType type) {
return (oldStoredGoods.containsKey(type))
? oldStoredGoods.get(type).intValue()
: 0;
}
/**
* Gets the amount of space that the goods in this container will consume.
* Each occupied cargo slot contains an amount in [1, CARGO_SIZE].
*
* @return The amount of space taken by this containers goods.
*/
public int getSpaceTaken() {
int count = 0;
for (Integer amount : storedGoods.values()) {
if (amount % CARGO_SIZE == 0) {
count += amount/CARGO_SIZE;
} else {
count += amount/CARGO_SIZE + 1;
}
}
return count;
}
/**
* Gets an <code>Iterator</code> of every <code>Goods</code> in this
* <code>GoodsContainer</code>. Each <code>Goods</code> have a maximum
* amount of CARGO_SIZE.
*
* @return The <code>Iterator</code>.
* @see #getCompactGoods
*/
public Iterator<Goods> getGoodsIterator() {
return getGoods().iterator();
}
/**
* Returns an <code>ArrayList</code> containing all
* <code>Goods</code> in this <code>GoodsContainer</code>. Each
* <code>Goods</code> has a maximum amount of CARGO_SIZE.
*
* @return The <code>ArrayList</code>.
* @see #getGoodsIterator
*/
public List<Goods> getGoods() {
ArrayList<Goods> totalGoods = new ArrayList<Goods>();
for (GoodsType goodsType : storedGoods.keySet()) {
int amount = storedGoods.get(goodsType).intValue();
while (amount > 0) {
totalGoods.add(new Goods(getGame(), parent, goodsType, (amount >= CARGO_SIZE ? CARGO_SIZE : amount)));
amount -= CARGO_SIZE;
}
}
return totalGoods;
}
/**
* Gets an <code>Iterator</code> of every <code>Goods</code> in this
* <code>GoodsContainer</code>. There is only one <code>Goods</code>
* for each type of goods.
*
* @return The <code>Iterator</code>.
* @see #getGoodsIterator
*/
public List<Goods> getCompactGoods() {
ArrayList<Goods> totalGoods = new ArrayList<Goods>();
for (Entry<GoodsType, Integer> entry : storedGoods.entrySet()) {
if (entry.getValue() > 0) {
totalGoods.add(new Goods(getGame(), parent, entry.getKey(), entry.getValue()));
}
}
return totalGoods;
}
/**
* Gets an <code>Iterator</code> of every <code>Goods</code> in this
* <code>GoodsContainer</code>. There is only one <code>Goods</code>
* for each type of goods.
*
* @return The <code>Iterator</code>.
* @see #getGoodsIterator
*/
public List<Goods> getFullGoods() {
ArrayList<Goods> totalGoods = new ArrayList<Goods>();
for (GoodsType goodsType : storedGoods.keySet()) {
totalGoods.add(new Goods(getGame(), parent, goodsType, storedGoods.get(goodsType)));
}
return totalGoods;
}
/**
* Prepares this <code>GoodsContainer</code> for a new turn.
*/
public void saveState() {
oldStoredGoods.clear();
for (Map.Entry<GoodsType, Integer> entry : storedGoods.entrySet()) {
oldStoredGoods.put(entry.getKey(), new Integer(entry.getValue().intValue()));
}
}
/**
* Has this goods containers contents changed from what was recorded
* last time the state was saved?
*
* @return True if the contents have changed.
*/
public boolean hasChanged() {
for (GoodsType type : getSpecification().getGoodsTypeList()) {
int oldCount = getOldGoodsCount(type);
int newCount = getGoodsCount(type);
if (oldCount != newCount) return true;
}
return false;
}
/**
* Fire property changes for all goods that have seen level changes
* since the last saveState().
*/
public void fireChanges() {
for (GoodsType type : getSpecification().getGoodsTypeList()) {
int oldCount = getOldGoodsCount(type);
int newCount = getGoodsCount(type);
if (oldCount != newCount) {
firePropertyChange(type.getId(), oldCount, newCount);
}
}
oldStoredGoods.clear();
}
/**
* This method writes an XML-representation of this object to
* the given stream.
*
* <br><br>
*
* 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());
if (showAll || toSavedGame || player == getOwner()) {
writeStorage(out, STORED_GOODS_TAG, storedGoods);
writeStorage(out, OLD_STORED_GOODS_TAG, oldStoredGoods);
}
out.writeEndElement();
}
private void writeStorage(XMLStreamWriter out, String tag, Map<GoodsType, Integer> storage)
throws XMLStreamException {
if (!storage.isEmpty()) {
out.writeStartElement(tag);
for (Map.Entry<GoodsType, Integer> entry : storage.entrySet()) {
out.writeStartElement(Goods.getXMLElementTagName());
out.writeAttribute("type", entry.getKey().getId());
out.writeAttribute("amount", entry.getValue().toString());
out.writeEndElement();
}
out.writeEndElement();
}
}
/**
* Initialize this object from an XML-representation of this object.
* @param in The input stream with the XML.
*/
protected void readAttributes(XMLStreamReader in) throws XMLStreamException {
setId(in.getAttributeValue(null, ID_ATTRIBUTE));
storedGoods.clear();
oldStoredGoods.clear();
}
@Override
protected void readChildren(XMLStreamReader in) throws XMLStreamException {
Map<GoodsType, Integer> storage;
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
if (in.getLocalName().equals(STORED_GOODS_TAG)) {
storage = storedGoods;
} else if (in.getLocalName().equals(OLD_STORED_GOODS_TAG)) {
storage = oldStoredGoods;
} else {
continue;
}
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
if (in.getLocalName().equals(Goods.getXMLElementTagName())) {
GoodsType goodsType = getGame().getSpecification()
.getGoodsType(in.getAttributeValue(null, "type"));
Integer amount = new Integer(in.getAttributeValue(null, "amount"));
storage.put(goodsType, amount);
}
in.nextTag();
}
}
}
/**
* Partial writer, so that "remove" messages can be brief.
*
* @param out The target stream.
* @param fields The fields to write.
* @throws XMLStreamException If there are problems writing the stream.
*/
@Override
protected void toXMLPartialImpl(XMLStreamWriter out, String[] fields)
throws XMLStreamException {
toXMLPartialByClass(out, getClass(), fields);
}
/**
* Partial reader, so that "remove" messages can be brief.
*
* @param in The input stream with the XML.
* @throws XMLStreamException If there are problems reading the stream.
*/
@Override
public void readFromXMLPartialImpl(XMLStreamReader in)
throws XMLStreamException {
readFromXMLPartialByClass(in, getClass());
}
/**
* Creates a <code>String</code> representation of this
* <code>GoodsContainer</code>.
*/
public String toString() {
StringBuffer sb = new StringBuffer(200);
sb.append("GoodsContainer with: ");
for (Map.Entry<GoodsType, Integer> entry : storedGoods.entrySet()) {
sb.append(entry.getKey() + "=" + entry.getValue() + ", ");
}
sb.setLength(sb.length() - 2);
return sb.toString();
}
/**
* Gets the tag name of the root element representing this object.
* @return "goodsContainer".
*/
public static String getXMLElementTagName() {
return "goodsContainer";
}
}