/*
* Copyright (c) Thomas Parker, 2015.
*
* This program 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 program 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package pcgen.output.publish;
import java.util.HashMap;
import java.util.Map;
import pcgen.base.util.CaseInsensitiveMap;
import pcgen.base.util.DoubleKeyMap;
import pcgen.cdom.base.ItemFacet;
import pcgen.cdom.base.SetFacet;
import pcgen.cdom.enumeration.CharID;
import pcgen.core.GameMode;
import pcgen.output.base.ModeModelFactory;
import pcgen.output.base.ModelFactory;
import pcgen.output.factory.ItemModelFactory;
import pcgen.output.factory.SetModelFactory;
import pcgen.output.model.BooleanOptionModel;
import freemarker.template.TemplateModel;
/**
* OutputDB is the OutputDatabase for building the Map to be provided to
* FreeMarker for output.
*/
public final class OutputDB
{
private OutputDB()
{
//Utility class should not be constructed
}
/**
* The Map of string names to output models (that are dynamic based on a PC)
*/
private static DoubleKeyMap<Object, Object, ModelFactory> outModels =
new DoubleKeyMap<>(
CaseInsensitiveMap.class, CaseInsensitiveMap.class);
/**
* The map of string names to models for global items (not PC dependent)
*/
private static Map<Object, TemplateModel> globalModels =
new CaseInsensitiveMap<>();
/**
* The Map of string names to output models for the Game Mode
*/
private static Map<Object, ModeModelFactory> modeModels =
new CaseInsensitiveMap<>();
/**
* Registers a new ModelFactory to be used in output
*
* @param name
* The interpolation for the ModelFactory
* @param modelFactory
* The ModelFactory to be used to generate the Models when the
* output Map is built
*/
public static void registerModelFactory(String name,
ModelFactory modelFactory)
{
if (modelFactory == null)
{
throw new IllegalArgumentException("Model Factory may not be null");
}
String[] locationElements = name.split("\\.");
if (locationElements.length == 0)
{
throw new IllegalArgumentException(
"Name may not be null or empty: " + name);
}
if (locationElements.length > 2)
{
throw new IllegalArgumentException(
"Name may only contain zero or one period");
}
String secondName =
(locationElements.length == 1) ? "" : locationElements[1];
ModelFactory old =
outModels.put(locationElements[0], secondName, modelFactory);
if (old != null)
{
throw new UnsupportedOperationException(
"Cannot have two Output Models using the same name: " + name);
}
}
/**
* Registers a new ItemFacet with the OutputDatabase using the given name as
* the interpolation for fetching information from the given ItemFacet.
*
* @param name
* The name as the interpolation for fetching information from
* the given ItemFacet during output
* @param facet
* The ItemFacet to be registered with the given name
*/
public static void register(String name, ItemFacet<CharID, ?> facet)
{
registerModelFactory(name, new ItemModelFactory(facet));
}
/**
* Registers a new SetFacet with the OutputDatabase using the given name as
* the interpolation for fetching information from the given SetFacet.
*
* @param name
* The name as the interpolation for fetching information from
* the given SetFacet during output
* @param facet
* The SetFacet to be registered with the given name
*/
public static void register(String name, SetFacet<CharID, ?> facet)
{
registerModelFactory(name, new SetModelFactory(facet));
}
/**
* Builds the PlayerCharacter data model for the given CharID.
*
* @param id
* The CharID for which the data model should be built
* @return A Map of the data model for the PlayerCharacter identified by the
* given CharID
*/
public static Map<String, Object> buildDataModel(CharID id)
{
Map<String, Object> input = new HashMap<>();
for (Object k1 : outModels.getKeySet())
{
for (Object k2 : outModels.getSecondaryKeySet(k1))
{
ModelFactory modelFactory = outModels.get(k1, k2);
TemplateModel model = modelFactory.generate(id);
String k1String = k1.toString();
if ("".equals(k2.toString()))
{
input.put(k1String, model);
}
else
{
ensureMap(input, k1String);
@SuppressWarnings("unchecked")
Map<Object, Object> m =
(Map<Object, Object>) input.get(k1String);
m.put(k2.toString(), model);
}
}
}
return input;
}
private static void ensureMap(Map<String, Object> input, String k1String)
{
if (!input.containsKey(k1String))
{
input.put(k1String, new HashMap<>());
}
}
/**
* Builds the "game mode" data model
*
* @return Returns a Map containing the "game mode" information
*/
public static Map<String, Object> buildModeDataModel(GameMode mode)
{
Map<String, Object> input = new HashMap<>();
for (Object key : modeModels.keySet())
{
ModeModelFactory modelFactory = modeModels.get(key);
input.put(key.toString(), modelFactory.generate(mode));
}
return input;
}
/**
* Registers a ModeModelFactory under the given name.
*
* Note that only one ModeModelFactory can be registered under a given (case
* insensitive) name. Additional items registered under the same name will
* cause an UnsupportedOperationException.
*
* @param name
* The Name the given ModeModelFactory should be registered under
* for use as an interpolation under gamemode. in FreeMarker
* @param factory
* The ModeModelFactory to be registered under the given name
*/
public static void registerMode(String name, ModeModelFactory factory)
{
if (factory == null)
{
throw new IllegalArgumentException("Model Factory may not be null");
}
int dotLoc = name.indexOf('.');
if (dotLoc != -1)
{
throw new IllegalArgumentException("Name may not contain a dot: "
+ name);
}
ModeModelFactory old = modeModels.put(name, factory);
if (old != null)
{
throw new UnsupportedOperationException(
"Cannot have two Mode Models using the same name: " + name);
}
}
/**
* Returns a specific portion of the PlayerCharacter data model for the
* given CharID and selection string.
*
* @param id
* The CharID for which the data model should be built
* @param keys
* A String (or array) of keys identifying the portion of the
* data model to be built
* @return An Iterable for the portion of the data model identified by the
* given Strings and the PlayerCharacter identified by the given
* CharID
*/
public static Iterable<?> getIterable(CharID id, String... keys)
{
String k1 = keys[0];
String k2 = (keys.length > 1) ? keys[1] : "";
ModelFactory modelFactory = outModels.get(k1, k2);
if (modelFactory == null)
{
return null;
}
return modelFactory.generate(id);
}
/**
* Returns true if the given interpolation is legal based on the items
* registered with OutputDB.
*
* @param interpolation
* The interpolation to be checked to see if it is legal
* @return true if the given interpolation is legal based on the items
* registered with OutputDB; false otherwise
*/
public static boolean isLegal(String interpolation)
{
return outModels.containsKey(interpolation);
}
/**
* Resets the Output Database, to be used when sources are purged/reloaded
* or around testing
*/
public static void reset()
{
outModels.clear();
globalModels.clear();
modeModels.clear();
}
/**
* Returns a map of the global TemplateModel objects (those that do not
* depend on a PC)
*
* Note that ownership of the returned map is transferred to the calling
* object, no changes to the returned map will impact OutputDB, nor will
* changes to OutputDB impact the returned Map.
*
* @return a Map of the global TemplateModel objects
*/
public static Map<Object, TemplateModel> getGlobal()
{
CaseInsensitiveMap<TemplateModel> map =
new CaseInsensitiveMap<>();
map.putAll(globalModels);
return map;
}
/**
* Registers a new Boolean Preference for inclusion in the global Models.
*
* @param pref
* The preference name, as identified in the preference file
* @param defaultValue
* The default value for the preference if it is not defined
*/
public static void registerBooleanPreference(String pref,
boolean defaultValue)
{
if ((pref == null) || (pref.isEmpty()))
{
throw new IllegalArgumentException(
"Preference Name may not be null or empty: " + pref);
}
addGlobalModel(pref, new BooleanOptionModel(pref, defaultValue));
}
/**
* Directly adds a new TemplateModel as part of the "Global" Models in
* OutputDB.
*
* @param name
* The name to be used when the TemplateModel is referred to in
* FreeMarker
* @param model
* The TemplateModel to be added to the global models
*/
public static void addGlobalModel(String name, TemplateModel model)
{
TemplateModel old = globalModels.put(name, model);
if (old != null)
{
throw new UnsupportedOperationException(
"Cannot have two Global Output Models using the same name: "
+ name);
}
}
}