/**
* 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 javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
/**
* The <code>Feature</code> class encapsulates a bonus or penalty
* that can be applied to any action within the game, most obviously
* combat.
*/
public abstract class Feature extends FreeColObject {
/**
* The source of this Feature, e.g. a UnitType.
*/
private FreeColObject source;
/**
* The first Turn in which this Feature applies.
*/
private Turn firstTurn;
/**
* The last Turn in which this Feature applies.
*/
private Turn lastTurn;
/**
* The duration of this Feature. By default, the duration is
* unlimited.
*/
private int duration = 0;
/**
* Transient features are provided by events such as disasters and
* goods parties, and need to be serialized by the
* FreeColGameObject they apply to.
*/
private boolean temporary;
/**
* A list of Scopes limiting the applicability of this Feature.
*/
private List<Scope> scopes;
/**
* Get the <code>TimeLimit</code> value.
*
* @return a <code>boolean</code> value
*/
public final boolean hasTimeLimit() {
return (firstTurn != null || lastTurn != null);
}
/**
* Get the <code>Scope</code> value.
*
* @return a <code>boolean</code> value
*/
public final boolean hasScope() {
return !(scopes == null || scopes.isEmpty());
}
/**
* Describe <code>getNameKey</code> method here.
*
* @return a <code>String</code> value
*/
public String getNameKey() {
return getId() + ".name";
}
/**
* Get the <code>Scopes</code> value.
*
* @return a <code>List<Scope></code> value
*/
public final List<Scope> getScopes() {
return scopes;
}
/**
* Set the <code>Scopes</code> value.
*
* @param newScopes The new Scopes value.
*/
public final void setScopes(final List<Scope> newScopes) {
this.scopes = newScopes;
}
/**
* Get the <code>firstTurn</code> value.
*
* @return a <code>Turn</code> value
*/
public final Turn getFirstTurn() {
return firstTurn;
}
/**
* Set the <code>firstTurn</code> value.
*
* @param newFirstTurn The new FirstTurn value.
*/
public final void setFirstTurn(final Turn newFirstTurn) {
this.firstTurn = newFirstTurn;
}
/**
* Get the <code>LastTurn</code> value.
*
* @return a <code>Turn</code> value
*/
public final Turn getLastTurn() {
return lastTurn;
}
/**
* Set the <code>LastTurn</code> value.
*
* @param newLastTurn The new LastTurn value.
*/
public final void setLastTurn(final Turn newLastTurn) {
this.lastTurn = newLastTurn;
}
/**
* Get the <code>Source</code> value.
*
* @return a <code>String</code> value
*/
public final FreeColObject getSource() {
return source;
}
/**
* Set the <code>Source</code> value.
*
* @param newSource The new Source value.
*/
public final void setSource(final FreeColObject newSource) {
this.source = newSource;
}
/**
* Get the <code>Duration</code> value.
*
* @return an <code>int</code> value
*/
public final int getDuration() {
return duration;
}
/**
* Set the <code>Duration</code> value.
*
* @param newDuration The new Duration value.
*/
public final void setDuration(final int newDuration) {
this.duration = newDuration;
}
/**
* Get the <code>Temporary</code> value.
*
* @return a <code>boolean</code> value
*/
public final boolean isTemporary() {
return temporary;
}
/**
* Set the <code>Temporary</code> value.
*
* @param newTemporary The new Temporary value.
*/
public final void setTemporary(final boolean newTemporary) {
this.temporary = newTemporary;
}
/**
* Returns true if the <code>appliesTo</code> method of at least
* one <code>Scope</code> object returns true.
*
* @param objectType a <code>FreeColGameObjectType</code> value
* @return a <code>boolean</code> value
*/
public boolean appliesTo(final FreeColGameObjectType objectType) {
if (!hasScope()) {
return true;
} else {
for (Scope scope : scopes) {
if (scope.appliesTo(objectType)) {
return true;
}
}
return false;
}
}
/**
* Returns whether this Feature applies to the given Turn.
*
* @param turn a <code>Turn</code> value
* @return a <code>boolean</code> value
*/
protected boolean appliesTo(Turn turn) {
return !(turn != null &&
(firstTurn != null && turn.getNumber() < firstTurn.getNumber() ||
lastTurn != null && turn.getNumber() > lastTurn.getNumber()));
}
/**
* Returns true if this Feature applies to both the given
* FreeColGameObjectType and the given Turn.
*
* @param objectType a <code>FreeColGameObjectType</code> value
* @param turn a <code>Turn</code> value
* @return a <code>boolean</code> value
*/
protected boolean appliesTo(final FreeColGameObjectType objectType, Turn turn) {
return appliesTo(turn) && appliesTo(objectType);
}
/**
* Returns true if the Feature has an lastTurn turn smaller than the
* turn given.
*
* @param turn a <code>Turn</code> value
* @return a <code>boolean</code> value
*/
public boolean isOutOfDate(Turn turn) {
return (turn != null &&
(lastTurn != null && turn.getNumber() > lastTurn.getNumber()));
}
/**
* {@inheritDoc}
*/
public int hashCode() {
int hash = 7;
hash += 31 * hash + (getId() == null ? 0 : getId().hashCode());
hash += 31 * hash + (source == null ? 0 : source.hashCode());
hash += 31 * hash + (firstTurn == null ? 0 : firstTurn.getNumber());
hash += 31 * hash + (lastTurn == null ? 0 : lastTurn.getNumber());
hash += 31 * hash + duration;
hash += 31 * (temporary ? 1 : 0);
if (scopes != null) {
for (Scope scope : scopes) {
// TODO: is this safe? It is an easy way to ignore the order
// of scope elements.
hash += scope.hashCode();
}
}
return hash;
}
/**
* {@inheritDoc}
*/
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof Feature) {
Feature feature = (Feature) o;
if (getId() == null) {
if (feature.getId() != null) {
return false;
}
} else if (feature.getId() == null) {
return false;
} else if (!getId().equals(feature.getId())) {
return false;
}
if (source != feature.source) {
return false;
}
if (firstTurn == null) {
if (feature.firstTurn != null) {
return false;
}
} else if (feature.firstTurn == null) {
return false;
} else if (firstTurn.getNumber() != feature.firstTurn.getNumber()) {
return false;
}
if (duration != feature.duration) {
return false;
}
if (temporary != feature.temporary) {
return false;
}
if (scopes == null) {
if (feature.scopes != null) {
return false;
}
} else if (feature.scopes == null) {
return false;
} else {
// not very efficient, but we do not expect many
// scopes
for (Scope scope : scopes) {
if (!feature.scopes.contains(scope)) {
return false;
}
}
for (Scope scope : feature.scopes) {
if (!scopes.contains(scope)) {
return false;
}
}
}
return true;
} else {
return false;
}
}
protected void copy(Feature other) {
setId(other.getId());
this.source = other.source;
this.firstTurn = other.firstTurn;
this.lastTurn = other.lastTurn;
this.duration = other.duration;
this.temporary = other.temporary;
setScopes(other.getScopes());
}
/**
* Write the attributes of this object to a stream.
*
* @param out The target stream.
* @throws XMLStreamException if there are any problems writing
* to the stream.
*/
@Override
protected void writeAttributes(XMLStreamWriter out)
throws XMLStreamException {
super.writeAttributes(out);
if (getSource() != null) {
out.writeAttribute("source", getSource().getId());
}
if (getFirstTurn() != null) {
out.writeAttribute("firstTurn",
String.valueOf(getFirstTurn().getNumber()));
}
if (getLastTurn() != null) {
out.writeAttribute("lastTurn",
String.valueOf(getLastTurn().getNumber()));
}
if (duration != 0) {
out.writeAttribute("duration", Integer.toString(duration));
}
if (temporary) {
out.writeAttribute("temporary", "true");
}
}
/**
* Write the children of this object to a stream.
*
* @param out The target stream.
* @throws XMLStreamException if there are any problems writing
* to the stream.
*/
@Override
protected void writeChildren(XMLStreamWriter out)
throws XMLStreamException {
super.writeChildren(out);
if (getScopes() != null) {
for (Scope scope : getScopes()) {
scope.toXMLImpl(out);
}
}
}
/**
* Reads the attributes of this object from an XML stream.
*
* @param in The XML input stream.
* @throws XMLStreamException if a problem was encountered
* during parsing.
*/
@Override
protected void readAttributes(XMLStreamReader in)
throws XMLStreamException {
// @compat 0.9.x
if (in.getAttributeValue(null, ID_ATTRIBUTE_TAG) == null
&& "model.colony.colonyGoodsParty".equals(in.getAttributeValue(null, "source"))) {
setId("model.modifier.colonyGoodsParty");
setSource(getSpecification().getType("model.source.colonyGoodsParty"));
// end compatibility code
} else {
super.readAttributes(in);
String sourceId = in.getAttributeValue(null, "source");
if (sourceId == null) {
setSource(null);
} else if (sourceId.equals("model.monarch.colonyGoodsParty")) { // @compat 0.9.x
setSource(getSpecification().getType("model.source.colonyGoodsParty"));
// end compatibility code
} else if (getSpecification() != null) {
setSource(getSpecification().getType(sourceId));
}
}
String firstTurn = in.getAttributeValue(null, "firstTurn");
if (firstTurn != null) {
setFirstTurn(new Turn(Integer.parseInt(firstTurn)));
}
String lastTurn = in.getAttributeValue(null, "lastTurn");
if (lastTurn != null) {
setLastTurn(new Turn(Integer.parseInt(lastTurn)));
}
duration = getAttribute(in, "duration", 0);
temporary = getAttribute(in, "temporary", false);
}
/**
* Reads the children of this object from an XML stream.
*
* @param in The XML input stream.
* @throws XMLStreamException if a problem was encountered
* during parsing.
*/
@Override
protected void readChildren(XMLStreamReader in)
throws XMLStreamException {
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
String childName = in.getLocalName();
if (Scope.getXMLElementTagName().equals(childName)) {
Scope scope = new Scope(in);
if (getScopes() == null) {
setScopes(new ArrayList<Scope>());
}
getScopes().add(scope);
} else {
logger.finest("Parsing of " + childName
+ " is not implemented yet");
while (in.nextTag() != XMLStreamConstants.END_ELEMENT ||
!in.getLocalName().equals(childName)) {
in.nextTag();
}
}
}
}
}