Java tutorial
/** * Simple financial systemic risk simulator for Java * http://code.google.com/p/systemic-risk/ * * Copyright (c) 2011, 2012 * Gilbert Peffer, CIMNE * gilbert.peffer@gmail.com * All rights reserved * * This software is open-source under the BSD license; see * http://code.google.com/p/systemic-risk/wiki/SoftwareLicense */ package info.financialecology.finance.utilities.datastruct; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.data.time.RegularTimePeriod; /** * This class represents data in table form and provides methods to create, * transform, and format the table. * <p> * Tables are based on the <a href="http://tinyurl.com/cmdhzwn"> * DefaultCategoryDataset</a> from the JFree library, where data * is stored under row and column keys. The keys used in the * <code>VersatileDataTable</code> class are strings. * <p> * Every column in the table contains values for a particular * variable, and the keys of these variables are related to, * but not identical with the column keys. While the key that * identifies an {@link VersatileTimeSeries} is composed of * the variable name, the dimension indices, and the run and * experiment indices, the column key associated with a * particular variable is composed of the variable name and the * dimension indices, while the run and experiment indices are * used to construct the row key. * * Example: * * price_1 price_2 * --------------------------------- * r1_e1 | 100.34 98.52 * r2_e1 | 101.06 96.93 * r1_e2 | 99.41 92.23 * r2_e2 | 98.30 89.90 * * @author Gilbert Peffer * @see VersatileTimeSeries * */ public class VersatileDataTable extends DefaultCategoryDataset { private static final long serialVersionUID = -1887356362358556552L; private String id; /** * A map to transform a variable's dimension index, to improve human * readability. Used to format output of data tables. * @see #newIndexMap(String, String, String...) */ private static HashMap<String, HashMap<String, String>> indexMap; // private HashSet<String> varNames; /**==================================================================== * * GLOBAL AND INSTANCE-LEVEL PARAMETERS * ------------------------------------ * * When writing time series for instance to the console, the * debug window, or to a text file, the output format is * determined on the basis of a number of parameters. * * Formatting parameters are defined both on a class level as * well as on an instance level. Parameters are initialised with * predetermined, class level values that can be overwritten, * both on the class level (using the set functions defined in * StaticInternalParams) as well as on the instance level (using * the overwrite functions defined in InternalParams). Overwriting * parameters on the instance level allows special formatting for * a single time series object. * **====================================================================*/ /** * The width of the output column for the output of the data points. * <p> * Allows for adjusting the width depending for instance on the chosen * output accuracy for the data points. The column width together with the * {@link #NUMBER_ACCURACY NUMBER_ACCURACY} and the {@link #NUMBER_TYPE NUMBER_TYPE} * are used to format the standard output of the time series. */ private static int COLUMN_WIDTH = 11; /** * The number of significant decimals to the right of the decimal point * for the output of the data points. * <p> * The number accuracy together with the {@link #NUMBER_TYPE NUMBER_TYPE} * and the {@link #COLUMN_WIDTH COLUMN_WIDTH} are used to format the standard output * of the time series. */ private static int NUMBER_ACCURACY = 6; /** * The standard numeric format string for the output of the data points. * <p> * There exist numerous numeric formats such as decimal ("d"), percent ("p") * and scientific ("g"). The numeric format string together with the * {@link #NUMBER_ACCURACY NUMBER_ACCURACY} and the {@link #COLUMN_WIDTH COLUMN_WIDTH} * are used to format the standard output of the time series. * * @see <a href="http://tinyurl.com/63w7zw">Standard numeric format strings</a> */ private static String NUMBER_TYPE = "g"; /** * The symbol separating variables and indices from other indices. * <p> * Variables can be indexed for a number of reasons. First, variables * can be vector-valued and the index indicates the position of the value * in the vector. Second, for experiments consisting of several runs and * for multiple experiments, variables are indexed with the run and the * experiment index. * <p> * Indexed variables are used as keys to store data and as column headers * for standard output and for data columns in csv files. The index uniquely * identifies a data set in a simulation. * <p> * <b>Note:</b> To store variables that are time-indexed (i.e. time series) * use the {@link VersatileTimeSeries} class. * <p> * <b>Examples</b>: final_price_1, final_price_2, vol_r4, kurt_e2_r13; note * that these variables are not time-indexed, but store cross-sectional or * aggregate data. E.g. vol_r4 might represent the overall volatility of * returns for run 4. */ private static String INDEX_SEPARATOR = "_"; private InternalParams internalParams; /** * The global data structure storing data table output format parameters. * <p> * Provides static setters for the parameters. See the field summary of * {@link VersatileDataTable} for a description of the corresponding parameters. * <p> * Instance-level (local) parameters are specific to the object while the global * parameters are static and hence have class-level scope. The global parameters * are used to set parameter values for all <code>VersatileDataTable</code> * objects, while the local parameters can be used to overwrite the global values * for a particular object. */ public static class StaticInternalParams { public static void setIndexSeperator(String sIndexSeparator) { INDEX_SEPARATOR = sIndexSeparator; } public static void setColumnWidth(int columnWidth) { COLUMN_WIDTH = columnWidth; } public static void setNumberAccuracy(int numberAccuracy) { NUMBER_ACCURACY = numberAccuracy; } public static void setNumberType(String numberType) { NUMBER_TYPE = numberType; } } /** * The local data structure for data table output format parameters. * <p> * For an explanation of local vs. global formats, see <code>StaticInternalParams</code>. * See the field summary in {@link VersatileDataTable} for a description of the * format parameters. * <p> * This class provides getters that return the data table parameter values and * methods that allow overriding global values of these parameters. */ public class InternalParams { /** * Local value of time series format parameter. * * @see VersatileDataTable#INDEX_SEPARATOR */ private String indexSeparator; /** * Local value of time series format parameter. * * @see VersatileDataTable#COLUMN_WIDTH */ private int columnWidth; /** * Local value of time series format parameter. * * @see VersatileDataTable#NUMBER_ACCURACY */ private int numberAccuracy; /** * Local value of time series format parameter. * * @see VersatileDataTable#NUMBER_TYPE */ private String numberType; /** * Local value of time series format parameter. * * @see #getNumberFormat() */ private String numberFormat; /** * Initialises local parameters to global (class-level) * values. */ protected InternalParams() { indexSeparator = INDEX_SEPARATOR; columnWidth = COLUMN_WIDTH; numberAccuracy = NUMBER_ACCURACY; numberType = NUMBER_TYPE; numberFormat = generateNumberFormat(); } /** * Makes a copy of the parameter object. * * @param params the original parameter object */ protected InternalParams(InternalParams params) { // copy constructor this.indexSeparator = params.getIndexSeparator(); this.columnWidth = params.getColumnWidth(); this.numberAccuracy = params.getNumberAccuracy(); this.numberType = params.getNumberType(); this.numberFormat = params.getNumberFormat(); } /** * Overrides the field {@link VersatileDataTable#INDEX_SEPARATOR} * * @param indexSeparator the string symbol separating variable names * and indices from other indices. */ public void overrideIndexSeparator(String sIndexSeparator) { indexSeparator = sIndexSeparator; } /** * Overrides the field {@link VersatileDataTable#COLUMN_WIDTH} * * @param columnWidth the width of the column holding time series data. */ public void overrideColumnWidth(int columnWidth) { this.columnWidth = columnWidth; this.numberFormat = generateNumberFormat(); } /** * Overrides the field {@link VersatileDataTable#NUMBER_ACCURACY} * * @param numberAccuracy the number of significant decimals to the right * of the decimal point. */ public void overrideNumberAccuracy(int numberAccuracy) { this.numberAccuracy = numberAccuracy; this.numberFormat = generateNumberFormat(); } /** * Overrides the field {@link VersatileDataTable#NUMBER_TYPE} * * @param numberType the string indicating the number format ("g", "d", etc.) */ public void overrideNumberType(String numberType) { this.numberType = numberType; this.numberFormat = generateNumberFormat(); } /** * Obtain the object-level value of the time series format parameter. * * @see #overrideIndexSeparator(String) */ public String getIndexSeparator() { return indexSeparator; } /** * Obtain the object-level value of the time series format parameter. * * @see #overrideColumnWidth(int) */ public int getColumnWidth() { return columnWidth; } /** * Obtain the object-level value of the time series format parameter. * * @see #overrideNumberAccuracy(int) */ public int getNumberAccuracy() { return numberAccuracy; } /** * Obtain the object-level value of the time series format parameter. * * @see #overrideNumberType(String) */ public String getNumberType() { return numberType; } /** * Obtain the object-level value of the time series format parameter. * * @see #generateNumberFormat() */ public String getNumberFormat() { return numberFormat; } /** * Generate the number format string based on the {@link #columnWidth}, * {@link #numberAccuracy}, and {@link #numberType}. * * @return the format string */ private String generateNumberFormat() { return "%" + getColumnWidth() + "." + getNumberAccuracy() + getNumberType(); } } /**==================================================================== * * CONSTRUCTORS * ------------ * **====================================================================*/ public VersatileDataTable(String id) { this.id = id; internalParams = new InternalParams(); // varNames = new HashSet<String>(); if (indexMap == null) indexMap = new HashMap<String, HashMap<String, String>>(); } // public VersatileDataTable(String id, String... sVarNames) { // this(id); // // for (String varName : sVarNames) // varNames.add(varName); // } // // public void addVariable(String sVarName) { // Assertion.assertStrict(!varNames.contains(sVarName), Assertion.Level.ERR, "The variable '" + sVarName + "' in the VersatileDataTable '" + thisId + "' already exists"); // varNames.add(sVarName); // } // // public void addVariables(String... varNames) { // // for (String varName : varNames) { // addVariable(varName); // } // } // // public void addVariables(int numIndexed, String... varNames) { // Assertion.assertStrict(numIndexed > 0, Assertion.Level.ERR, "The number of indexed variables in the VersatileDataTable '" + thisId + "' has to be larger than '0', but is " + numIndexed); // // if (numIndexed == 1) addVariables(varNames); // // for (String varName : varNames) { // for (int i = 1; i <= numIndexed; i++) // addVariable(varName + internalParams.getIndexSeparator() + i); // } // } public InternalParams getInternalParams() { return internalParams; } /**==================================================================== * * ADDING VALUES TO THE TABLE * -------------------------- * * Methods that add values to the table. * **====================================================================*/ /** * Adds a value to the table where the complete variable key is * provided. * <p> * <b>Example:</b> For <code>order_2_81_r2_e4=100</code>, the value 100 * is written into the table cell with column key <code>order_2_81</code> * and row key <code>r2_e4</code>. For <code>vol_r1=0.8</code>, the value 0.8 * is written into the table cell with column key <code>vol</code> * and row key <code>r1</code>. * @param key the key that uniquely identifies the variable * @param value the value of the variable */ public void addValue(String key, double value) { key = key.toLowerCase(); int iExp = extractExperimentIndex(key); // TODO centralise extraction methods int iRun = extractRunIndex(key); String name = extractVariableName(key); ArrayList<String> indices = extractVariableIndices(key); addValue(iExp, iRun, name, value, generateIndexString(indices)); } /** * Adds a set of values to the table where the complete variable keys * are provided. The variable keys and associated values are provided * as a hash map, where the variable key serves as the hash map key. * <p> * See {@link #addValue(String, double)} for some examples. * @param map the hash map that stores the variable values under the * unique variable keys */ public void addValue(HashMap<String, Double> map) { for (String label : map.keySet()) { String name = extractVariableName(label); double value = map.get(label); int iExp = extractExperimentIndex(label); int iRun = extractRunIndex(label); ArrayList<String> indices = extractVariableIndices(label); addValue(iExp, iRun, name, value, generateIndexString(indices)); } } /** * Adds a value to the table where only variable name and optional * dimension indices are provided. * <p> * <b>Note</b> The variable argument list permits zero arguments. * @param varName the variable name (without indices) * @param value the value of the variable * @param indices the dimension indices (zero or more) */ public void addValue(String varName, double value, int... indices) { addValue(0, 0, varName, value, indices); } /** * Adds a value to the table where only variable name, run index, and * optional dimension indices are provided. * <p> * <b>Note</b> The variable argument list permits zero arguments. * @param varName the variable name (without indices) * @param value the value of the variable * @param iRun the run index * @param indices the dimension indices (zero or more) */ public void addValue(int iRun, String varName, double value, int... indices) { addValue(0, iRun, varName, value, indices); } /** * Adds a value to the table where the variable name, run index, * experiment index and optional dimension indices are provided. Effectively * the same than {@link #addValue(String, double), but the variable * key in this method is split into its components. * <p> * <b>Note</b> The variable argument list permits zero arguments. * @param varName the variable name (without indices) * @param value the value of the variable * @param iRun the run index * @param iExp the run index * @param indices the dimension indices (zero or more) */ public void addValue(int iExp, int iRun, String varName, double value, int... indices) { addValue(iExp, iRun, varName, value, generateIndexString(indices)); } /** * Adds a value to the table when the dimension, run, and * experiment indices are given. * <p> * Every column in the table contains values for a particular * variable, and the keys of these variables are related to, * but not identical with the column keys. While the key that * identifies an {@link VersatileTimeSeries} is composed of * the variable name, the dimension indices, and the run and * experiment indices, the column key associated with a * particular variable is composed of the variable name and the * dimension indices, while the run and experiment indices are * used to construct the row key. * <p> * <b>Example:</b> * <p> *                 price_1   price_2 <br> * ---------------------------------<br> *   r1_e1 |   100.34     98.52 <br> *   r2_e1 |   101.06     96.93 <br> *   r1_e2 |   99.41       92.23 <br> *   r2_e2 |   98.30       89.90 <br> * * @param varName the name of the variable (without indices) * @param value the value of the variable * @param iExp the experiment index * @param iRun the run index * @param indices the dimension indices (as a string, e.g. "1_12_2") */ public void addValue(int iExp, int iRun, String varName, double value, String indices) { String rowLabel = ""; if (iRun > 0) { rowLabel = "r" + iRun; if (iExp > 0) rowLabel += internalParams.getIndexSeparator() + "e" + iExp; } else if (iExp > 0) rowLabel = "e" + iExp; else rowLabel = "Simulation"; varName += internalParams.getIndexSeparator() + indices; // Insert 'value' into table at row = 'rowLabel' and col = 'varName' addValue(value, rowLabel, varName); } /** * Adds a value to the data table, where this value is extracted from a * time series at a given tick/time/date. The column and row keys are derived * from the time series key (the column key is composed of the variable name * plus the dimension indices; the row key is composed of the run and experiment * indices, e.g. <code>r3_e5</code>). * @param ats the time series * @param tick the tick/time/date at which to extract the value */ public void addTimeSlice(VersatileTimeSeries ats, RegularTimePeriod tick) { int iExp = 0, iRun = 0; double value = ats.getValue(tick).doubleValue(); String key = (String) ats.getKey(); String exp = ats.extractExperimentIndex(key); String run = ats.extractRunIndex(key); String name = ats.extractVariableName(key); ArrayList<String> indices = ats.extractVariableIndices(key); // if (exp != null) { // exp = exp.toLowerCase(); // iExp = Integer.valueOf(StringUtils.remove(exp, 'e')); // } // // if (run != null) { // run = run.toLowerCase(); // iRun = Integer.valueOf(StringUtils.remove(run, 'r')); // } // // if (iExp != 0) { // if (iRun != 0) // addValue(iExp, iRun, name, value, generateIndexString(indices)); // } // else { // if (iRun != 0) // addValue(0, iRun, name, value, generateIndexString(indices)); // if (iRun == 0) // addValue(0, 0, name, value, generateIndexString(indices)); // } addValue(key, value); } /** * Adds values to the data table, where these values are extracted at a * given tick/time/date from a collection of time series. * <p> * See {@link #addTimeSlice(VersatileTimeSeries, RegularTimePeriod)} for * more details. * @param atsc the time series collection * @param tick the tick/time/date at which to extract the value */ public void addTimeSlice(VersatileTimeSeriesCollection atsc, RegularTimePeriod tick) { List<VersatileTimeSeries> atsList = atsc.getSeries(); for (VersatileTimeSeries ats : atsList) addTimeSlice(ats, tick); } /**==================================================================== * * ALGEBRAIC AND STATISTICAL OPERATORS * ----------------------------------- * **====================================================================*/ /** * Merges a data table with this table. The columns of the table passed * as an argument are added to this table. * <p> * <b>Note:</b> This method does not enforce consistency between both tables. * This means that the neither the number of rows nor the row labels have * to coincide. * @param acds the table that is to be merged with this table */ public void merge(VersatileDataTable acds) { List<String> rowKeys = acds.getRowKeys(); List<String> columnKeys = acds.getColumnKeys(); // TODO optionally check for row label consistency, via additional bool argument for (String rowKey : rowKeys) { for (String columnKey : columnKeys) { addValue(acds.getValue(rowKey, columnKey), rowKey, columnKey); } } } /** * Multiplies the values of a given subset of the variables in the * table with a factor. * <p> * String labels passed as arguments are used * to match the column keys that identify which variables are to be * multiplied. E.g. the label <code>price</code> matches the column * keys <code>price_1</code> and <code>price_2</code>. * @param multipler factor with which to multiple the selected columns * @param labels a set of filters to match column keys against */ public void columnMultiply(double multipler, String... labels) { List<String> columnKeys = getColumnKeys(); List<String> rowKeys = getRowKeys(); for (String label : labels) { for (String columnKey : columnKeys) { if (isSubsetOf(columnKey, label)) { for (String rowKey : rowKeys) { if (!StringUtils.startsWith(rowKey, "#")) { // exclude calculated elements of the table, e.g. column average double value = getValue(rowKey, columnKey).doubleValue(); setValue(value * multipler, rowKey, columnKey); } } } } } } /** * Computes the column-wise average of values whose column key matches * any of the labels passed as arguments. The result is appended * to the bottom of the table and prefixed with a '#' symbol * to prevent methods such as {@link #columnMultiply(double, String...)} * to operate on them. * @param rowLabel the name of the result as it should appear in the * leftmost row * @param labels a set of filters to match column keys against */ public void insertColumnAverage(String rowLabel, String... labels) { List<String> columnKeys = getColumnKeys(); List<String> rowKeys = getRowKeys(); rowLabel = "# " + rowLabel; for (String label : labels) { for (String columnKey : columnKeys) { double avg = 0, count = 0; if (isSubsetOf(columnKey, label)) { for (String rowKey : rowKeys) { if (!StringUtils.startsWith(rowKey, "#")) { avg += getValue(rowKey, columnKey).doubleValue(); count++; } } } if (count > 0) { avg = avg / count; setValue(avg, rowLabel, columnKey); } } } } /** * Computes the column-wise standard deviation of values whose column * key matches any of the labels passed as arguments. The result is * appended to the bottom of the table and prefixed with a '#' symbol * to prevent methods such as {@link #columnMultiply(double, String...)} * to operate on them. * @param rowLabel the name of the result as it should appear in the * leftmost row * @param labels a set of filters to match column keys against */ public void insertColumnStdev(String rowLabel, String... labels) { DescriptiveStatistics stats = new DescriptiveStatistics(); List<String> columnKeys = getColumnKeys(); List<String> rowKeys = getRowKeys(); rowLabel = "# " + rowLabel; for (String label : labels) { for (String columnKey : columnKeys) { if (isSubsetOf(columnKey, label)) { for (String rowKey : rowKeys) { if (!StringUtils.startsWith(rowKey, "#")) { stats.addValue(getValue(rowKey, columnKey).doubleValue()); } } } setValue(stats.getStandardDeviation(), rowLabel, columnKey); stats.clear(); } } } /**==================================================================== * * 'KEY' OPERATORS * --------------- * * Key operators are methods that operate on the column key. * **====================================================================*/ // TODO move these methods to a utility class since they are also defined in the class AdvancedTimeSeries /** * Extracts the string representing the variable name from the <code>key</code>. * For instance, the variable name for the label <code>price_2_r3_e1</code> * is <code>price</code>. * <p> * @param key the label from which we want to extract the variable name * @return the variable name or null if not found */ public String extractVariableName(String key) { String[] tokens = key.split(internalParams.getIndexSeparator()); int count = 0; String varName = null; for (String token : tokens) { if (isInteger(token)) break; else if (token.startsWith("e") && isInteger(token.substring(1))) break; else if (token.startsWith("r") && isInteger(token.substring(1))) break; count++; } if (count == tokens.length) varName = key; else if (count != 0) { varName = tokens[0]; for (int i = 1; i < count; i++) varName += internalParams.getIndexSeparator() + tokens[i]; } return varName; } /** * Extracts the dimension indices from the <code>key</code> and adds them * to an array. For instance, the indices for the key * <code>order_4_825_r12</code> are "4" and "825". * <p> * @param key the label from which we want to extract the dimension indices * @return an <code>ArrayList</code> of dimension indices */ public ArrayList<String> extractVariableIndices(String key) { String[] tokens = key.split(internalParams.getIndexSeparator()); ArrayList<String> varIndices = new ArrayList<String>(); for (String token : tokens) { if (isInteger(token)) varIndices.add(token); } return varIndices; } /** * Extracts the experiment index from the <code>key</code>. For instance * the index for the label <code>vol_r1_e5</code> is "e5". * <p> * @param key the label from which we want to extract the experiment index * @return the experiment index */ public int extractExperimentIndex(String key) { String[] tokens = key.split(internalParams.getIndexSeparator()); int expIndex = -1; for (String token : tokens) { if (token.startsWith("e") && isInteger(token.substring(1))) expIndex = Integer.valueOf(token.substring(1)); } return expIndex; } /** * Extracts the run index from the <code>key</code>. For instance * the index for the label <code>vol_r1_e5</code> is "r1". * <p> * @param key the label from which we want to extract the run index * @return the run index */ public int extractRunIndex(String key) { String[] tokens = key.split(internalParams.getIndexSeparator()); int runIndex = -1; for (String token : tokens) { if (token.startsWith("r") && isInteger(token.substring(1))) runIndex = Integer.valueOf(token.substring(1)); } return runIndex; } private String generateIndexString(ArrayList<String> indices) { String sep = ""; for (int i = 0; i < indices.size(); i++) { if (i < indices.size() - 1) sep += indices.get(i) + internalParams.getIndexSeparator(); else sep += indices.get(i); } return sep; } /** * Generates a index string based on numeric indices. Used to construct * a string representation of a variable's dimension indices. * <p> * <b>Example:</b> For dimension indices 4 and 56, the string * representation is "4_56" (index separator is '_' in this case) * @param indices a set of indices * @return a string representation of the dimension indices */ private String generateIndexString(int... indices) { String sep = ""; // TODO validate number of indices > 0 for (int i = 0; i < indices.length; i++) { if (i < indices.length - 1) sep += indices[i] + internalParams.getIndexSeparator(); else sep += indices[i]; } return sep; } /** * Checks whether one string matches another using the 'variable name, * dimension indices, run index, experiment index' pattern. This method is * very similar to {@link VersatileTimeSeries#matches(String)}, but without * run and experiment matching. * <p> * See {@link VersatileTimeSeries#matches(String)} for more details * <p> * <b>Example:</b> The string <code>price_4</code> matches the string * <code>price</code>. * @param subset the string to match * @param superset the string against which to match * @return true, if there is a match * @see VersatileTimeSeries#matches(String) */ public Boolean isSubsetOf(String subset, String superset) { // String key = (String) getKey(); // TODO rename method to 'matches' // TODO the method in AdvancedTimeSeries is almost the same and can actually be used here; move both to a utility class for index matching and construction // Does the variable name coincide? String testForVariableName = extractVariableName(subset); if (testForVariableName != null) { if (!extractVariableName(superset.toLowerCase()).equalsIgnoreCase(testForVariableName)) return false; } // Does the index coincide? (indices that ought to be ignored should be set to '-1' in subset ArrayList<String> subsetIndices = extractVariableIndices(subset); ArrayList<String> supersetIndices = extractVariableIndices(superset); for (int i = 0; i < supersetIndices.size(); i++) { int supersetIndex = Integer.valueOf(supersetIndices.get(i)); if (supersetIndex != -1) if (subsetIndices.size() < i + 1) return false; else if (supersetIndex != Integer.valueOf(subsetIndices.get(i))) return false; } return true; } /**==================================================================== * * INDEX MAPPING * ------------- * * A number of methods are provided to transform indices into * more human readable formats. This is used mainly for output * formatting. For instance, rather than print 'cash_1', we can * use the method <code>newIndexMap</code> to map index "1" into * a new index "bank". This has only an effect on how the labels * are written to the output. * * The method <code>printDecoratedTable()</code> for * instance uses the index map when printing out formatted * time series tables. * **====================================================================*/ /** * Creates a table of mappings from original to new indices. * <p> * The principal use of this method is to provide a way of transforming * dimension indices into something more human readable. It only has * effects in terms of how labels are written to the output. * <p> * The method {@link #printDecoratedTable()} uses the index * map when printing out formatted time series tables. * <p> * See {@link VersatileTimeSeriesCollection#newIndexMap(String, String, String...)} * for more details * @param loc the location of the dimension index that is mapped * @param mappings the pairs of index names */ public void newIndexMap(String name, String loc, String... mappings) { HashMap<String, String> map = new HashMap<String, String>(); indexMap.put(name, map); for (int i = 0; i < mappings.length; i += 2) map.put(loc + "-" + mappings[i], mappings[i + 1]); } /** * Creates an dimension index string of a variable based * on the index map. * <p> * <b>Example:</b> Consider the variable <code>order_2_65</code>. * If no index map is defined for this particular order series, then * the string returned by this method will be something like "2   * 65". If a map is defined for the first index, e.g. (...,"2", * "hedge",...), the string returned will be "hedge   65" * <p> * <b>Note:</b> This method produces index strings with padded values. * The size of the padding is not parameterised here. * @param ats the time series whose indices should be mapped, if * applicable * @return the string containing the mapped, partially mapped, or * original dimension indices */ private String mapIndices(String columnKey) { String mappedIndex = ""; String mapKey = ""; String variableName = extractVariableName(columnKey); HashMap<String, String> map = indexMap.get(variableName); ArrayList<String> indices = extractVariableIndices(columnKey); // TODO use getIndexSeparator for (int i = 1; i <= indices.size(); i++) { if (map == null) mappedIndex += "_" + indices.get(i - 1); else { mapKey = i + "-" + indices.get(i - 1); String entry = map.get(mapKey); if (entry == null) mappedIndex += "_" + indices.get(i - 1); else mappedIndex += "_" + entry; } // if (i < indices.size()) mappedIndex += " "; } return mappedIndex; } /**==================================================================== * * UTILITIES * --------- * **====================================================================*/ /** * Checks whether a given string represents an integer * @param str the string that is to be tested * @return true, if the string represents an integer */ private Boolean isInteger(String str) { try { Integer.parseInt(str); } catch (Exception e) { return false; } return true; } /**==================================================================== * * FORMATTED OUTPUT OF DATA TABLE * ------------------------------ * * Methods to generate formatted data tables. * **====================================================================*/ /** * Prints the table without title * @return the string containing the formatted table */ public String printDecoratedTable() { return printDecoratedTable(null); } /** * Prints the table with a title. * <p> * The table consists of a title, a header, and the values. * @return the string containing the formatted table */ public String printDecoratedTable(String title) { String ts = ""; // int padding = 8; // int margin = 2; // // if ((title != null) && !title.equalsIgnoreCase("")) { // ts += StringUtils.repeat(' ', margin) + StringUtils.repeat('*', 2 + 2 * padding + title.length()) + "\n"; // ts += StringUtils.repeat(' ', margin) + "*" + StringUtils.repeat(' ', padding) + title + StringUtils.repeat(' ', padding) + "*\n"; // ts += StringUtils.repeat(' ', margin) + StringUtils.repeat('*', 2 + 2 * padding + title.length()) + "\n\n"; // } ts += printRowSeparator("=") + "\n"; ts += " " + title + "\n"; ts += printRowSeparator("-") + "\n"; ts += printHeaders() + "\n"; ts += printRowSeparator("-") + "\n"; ts += printValues(); return ts; } /** * Prints a horizontal separator. Used for instance to separate * the header row from the values. The total width of the separator * depends on the column width parameter. * @param sep the separator symbol * @return a sequence of separator strings */ public String printRowSeparator(String sep) { String ts = sep; int nItems = getColumnCount(); ts += StringUtils.repeat(sep, 12); // TODO the value '12' should be a parameter for (int i = 0; i < nItems; i++) { ts += sep + StringUtils.repeat(sep, internalParams.getColumnWidth() + 1); } return ts + sep; } /** * Prints a header row. The total width of the header row * depends on the column width parameter. * @return the header */ public String printHeaders() { String ts = " "; int nItems = getColumnCount(); ts += StringUtils.repeat(' ', 12); for (int i = 0; i < nItems; i++) { String columnKey = (String) getColumnKey(i); String label = extractVariableName(columnKey); label += mapIndices(columnKey); ts += " " + StringUtils.leftPad(label, internalParams.getColumnWidth() + 1); } return ts += " "; } /** * Prints all rows containing values. * @return the header */ public String printValues() { String ts = ""; String tsLast = ""; List<String> rowKeys = getRowKeys(); List<String> columnKeys = getColumnKeys(); for (String rowKey : rowKeys) { if (StringUtils.startsWith(rowKey, "#")) { tsLast += " " + StringUtils.rightPad(rowKey, 8) + " | "; // TODO leftmost column width should be a parameter for (String columnKey : columnKeys) tsLast += " " + String.format(" " + internalParams.getNumberFormat(), getValue(rowKey, columnKey).doubleValue()); tsLast += "\n"; } else { ts += " " + StringUtils.rightPad(rowKey, 8) + " | "; for (String columnKey : columnKeys) ts += " " + String.format(" " + internalParams.getNumberFormat(), getValue(rowKey, columnKey).doubleValue()); ts += "\n"; } } // for (int i = 0; i < getRowCount(); i++) { // ts += " " + StringUtils.rightPad((String) getRowKey(i), 4) + " | "; // for (int j = 0; j < getColumnCount(); j++) // ts += " " + String.format(" " + internalParams.getNumberFormat(), getValue(i, j).doubleValue()); // ts += "\n"; // } if (!tsLast.equalsIgnoreCase("")) ts += printRowSeparator(".") + "\n" + tsLast; return ts + " "; } /** * Calls {@link #printDecoratedTable(String)}. * @return a string with the formatted table */ public String toString() { return printDecoratedTable(this.id); } }