/**
* 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.List;
import java.util.logging.Logger;
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 org.w3c.dom.Element;
/**
* A TradeRoute holds all information for a unit to follow along a trade route.
*/
public class TradeRoute extends FreeColGameObject
implements Cloneable, Ownable {
private static final Logger logger = Logger.getLogger(TradeRoute.class.getName());
private static final String CARGO_TAG = "cargo";
/**
* The name of this trade route.
*/
private String name;
/**
* The number of carriers using this route.
* (Only used in TradeRouteDialog for the present)
*/
private int count;
/**
* Whether the trade route has been modified. This is of interest only to
* the client and can be ignored for XML serialization.
*/
private boolean modified = false;
/**
* The <code>Player</code> who owns this trade route. This is necessary to
* ensure that malicious clients can not modify the trade routes of other
* players.
*/
private Player owner;
/**
* A list of stops.
*/
private List<Stop> stops = new ArrayList<Stop>();
/**
* Creates a new <code>TradeRoute</code> instance.
*
* @param game a <code>Game</code> value
* @param name a <code>String</code> value
* @param player a <code>Player</code> value
*/
public TradeRoute(Game game, String name, Player player) {
super(game);
this.name = name;
this.owner = player;
this.count = 0;
}
/**
* Creates a new <code>TradeRoute</code> instance.
*
* @param game a <code>Game</code> value
* @param in a <code>XMLStreamReader</code> value
* @exception XMLStreamException if an error occurs
*/
public TradeRoute(Game game, XMLStreamReader in) throws XMLStreamException {
super(game, in);
readFromXML(in);
}
/**
* Creates a new <code>TradeRoute</code> instance.
*
* @param game a <code>Game</code> value
* @param e an <code>Element</code> value
*/
public TradeRoute(Game game, Element e) {
super(game, e);
readFromXMLElement(e);
}
/**
* Copy all fields from another trade route to this one. This is useful when
* an updated route is received on the server side from the client.
*
* @param other The route to copy from.
*/
public synchronized void updateFrom(TradeRoute other) {
setName(other.getName());
setCount(other.getCount());
stops.clear();
for (Stop otherStop : other.getStops()) {
addStop(new Stop(otherStop));
}
}
/**
* Get the <code>Modified</code> value.
*
* @return a <code>boolean</code> value
*/
public final boolean isModified() {
return modified;
}
/**
* Set the <code>Modified</code> value.
*
* @param newModified The new Modified value.
*/
public final void setModified(final boolean newModified) {
this.modified = newModified;
}
/**
* Get the <code>Name</code> value.
*
* @return a <code>String</code> value
*/
public final String getName() {
return name;
}
/**
* Set the <code>Name</code> value.
*
* @param newName The new Name value.
*/
public final void setName(final String newName) {
this.name = newName;
}
/**
* Get the <code>Count</code> value.
*
* @return The count of trade route users.
*/
public int getCount() {
return count;
}
/**
* Set the <code>Count</code> value.
*
* @param newCount The new Count value.
*/
public void setCount(int newCount) {
count = newCount;
}
/**
* Add a new <code>Stop</code> to this trade route.
*
* @param stop The <code>Stop</code> to add.
*/
public void addStop(Stop stop) {
stops.add(stop);
}
/**
* Get the <code>Owner</code> value.
*
* @return a <code>Player</code> value
*/
public final Player getOwner() {
return owner;
}
/**
* Set the <code>Owner</code> value.
*
* @param newOwner The new Owner value.
*/
public final void setOwner(final Player newOwner) {
this.owner = newOwner;
}
public List<Unit> getAssignedUnits(){
List<Unit> list = new ArrayList<Unit>();
for(Unit unit : owner.getUnits()){
if(unit.getTradeRoute() == this){
list.add(unit);
}
}
return list;
}
/**
* Get the <code>Stops</code> value.
*
* @return an <code>ArrayList<Stop></code> value
*/
public final List<Stop> getStops() {
return stops;
}
/**
* Set the <code>Stops</code> value.
*
* @param newStops The new Stops value.
*/
public final void setStops(final List<Stop> newStops) {
this.stops = newStops;
}
/**
* Clone the trade route and return a deep copy.
* <p>
* The copied trade route has no reference back to the original and can
* safely be used as a temporary copy. It is NOT registered with the game,
* but will have the same unique id as the original.
*
* @return deep copy of trade route.
*/
public TradeRoute clone() {
try {
TradeRoute copy = (TradeRoute) super.clone();
copy.replaceStops(getStops());
return copy;
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("Clone should be supported!", e);
}
}
/**
* Replace all the stops for this trade route with the stops passed from
* another trade route.
*
* This method will create a deep copy as it creates new stops based on the given ones.
*
* @param otherStops The new stops to use.
* @see #clone()
*/
private void replaceStops(List<Stop> otherStops) {
stops = new ArrayList<Stop>();
for (Stop otherStop : otherStops) {
addStop(new Stop(otherStop));
}
}
public static boolean isStopValid(Unit unit, Stop stop) {
return TradeRoute.isStopValid(unit.getOwner(), stop);
}
public static boolean isStopValid(Player player, Stop stop) {
return (stop == null) ? false : stop.isValid();
}
public class Stop {
private Location location;
private List<GoodsType> cargo = new ArrayList<GoodsType>();
/**
* Whether the stop has been modified. This is of interest only to the
* client and can be ignored for XML serialization.
*/
private boolean modified = false;
public Stop(Location location) {
this.location = location;
}
/**
* Copy constructor. Creates a stop based on the given one.
*
* @param other
*/
public Stop(Stop other) {
this.location = other.location;
this.cargo = new ArrayList<GoodsType>(other.cargo);
}
/**
* Is this stop valid?
*
* @return True if the stop is valid.
*/
public boolean isValid() {
return location != null
&& !((FreeColGameObject) location).isDisposed()
&& !((location instanceof Ownable)
&& (!getOwner().owns((Ownable) location)));
}
/**
* Get the <code>Modified</code> value.
*
* @return a <code>boolean</code> value
*/
public final boolean isModified() {
return modified;
}
/**
* Set the <code>Modified</code> value.
*
* @param newModified The new Modified value.
*/
public final void setModified(final boolean newModified) {
this.modified = newModified;
}
/**
* Get the <code>Location</code> value.
*
* @return a <code>Location</code> value
*/
public final Location getLocation() {
return location;
}
/**
* Get the <code>Cargo</code> value.
*
* @return a cloned <code>ArrayList<Integer></code> value
*/
public final List<GoodsType> getCargo() {
return cargo;
}
/**
* Set the cargo values.
*
* @param cargo and arraylist of cargo values.
*/
public final void setCargo(List<GoodsType> cargo) {
this.cargo.clear();
this.cargo.addAll(cargo);
}
public void addCargo(GoodsType newCargo) {
cargo.add(newCargo);
}
public String toString() {
return (isValid()) ? getLocation().toString()
: "invalid stop";
}
}
/**
* Returns the tag name of the root element representing this object.
*
* @return "tradeRouteStop".
*/
public static String getStopXMLElementTagName() {
return "tradeRouteStop";
}
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("name", getName());
out.writeAttribute("owner", getOwner().getId());
for (Stop stop : stops) {
out.writeStartElement(getStopXMLElementTagName());
out.writeAttribute("location", stop.getLocation().getId());
for (GoodsType cargoType : stop.getCargo()) {
out.writeStartElement(CARGO_TAG);
out.writeAttribute(ID_ATTRIBUTE_TAG, cargoType.getId());
out.writeEndElement();
}
out.writeEndElement();
}
out.writeEndElement();
}
/**
* Nasty hack to find the stop location. Trade routes tend to precede
* the map so colonies are not yet defined when trade routes are read.
*/
private Location findLocation(Game game, String id) {
FreeColGameObject fcgo = game.getFreeColGameObject(id);
if (fcgo == null) {
if (id.startsWith(Colony.getXMLElementTagName())) {
return new Colony(game, id);
} else if (id.startsWith(Europe.getXMLElementTagName())) {
return new Europe(game, id);
} else {
try { throw new IllegalStateException("STOP = " + id + " => null"); } catch (Exception e) { e.printStackTrace(); }
return null;
}
}
return (Location) fcgo;
}
/**
* Initialize this object from an XML-representation of this object.
*
* @param in The input stream with the XML.
*/
protected void readFromXMLImpl(XMLStreamReader in) throws XMLStreamException {
setId(in.getAttributeValue(null, ID_ATTRIBUTE));
setName(in.getAttributeValue(null, "name"));
String ownerID = in.getAttributeValue(null, "owner");
Game game = getGame();
owner = (Player) game.getFreeColGameObject(ownerID);
if (owner == null) owner = new Player(game, ownerID);
stops.clear();
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
if (getStopXMLElementTagName().equals(in.getLocalName())) {
String locationId = in.getAttributeValue(null, "location");
Stop stop = new Stop(findLocation(game, locationId));
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
if (in.getLocalName().equals(CARGO_TAG)) {
String id = in.getAttributeValue(null, ID_ATTRIBUTE_TAG);
if (id == null) {
// TODO: remove support for old format
List<GoodsType> goodsList = getSpecification().getGoodsTypeList();
for (int cargoIndex : readFromArrayElement("cargo", in, new int[0])) {
stop.addCargo(goodsList.get(cargoIndex));
}
} else {
stop.addCargo(getSpecification().getGoodsType(id));
in.nextTag();
}
}
}
// Do not test stop.isValid(), the colony may not exist yet
stops.add(stop);
}
}
}
@Override
public String toString() {
return getName();
}
/**
* Returns the tag name of the root element representing this object.
*
* @return "tradeRoute".
*/
public static String getXMLElementTagName() {
return "tradeRoute";
}
}