/*
* Copyright 2012 Vincent Lhote
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package plugin.overland.model;
import java.io.File;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;
import java.util.logging.Level;
import org.jdom2.DocType;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import pcgen.system.LanguageBundle;
import pcgen.util.Logging;
import plugin.overland.gui.XMLFilter;
import plugin.overland.model.TravelMethodImplementation.Choice;
import plugin.overland.model.TravelMethodImplementation.Combo;
import plugin.overland.model.TravelMethodImplementation.Method;
import plugin.overland.model.TravelMethodImplementation.Pace;
import plugin.overland.util.Localized;
/**
* Builds a Travel Method instance from an XML document.
*
* @author Vincent Lhote
*/
public class TravelMethodFactory
{
/** Default locale for number parsing */
public static final Locale DEFAULT_LOCALE = Locale.UK;
/** directory where the XML and DTD is stored, under the plugin specific directory */
private static final String DIR_TRAVELMETHODS = "travel_methods"; //$NON-NLS-1$
// ### XML Constants ###
private static final String XML_ELEMENT_TRAVEL = "travel"; //$NON-NLS-1$
private static final String XML_ELEMENT_COMBO = "combo"; //$NON-NLS-1$
private static final String XML_ELEMENT_CHOICE = "choice"; //$NON-NLS-1$
private static final String XML_ELEMENT_CHOOSE_FROM = "chooseFrom"; //$NON-NLS-1$
private static final String XML_ELEMENT_PACE = "pace"; //$NON-NLS-1$
private static final String XML_ELEMENT_METHOD = "method"; //$NON-NLS-1$
private static final String XML_ELEMENT_ROUTE = "route"; //$NON-NLS-1$
private static final String XML_ELEMENT_TERRAIN = "terrain"; //$NON-NLS-1$
private static final String XML_ELEMENT_WAY = "way"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_HOURSINDAY = "hoursInDay"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_MPH = "mph"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_KMH = "kmh"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_COMMENT = "comment"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_DAYS = "days"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_ADDKMH = "addKmh"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_ADDMPH = "addMph"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_MULT = "mult"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_ID = "id"; //$NON-NLS-1$
private static final String XML_ATTRIBUTE_NUMBERFORMAT = "numberFormat"; //$NON-NLS-1$
// ### Factory methods ###
public static Vector<TravelMethod> load(File datadir)
{
//Create a new list for the travel methods
Vector<TravelMethod> tms = new Vector<>();
File path = new File(datadir, DIR_TRAVELMETHODS);
if (path.isDirectory())
{
File[] dataFiles = path.listFiles(new XMLFilter());
SAXBuilder builder = new SAXBuilder();
for (int i = 0; i < dataFiles.length; i++)
{
try
{
Document methodSet = builder.build(dataFiles[i]);
DocType dt = methodSet.getDocType();
if (dt.getElementName().equals(XML_ELEMENT_TRAVEL))
{
//Do work here
TravelMethod tm = TravelMethodFactory.create(methodSet);
tms.add(tm);
}
}
catch (Exception e)
{
Logging.errorPrint(e.getMessage(), e);
}
}
}
else
{
Logging.errorPrintLocalised("in_plugin_overland_noDatafile", path.getPath()); //$NON-NLS-1$
}
return tms;
}
public static TravelMethod create(Document methodSet)
{
Localized name;
Map<String, Map<String, Combo>> multByRoadByTerrains;
Map<String, List<Localized>> terrains2;
Map<String, Map<Localized, String>> terrainsById2;
Map<String, List<Localized>> routes2;
Map<String, Map<Localized, String>> routesById2;
List<Method> methods;
Element travel = methodSet.getRootElement();
NumberFormat nf = getNumberFormat(travel);
name = new Localized(travel);
multByRoadByTerrains = new HashMap<>();
terrains2 = new HashMap<>();
terrainsById2 = new HashMap<>();
routes2 = new HashMap<>();
routesById2 = new HashMap<>();
methods = new ArrayList<>();
for (Object methodObj : travel.getChildren())
{
Element child = (Element) methodObj;
if (child.getName().equals(XML_ELEMENT_WAY))
{
String wayId = child.getAttributeValue(XML_ATTRIBUTE_ID);
List<Localized> terrains = new ArrayList<>();
terrains2.put(wayId, terrains);
List<Localized> routes = new ArrayList<>();
routes2.put(wayId, routes);
Map<Localized, String> terrainsById = new HashMap<>();
terrainsById2.put(wayId, terrainsById);
Map<Localized, String> routesById = new HashMap<>();
routesById2.put(wayId, routesById);
for (Object o : child.getChildren())
{
if (o instanceof Element)
{
Element grandchild = (Element) o;
if (grandchild.getName().equals(XML_ELEMENT_TERRAIN))
{
String id = grandchild.getAttributeValue(XML_ATTRIBUTE_ID);
Localized terrain = new Localized(grandchild);
terrains.add(terrain);
terrainsById.put(terrain, id);
if (!multByRoadByTerrains.containsKey(id))
{
multByRoadByTerrains.put(id, new TreeMap<>());
}
}
else if (grandchild.getName().equals(XML_ELEMENT_ROUTE))
{
String id = grandchild.getAttributeValue(XML_ATTRIBUTE_ID);
Localized route = new Localized(grandchild);
routes.add(route);
routesById.put(route, id);
for (Object gcc : grandchild.getChildren(XML_ELEMENT_COMBO))
{
if (gcc instanceof Element)
{
Element grandgrandchild = (Element) gcc;
String idTerrain = grandgrandchild.getAttributeValue(XML_ELEMENT_TERRAIN);
Number mult = parseNumber(nf, grandgrandchild, XML_ATTRIBUTE_MULT, 1);
Number addMph = parseNumber(nf, grandgrandchild, XML_ATTRIBUTE_ADDMPH, 0);
Number addKmh = parseNumber(nf, grandgrandchild, XML_ATTRIBUTE_ADDKMH, 0);
if (!multByRoadByTerrains.containsKey(idTerrain))
{
multByRoadByTerrains.put(idTerrain, new TreeMap<>());
}
multByRoadByTerrains.get(idTerrain).put(id, new Combo(mult, addMph, addKmh));
}
}
}
}
}
// Sort the terrains by locale name
// TODO sort, but with one that do toString on the object. Collections.sort(terrains, Collator.getInstance());
// not sorting routes intentionally (it goes from easier to navigate to hardest)
}
else if (child.getName().equals(XML_ELEMENT_METHOD))
{
String way = child.getAttributeValue(XML_ELEMENT_WAY);
Method method = new Method(new Localized(child), way);
methods.add(method);
for (Object o : child.getChildren())
{
if (o instanceof Element)
{
Element grandchild = (Element) o;
if (grandchild.getName().equals(XML_ELEMENT_PACE))
{
Localized pace = new Localized(grandchild);
boolean useDays = Boolean.parseBoolean(grandchild.getAttributeValue(XML_ATTRIBUTE_DAYS));
Localized comment = new Localized(grandchild, XML_ATTRIBUTE_COMMENT);
Number mult = parseNumber(nf, grandchild, XML_ATTRIBUTE_MULT, 1);
Pace newPace = new Pace(pace, comment, useDays, mult);
method.add(newPace);
}
if (grandchild.getName().equals(XML_ELEMENT_CHOOSE_FROM))
{
Number kmh = parseNumber(nf, grandchild, XML_ATTRIBUTE_KMH, 0.75); // XXX other default?
Number mph = parseNumber(nf, grandchild, XML_ATTRIBUTE_MPH, 0.5); // XXX other default?
Number hoursInDay = parseNumber(nf, grandchild, XML_ATTRIBUTE_HOURSINDAY, 24); // XXX other default?
for (Object o2 : grandchild.getChildren(XML_ELEMENT_CHOICE))
{
if (o2 instanceof Element)
{
Element grandgrandchild = (Element) o2;
Localized choiceName = new Localized(grandgrandchild);
Number mult = parseNumber(nf, grandgrandchild, XML_ATTRIBUTE_MULT, 1);
Choice c =
new Choice(choiceName, hoursInDay, mult.doubleValue() * kmh.doubleValue(),
mult.doubleValue() * mph.doubleValue());
method.add(c);
}
}
}
}
}
}
}
return new TravelMethodImplementation(name, multByRoadByTerrains, terrains2, terrainsById2, routes2, routesById2, methods);
}
/**
* @param nf
* @param string
* @param i
* @return
*/
private static Number parseNumber(NumberFormat nf, Element e, String string, Number def)
{
Number n = def;
String attributeValue = e.getAttributeValue(string);
if (attributeValue != null)
try
{
n = nf.parse(attributeValue);
}
catch (ParseException exception)
{
// TODO Auto-generated catch block
exception.printStackTrace();
}
return n;
}
/**
* Use the XML defined locale to provide a number format instance.
* Use {@link #DEFAULT_LOCALE} if no locale are specified specified.
* @param e an XML element with {@value #XML_ATTRIBUTE_NUMBERFORMAT}
* @return a number format
*/
public static NumberFormat getNumberFormat(Element e)
{
String numFormLoc = e.getAttributeValue(XML_ATTRIBUTE_NUMBERFORMAT, ""); //$NON-NLS-1$
String[] split = numFormLoc.split("_"); //$NON-NLS-1$
Locale l = null;
switch (split.length)
{
case 0:
// Default numberLocale
l = DEFAULT_LOCALE;
break;
case 1:
l = new Locale(split[0]);
break;
case 2:
l = new Locale(split[0], split[1]);
break;
case 3:
l = new Locale(split[0], split[1], split[2]);
break;
default:
Logging
.log(Level.WARNING, LanguageBundle.getFormattedString(
"in_log_localeInvalid", numFormLoc, split[0], split[1], split[2])); //$NON-NLS-1$
l = new Locale(split[0], split[1], split[2]);
break;
}
return NumberFormat.getNumberInstance(l);
}
}