/**
* 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>StringTemplate</code> represents a non-localized string
* that can be localized by looking up its value in a message bundle
* or similar Map. The StringTemplate may contain variables (keys)
* delimited by the '%' character, such as "%amount%" that will be
* replaced with a string or a StringTemplate. If the StringTemplate
* contains replacement values but no keys, then it is considered a
* "label" StringTemplate, and its value will be used to join the
* replacement values.
*
* @version 1.0
*/
public class StringTemplate extends FreeColObject {
/**
* The type of this StringTemplate, either NAME, a proper name
* that must not be localized (e.g. "George Washington"), or KEY,
* a string that must be localized (e.g. "model.goods.food.name"),
* or TEMPLATE, a key with replacements to apply to the localized
* value of the key, or LABEL, a separator string that will be
* used to join the replacement values.
*/
public static enum TemplateType { NAME, KEY, TEMPLATE, LABEL }
/**
* The TemplateType of this StringTemplate. Defaults to KEY.
*/
private TemplateType templateType = TemplateType.KEY;
/**
* An alternative key to use if the Id is not contained in the
* message bundle.
*/
private String defaultId;
/**
* The keys to replace within the string template.
*/
private List<String> keys;
/**
* The values with which to replace the keys in the string template.
*/
private List<StringTemplate> replacements;
protected StringTemplate() {
// empty constructor
}
public StringTemplate(String id, StringTemplate template) {
setId(id);
this.templateType = template.templateType;
this.keys = template.keys;
this.replacements = template.replacements;
}
/**
* Creates a new <code>Template</code> instance.
*
* @param template a <code>String</code> value
* @param templateType a <code>TemplateType</code> value
*/
protected StringTemplate(String template, TemplateType templateType) {
setId(template);
this.templateType = templateType;
switch (templateType) {
case TEMPLATE:
keys = new ArrayList<String>();
case LABEL:
replacements = new ArrayList<StringTemplate>();
}
}
/**
* Get the <code>DefaultId</code> value.
*
* @return a <code>String</code> value
*/
public final String getDefaultId() {
return defaultId;
}
/**
* Set the <code>DefaultId</code> value.
*
* @param newDefaultId The new DefaultId value.
* @return a <code>StringTemplate</code> value
*/
public StringTemplate setDefaultId(final String newDefaultId) {
this.defaultId = newDefaultId;
return this;
}
// Factory methods
public static StringTemplate name(String value) {
return new StringTemplate(value, TemplateType.NAME);
}
public static StringTemplate key(String value) {
return new StringTemplate(value, TemplateType.KEY);
}
public static StringTemplate template(String value) {
return new StringTemplate(value, TemplateType.TEMPLATE);
}
public static StringTemplate label(String value) {
return new StringTemplate(value, TemplateType.LABEL);
}
/**
* Get the <code>TemplateType</code> value.
*
* @return a <code>TemplateType</code> value
*/
public final TemplateType getTemplateType() {
return templateType;
}
/**
* Get the <code>Keys</code> value.
*
* @return a <code>List<String></code> value
*/
public final List<String> getKeys() {
return keys;
}
/**
* Get the <code>Replacements</code> value.
*
* @return a <code>List<StringTemplate></code> value
*/
public final List<StringTemplate> getReplacements() {
return replacements;
}
/**
* Return the replacement value for a given key, or null if there
* is none.
*
* @param key a <code>String</code> value
* @return a <code>String</code> value
*/
public final StringTemplate getReplacement(String key) {
for (int index = 0; index < keys.size(); index++) {
if (key.equals(keys.get(index))) {
if (replacements.size() > index) {
return replacements.get(index);
} else {
return null;
}
}
}
return null;
}
/**
* Add a new key and replacement to the StringTemplate. This is
* only possible if the StringTemplate is of type TEMPLATE.
*
* @param key a <code>String</code> value
* @param value a <code>String</code> value
* @return a <code>StringTemplate</code> value
*/
public StringTemplate add(String key, String value) {
if (templateType == TemplateType.TEMPLATE) {
keys.add(key);
replacements.add(new StringTemplate(value, TemplateType.KEY));
} else {
throw new IllegalArgumentException("Cannot add key-value pair to StringTemplate type "
+ templateType.toString());
}
return this;
}
/**
* Add a replacement value without a key to the StringTemplate.
* This is only possible if the StringTemplate is of type LABEL.
*
* @param value a <code>String</code> value
* @return a <code>StringTemplate</code> value
*/
public StringTemplate add(String value) {
if (templateType == TemplateType.LABEL) {
replacements.add(new StringTemplate(value, TemplateType.KEY));
} else {
throw new IllegalArgumentException("Cannot add a single string to StringTemplate type "
+ templateType.toString());
}
return this;
}
/**
* Add a new key and replacement to the StringTemplate. The
* replacement must be a proper name. This is only possible if the
* StringTemplate is of type TEMPLATE.
*
* @param key a <code>String</code> value
* @param value a <code>String</code> value
* @return a <code>StringTemplate</code> value
*/
public StringTemplate addName(String key, String value) {
if (templateType == TemplateType.TEMPLATE) {
keys.add(key);
replacements.add(new StringTemplate(value, TemplateType.NAME));
} else {
throw new IllegalArgumentException("Cannot add key-value pair to StringTemplate type "
+ templateType.toString());
}
return this;
}
/**
* Add a new key and replacement to the StringTemplate. The
* replacement must be a proper name. This is only possible if the
* StringTemplate is of type TEMPLATE.
*
* @param key a <code>String</code> value
* @param object a <code>FreeColObject</code> value
* @return a <code>StringTemplate</code> value
*/
public StringTemplate addName(String key, FreeColObject object) {
if (templateType == TemplateType.TEMPLATE) {
keys.add(key);
replacements.add(new StringTemplate(object.getId() + ".name", TemplateType.KEY));
} else {
throw new IllegalArgumentException("Cannot add key-value pair to StringTemplate type "
+ templateType.toString());
}
return this;
}
/**
* Add a replacement value without a key to the StringTemplate.
* The replacement must be a proper name. This is only possible
* if the StringTemplate is of type LABEL.
*
* @param value a <code>String</code> value
* @return a <code>StringTemplate</code> value
*/
public StringTemplate addName(String value) {
if (templateType == TemplateType.LABEL) {
replacements.add(new StringTemplate(value, TemplateType.NAME));
} else {
throw new IllegalArgumentException("Cannot add a single string to StringTemplate type "
+ templateType.toString());
}
return this;
}
/**
* Add a key and an integer value to replace it to this
* StringTemplate.
*
* @param key a <code>String</code> value
* @param amount a <code>Number</code> value
* @return a <code>StringTemplate</code> value
*/
public StringTemplate addAmount(String key, Number amount) {
addName(key, amount.toString());
return this;
}
/**
* Add a key and a StringTemplate to replace it to this
* StringTemplate.
*
* @param key a <code>String</code> value
* @param template a <code>StringTemplate</code> value
* @return a <code>StringTemplate</code> value
*/
public StringTemplate addStringTemplate(String key, StringTemplate template) {
if (templateType == TemplateType.TEMPLATE) {
keys.add(key);
replacements.add(template);
} else {
throw new IllegalArgumentException("Cannot add a key-template pair to a StringTemplate type "
+ templateType.toString());
}
return this;
}
/**
* Add a StringTemplate to this LABEL StringTemplate.
*
* @param template a <code>StringTemplate</code> value
* @return a <code>StringTemplate</code> value
*/
public StringTemplate addStringTemplate(StringTemplate template) {
if (templateType == TemplateType.LABEL) {
replacements.add(template);
} else {
throw new IllegalArgumentException("Cannot add a StringTemplate to StringTemplate type "
+ templateType.toString());
}
return this;
}
public String toString() {
String result = templateType.toString() + ": ";
switch (templateType) {
case LABEL:
if (replacements == null) {
result += getId();
} else {
for (StringTemplate object : replacements) {
result += object + getId();
}
}
break;
case TEMPLATE:
result += getId();
if (defaultId != null) {
result += " (" + defaultId + ")";
}
result += " [";
for (int index = 0; index < keys.size(); index++) {
result += "[" + keys.get(index) + ": "
+ replacements.get(index).toString() + "]";
}
result += "]";
break;
case KEY:
result += getId();
if (defaultId != null) {
result += " (" + defaultId + ")";
}
break;
case NAME:
default:
result += getId();
}
return result;
}
public boolean equals(Object o) {
if (o instanceof StringTemplate) {
StringTemplate t = (StringTemplate) o;
if (!getId().equals(t.getId()) || templateType != t.templateType) {
return false;
}
if (defaultId == null) {
if (t.defaultId != null) {
return false;
}
} else if (t.defaultId == null) {
return false;
} else if (!defaultId.equals(t.defaultId)) {
return false;
}
if (templateType == TemplateType.LABEL) {
if (replacements.size() == t.replacements.size()) {
for (int index = 0; index < replacements.size(); index++) {
if (!replacements.get(index).equals(t.replacements.get(index))) {
return false;
}
}
} else {
return false;
}
} else if (templateType == TemplateType.TEMPLATE) {
if (keys.size() == t.keys.size()
&& replacements.size() == t.replacements.size()
&& keys.size() == replacements.size()) {
for (int index = 0; index < replacements.size(); index++) {
if (!keys.get(index).equals(t.keys.get(index))
|| !replacements.get(index).equals(t.replacements.get(index))) {
return false;
}
}
} else {
return false;
}
}
return true;
} else {
return false;
}
}
public int hashCode() {
int result = 17;
result = result * 31 + getId().hashCode();
result = result * 31 + templateType.ordinal();
if (defaultId != null) {
result = result * 31 + defaultId.hashCode();
}
if (templateType == TemplateType.LABEL) {
for (StringTemplate replacement : replacements) {
result = result * 31 + replacement.hashCode();
}
} else if (templateType == TemplateType.TEMPLATE) {
for (int index = 0; index < keys.size(); index++) {
result = result * 31 + keys.get(index).hashCode();
result = result * 31 + replacements.get(index).hashCode();
}
}
return result;
}
/**
* This method writes an XML-representation of this object to
* the given stream.
*
* @param out The target stream.
* @throws XMLStreamException if there are any problems writing
* to the stream.
*/
protected void toXMLImpl(XMLStreamWriter out) throws XMLStreamException {
super.toXML(out, getXMLElementTagName());
}
/**
* 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);
out.writeAttribute("templateType", templateType.toString());
if (defaultId != null) {
out.writeAttribute("defaultId", defaultId);
}
}
/**
* 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 (keys != null) {
for (String key : keys) {
out.writeStartElement("key");
out.writeAttribute(VALUE_TAG, key);
out.writeEndElement();
}
}
if (replacements != null) {
for (StringTemplate replacement : replacements) {
replacement.toXMLImpl(out);
}
}
}
/**
* Reads the attributes of this object from an XML stream.
*
* @param in The XML input stream.
* @exception XMLStreamException if a problem was encountered
* during parsing.
*/
@Override
protected void readAttributes(XMLStreamReader in)
throws XMLStreamException {
// @compat 0.10.x
String id = in.getAttributeValue(null, ID_ATTRIBUTE_TAG);
if (id == null) {
id = in.getAttributeValue(null, ID_ATTRIBUTE);
}
setId(id);
String typeString = in.getAttributeValue(null, "templateType");
if (typeString == null) {
templateType = TemplateType.TEMPLATE;
} else {
templateType = Enum.valueOf(TemplateType.class, typeString);
}
// end compatibility code
defaultId = in.getAttributeValue(null, "defaultId");
switch (templateType) {
case TEMPLATE:
keys = new ArrayList<String>();
case LABEL:
replacements = new ArrayList<StringTemplate>();
}
}
/**
* Reads the children of this object from an XML stream.
*
* @param in The XML input stream.
* @exception XMLStreamException if a problem was encountered
* during parsing.
*/
@Override
protected void readChildren(XMLStreamReader in) throws XMLStreamException {
while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
if ("key".equals(in.getLocalName())) {
keys.add(in.getAttributeValue(null, VALUE_TAG));
in.nextTag();
} else if (getXMLElementTagName().equals(in.getLocalName())) {
StringTemplate replacement = new StringTemplate();
replacement.readFromXML(in);
replacements.add(replacement);
} else if ("data".equals(in.getLocalName())) { // @compat 0.9.x
readOldFormat(readFromArrayElement("data", in, new String[0]));
// end compatibility code
} else if ("strings".equals(in.getLocalName())) { // @compat 0.9.x
// TODO: remove compatibility code for HistoryEvent
readOldFormat(readFromArrayElement("strings", in, new String[0]));
// end compatibility code
}
}
}
// @compat 0.9.x
private void readOldFormat(String[] data) {
for (int index = 0; index < data.length; index += 2) {
keys.add(data[index]);
replacements.add(new StringTemplate(data[index + 1], TemplateType.NAME));
}
}
// end compatibility code
/**
* Returns the tag name of the root element representing this object.
*
* @return "stringTemplate".
*/
public static String getXMLElementTagName() {
return "stringTemplate";
}
}