FreeMarker Output

From PCGen
Jump to: navigation, search

Principles

PCGen's output system going forward is based on FreeMarker and their template system. As much as possible, we adhere to their concepts and terms, so that we do not have to build translations.

Key FreeMarker Concepts

Some of the key FreeMarker concepts to understand are expressions, variables and subvariables. When a template is processed, these items are where FreeMarker places content. If looking at a Player Character's languages, the expression may be ${pc.languages}. "pc" is a variable and "pc.languages" is a subvariable.

An expression may be used with or without a leading dollar sign, depending on the location where it is used. Refer to the documentation for more information.

Key Variables

At the top level, there are 3 key variables in PCGen:

  • pc : This contains information about the Player Character being processed
  • gamemode : This contains information about the loaded Game Mode (things like UnitSet)
  • pcgen : This contains information that is 'global' to PCGen (such as preferences)

Within the "pc" variable, there are subvariables that have either sequences or individual CDOMObjects (this is what is used to store things like Skills, Abilities, etc). Each of these has a series of additional items that can be used. (In FreeMarker terms, a CDOMObject is exposed to the template as a TemplateHashModel).

Name Clarification

To clarify naming, there are a few key subvariable constructs to recognize

  • *.key : This represents the (unique) key of an object (what is set by the KEY: LST token)
  • *.displayname : This represents the (not necessarily unique) name that appears at the beginning of the line in an LST file
  • *.outputname : This represents the output name (as set by the OUTPUTNAME LST token)

Dynamic subvariables

Explicit Visibility

A number of subsystems (at the moment FACT Token and FACTSET Token) allow for items to be added to the output from a CDOMObject. These are controlled in the Data Control file, depending on whether the specific item is set to visibility of YES or EXPORT.

No two dynamic subvariables on CDOMObjects may share a name (otherwise the system would not know what to output!).

Default Visibility

In the future, there will be some tokens that provide default visibility to output. This may include things like [1], which might have its own subvariable like *.info.name where * is a CDOMObject (acting as a TemplateHashModel) and the "name" is the name used as the first argument after "INFO:" in the LST data.

The Code

There are 7 packages within pcgen.output that encompass the new system (which acts as an interface from FreeMarker to the core).

Base

pcgen.output.base represents the classes which are the foundation for the new output system. These are the shared interfaces and other (usually very simple) support structure upon which the rest of the package is built.

Publish

pcgen.output.publish represents the publication of items to FreeMarker. Specifically today this is drawn upon by ExportHandler (which is the class that literally calls FreeMarker)

Library

pcgen.output.library is our "static" library of behavior. It is where we have a custom "wrapper" class that is more advanced than FreeMarker's ObjectWrapper.SIMPLE_WRAPPER. We use this Library of Wrappers to store the intelligence for how to wrap our custom objects, including (but certainly not limited to) CDOMObject. This wrapper library uses wrappers in order to return models.

Wrapper

pcgen.output.wrapper contains the wrapper classes that are stored within the library. Each of these is custom written to work with a specific class within PCGen to "wrap" that object into a TemplateModel. Most of the wrappers are fairly simple, just returning an instance from our model directory. The CDOMObjectWrapper is more complex and is discussed separately below.

Factory

pcgen.output.factory contains a set of classes that can be thought of as a specialized form of Wrapper. They perform the same basic function of wrapping an object into a TemplateModel, but do so with additional information (specifically the CharID of the Player Character). This means that they are a form of "dynamic wrapper". This is used for wrapping classes that are static/shared across PCGen, but have specific content for each Player Character. The Facet system specifically falls into this category of objects, so when information is drawn from a Facet, it is pulled from a TemplateModel that was generated in a factory.

Model

pcgen.output.model contains the custom classes that wrap our objects into TemplateModel objects. When either Wrapper or Factory objects return a TemplateModel, they are often returning a model within this package (there are cases where we can use a simple model from FreeMarker, but all of our custom classes, and some Java collection classes, have custom models we have written

Actor

pcgen.output.actor contains a set of classes that produce the dynamic subvariables referenced above. These related to the CDOMObjectWrapper object, and are discussed further below.

Information Flow from a CDOMObject

The information flow from a CDOMObject is particularly complex, so a separate explanation that ties together the process and timing of information is provided here.

When data is loaded, we know a set of information, including what FACTs are valid in the data, as well as other built-ins like StringKey or IntegerKey that may contain relevant information. However, we don't know what CDOMObject it will be called on when data is loaded (since it will be called on many different objects throughout the processing of FreeMarker templates). So we need to have an object that knows how to get the appropriate information. This is an "Actor". How the information is retrieved might be:

  • Statically: Some information has a deterministic location, such as OUTPUTNAME. This is simply hardcoded to call the right method
  • Dynamically: Some information requires a specific key to grab the information (a StringKey or FactKey). These Actors are loaded with the appropriate key when they are constructed.

Note that any given Actor may support only a limited set of CDOMObject classes. However, we follow the same rules as the LST load tokens (and FACT) where there can be "global" coverage of all CDOMObjects (e.g. KEY) or "local" coverage (imagine pulling base movement from a Race).

In order to handle this dynamic coverage, we need a "database" that associates the actor to where it is legal. We also need to store the string used for the subvariable in FreeMarker (so *.outputname might be valid subvariable on Race objects and use OutputNameActor). This set of information is managed by the CDOMObjectWrapper object.

Specifically, the CDOMObjectWrapper has a CDOMObjectWrapperInfo for each class. Each CDOMObjectWrapperInfo is initialized with the "parent class" CDOMObjectWrapperInfo, so that it can default to the parent if that is where the subvariable lies (this works recursively). The CDOMObjectWrapperInfo then has the map from subvariables to Actors.

At runtime, when the Library asks CDOMObjectWrapper to wrap a CDOMObject, the TemplateModel that is returned is a CDOMObjectModel. This is initialized with both the CDOMObject being wrapped as well as the CDOMObjectWrapperInfo. When an subvariable is requested (remember that CDOMObjectModel implements TemplateHashModel), then the CDOMObjectWrapperInfo is asked for the appropriate Actor. (An error is thrown if no actor exists for that subvariable, just as any other bad subvariable would produce an error in FreeMarker). If there is a valid Actor, then that Actor is passed the CDOMObject and the appropriate TemplateModel of the content is returned to FreeMarker for further processing (there may actually be a sub-subvariable, so to speak).