/** * 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.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.HashSet; import java.util.List; import java.util.Set; 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 net.sf.freecol.common.model.PlayerExploredTile; /** * Contains <code>TileItem</code>s and can be used by a {@link Tile} * to make certain tasks easier. */ public class TileItemContainer extends FreeColGameObject { @SuppressWarnings("unused") private static final Logger logger = Logger.getLogger(TileItemContainer.class.getName()); /** * The owner of this <code>TileItemContainer</code>. */ private Tile tile; /** * All tile items sorted by zIndex. */ private List<TileItem> tileItems = new ArrayList<TileItem>(); // sort tile items ascending by zIndex private final Comparator<TileItem> tileItemComparator = new Comparator<TileItem>() { public int compare(TileItem tileItem1, TileItem tileItem2) { return tileItem1.getZIndex() - tileItem2.getZIndex(); } }; // ------------------------------------------------------------ constructor /** * Creates an empty <code>TileItemContainer</code>. * * @param game The <code>Game</code> in which this <code>TileItemContainer</code> belong. * @param tile The <code>Tile</code> this <code>TileItemContainer</code> will be containg TileItems for. */ public TileItemContainer(Game game, Tile tile) { super(game); if (tile == null) { throw new IllegalArgumentException("Tile must not be 'null'."); } this.tile = tile; } /** * Initiates a new <code>TileItemContainer</code> from an XML stream. * * @param game The <code>Game</code> in which this <code>TileItemContainer</code> * belong. * @param tile The <code>Tile</code> using this <code>TileItemContainer</code> * for storing it's TileItem. * @param in The input stream containing the XML. * @throws XMLStreamException if a problem was encountered * during parsing. */ public TileItemContainer(Game game, Tile tile, XMLStreamReader in) throws XMLStreamException { super(game, in); if (tile == null) { throw new IllegalArgumentException("Tile must not be 'null'."); } this.tile = tile; readFromXML(in); } // ------------------------------------------------------------ checking/retrieval functions /** * Invalidate the production cache of the owning colony, if there * is one, but only if the tile is actually being used. */ private void invalidateCache() { Colony colony = tile.getColony(); if (colony != null && colony.isTileInUse(tile)) { colony.invalidateCache(); } } /** * Return the <code>Tile</code> this TileItemContainer belongs to. * * @return a <code>Tile</code> value */ public Tile getTile() { return tile; } /** * Get the <code>TileItems</code> value. * * @return a <code>List<TileItem></code> value */ public final List<TileItem> getTileItems() { return tileItems; } /** * Set the <code>TileItems</code> value. * * @param newTileItems The new TileItems value. */ public final void setTileItems(final List<TileItem> newTileItems) { this.tileItems = newTileItems; invalidateCache(); } /** * Returns the <code>Resource</code> item or null. * * @return a <code>Resource</code> value */ public Resource getResource() { for (TileItem item : tileItems) { if (item instanceof Resource) { return (Resource) item; } } return null; } /** * Gets the tile improvement of the given type if any. * * @param type The <code>TileImprovementType</code> to look for. * @return The tile improvement of the given type if present, * otherwise null. */ public TileImprovement getImprovement(TileImprovementType type) { for (TileItem item : tileItems) { if (item instanceof TileImprovement && ((TileImprovement) item).getType() == type) { return (TileImprovement) item; } } return null; } /** * Returns the road improvement or null. * * @return a <code>TileImprovement</code> value */ public TileImprovement getRoad() { for (TileItem item : tileItems) { if (item instanceof TileImprovement && ((TileImprovement) item).isRoad()) { return (TileImprovement) item; } } return null; } /** * Returns the river improvement or null. * * @return a <code>TileImprovement</code> value */ public TileImprovement getRiver() { for (TileItem item : tileItems) { if (item instanceof TileImprovement && ((TileImprovement) item).isRiver()) { return (TileImprovement) item; } } return null; } /** * Get the <code>LostCityRumour</code> value. * * @return a <code>LostCityRumour</code> value */ public final LostCityRumour getLostCityRumour() { for (TileItem item : tileItems) { if (item instanceof LostCityRumour) { return (LostCityRumour) item; } } return null; } /** * Remove improvements incompatible with the given TileType. This * method is called whenever the type of the container's tile * changes, i.e. due to clearing. */ public void removeIncompatibleImprovements() { TileType tileType = tile.getType(); Iterator<TileItem> iterator = tileItems.iterator(); boolean removed = false; while (iterator.hasNext()) { TileItem item = iterator.next(); if (!item.isTileTypeAllowed(tileType)) { iterator.remove(); item.dispose(); removed = true; } } if (removed) { invalidateCache(); } } /** * Returns a <code>List</code> of the <code>TileImprovement</code>s * in this <code>TileItemContainer</code>. * * @return The <code>List</code>. */ public List<TileImprovement> getImprovements() { return getImprovements(false); } /** * Returns a <code>List</code> of the completed * <code>TileImprovement</code>s in this * <code>TileItemContainer</code>. * * @return The <code>List</code>. */ public List<TileImprovement> getCompletedImprovements() { return getImprovements(true); } /** * Returns a <code>List</code> of the <code>TileImprovement</code>s * in this <code>TileItemContainer</code>. * * @return The <code>List</code>. */ private List<TileImprovement> getImprovements(boolean completedOnly) { List<TileImprovement> improvements = new ArrayList<TileImprovement>(); for (TileItem item : tileItems) { if (item instanceof TileImprovement && (!completedOnly || ((TileImprovement) item).isComplete())) { improvements.add((TileImprovement) item); } } return improvements; } /** * Determine the total bonus for a GoodsType. Checks Resource and * all Improvements, unless onlyNatural is <code>true</code>, in * which case only natural Improvements will be considered. This * is necessary in order to calculate secondary production, which * does not profit from artificial Improvements, such as plowing. * * @param g a <code>GoodsType</code> value * @param unitType an <code>UnitType</code> value * @param tilePotential an <code>int</code> value * @param onlyNatural a <code>boolean</code> value * @return The total bonus */ public int getTotalBonusPotential(GoodsType g, UnitType unitType, int tilePotential, boolean onlyNatural) { int potential = tilePotential; int improvementBonus = 0; for (TileItem item : tileItems) { if (item instanceof TileImprovement) { TileImprovement improvement = (TileImprovement) item; if (improvement.getType().isNatural() || !onlyNatural) { improvementBonus += improvement.getBonus(g); } } else if (item instanceof Resource) { potential = ((Resource) item).getBonus(g, unitType, potential); } } if (potential > 0) { potential += improvementBonus; } return potential; } /** * Describe <code>getProductionBonus</code> method here. * * @param goodsType a <code>GoodsType</code> value * @param unitType a <code>UnitType</code> value * @return a <code>Modifier</code> value */ public Set<Modifier> getProductionBonus(GoodsType goodsType, UnitType unitType) { Set<Modifier> result = new HashSet<Modifier>(); for (TileItem item : tileItems) { if (item instanceof Resource) { result.addAll(((Resource) item).getType().getProductionModifier(goodsType, unitType)); } else if (item instanceof TileImprovement) { Modifier modifier = ((TileImprovement) item).getProductionModifier(goodsType); if (modifier != null) { result.add(modifier); } } } return result; } /** * Determine the movement cost to this <code>Tile</code> from * another <code>Tile</code>. * Does not consider special unit abilities. * * @param basicMoveCost The basic cost. * @param fromTile The <code>Tile</code> to move from. * @return The movement cost. */ public int getMoveCost(int basicMoveCost, Tile fromTile) { int moveCost = basicMoveCost; for (TileItem item : tileItems) { if (item instanceof TileImprovement && ((TileImprovement) item).isComplete()) { moveCost = ((TileImprovement) item).getMovementCost(moveCost, fromTile); } } return moveCost; } // ------------------------------------------------------------ add/remove from container /** * Adds a <code>TileItem</code> to this container. * * @param item The TileItem to add to this container. * @return The added TileItem or the existing TileItem or <code>null</code> on error */ public TileItem addTileItem(TileItem item) { if (item == null) { return null; } else { for (int index = 0; index < tileItems.size(); index++) { TileItem oldItem = tileItems.get(index); if (item instanceof TileImprovement && oldItem instanceof TileImprovement && ((TileImprovement) oldItem).getType().getId() .equals(((TileImprovement) item).getType().getId())) { if (((TileImprovement) oldItem).getMagnitude() < ((TileImprovement) item).getMagnitude()) { tileItems.set(index, item); oldItem.dispose(); invalidateCache(); return item; } else { // Found it, but not replacing. return oldItem; } } else if (oldItem.getZIndex() > item.getZIndex()) { tileItems.add(index, item); invalidateCache(); return item; } } tileItems.add(item); invalidateCache(); return item; } } /** * Removes TileItem from this container. * * @param item The TileItem to remove from this container. * @return The TileItem that has been removed from this container (if any). */ public TileItem removeTileItem(TileItem item) { boolean removed = tileItems.remove(item); if (removed) { invalidateCache(); return item; } else { return null; } } public <T extends TileItem> void removeAll(Class<T> c) { Iterator<TileItem> iterator = tileItems.iterator(); while (iterator.hasNext()) { if (c.isInstance(iterator.next())) { iterator.remove(); } } } public void copyFrom(TileItemContainer tic) { copyFrom(tic, true, false); } public void copyFrom(TileItemContainer tic, boolean importResources) { copyFrom(tic, importResources, false); } public void copyFrom(TileItemContainer tic, boolean importResources, boolean copyOnlyNatural) { tileItems.clear(); for (TileItem item : tic.getTileItems()) { if (item instanceof Resource) { if (importResources) { Resource ticR = (Resource) item; Resource r = new Resource(getGame(), tile, ticR.getType(), ticR.getQuantity()); tileItems.add(r); } } else if (item instanceof LostCityRumour && !copyOnlyNatural) { LostCityRumour ticR = (LostCityRumour) item; LostCityRumour r = new LostCityRumour(getGame(), tile, ticR.getType(), ticR.getName()); addTileItem(r); } else if (item instanceof TileImprovement) { if (!copyOnlyNatural || ((TileImprovement) item).getType().isNatural()) { TileImprovement ti = (TileImprovement) item; TileImprovement newTI = new TileImprovement(getGame(), tile, ti.getType()); newTI.setMagnitude(ti.getMagnitude()); newTI.setStyle(ti.getStyle()); newTI.setTurnsToComplete(ti.getTurnsToComplete()); addTileItem(newTI); } } } } /** * Checks if the specified <code>TileItem</code> is in this container. * * @param t The <code>TileItem</code> to test the presence of. * @return The result. */ public boolean contains(TileItem t) { return tileItems.contains(t); } /** * Checks if a TileImprovement of this Type is already in this container. * * @param type The <code>TileImprovementType</code> to test the presence of. * @return The result. */ public TileImprovement findTileImprovementType(TileImprovementType type) { for (TileItem item : tileItems) { if (item instanceof TileImprovement && ((TileImprovement) item).getType() == type) { return (TileImprovement) item; } } return null; } /** * Will check whether this tile has a completed improvement of the given * type. * * Useful for checking whether the tile for instance has a road or is * plowed. * * @param type * The type to check for. * @return Whether the tile has the improvement and the improvement is * completed. */ public boolean hasImprovement(TileImprovementType type) { TileImprovement improvement = findTileImprovementType(type); return improvement != null && improvement.isComplete(); } /** * Removes all references to this object. */ public void dispose() { tileItems.clear(); super.dispose(); } // ------------------------------------------------------------ manipulation methods /** * Creates a river <code>TileImprovement</code> and adds to this Tile/Container. * Checking for overwrite is done by {@link #addTileItem}. * @param magnitude The Magnitude of the river to be created * @param style an <code>int</code> value * @return The new river added, or the existing river TileImprovement */ public TileImprovement addRiver(int magnitude, int style) { if (magnitude == TileImprovement.NO_RIVER) { return null; } TileImprovement river = new TileImprovement(getGame(), tile, getSpecification() .getTileImprovementType("model.improvement.river")); river = (TileImprovement) addTileItem(river); river.setMagnitude(magnitude); river.setStyle(style); invalidateCache(); return river; } /** * Removes the river <code>TileImprovement</code> from this Tile/Container. */ // Change neighbours' River Style with {@link #adjustNeighbourRiverStyle}. public TileImprovement removeRiver() { Iterator<TileItem> iterator = tileItems.iterator(); while (iterator.hasNext()) { TileItem item = iterator.next(); if (item instanceof TileImprovement && ((TileImprovement) item).isRiver()) { iterator.remove(); invalidateCache(); return (TileImprovement) item; } } return null; } /** * 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 { PlayerExploredTile pet = (showAll || toSavedGame) ? null : tile.getPlayerExploredTile(player); // Start element: out.writeStartElement(getXMLElementTagName()); out.writeAttribute(ID_ATTRIBUTE, getId()); out.writeAttribute("tile", tile.getId()); if (showAll || toSavedGame || player.canSee(tile)) { for (TileItem item : tileItems) { item.toXML(out, player, showAll, toSavedGame); } } else if (pet != null) { List<TileItem> petItems = pet.getAllTileItems(); Collections.sort(petItems, tileItemComparator); for (TileItem item : petItems) { item.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. */ protected void readFromXMLImpl(XMLStreamReader in) throws XMLStreamException { setId(in.getAttributeValue(null, ID_ATTRIBUTE)); tile = (Tile) getGame().getFreeColGameObject(in.getAttributeValue(null, "tile")); if (tile == null) { tile = new Tile(getGame(), in.getAttributeValue(null, "tile")); } tileItems.clear(); while (in.nextTag() != XMLStreamConstants.END_ELEMENT) { TileItem item = (TileItem) getGame().getFreeColGameObject(in.getAttributeValue(null, ID_ATTRIBUTE)); if (item == null) { if (in.getLocalName().equals(Resource.getXMLElementTagName())) { item = new Resource(getGame(), in); } else if (in.getLocalName().equals(LostCityRumour.getXMLElementTagName())) { item = new LostCityRumour(getGame(), in); } else if (in.getLocalName().equals(TileImprovement.getXMLElementTagName())) { item = new TileImprovement(getGame(), in); } } else { item.readFromXML(in); } tileItems.add(item); } // TODO: remove this some time; at this point, sorting is only // required for old savegames Collections.sort(tileItems, tileItemComparator); } /** * Creates a <code>String</code> representation of this * <code>TileItemContainer</code>. */ @Override public String toString() { StringBuffer sb = new StringBuffer(60); sb.append("TileItemContainer with: "); for (TileItem item : tileItems) { sb.append(item.toString() + ", "); } return sb.toString(); } /** * Gets the tag name of the root element representing this object. * * @return "tileitemcontainer". */ public static String getXMLElementTagName() { return "tileitemcontainer"; } }