/*
* VariableReport.java
* Copyright James Dempsey, 2013
*
* 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
*
* Created on 12/04/2013 5:34:17 PM
*
* $Id$
*/
package pcgen.persistence.lst.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import pcgen.cdom.enumeration.ListKey;
import pcgen.core.Campaign;
import pcgen.core.GameMode;
import pcgen.core.Globals;
import pcgen.core.SystemCollections;
import pcgen.io.PCGFile;
import pcgen.persistence.lst.CampaignSourceEntry;
import pcgen.system.ConfigurationSettings;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* The Class {@code VariableReport} produces a report on variable
* definitions within the PCGen LST data.
*
* <br>
*
* @author James Dempsey <jdempsey@users.sourceforge.net>
*/
public class VariableReport
{
/**
* Produce the variable report in the requested formats.
*
* @param reportNameMap A map of formats to be output and the names of
* the files to contain the report for the format.
* @throws IOException If the template cannot be accessed or the file cannot be written to.
* @throws TemplateException If there is an error in processing the template.
*/
public void runReport(Map<ReportFormat, String> reportNameMap)
throws IOException, TemplateException
{
List<GameMode> games = SystemCollections.getUnmodifiableGameModeList();
Map<String, List<VarDefine>> gameModeVarMap =
new TreeMap<>();
Map<String, Integer> gameModeVarCountMap =
new TreeMap<>();
for (GameMode gameMode : games)
{
List<VarDefine> varList = new ArrayList<>();
Map<String, Integer> varCountMap = new HashMap<>();
Set<File> processedLstFiles = new HashSet<>();
List<Campaign> campaignsForGameMode =
getCampaignsForGameMode(gameMode);
for (Campaign campaign : campaignsForGameMode)
{
processCampaign(campaign, varList, varCountMap,
processedLstFiles);
}
Collections.sort(varList);
gameModeVarMap.put(gameMode.getName(), varList);
gameModeVarCountMap.put(gameMode.getName(), varCountMap.size());
}
for (Entry<ReportFormat, String> reportRequest : reportNameMap
.entrySet())
{
Writer file = new FileWriter(new File(reportRequest.getValue()));
try
{
outputReport(gameModeVarMap, gameModeVarCountMap,
reportRequest.getKey(), file);
}
finally
{
IOUtils.closeQuietly(file);
}
}
}
/**
* Output the variable report for the supplied data in a particular format.
*
* @param gameModeVarMap The map of variable definitions for each game mode.
* @param gameModeVarCountMap The map of the number of variables for each game mode.
* @param reportFormat The format in which to output the report.
* @param outputWriter The writer to output the report to.
* @throws IOException If the template cannot be accessed or the writer cannot be written to.
* @throws TemplateException If there is an error in processing the template.
*/
public void outputReport(Map<String, List<VarDefine>> gameModeVarMap,
Map<String, Integer> gameModeVarCountMap, ReportFormat reportFormat,
Writer outputWriter) throws IOException, TemplateException
{
// Configuration
Writer file = null;
Configuration cfg = new Configuration();
int dataPathLen = ConfigurationSettings.getPccFilesDir().length();
try
{
// Set Directory for templates
File codeDir = new File("code");
File templateDir = new File(codeDir, "templates");
cfg.setDirectoryForTemplateLoading(templateDir);
// load template
Template template = cfg.getTemplate(reportFormat.getTemplate());
// data-model
Map<String, Object> input = new HashMap<>();
input.put("gameModeVarMap", gameModeVarMap);
input.put("gameModeVarCountMap", gameModeVarCountMap);
input.put("pathIgnoreLen", dataPathLen + 1);
// Process the template
template.process(input, outputWriter);
outputWriter.flush();
}
finally
{
if (file != null)
{
try
{
file.close();
}
catch (Exception e2)
{
}
}
}
}
private List<Campaign> getCampaignsForGameMode(GameMode game)
{
List<String> gameModeList = new ArrayList<>();
gameModeList.addAll(game.getAllowedModes());
// Only add those campaigns in the user's chosen folder and game mode
List<Campaign> allCampaigns = Globals.getCampaignList();
Set<Campaign> gameModeCampaigns = new HashSet<>();
for (Campaign campaign : allCampaigns)
{
if (campaign.containsAnyInList(ListKey.GAME_MODE, gameModeList))
{
gameModeCampaigns.add(campaign);
for (CampaignSourceEntry fName : campaign
.getSafeListFor(ListKey.FILE_PCC))
{
URI uri = fName.getURI();
if (PCGFile.isPCGenCampaignFile(uri))
{
Campaign c = Globals.getCampaignByURI(uri, false);
if (c != null)
{
gameModeCampaigns.add(c);
}
}
}
}
}
return new ArrayList<>(gameModeCampaigns);
}
private List<File> processCampaign(Campaign campaign,
List<VarDefine> varList, Map<String, Integer> varCountMap,
Set<File> processedLstFiles) throws FileNotFoundException, IOException
{
List<CampaignSourceEntry> cseList =
new ArrayList<>();
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_LST_EXCLUDE));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_RACE));
//TODO: Handle class with a special processor that tracks the class, sublass and level
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_CLASS));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_COMPANION_MOD));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_SKILL));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_ABILITY_CATEGORY));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_ABILITY));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_FEAT));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_DEITY));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_DOMAIN));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_WEAPON_PROF));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_ARMOR_PROF));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_SHIELD_PROF));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_EQUIP));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_SPELL));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_LANGUAGE));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_TEMPLATE));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_EQUIP_MOD));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_KIT));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_BIO_SET));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_VARIABLE));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_DYNAMIC));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_DATACTRL));
cseList.addAll(campaign.getSafeListFor(ListKey.FILE_GLOBALMOD));
List<File> missingLstFiles = new ArrayList<>();
for (CampaignSourceEntry cse : cseList)
{
File lstFile = new File(cse.getURI());
if (!lstFile.exists())
{
missingLstFiles.add(lstFile);
continue;
}
if (processedLstFiles.contains(lstFile))
{
continue;
}
processedLstFiles.add(lstFile);
processLstFile(varList, varCountMap, lstFile);
}
return missingLstFiles;
}
private void processLstFile(List<VarDefine> varList,
Map<String, Integer> varCountMap, File file)
throws FileNotFoundException, IOException
{
try (BufferedReader br = new BufferedReader(new FileReader(file)))
{
Map<String, String> varUseMap = new HashMap<>();
String line = br.readLine();
while (line != null)
{
if (line.startsWith("###VAR:"))
{
// ###VAR:SomeVar<tab>USE:Explanation of usage of SomeVar
String varUse[] = line.trim().split("\t");
if (varUse.length >= 2 && varUse[1].startsWith("USE:"))
{
varUseMap.put(varUse[0].substring(7), varUse[1].substring(4));
}
}
else if (!line.startsWith("#") && StringUtils.isNotBlank(line))
{
String tokens[] = line.split("\t");
String object = tokens[0];
for (String tok : tokens)
{
if (tok.startsWith("DEFINE:"))
{
String define[] = tok.split("[:|]");
String varName = define[1];
if (define.length > 1
&& !varName.startsWith("LOCK.")
&& !varName.startsWith("UNLOCK."))
{
varList.add(new VarDefine(varName, object,
file, varUseMap.get(varName)));
Integer count = varCountMap.get(varName);
if (count == null)
{
count = 0;
}
count++;
varCountMap.put(varName, count);
}
}
}
}
line = br.readLine();
}
}
}
/**
* A format in which the variable report can be produced. Matches the format
* to the FreeMarker template to be used for that format.
*/
public enum ReportFormat
{
/** Report formatted for web viewing. */
HTML("variable-report-html.ftl"),
/** Report formatted for spreadsheet viewing. */
CSV("variable-report-csv.ftl");
private final String template;
ReportFormat(String template)
{
this.template = template;
}
/**
* @return the FreeMarker template used to produce the format
*/
public String getTemplate()
{
return template;
}
}
/**
* The Class {@code VarDefine} contains a single definition of
* a variable.
*/
public static class VarDefine implements Comparable<VarDefine>
{
private final String varName;
private final String definingObject;
private File definingFile;
private String use;
/**
* Create a new instance of VarDefine.
* @param varName The name of the variable.
* @param definingObject The name of the object defining the variable.
* @param definingFile The file in which the variable is defined.
* @param use The use as described by the author of the variable.
*/
public VarDefine(String varName, String definingObject,
File definingFile, String use)
{
this.varName = varName;
this.definingObject = definingObject;
this.definingFile = definingFile;
this.use = use;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result =
prime * result
+ ((varName == null) ? 0 : varName.hashCode());
result =
prime
* result
+ ((definingObject == null) ? 0 : definingObject
.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof VarDefine))
{
return false;
}
VarDefine other = (VarDefine) obj;
if (varName == null)
{
if (other.varName != null)
{
return false;
}
}
else if (!varName.equals(other.varName))
{
return false;
}
if (definingObject == null)
{
if (other.definingObject != null)
{
return false;
}
}
else if (!definingObject.equals(other.definingObject))
{
return false;
}
return true;
}
@Override
public int compareTo(VarDefine other)
{
if (varName == null)
{
if (other.varName != null)
{
return -1;
}
return 0;
}
else if (other.varName == null)
{
return 1;
}
return varName.compareToIgnoreCase(other.varName);
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder(100);
builder.append("VarDefine [varName=");
builder.append(varName);
builder.append(", definingObject=");
builder.append(definingObject);
builder.append(", definingFile=");
builder.append(definingFile);
builder.append(", use=");
builder.append(use);
builder.append("]");
return builder.toString();
}
/**
* @return the varName
*/
public String getVarName()
{
return varName;
}
/**
* @return the definingObject
*/
public String getDefiningObject()
{
return definingObject;
}
/**
* @return the definingFile
*/
public File getDefiningFile()
{
return definingFile;
}
/**
* @param definingFile the definingFile to set
*/
public void setDefiningFile(File definingFile)
{
this.definingFile = definingFile;
}
/**
* @return the use
*/
public String getUse()
{
return use;
}
/**
* @param use the use to set
*/
public void setUse(String use)
{
this.use = use;
}
}
}